feat: specialize generic type and value parameters

This commit is contained in:
2026-10-26 13:10:52 +01:00
parent 9058135326
commit 6886d743b5
18 changed files with 2499 additions and 103 deletions
+51 -27
View File
@@ -34,6 +34,13 @@ State :: struct {
match_arm_body bool = false
}
@hide:file
ParameterName :: struct {
token lexer.Token
start u32
comptime bool
}
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
#! `allocator` supplies returned node storage and temporary lists released before return.
#! the caller owns the Tree and must release it with ast.deinit.
@@ -178,13 +185,20 @@ parse_parameters proc(
state @mut State, params @mut arraylist.ArrayList(ast.NodeId),
) void ! ParseError {
skip_newlines(state)
names arraylist.ArrayList(lexer.Token) := arraylist.init(state.allocator)
names arraylist.ArrayList(ParameterName) := arraylist.init(state.allocator)
defer arraylist.deinit(&names)
while !next_is(state, .close_paren) {
arraylist.clear(&names)
while true {
start :: current(state).span.start
comptime :: next_is(state, .dollar)
if (comptime) state.next_token += 1
token :: try consume(state, .ident)
try arraylist.append(&names, token)
try arraylist.append(&names, ParameterName{
token = token,
start = start,
comptime = comptime,
})
if (starts_type(current(state).kind)) break
_ = try consume(state, .comma)
}
@@ -193,11 +207,12 @@ parse_parameters proc(
for (names.items) |parameter_name| {
param_id :: try ast.add_param(
state.builder,
ast.Span{ start = parameter_name.span.start, end = type_end, source = parameter_name.span.source },
parameter_name.span,
parameter_name.data.str_id,
ast.Span{ start = parameter_name.start, end = type_end, source = parameter_name.token.span.source },
parameter_name.token.span,
parameter_name.token.data.str_id,
type_id,
)
if (parameter_name.comptime) ast.set_param_comptime(state.builder, param_id)
try arraylist.append(params, param_id)
}
skip_newlines(state)
@@ -733,25 +748,7 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
mutable :: next_is(state, .mut)
if (mutable) state.next_token += 1
if (next_is(state, .proc)) {
state.next_token += 1
_ = try consume(state, .open_paren)
params := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&params)
try parse_parameters(state, &params)
_ = try consume(state, .close_paren)
result :: try parse_type(state)
span :: ast.Span{
start = token.span.start,
end = state.tokens[state.next_token - 1].span.end,
source = token.span.source,
}
if (mutable or token.kind == .star) {
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_pointer_type, span)
return try ast.add_invalid_expr(state.builder, span, diagnostic)
}
return try ast.add_function_type(state.builder, span, result, params.items)
}
if (next_is(state, .proc)) return try parse_function_type(state, token.span, mutable or token.kind == .star)
element :: try parse_type_atom(state)
return try ast.add_pointer_type(state.builder, ast.Span{
start = token.span.start,
@@ -799,13 +796,19 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
count, element, mutable,
)
}
if next_is(state, .proc) return try parse_function_type(state, current(state).span, false)
if next_is(state, .struct) return try parse_record_type(state)
if next_is(state, .enum) return try parse_enum_type(state)
if next_is(state, .union) return try parse_union_type(state)
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
token :: try consume(state, .ident)
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
while next_is(state, .dot) {
while true {
if (next_is(state, .open_paren)) {
type = try parse_call(state, type, token.span.start, false)
continue
}
if (!next_is(state, .dot)) break
state.next_token += 1
member :: try consume_member(state, false)
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
@@ -818,12 +821,33 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
return type
}
@hide:file
parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.NodeId ! ParseError {
_ = try consume(state, .proc)
_ = try consume(state, .open_paren)
params := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&params)
try parse_parameters(state, &params)
_ = try consume(state, .close_paren)
result :: try parse_type(state)
span :: ast.Span{
start = start.start,
end = state.tokens[state.next_token - 1].span.end,
source = start.source,
}
if (invalid) {
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_pointer_type, span)
return try ast.add_invalid_expr(state.builder, span, diagnostic)
}
return try ast.add_function_type(state.builder, span, result, params.items)
}
@hide:file
starts_type proc(kind lexer.TokenKind) bool {
return (
kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
or kind == .at or kind == .star or kind == .question or kind == .open_paren
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc
)
}
@@ -1576,7 +1600,7 @@ named_binding_starts proc(state @State) bool {
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.at, .star, .question: return try parse_type(state)
.at, .star, .question, .proc: return try parse_type(state)
.struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state)
.union: return try parse_union_type(state)
+86 -1
View File
@@ -107,10 +107,95 @@ parses_function_structure_through_tree_views test {
try testing.expect_equal("U", strpool.get_str(&strings, ast.name_id(&tree, third.type))?)
try testing.expect_equal(first.type, second.type)
try testing.expect(first.type != third.type)
try testing.expect(!first.comptime)
try testing.expect(!second.comptime)
try testing.expect(!third.comptime)
try testing.expect(!ast.function_generic(&tree, declaration))
try testing.expect_equal(1, ast.block_statement_count(&tree, function.body))
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
parses_compile_time_parameter_groups_and_callbacks test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`generic proc($T type, $K, $V type, $N usize, $first, second T, $build proc(value T) T, legacy @proc(value T) T) T {}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
declaration :: ast.package_decl(&tree, 0)
try testing.expect(ast.function_generic(&tree, declaration))
expected [8]bool :: [true, true, true, true, true, false, true, false]
try testing.expect_equal(expected.len, ast.function_param_count(&tree, declaration))
for (expected) |comptime, index| {
parameter :: ast.param(&tree, ast.function_param(&tree, declaration, index))
try testing.expect_equal(comptime, parameter.comptime)
}
first :: ast.param(&tree, ast.function_param(&tree, declaration, 0))
key :: ast.param(&tree, ast.function_param(&tree, declaration, 1))
value :: ast.param(&tree, ast.function_param(&tree, declaration, 2))
count :: ast.param(&tree, ast.function_param(&tree, declaration, 3))
try testing.expect_equal("T", ast.source_text(&tree, first.name))
try testing.expect_equal("$T type", ast.source_text(&tree, first.span))
try testing.expect_equal("$K, $V type", ast.source_text(&tree, key.span))
try testing.expect_equal("$V type", ast.source_text(&tree, value.span))
try testing.expect_equal(key.type, value.type)
try testing.expect_equal("usize", ast.source_text(&tree, ast.node_span(&tree, count.type)))
callback :: ast.param(&tree, ast.function_param(&tree, declaration, 6)).type
legacy :: ast.param(&tree, ast.function_param(&tree, declaration, 7)).type
try testing.expect_equal(ast.ExprKind.function_type, ast.expr_kind(&tree, callback))
try testing.expect_equal(ast.ExprKind.function_type, ast.expr_kind(&tree, legacy))
try testing.expect_equal("proc(value T) T", ast.source_text(&tree, ast.node_span(&tree, callback)))
try testing.expect_equal("@proc(value T) T", ast.source_text(&tree, ast.node_span(&tree, legacy)))
try testing.expect_equal(1, ast.function_param_count(&tree, callback))
try testing.expect(!ast.param(&tree, ast.function_param(&tree, callback, 0)).comptime)
}
parses_dependent_type_calls_in_annotations_and_constructors test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`example proc($T type, $N usize, input library.Buffer(T, N)) library.ArrayList(T) {
` direct library.Buffer(T, N) :: library.Buffer(T, N){}
` chained library.Factory(T).Buffer(N) :: library.Factory(T).Buffer(N){}
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
declaration :: ast.package_decl(&tree, 0)
function :: ast.function_decl(&tree, declaration)
input :: ast.param(&tree, ast.function_param(&tree, declaration, 2))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, input.type))
try testing.expect_equal(2, ast.call_arg_count(&tree, input.type))
try testing.expect_equal("library.Buffer", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, input.type).callee)))
try testing.expect_equal("T", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, input.type, 0))))
try testing.expect_equal("N", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, input.type, 1))))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, function.return_type))
try testing.expect_equal(1, ast.call_arg_count(&tree, function.return_type))
direct :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, direct.type?))
constructor :: ast.record_literal(&tree, direct.initializer).type?
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, constructor))
try testing.expect_equal("library.Buffer(T, N)", ast.source_text(&tree, ast.node_span(&tree, constructor)))
chained :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
selected :: ast.selection_expr(&tree, ast.call_expr(&tree, chained.type?).callee)
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, selected.base))
try testing.expect_equal("Buffer", ast.source_text(&tree, ast.node_span(&tree, selected.member)))
try testing.expect_equal(1, ast.call_arg_count(&tree, chained.type?))
}
recovers_invalid_declaration_and_preserves_sibling test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
@@ -1355,7 +1440,7 @@ preserves_array_error_ids_control_bodies_and_following_siblings test {
defer diagnostics.deinit(&diagnostic_store)
source ::
`main proc() void {
` bad [$]i64 :: [$, 2]
` bad [~]i64 :: [~, 2]
` while (items[0) { break }
` for items[0 |i| { continue }
` unfinished :: [1,