feat: specialize generic type and value parameters
This commit is contained in:
+51
-27
@@ -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(¶ms)
|
||||
try parse_parameters(state, ¶ms)
|
||||
_ = 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(¶ms)
|
||||
try parse_parameters(state, ¶ms)
|
||||
_ = 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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user