feat: expand constant inline for loops

This commit is contained in:
2026-10-28 11:22:41 +01:00
parent 741ebbf484
commit 20539ab455
13 changed files with 772 additions and 14 deletions
+108
View File
@@ -1232,6 +1232,114 @@ parses_range_endpoints_and_capture_shapes test {
try testing.expect_equal(ast.name_id(&tree, fourth.index?), ast.name_id(&tree, index_reference))
}
parses_inline_loops_and_clones_capture_metadata 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() void {
` inline for 0..=2 |item| {
` for "ab" |byte, index| { continue }
` }
` for [1, 2] |value, index| {}
` inline for [i32, u8] |@T, _| {}
` inline for ("xy") |byte, index| continue
`}
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
try testing.expect_equal(4, ast.block_statement_count(&tree, body))
first :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 0))
try testing.expect(first.is_inline and !first.pointer_capture and !first.index_capture)
try testing.expect(ast.range_expr(&tree, first.iterable).inclusive)
try testing.expect_equal("inline", ast.source_text(&tree, ast.Span{
start = first.span.start, end = first.span.start + 6, source = first.span.source,
}))
nested :: ast.for_stmt(&tree, ast.block_statement(&tree, first.body, 0))
try testing.expect(!nested.is_inline and nested.index_capture)
try testing.expect_equal(ast.ExprKind.literal_string, ast.expr_kind(&tree, nested.iterable))
ordinary :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect(!ordinary.is_inline and ordinary.index_capture)
try testing.expect_equal(2, ast.array_element_count(&tree, ordinary.iterable))
pointer :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2))
try testing.expect(pointer.is_inline and pointer.pointer_capture and pointer.index_capture)
try testing.expect(pointer.index == null)
try testing.expect_equal("T", strpool.get_str(&strings, ast.name_id(&tree, pointer.capture?))?)
indexed :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 3))
try testing.expect(indexed.is_inline and !indexed.pointer_capture and indexed.index_capture)
try testing.expect_equal("index", strpool.get_str(&strings, ast.name_id(&tree, indexed.index?))?)
try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(
&tree, ast.block_statement(&tree, indexed.body, 0),
))
}
original_body :: ast.function_decl(&tree, ast.child(&tree, root, 0)).body
cloned_body :: ast.function_decl(&tree, ast.child(&tree, cloned, 0)).body
first :: ast.for_stmt(&tree, ast.block_statement(&tree, original_body, 0))
copy :: ast.for_stmt(&tree, ast.block_statement(&tree, cloned_body, 0))
try testing.expect(copy.capture? != first.capture?)
try testing.expect_equal(mapping[usize(first.capture?)], copy.capture?)
try testing.expect_equal(first.span.start, copy.span.start)
try testing.expect_equal(first.span.end, copy.span.end)
try testing.expect_equal(first.span.source, copy.span.source)
}
rejects_malformed_inline_forms_without_losing_siblings test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
cases [7][]u8 :: [
"inline while true {}",
"inline match value { else: {} }",
"inline 0..2 |item| {}",
"inline inline for 0..2 |item| {}",
"inline",
"inline for 0..2 || {}",
"inline for 0..2 |item,| {}",
]
for (cases) |header| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source arraylist.ArrayList(u8) := arraylist.init(mem.c_allocator)
defer arraylist.deinit(&source)
for ("run proc() void {\n") |byte| try arraylist.append(&source, byte)
for (header) |byte| try arraylist.append(&source, byte)
for ("\ninline for (0..2) |item| continue\nkept :: 7\n}\nafter :: 9\n") |byte|
try arraylist.append(&source, byte)
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source.items, &strings, &diagnostic_store)
tree := try parse(mem.c_allocator, scan_state.tokens.items, source.items, &diagnostic_store)
defer ast.deinit(&tree)
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
try testing.expect_equal(2, ast.package_decl_count(&tree))
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
preserved :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect(preserved.is_inline)
try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(
&tree, ast.block_statement(&tree, preserved.body, 0),
))
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2))
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
try testing.expect_equal("after", ast.source_text(
&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name,
))
}
}
rejects_bro_invalid_range_headers_without_losing_siblings test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)