feat: execute Bro-compatible integer range loops

This commit is contained in:
2026-10-10 01:47:30 +02:00
parent d51ca57c9e
commit cf39481bfc
16 changed files with 1416 additions and 16 deletions
+437
View File
@@ -1692,3 +1692,440 @@ loop_controls_do_not_guarantee_function_returns test {
try testing.expect_equal(1, code_count(&diagnostic_store, .break_outside_loop))
try testing.expect_equal(5, diagnostics.count(&diagnostic_store))
}
range_bounds_receive_integer_context_without_joining_the_range_or_capture test {
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`limit :: seed
`seed := 3
`endpoint proc() int { return 4 }
`consume proc(value int) void {}
`main proc() void {
` for (-2)..(limit + 1) |item| {
` consume(item)
` copy :: item
` while false { consume(copy) }
` }
` for (0..=endpoint()) |_| consume(1)
` for 0..2 |item| { consume(item) }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
defer sema.deinit(&resolution)
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
try expect_kind(&result, ast.package_decl(&tree, 0), .int)
try expect_kind(&result, ast.package_decl(&tree, 1), .int)
main_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
for (usize(0)..usize(3)) |index| {
statement :: ast.block_statement(&tree, main_body, index)
loop :: ast.for_stmt(&tree, statement)
range_view :: ast.range_expr(&tree, loop.iterable)
try expect_kind(&result, statement, .void)
try expect_kind(&result, loop.iterable, .range)
try expect_kind(&result, range_view.start, .int)
try expect_kind(&result, range_view.end, .int)
if (loop.capture) |capture| {
try expect_kind(&result, capture, .int)
try testing.expect_equal(capture, sema.binding(&resolution, capture).data.declaration)
call :: ast.statement_expr(&tree, ast.block_statement(&tree, loop.body, 0))
item :: ast.call_arg(&tree, call, 0)
try expect_kind(&result, item, .int)
try testing.expect_equal(capture, sema.binding(&resolution, item).data.declaration)
}
}
first :: ast.for_stmt(&tree, ast.block_statement(&tree, main_body, 0))
first_range :: ast.range_expr(&tree, first.iterable)
try expect_kind(&result, ast.unary_expr(&tree, first_range.start).operand, .int)
upper :: ast.binary_expr(&tree, first_range.end)
try expect_kind(&result, upper.left, .int)
try expect_kind(&result, upper.right, .int)
try expect_kind(&result, ast.block_statement(&tree, first.body, 1), .int)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
range_checks_both_bounds_and_preserves_fixed_declarations_and_source_errors test {
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`real float :: 2
`flag :: true
`alias :: flag
`nothing proc() void {}
`main proc() void {
` for 1.5..2.5 |_| {}
` for alias.."text" |_| {}
` for nothing..nothing() |_| {}
` for real..3 |_| {}
` for absent_start..absent_end |_| {}
` for 0..1 |_| { sibling :: 7 }
` for flexible..floating_alias |_| {}
`}
`floating float :: floating_alias
`floating_alias :: flexible
`flexible := 3
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
defer sema.deinit(&resolution)
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
try expect_kind(&result, ast.package_decl(&tree, 0), .float)
try expect_kind(&result, ast.package_decl(&tree, 1), .bool)
try expect_kind(&result, ast.package_decl(&tree, 2), .bool)
try expect_kind(&result, ast.package_decl(&tree, 3), .function)
main_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
for (usize(0)..usize(3)) |index| {
statement :: ast.block_statement(&tree, main_body, index)
loop :: ast.for_stmt(&tree, statement)
range_view :: ast.range_expr(&tree, loop.iterable)
first_error :: try expect_error(&result, &diagnostic_store, range_view.start, .invalid_range_bound, ast.node_span(&tree, range_view.start))
second_error :: try expect_error(&result, &diagnostic_store, range_view.end, .invalid_range_bound, ast.node_span(&tree, range_view.end))
try testing.expect(first_error != second_error)
try testing.expect_equal(type_of(&result, loop.iterable).diagnostic, type_of(&result, statement).diagnostic)
}
mixed :: ast.for_stmt(&tree, ast.block_statement(&tree, main_body, 3))
mixed_range :: ast.range_expr(&tree, mixed.iterable)
_ = try expect_error(&result, &diagnostic_store, mixed_range.start, .invalid_range_bound, ast.node_span(&tree, mixed_range.start))
try expect_kind(&result, mixed_range.end, .int)
unknown :: ast.for_stmt(&tree, ast.block_statement(&tree, main_body, 4))
unknown_range :: ast.range_expr(&tree, unknown.iterable)
unknown_bounds [2]ast.NodeId :: [unknown_range.start, unknown_range.end]
for unknown_bounds |bound| {
original :: sema.binding(&resolution, bound).data.diagnostic
_ = try expect_error(&result, &diagnostic_store, bound, .unknown_name, ast.node_span(&tree, bound))
try testing.expect_equal(original, type_of(&result, bound).diagnostic)
}
sibling :: ast.for_stmt(&tree, ast.block_statement(&tree, main_body, 5))
try expect_kind(&result, sibling.iterable, .range)
try expect_kind(&result, ast.block_statement(&tree, sibling.body, 0), .int)
floating_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, main_body, 6))
floating_range :: ast.range_expr(&tree, floating_loop.iterable)
floating_start_error :: try expect_error(&result, &diagnostic_store, floating_range.start, .invalid_range_bound, ast.node_span(&tree, floating_range.start))
floating_end_error :: try expect_error(&result, &diagnostic_store, floating_range.end, .invalid_range_bound, ast.node_span(&tree, floating_range.end))
try testing.expect(floating_start_error != floating_end_error)
for (usize(5)..usize(8)) |index| try expect_kind(&result, ast.package_decl(&tree, index), .float)
try testing.expect_equal(9, code_count(&diagnostic_store, .invalid_range_bound))
try testing.expect_equal(2, code_count(&diagnostic_store, .unknown_name))
try testing.expect_equal(0, code_count(&diagnostic_store, .invalid_for_iterable))
try testing.expect_equal(0, code_count(&diagnostic_store, .cannot_infer_type))
}
range_values_nonrange_iterables_and_capture_modes_have_explicit_errors test {
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`stored :: 0..3
`produced proc() int { return 0..3 }
`consume proc(value int) void {}
`scalar proc() int { return 2 }
`main proc() void {
` 0..3
` consume(0..3)
` local :: 0..3
` for 3 |_| { absent_scalar }
` for true |_| {}
` for "text" |_| {}
` for scalar() |_| {}
` for stored |_| {}
` for 0..3 |@item| { item }
` for 0..3 |item, index| { item + index }
` for 0..3 |_, _| {}
` for 0..3 |@item, _| {}
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
defer sema.deinit(&resolution)
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
stored :: ast.package_decl(&tree, 0)
stored_value :: ast.statement_decl(&tree, stored).initializer
stored_error :: try expect_error(&result, &diagnostic_store, stored_value, .range_value_not_supported, ast.node_span(&tree, stored_value))
try testing.expect_equal(stored_error, type_of(&result, stored).diagnostic)
produced_body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
returned :: ast.return_stmt(&tree, ast.block_statement(&tree, produced_body, 0)).value?
_ = try expect_error(&result, &diagnostic_store, returned, .range_value_not_supported, ast.node_span(&tree, returned))
main_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
standalone :: ast.statement_expr(&tree, ast.block_statement(&tree, main_body, 0))
call :: ast.statement_expr(&tree, ast.block_statement(&tree, main_body, 1))
local_value :: ast.statement_decl(&tree, ast.block_statement(&tree, main_body, 2)).initializer
values [3]ast.NodeId :: [standalone, ast.call_arg(&tree, call, 0), local_value]
for values |value| {
_ = try expect_error(&result, &diagnostic_store, value, .range_value_not_supported, ast.node_span(&tree, value))
}
for (usize(3)..usize(7)) |index| {
statement :: ast.block_statement(&tree, main_body, index)
loop :: ast.for_stmt(&tree, statement)
original :: try expect_error(&result, &diagnostic_store, loop.iterable, .invalid_for_iterable, ast.node_span(&tree, loop.iterable))
try testing.expect_equal(original, type_of(&result, statement).diagnostic)
}
stored_loop :: ast.block_statement(&tree, main_body, 7)
try testing.expect_equal(stored_error, type_of(&result, stored_loop).diagnostic)
modes [4]diagnostics.Code :: [.range_pointer_capture, .range_index_capture, .range_index_capture, .range_pointer_capture]
for modes |code, index| {
statement :: ast.block_statement(&tree, main_body, index + 8)
_ = try expect_error(&result, &diagnostic_store, statement, code, ast.node_span(&tree, statement))
}
indexed :: ast.for_stmt(&tree, ast.block_statement(&tree, main_body, 9))
try expect_kind(&result, indexed.capture?, .int)
try expect_kind(&result, indexed.index?, .int)
try expect_kind(&result, ast.statement_expr(&tree, ast.block_statement(&tree, indexed.body, 0)), .int)
try testing.expect_equal(5, code_count(&diagnostic_store, .range_value_not_supported))
try testing.expect_equal(4, code_count(&diagnostic_store, .invalid_for_iterable))
try testing.expect_equal(2, code_count(&diagnostic_store, .range_pointer_capture))
try testing.expect_equal(3, code_count(&diagnostic_store, .range_index_capture))
try testing.expect_equal(1, code_count(&diagnostic_store, .unknown_name))
try testing.expect_equal(0, code_count(&diagnostic_store, .return_type_mismatch))
try testing.expect_equal(0, code_count(&diagnostic_store, .type_mismatch))
}
range_captures_are_fixed_readonly_and_assignment_values_do_not_retarget_bounds test {
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`consume proc(value float) void {}
`returned proc() float {
` for 0..2 |item| { return item }
` return 1
`}
`main proc() void {
` limit := 3
` real := 1.5
` for 0..limit |item| {
` item = 2
` if true { item = 1.5 }
` while false { item = -"bad" }
` real = item
` real = limit
` consume(item)
` copy :: item
` consume(copy)
` }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
defer sema.deinit(&resolution)
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
returned_body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
returned_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, returned_body, 0))
returned_value :: ast.return_stmt(&tree, ast.block_statement(&tree, returned_loop.body, 0)).value?
_ = try expect_error(&result, &diagnostic_store, returned_value, .return_type_mismatch, ast.node_span(&tree, returned_value))
try expect_kind(&result, returned_loop.capture?, .int)
main_body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
limit :: ast.block_statement(&tree, main_body, 0)
real :: ast.block_statement(&tree, main_body, 1)
loop_statement :: ast.block_statement(&tree, main_body, 2)
loop :: ast.for_stmt(&tree, loop_statement)
try expect_kind(&result, limit, .int)
try expect_kind(&result, real, .float)
try expect_kind(&result, loop_statement, .void)
try expect_kind(&result, loop.iterable, .range)
try expect_kind(&result, ast.range_expr(&tree, loop.iterable).end, .int)
try expect_kind(&result, loop.capture?, .int)
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, loop.body, 1))
inner :: ast.while_stmt(&tree, ast.block_statement(&tree, loop.body, 2))
readonly [3]ast.NodeId :: [
ast.block_statement(&tree, loop.body, 0),
ast.block_statement(&tree, conditional.then_block, 0),
ast.block_statement(&tree, inner.body, 0),
]
for readonly |statement| {
assignment :: ast.assignment_stmt(&tree, statement)
_ = try expect_error(&result, &diagnostic_store, statement, .invalid_assignment_target, ast.node_span(&tree, assignment.target))
try expect_kind(&result, assignment.target, .int)
}
dead_value :: ast.assignment_stmt(&tree, readonly[2]).value
_ = try expect_error(&result, &diagnostic_store, dead_value, .invalid_operand, ast.node_span(&tree, dead_value))
for (usize(3)..usize(5)) |index| {
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, loop.body, index))
_ = try expect_error(&result, &diagnostic_store, assignment.value, .type_mismatch, ast.node_span(&tree, assignment.value))
try expect_kind(&result, assignment.target, .float)
}
for (usize(5)..usize(8)) |index| {
if (index == 6) continue
call :: ast.statement_expr(&tree, ast.block_statement(&tree, loop.body, index))
argument :: ast.call_arg(&tree, call, 0)
_ = try expect_error(&result, &diagnostic_store, argument, .type_mismatch, ast.node_span(&tree, argument))
}
try expect_kind(&result, ast.block_statement(&tree, loop.body, 6), .int)
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_assignment_target))
try testing.expect_equal(4, code_count(&diagnostic_store, .type_mismatch))
try testing.expect_equal(1, code_count(&diagnostic_store, .return_type_mismatch))
try testing.expect_equal(1, code_count(&diagnostic_store, .invalid_operand))
try testing.expect_equal(0, code_count(&diagnostic_store, .invalid_range_bound))
try testing.expect_equal(0, code_count(&diagnostic_store, .missing_return))
}
range_loop_return_flow_and_controls_are_lexical_and_body_errors_are_isolated test {
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`missing proc() int { for 0..=0 |_| { return 1 } }
`bad_body proc() int { for 2..1 |_| { -"bad" } }
`breaking proc() int { for 0..2 |_| { break } }
`continuing proc() int { for 2..1 |_| { continue } }
`closed proc() int {
` for 2..1 |_| { -"bad" }
` return 7
`}
`scopes proc(flag bool) void {
` for 0..2 |item| {
` if (flag) break else continue
` while flag {
` if (flag) continue else break
` for 0..item |_| { continue }
` }
` absent
` }
` while flag { for 0..2 |_| { break } }
` break
` continue
` item
`}
`callee proc() void {
` break
` continue
`}
`caller proc() void {
` for 0..2 |_| {
` callee()
` break
` }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
defer sema.deinit(&resolution)
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
for (usize(0)..usize(4)) |index| {
function :: ast.package_decl(&tree, index)
body :: ast.function_decl(&tree, function).body
try expect_kind(&result, function, .function)
_ = try expect_error(&result, &diagnostic_store, body, .missing_return, ast.node_span(&tree, body))
try expect_kind(&result, ast.block_statement(&tree, body, 0), .void)
}
closed_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
closed_statement :: ast.block_statement(&tree, closed_body, 0)
closed_loop :: ast.for_stmt(&tree, closed_statement)
dead_value :: ast.statement_expr(&tree, ast.block_statement(&tree, closed_loop.body, 0))
original :: try expect_error(&result, &diagnostic_store, dead_value, .invalid_operand, ast.node_span(&tree, dead_value))
try testing.expect_equal(original, type_of(&result, closed_body).diagnostic)
try expect_kind(&result, closed_statement, .void)
try expect_kind(&result, ast.block_statement(&tree, closed_body, 1), .void)
scopes_body :: ast.function_decl(&tree, ast.package_decl(&tree, 5)).body
outer_statement :: ast.block_statement(&tree, scopes_body, 0)
outer :: ast.for_stmt(&tree, outer_statement)
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, outer.body, 0))
inner_statement :: ast.block_statement(&tree, outer.body, 1)
inner :: ast.while_stmt(&tree, inner_statement)
inner_if :: ast.if_stmt(&tree, ast.block_statement(&tree, inner.body, 0))
nested_statement :: ast.block_statement(&tree, inner.body, 1)
nested :: ast.for_stmt(&tree, nested_statement)
controls [5]ast.NodeId :: [
ast.block_statement(&tree, conditional.then_block, 0),
ast.block_statement(&tree, conditional.else_block?, 0),
ast.block_statement(&tree, inner_if.then_block, 0),
ast.block_statement(&tree, inner_if.else_block?, 0),
ast.block_statement(&tree, nested.body, 0),
]
for (controls) |statement| try expect_kind(&result, statement, .void)
try expect_kind(&result, outer_statement, .void)
try expect_kind(&result, inner_statement, .void)
try expect_kind(&result, nested_statement, .void)
absent :: ast.statement_expr(&tree, ast.block_statement(&tree, outer.body, 2))
try testing.expect_equal(sema.binding(&resolution, absent).data.diagnostic, type_of(&result, absent).diagnostic)
escaped :: ast.statement_expr(&tree, ast.block_statement(&tree, scopes_body, 4))
try testing.expect_equal(sema.binding(&resolution, escaped).data.diagnostic, type_of(&result, escaped).diagnostic)
try testing.expect_equal(4, code_count(&diagnostic_store, .missing_return))
try testing.expect_equal(2, code_count(&diagnostic_store, .invalid_operand))
try testing.expect_equal(2, code_count(&diagnostic_store, .unknown_name))
try testing.expect_equal(2, code_count(&diagnostic_store, .break_outside_loop))
try testing.expect_equal(2, code_count(&diagnostic_store, .continue_outside_loop))
}
invalid_range_capture_bindings_keep_resolver_diagnostic_ids_without_erasing_headers test {
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`main proc() void {
` existing :: 7
` for 0..1 |existing| { sibling :: 1 }
` for 0..1 |int| { sibling :: 2 }
` for 0..1 |item| { item :: 3 }
` for 0..1 |item| { copy :: item }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
defer sema.deinit(&resolution)
previous_count :: diagnostics.count(&diagnostic_store)
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
function :: ast.package_decl(&tree, 0)
body :: ast.function_decl(&tree, function).body
try expect_kind(&result, function, .function)
try expect_kind(&result, ast.block_statement(&tree, body, 0), .int)
for (usize(1)..usize(3)) |index| {
statement :: ast.block_statement(&tree, body, index)
loop :: ast.for_stmt(&tree, statement)
capture :: loop.capture?
binding :: sema.binding(&resolution, capture)
try testing.expect_equal(sema.BindingKind.invalid, binding.kind)
try testing.expect_equal(TypeKind.invalid, type_of(&result, capture).kind)
try testing.expect_equal(binding.data.diagnostic, type_of(&result, capture).diagnostic)
try testing.expect_equal(binding.data.diagnostic, type_of(&result, loop.body).diagnostic)
try expect_kind(&result, statement, .void)
try expect_kind(&result, loop.iterable, .range)
try expect_kind(&result, ast.block_statement(&tree, loop.body, 0), .int)
}
duplicate_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 3))
duplicate :: ast.block_statement(&tree, duplicate_loop.body, 0)
try expect_kind(&result, duplicate_loop.capture?, .int)
try testing.expect_equal(sema.binding(&resolution, duplicate).data.diagnostic, type_of(&result, duplicate).diagnostic)
sibling_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 4))
try expect_kind(&result, sibling_loop.capture?, .int)
try expect_kind(&result, ast.block_statement(&tree, sibling_loop.body, 0), .int)
try testing.expect_equal(previous_count, diagnostics.count(&diagnostic_store))
}