feat: expand nominal inline match arms

This commit is contained in:
2026-10-28 12:39:28 +01:00
parent dc7b2e40e2
commit 5cf9e15661
13 changed files with 908 additions and 49 deletions
+51 -26
View File
@@ -1048,27 +1048,39 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
start :: current(state).span
patterns arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&patterns)
catch_all :: next_is(state, .else)
is_inline :: next_is(state, .inline)
if (is_inline) state.next_token += 1
catch_all :: !is_inline and next_is(state, .else)
capture ?ast.NodeId := null
tag_capture ?ast.NodeId := null
tag_capture_present := false
if (catch_all) state.next_token += 1
else {
else if (!is_inline) {
while true {
try arraylist.append(&patterns, try parse_guard_expr(state))
if (!next_is(state, .comma)) break
state.next_token += 1
skip_newlines(state)
}
if next_is(state, .pipe) {
}
if (is_inline or (!catch_all and next_is(state, .pipe))) {
_ = try consume(state, .pipe)
# captures are by value; pointer capture tokens are not identifiers.
token :: try consume(state, .ident)
if (
token.span.end != token.span.start + 1
or state.builder.source[usize(token.span.start)] != '_'
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
if (is_inline and next_is(state, .comma)) {
state.next_token += 1
# pointer payload captures are outside the current runtime representation.
if (next_is(state, .at) or next_is(state, .star)) return .unexpected_token
token :: try consume(state, .ident)
tag_capture_present = true
tag :: try consume(state, .ident)
if (
token.span.end != token.span.start + 1
or state.builder.source[usize(token.span.start)] != '_'
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
_ = try consume(state, .pipe)
tag.span.end != tag.span.start + 1
or state.builder.source[usize(tag.span.start)] != '_'
) tag_capture = try ast.add_identifier(state.builder, tag.span, tag.data.str_id)
}
_ = try consume(state, .pipe)
}
_ = try consume(state, .colon)
skip_newlines(state)
@@ -1079,24 +1091,37 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
.open_curly, .return, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
else: false
}
body ast.NodeId := undefined
expression := false
if (statement_body or named_binding_starts(state)) {
body :: try parse_arm(state, false)
return try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
}, patterns.items, capture, body, false, catch_all)
body = try parse_arm(state, false)
} else {
value :: try parse_expr(state)
if next_is(state, .equal) {
statement :: try parse_assignment(state, value, false)
statements [1]ast.NodeId :: [statement]
body = try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
} else {
body = value
expression = true
}
tree :: ast.view(state.builder, value)
if (expression and ast.expr_kind(&tree, value) == .match_expr
and ast.match_arm_count(&tree, value) > 0) {
nested :: ast.match_arm_view(&tree, ast.match_arm(&tree, value, 0))
if (!nested.expression) {
statement :: try ast.add_expression_stmt(state.builder, value)
statements [1]ast.NodeId :: [statement]
body = try ast.add_block(state.builder, ast.builder_span(state.builder, value), statements[..])
expression = false
}
}
}
value :: try parse_expr(state)
if next_is(state, .equal) {
statement :: try parse_assignment(state, value, false)
statements [1]ast.NodeId :: [statement]
body :: try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
return try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
}, patterns.items, capture, body, false, catch_all)
}
return try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, value).end, source = start.source,
}, patterns.items, capture, value, true, catch_all)
arm :: try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
}, patterns.items, capture, body, expression, catch_all)
if (is_inline) ast.set_match_arm_inline(state.builder, arm, tag_capture, tag_capture_present)
return arm
}
@hide:file
+76
View File
@@ -1899,3 +1899,79 @@ rejects_pointer_match_captures_and_preserves_siblings test {
try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name))
}
parses_and_clones_inline_match_captures_and_body_forms test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`run proc(value int) void {
` match value { .if, .return: 1, inline |payload, tag|: payload }
` match value { inline |_, _|: return }
` match value { inline |_|: value = 1 }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
builder := ast.init_builder(mem.c_allocator, source)
defer ast.deinit_builder(&builder)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
original :: ast.view(&builder, root)
mapping :: try mem.alloc(ast.NodeId, mem.c_allocator, ast.node_count(&original))
defer mem.free(mem.c_allocator, mapping)
mem.set(mapping, maxval!(ast.NodeId))
cloned :: try ast.clone_subtree(&builder, root, mapping)
tree :: ast.view(&builder, root)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
roots [2]ast.NodeId :: [root, cloned]
for (roots) |package| {
body :: ast.function_decl(&tree, ast.child(&tree, package, 0)).body
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
ordinary :: ast.match_arm(&tree, matched, 0)
try testing.expect_equal(2, ast.match_pattern_count(&tree, ordinary))
try testing.expect(!ast.match_arm_view(&tree, ordinary).is_inline)
arm_id :: ast.match_arm(&tree, matched, 1)
arm :: ast.match_arm_view(&tree, arm_id)
try testing.expect(arm.is_inline and arm.expression and arm.tag_capture_present)
try testing.expect_equal(0, ast.match_pattern_count(&tree, arm_id))
try testing.expect_equal(3, ast.child_count(&tree, arm_id))
try testing.expect_equal(arm.body, ast.child(&tree, arm_id, 0))
try testing.expect_equal(arm.capture?, ast.child(&tree, arm_id, 1))
try testing.expect_equal(arm.tag_capture?, ast.child(&tree, arm_id, 2))
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, arm.capture?)))
try testing.expect_equal("tag", ast.source_text(&tree, ast.node_span(&tree, arm.tag_capture?)))
for (usize(1)..usize(3)) |i| {
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, body, i))
discarded :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
try testing.expect(discarded.is_inline and !discarded.expression)
try testing.expect(discarded.capture == null and discarded.tag_capture == null)
try testing.expect_equal(i == 1, discarded.tag_capture_present)
}
}
}
rejects_malformed_inline_match_captures test {
sources [8][]u8 :: [
"run proc() void { match value { inline ||: 1 } }",
"run proc() void { match value { inline |a, b, c|: 1 } }",
"run proc() void { match value { inline |@a|: 1 } }",
"run proc() void { match value { inline |*a|: 1 } }",
"run proc() void { match value { inline |a, @b|: 1 } }",
"run proc() void { match value { inline |a: 1 } }",
"run proc() void { match value { inline a|: 1 } }",
"run proc() void { match value { inline |a,|: 1 } }",
]
for (sources) |source| {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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(diagnostics.count(&diagnostic_store) > 0)
}
}