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
+11 -2
View File
@@ -11,7 +11,7 @@
#! * arm ...... -> block | stmt (single statements become canonical blocks)
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! braceless guards/iterables require parentheses unless they are calls or optional captures; else binds to the nearest if.
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/&/try higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses.
#! binary operators associate left; unary operators associate right.
@@ -400,6 +400,7 @@ ForView :: struct {
pointer_capture bool
index_capture bool
body NodeId
is_inline bool = false
}
InvalidView :: struct {
@@ -994,6 +995,13 @@ add_for_stmt proc(
}, span)
}
set_for_inline proc(builder @mut Builder, id NodeId) void {
node :: &builder.storage.nodes.items[usize(id)]
debug.assert(node.kind == .stmt_for)
flags :: &builder.storage.extra.items[usize(node.data1.extra) + 3]
flags^ = flags^ | 2
}
add_statement_decl proc(
builder @mut Builder,
span Span,
@@ -1310,8 +1318,9 @@ for_stmt proc(tree @Tree, id NodeId) ForView {
capture = maybe_node_id(tree.storage.extra.items[extra]),
index = maybe_node_id(tree.storage.extra.items[extra + 1]),
pointer_capture = tree.storage.extra.items[extra + 2] != 0,
index_capture = tree.storage.extra.items[extra + 3] != 0,
index_capture = (tree.storage.extra.items[extra + 3] & 1) != 0,
body = node.data0.node_id,
is_inline = (tree.storage.extra.items[extra + 3] & 2) != 0,
}
}
+1
View File
@@ -101,6 +101,7 @@ render_block proc(renderer @mut Renderer, tree @ast.Tree, block ast.NodeId, dept
}
.for_stmt: {
loop :: ast.for_stmt(tree, statement)
if (loop.is_inline) try append_text(renderer, "inline ")
try append_text(renderer, "for ")
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, loop.iterable)))
try append_text(renderer, " |")
+68
View File
@@ -338,6 +338,74 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
}
}
recompiles_imported_inline_for_without_retaining_expansions test {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
modules [2]mut ir.Module := undefined
{
session := init(mem.c_allocator)
defer deinit(&session)
root :: try add_package(&session)
dependency :: try add_package(&session)
root_bytes := try owned_text(
`dep :: import "../dependency"
`touch proc(value i16) i16 { return value }
`main proc() i16 {
` total i16 := 0
` inline for dep.values() |value, index| {
` at usize :: index
` total = total + touch(value) + i16(at)
` }
` inline for dep.types() |T| {
` if (T == i16) {
` value T :: total
` total = touch(value)
` } else {
` value T :: true
` if (!value) return 0
` }
` }
` inline for (i8(0)..i8(2)) |outer| {
` inline for [i8(0), i8(1)] |inner| {
` for (0..usize(2)) |index| {
` if (index == 0) continue
` total = total + i16(outer + inner)
` }
` }
` }
` return total
`}
,
)
defer arraylist.deinit(&root_bytes)
dependency_bytes := try owned_text(
`values proc() [2]i16 { return [17, 20] }
`types proc() [2]type { return [i16, bool] }
,
)
defer arraylist.deinit(&dependency_bytes)
root_source :: try add_source(&session, root, "app/main.hon", &root_bytes, &diagnostic_store)
_ = try add_source(&session, dependency, "dependency/values.hon", &dependency_bytes, &diagnostic_store)
try arraylist.append(&session.imports, Import{
node = declaration(&session, root_source, 0).node,
target = dependency,
})
modules[0] = try compile(&session, root, &diagnostic_store)
errdefer ir.deinit(&modules[0])
modules[1] = try compile(&session, root, &diagnostic_store)
}
defer ir.deinit(&modules[0])
defer ir.deinit(&modules[1])
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
for (0..modules.len) |index| {
outcome := try interpreter.run(mem.c_allocator, &modules[index], &diagnostic_store)
defer interpreter.deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.integer)
}
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
recompiles_generated_records_without_borrowing_previous_ir test {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
+2
View File
@@ -113,6 +113,7 @@ Code :: enum {
specialization_limit
comptime_execution_limit
comptime_value_escape
inline_loop_control
invalid_generated_field_name
invalid_struct_layout
invalid_c_record_field_type
@@ -250,6 +251,7 @@ messages :: $enummap.init(Code, []u8, {
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
comptime_execution_limit = "compile-time execution exceeds its recursion or step limit",
comptime_value_escape = "compile-time result cannot escape evaluation as a source value",
inline_loop_control = "break or continue targeting an inline loop must be compile-time-resolvable",
invalid_generated_field_name = "generated field name must be an ASCII identifier",
invalid_struct_layout = "generated record layout must be .auto or .c; C-layout records require at least one field",
invalid_c_record_field_type = "record field is not a supported C-layout value type",
+1
View File
@@ -33,6 +33,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "errdefer", .errdefer },
{ "if", .if },
{ "for", .for },
{ "inline", .inline },
{ "else", .else },
{ "while", .while },
{ "true", .true },
+19
View File
@@ -123,6 +123,25 @@ scans_loop_controls_with_exact_keyword_boundaries test {
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
scans_inline_keyword_without_splitting_identifiers test {
pool := strpool.init(mem.c_allocator)
defer strpool.deinit(&pool)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
state := init(mem.c_allocator)
defer deinit(&state)
try scan(&state, "inline for inlined inline_1 inline2", &pool, &diagnostic_store)
expected [6]TokenKind :: [.inline, .for, .ident, .ident, .ident, .eof]
try testing.expect_equal(expected.len, state.tokens.items.len)
for (expected) |kind, index| try testing.expect_equal(kind, state.tokens.items[index].kind)
try testing.expect_equal(u32(0), state.tokens.items[0].span.start)
try testing.expect_equal(u32(6), state.tokens.items[0].span.end)
try testing.expect_equal("inlined", strpool.get_str(&pool, state.tokens.items[2].data.str_id)?)
try testing.expect_equal("inline_1", strpool.get_str(&pool, state.tokens.items[3].data.str_id)?)
try testing.expect_equal("inline2", strpool.get_str(&pool, state.tokens.items[4].data.str_id)?)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
scans_ranges_without_consuming_float_dots test {
pool := strpool.init(mem.c_allocator)
defer strpool.deinit(&pool)
+1
View File
@@ -21,6 +21,7 @@ TokenKind :: enum {
if, else
for, while
match
inline
proc, return
break, continue
try, catch
+13 -8
View File
@@ -293,7 +293,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
match current(state).kind {
.if: return try parse_if(state)
.while: return try parse_while(state, allow_else)
.for: return try parse_for(state, allow_else)
.for, .inline: return try parse_for(state, allow_else)
.return: return try parse_return(state, allow_else)
.break, .continue: return try parse_loop_control(state, allow_else)
.defer, .errdefer: return try parse_cleanup(state, allow_else)
@@ -510,7 +510,10 @@ parse_captures proc(state @mut State) CaptureList ! ParseError {
@hide:file
parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
keyword :: try consume(state, .for)
keyword :: current(state)
inlined :: next_is(state, .inline)
if (inlined) state.next_token += 1
_ = try consume(state, .for)
skip_newlines(state)
iterable_start :: state.next_token
iterable := try parse_guard_expr(state)
@@ -556,7 +559,7 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
and !next_is(state, .open_curly)
) state.next_token = after_captures
body :: try parse_arm(state, allow_else)
return try ast.add_for_stmt(
id :: try ast.add_for_stmt(
state.builder,
ast.Span{
start = keyword.span.start,
@@ -565,6 +568,8 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
},
iterable, captures.item, captures.index, captures.pointer, captures.indexed, body,
)
if (inlined) ast.set_for_inline(state.builder, id)
return id
}
@hide:file
@@ -599,7 +604,7 @@ parse_guard proc(state @mut State) ast.NodeId ! ParseError {
and !next_is(state, .newline) and !next_is(state, .eof)
and !next_is(state, .else) and !next_is(state, .return)
and !next_is(state, .if) and !next_is(state, .while)
and !next_is(state, .for)
and !next_is(state, .for) and !next_is(state, .inline)
and !next_is(state, .break) and !next_is(state, .continue)
) {
if (next_is(state, .ident) and next_n_is(state, 1, .equal)) break
@@ -850,7 +855,7 @@ starts_type proc(kind lexer.TokenKind) bool {
@hide:file
member_name proc(kind lexer.TokenKind) bool {
return match kind {
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
.ident, .if, .else, .for, .inline, .while, .proc, .return, .break, .continue,
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
else: false
@@ -1071,7 +1076,7 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
state.match_arm_body = true
defer state.match_arm_body = previous
statement_body :: match current(state).kind {
.open_curly, .return, .break, .continue, .if, .while, .for, .defer, .errdefer: true
.open_curly, .return, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
else: false
}
if (statement_body or named_binding_starts(state)) {
@@ -1703,7 +1708,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
is_expression_boundary proc(kind lexer.TokenKind) bool {
return match kind {
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .if, .while, .for, .defer, .errdefer, .eof: true
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .if, .while, .for, .inline, .defer, .errdefer, .eof: true
else: false
}
}
@@ -1842,7 +1847,7 @@ recovery_closes_delimiters proc(state @State, paren_depth, square_depth usize) b
for (state.next_token)..(state.tokens.len) |i| {
kind :: state.tokens[i].kind
match kind {
.open_curly, .close_curly, .eof, .if, .else, .while, .for,
.open_curly, .close_curly, .eof, .if, .else, .while, .for, .inline,
.return, .break, .continue, .defer, .errdefer: return false
.ident: {
if (i + 1 < state.tokens.len) {
+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)
+170 -1
View File
@@ -121,6 +121,8 @@ elaborate proc(
id :: ast.NodeId(i)
class :: ast.node_class(&original, id)
if (class == .expression and ast.expr_kind(&original, id) == .comptime_expr) needed = true
if (class == .statement and ast.stmt_kind(&original, id) == .for_stmt
and ast.for_stmt(&original, id).is_inline) needed = true
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
name :: ast.builtin_call(&original, id).name
text :: ast.source_text(&original, ast.node_span(&original, name))
@@ -231,6 +233,11 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
if (state.expanded.items[usize(id)] != 0) return
state.expanded.items[usize(id)] = 1
class :: ast.node_class(&state.tree, id)
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt
and ast.for_stmt(&state.tree, id).is_inline) {
try expand_inline_for(state, id)
return
}
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
expression :: ast.statement_expr(&state.tree, id)
if (
@@ -1520,6 +1527,8 @@ TypeExecution :: struct {
returned ast.NodeId = no_node
result_type ast.NodeId
loop_depth usize = 0
inline_body ast.NodeId = no_node
inline_blocks ?@mut arraylist.ArrayList(ast.NodeId) = null
}
@hide:file
@@ -1609,6 +1618,152 @@ nominal_type_value proc(state @State, id ast.NodeId) bool {
return kind == .record_type or kind == .union_type or kind == .enum_type or kind == .distinct_type
}
# transfers targeting an inline loop must be selected before checking runtime
# bodies. nested loops own their transfers and are elaborated independently.
@hide:file
inline_has_transfer proc(state @State, id ast.NodeId) bool {
class :: ast.node_class(&state.tree, id)
if (class == .function) return false
if (class == .statement) {
kind :: ast.stmt_kind(&state.tree, id)
if (kind == .for_stmt or kind == .while_stmt) return false
if (kind == .break_stmt or kind == .continue_stmt) return true
}
for (0..ast.child_count(&state.tree, id)) |i| {
if (inline_has_transfer(state, ast.child(&state.tree, id, i))) return true
}
return false
}
@hide:file
inline_statement proc(state @mut State, id ast.NodeId) TypeFlow ! Error {
kind :: ast.stmt_kind(&state.tree, id)
if (kind == .break_stmt) return .break
if (kind == .continue_stmt) return .continue
if (kind == .block_stmt)
return try inline_block(state, ast.statement_block(&state.tree, id))
selected := no_node
if (kind == .if_stmt) {
conditional :: ast.if_stmt(&state.tree, id)
explicit :: ast.expr_kind(&state.tree, conditional.condition) == .comptime_expr
condition :: if (explicit) ast.comptime_operand(&state.tree, conditional.condition) else conditional.condition
try expand(state, condition)
if (conditional.capture == null and (explicit or branch_constant(state, condition, 0))) {
value :: try evaluate_ir(state, condition, try builtin(state, condition, .bool))
if (value.kind != .boolean) return .invalid
if (value.boolean) selected = conditional.then_block
else if (conditional.else_block) |other| selected = other
else selected = try ast.add_block(state.builder, conditional.span, &[])
}
}
if (kind == .expression) {
expression :: ast.statement_expr(&state.tree, id)
if (ast.expr_kind(&state.tree, expression) == .match_expr) {
subject :: ast.match_expr(&state.tree, expression).subject
explicit :: ast.expr_kind(&state.tree, subject) == .comptime_expr
try expand(state, subject)
if (explicit or branch_constant(state, subject, 0)) {
index :: try type_match_arm(state, expression)
if (index == no_index) return .invalid
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
body :: try type_match_body(state, expression, arm)
if (body == no_node) return .invalid
if (!arm.expression) selected = body
else {
statement :: try ast.add_expression_stmt(state.builder, body)
statements [1]ast.NodeId :: [statement]
selected = try ast.add_block(state.builder, ast.node_span(&state.tree, id), statements[..])
}
}
}
}
if (selected != no_node) {
try refresh(state)
flow :: try inline_block(state, selected)
replacement :: try ast.add_block_stmt(state.builder, selected)
try replace(state, id, replacement)
try refresh(state)
return flow
}
if (inline_has_transfer(state, id)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.inline_loop_control, ast.node_span(&state.tree, id))
return .invalid
}
try expand(state, id)
return .next
}
@hide:file
inline_block proc(state @mut State, block ast.NodeId) TypeFlow ! Error {
statements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&statements)
flow := TypeFlow.next
for (0..ast.block_statement_count(&state.tree, block)) |i| {
statement :: ast.block_statement(&state.tree, block, i)
if (!try type_step(state, statement)) return .invalid
flow = try inline_statement(state, statement)
if (flow == .invalid) return flow
kind :: ast.stmt_kind(&state.tree, statement)
if (kind != .break_stmt and kind != .continue_stmt) try arraylist.append(&statements, statement)
if (flow != .next) break
}
replacement :: try ast.add_block(state.builder, ast.node_span(&state.tree, block), statements.items)
try replace(state, block, replacement)
try refresh(state)
return flow
}
@hide:file
expand_inline_for proc(state @mut State, id ast.NodeId) void ! Error {
loop :: ast.for_stmt(&state.tree, id)
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
state.type_execution_depth += 1
defer state.type_execution_depth -= 1
state.evaluation_diagnostic = diagnostics.no_diagnostic
blocks := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&blocks)
execution := TypeExecution{
locals = arraylist.init(state.allocator),
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
first_owned = ast.NodeId(0), result_type = no_node,
inline_body = loop.body, inline_blocks = &blocks,
}
defer arraylist.deinit(&execution.locals)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
for (0..usize(2)) |i| {
capture :: if (i == 0) loop.capture else loop.index
if (capture) |node| {
resolved :: binding(state, node)
if (resolved.kind == .invalid) state.evaluation_diagnostic = resolved.data.diagnostic
}
}
if (loop.pointer_capture)
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, loop.span)
flow := TypeFlow.invalid
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
flow = try execute_type_for(state, &execution, id)
if (flow != .next or state.evaluation_diagnostic != diagnostics.no_diagnostic) {
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_argument_required, loop.span)
invalid :: try ast.add_invalid_stmt(state.builder, loop.span, state.evaluation_diagnostic)
try replace(state, id, invalid)
try refresh(state)
return
}
statements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&statements)
for (blocks.items) |block| {
statement :: try ast.add_block_stmt(state.builder, block)
try arraylist.append(&statements, statement)
}
block :: try ast.add_block(state.builder, loop.span, statements.items)
replacement :: try ast.add_block_stmt(state.builder, block)
try replace(state, id, replacement)
try refresh(state)
}
# select with ordinary checked IR so patterns, overlap and exhaustiveness have
# the same rules as runtime matching, without checking unexecuted type bodies.
@hide:file
@@ -2373,6 +2528,13 @@ type_assignment_value proc(
@hide:file
execute_type_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId) TypeFlow ! Error {
if (block == execution.inline_body) {
snapshot :: try type_snapshot(state, execution, block)
flow :: try inline_block(state, snapshot)
if (flow == .invalid) return flow
try arraylist.append(execution.inline_blocks?, snapshot)
return flow
}
saved :: execution.locals.items.len
defer execution.locals.items = execution.locals.items[..saved]
for (0..ast.block_statement_count(&state.tree, block)) |i| {
@@ -3284,8 +3446,15 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
.array_literal: {
count :: ast.array_element_count(&state.tree, id)
if (count == 0) return no_node
element :: try expression_type(state, ast.array_element(&state.tree, id, 0), depth + 1)
element := try expression_type(state, ast.array_element(&state.tree, id, 0), depth + 1)
if (element == no_node) return no_node
# inferred values have concrete default widths even when a cast or
# procedure result used the int/float annotation shorthand.
scalar :: binding(state, element)
if (scalar.kind == .builtin) {
if (scalar.data.builtin == .int) element = try builtin(state, element, .i64)
if (scalar.data.builtin == .float) element = try builtin(state, element, .f64)
}
length :: try ast.add_integer(state.builder, ast.node_span(&state.tree, id), u64(count))
try refresh(state)
result :: try ast.add_array_type(state.builder, ast.node_span(&state.tree, id), length, element, false)