feat: support yielding value blocks
This commit is contained in:
+41
-4
@@ -8,11 +8,13 @@
|
||||
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
||||
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
||||
#! value expressions also accept 'if' guard ['|' identifier '|'] expr 'else' expr, canonicalized to matches; else is required.
|
||||
#! bare braces containing statement syntax form a value block; contextual record literals keep their initializer grammar.
|
||||
#! 'yield' requires an expression on the same line and targets the nearest enclosing value construct, not a statement block.
|
||||
#! * block .... -> '{' {stmt} '}'
|
||||
#! * 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 | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'yield' 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, explicit comptime expressions 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.
|
||||
@@ -82,6 +84,7 @@ ExprKind :: enum {
|
||||
comptime_expr
|
||||
literal_void
|
||||
literal_undefined
|
||||
block_expr
|
||||
}
|
||||
|
||||
DeclKind :: enum {
|
||||
@@ -96,6 +99,7 @@ StmtKind :: enum {
|
||||
expression
|
||||
assignment_stmt
|
||||
return_stmt
|
||||
yield_stmt
|
||||
break_stmt
|
||||
continue_stmt
|
||||
if_stmt
|
||||
@@ -190,6 +194,8 @@ NodeKind :: enum {
|
||||
literal_float_value
|
||||
literal_void
|
||||
literal_undefined
|
||||
expr_block
|
||||
stmt_yield
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -207,7 +213,7 @@ NodeData :: union {
|
||||
@hide:file foreign_type_flag u8 :: 0b0010
|
||||
@hide:file variadic_flag u8 :: 0b0100
|
||||
@hide:file opaque_flag u8 :: 0b1000
|
||||
@hide:file optional_match_flag u8 :: 0b0001_0000
|
||||
@hide:file optional_match_flag u8 :: 0b00010000
|
||||
|
||||
@hide:file
|
||||
Node :: struct {
|
||||
@@ -956,6 +962,13 @@ add_block_stmt proc(builder @mut Builder, body NodeId) NodeId ! mem.AllocError {
|
||||
}, builder_span(builder, body))
|
||||
}
|
||||
|
||||
add_block_expr proc(builder @mut Builder, span Span, body NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_block,
|
||||
main = NodeData{ node_id = body },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_return_stmt proc(builder @mut Builder, span Span, value ?NodeId) NodeId ! mem.AllocError {
|
||||
raw_value :: if (value) |id| id else no_node
|
||||
return try add_node(builder, Node{
|
||||
@@ -964,6 +977,13 @@ add_return_stmt proc(builder @mut Builder, span Span, value ?NodeId) NodeId ! me
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_yield_stmt proc(builder @mut Builder, span Span, value NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .stmt_yield,
|
||||
main = NodeData{ node_id = value },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_break_stmt proc(builder @mut Builder, span Span) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{ kind = .stmt_break }, span)
|
||||
}
|
||||
@@ -1316,6 +1336,7 @@ stmt_kind proc(tree @Tree, id NodeId) StmtKind {
|
||||
.stmt_decl: .declaration
|
||||
.stmt_expr: .expression
|
||||
.stmt_return: .return_stmt
|
||||
.stmt_yield: .yield_stmt
|
||||
.stmt_break: .break_stmt
|
||||
.stmt_continue: .continue_stmt
|
||||
.stmt_if: .if_stmt
|
||||
@@ -1352,6 +1373,12 @@ statement_block proc(tree @Tree, id NodeId) NodeId {
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
block_expr proc(tree @Tree, id NodeId) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_block)
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
return_stmt proc(tree @Tree, id NodeId) ReturnView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .stmt_return)
|
||||
@@ -1361,6 +1388,15 @@ return_stmt proc(tree @Tree, id NodeId) ReturnView {
|
||||
}
|
||||
}
|
||||
|
||||
yield_stmt proc(tree @Tree, id NodeId) ReturnView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .stmt_yield)
|
||||
return ReturnView{
|
||||
span = get_span(&tree.storage, id),
|
||||
value = node.main.node_id,
|
||||
}
|
||||
}
|
||||
|
||||
if_stmt proc(tree @Tree, id NodeId) IfView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .stmt_if)
|
||||
@@ -1543,6 +1579,7 @@ expression_kind proc(kind NodeKind) ExprKind {
|
||||
.expr_catch: .catch_expr
|
||||
.expr_typed_constant: .typed_constant
|
||||
.expr_comptime: .comptime_expr
|
||||
.expr_block: .block_expr
|
||||
.literal_float_value: .literal_float
|
||||
.literal_void: .literal_void
|
||||
.literal_undefined: .literal_undefined
|
||||
@@ -1954,7 +1991,7 @@ node_layout proc(node Node) NodeLayout {
|
||||
layout.direct_children = 6
|
||||
.expr_pointer_type: layout.direct_children = 4
|
||||
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_union_tag_type, .expr_enum_literal,
|
||||
.expr_try, .expr_comptime: layout.direct_children = 1
|
||||
.expr_try, .expr_comptime, .expr_block: layout.direct_children = 1
|
||||
.expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal,
|
||||
.expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match,
|
||||
.block, .package: {
|
||||
@@ -2003,7 +2040,7 @@ node_layout proc(node Node) NodeLayout {
|
||||
layout.extra_count = 3
|
||||
layout.extra_children = 7
|
||||
}
|
||||
.stmt_expr, .stmt_return, .stmt_block, .stmt_defer, .stmt_errdefer: {
|
||||
.stmt_expr, .stmt_return, .stmt_yield, .stmt_block, .stmt_defer, .stmt_errdefer: {
|
||||
layout.class = .statement
|
||||
layout.direct_children = 1
|
||||
if (node.kind == .stmt_defer or node.kind == .stmt_errdefer)
|
||||
|
||||
@@ -72,6 +72,12 @@ render_block proc(renderer @mut Renderer, tree @ast.Tree, block ast.NodeId, dept
|
||||
}
|
||||
try append_text(renderer, "\n")
|
||||
}
|
||||
.yield_stmt: {
|
||||
try append_text(renderer, "yield ")
|
||||
value :: ast.yield_stmt(tree, statement).value?
|
||||
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, value)))
|
||||
try append_text(renderer, "\n")
|
||||
}
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
try append_text(renderer, "if ")
|
||||
|
||||
@@ -801,3 +801,49 @@ file_local_declarations_are_isolated_from_siblings_and_import_selection test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yielding_blocks_recompile_owned_constants_and_generated_types 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)
|
||||
bytes := try owned_text(
|
||||
`Choose proc(wide bool) type {
|
||||
` return { Selected := i8
|
||||
` if (wide) Selected = i16
|
||||
` yield Selected }
|
||||
`}
|
||||
`Box proc(wide bool) type {
|
||||
` return struct { value Choose(wide) = { yield 42 } }
|
||||
`}
|
||||
`compute proc() i16 {
|
||||
` return { value i16 := 40
|
||||
` defer value = 0
|
||||
` yield value + 2 }
|
||||
`}
|
||||
`main proc() i16 {
|
||||
` value Box(true) :: {}
|
||||
` constant :: $(compute())
|
||||
` return { yield if (constant == value.value) constant else -1 }
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&bytes)
|
||||
_ = try add_source(&session, root, "yielding.hon", &bytes, &diagnostic_store)
|
||||
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 modules |module| {
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ Code :: enum {
|
||||
cannot_infer_type
|
||||
missing_return
|
||||
return_type_mismatch
|
||||
yield_outside_block
|
||||
missing_yield
|
||||
yield_type_mismatch
|
||||
condition_not_bool
|
||||
invalid_assignment_target
|
||||
break_outside_loop
|
||||
@@ -181,6 +184,9 @@ messages :: $enummap.init(Code, []u8, {
|
||||
cannot_infer_type = "cannot infer a type without a type anchor",
|
||||
missing_return = "nonvoid function can finish without returning a value",
|
||||
return_type_mismatch = "return does not match the function result type",
|
||||
yield_outside_block = "yield requires an enclosing value block",
|
||||
missing_yield = "nonvoid value block can finish without yielding a value",
|
||||
yield_type_mismatch = "yield does not match the value block result type",
|
||||
condition_not_bool = "condition must have type bool",
|
||||
invalid_assignment_target = "assignment requires a mutable local binding",
|
||||
break_outside_loop = "break requires an enclosing loop",
|
||||
@@ -253,7 +259,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
incompatible_propagated_error = "propagated errors are not a subset of the function error channel",
|
||||
catch_type_mismatch = "catch handler must produce the success type or terminate",
|
||||
errdefer_outside_fallible = "errdefer requires an enclosing fallible function",
|
||||
cleanup_control_flow = "cleanup cannot escape with return, break, continue, or try",
|
||||
cleanup_control_flow = "cleanup cannot transfer control out of itself",
|
||||
comptime_argument_required = "generic argument must be a compile-time value",
|
||||
cannot_infer_generic = "cannot infer consistent compile-time arguments from runtime arguments",
|
||||
specialization_cycle = "compile-time specialization depends on its unfinished result",
|
||||
|
||||
@@ -25,6 +25,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "null", .null },
|
||||
{ "undefined", .undefined },
|
||||
{ "return", .return },
|
||||
{ "yield", .yield },
|
||||
{ "break", .break },
|
||||
{ "continue", .continue },
|
||||
{ "try", .try },
|
||||
|
||||
@@ -275,3 +275,18 @@ scans_import_keywords_and_preserves_source_in_tokens_and_diagnostics test {
|
||||
try testing.expect_equal(state.source_id, diagnostic.span.source)
|
||||
}
|
||||
}
|
||||
|
||||
reserves_yield_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, "yield yielding yield_1 .yield", &pool, &diagnostic_store)
|
||||
expected [6]TokenKind :: [.yield, .ident, .ident, .dot, .yield, .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("yield", strpool.get_str(&pool, state.tokens.items[0].data.str_id)?)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ TokenKind :: enum {
|
||||
match
|
||||
inline
|
||||
proc, return
|
||||
yield
|
||||
break, continue
|
||||
try, catch
|
||||
defer, errdefer
|
||||
|
||||
+82
-4
@@ -24,6 +24,15 @@ LoopTargets :: struct {
|
||||
cleanup_depth usize
|
||||
}
|
||||
|
||||
@hide:file
|
||||
ValueTarget :: struct {
|
||||
join ir.BlockId
|
||||
slot u32
|
||||
type typecheck.Type
|
||||
cleanup_depth usize
|
||||
reached bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
State :: struct {
|
||||
tree @ast.Tree
|
||||
@@ -40,6 +49,7 @@ State :: struct {
|
||||
block ir.BlockId = ir.BlockId(0)
|
||||
terminated bool = false
|
||||
loop ?LoopTargets = null
|
||||
value_target ?@mut ValueTarget = null
|
||||
return_type typecheck.Type = typecheck.Type{}
|
||||
fallthrough_block ?ast.NodeId = null
|
||||
fallthrough_diagnostic ?diagnostics.DiagnosticId = null
|
||||
@@ -412,6 +422,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
||||
state.block = ir.BlockId(0)
|
||||
state.terminated = false
|
||||
state.loop = null
|
||||
state.value_target = null
|
||||
state.fallthrough_block = declaration.body
|
||||
state.fallthrough_diagnostic = typecheck.fallthrough_diagnostic(state.checked, id)
|
||||
state.return_type = typecheck.type_of(state.checked, declaration.return_type)
|
||||
@@ -559,8 +570,53 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
|
||||
if (!state.terminated) try lower_cleanups(state, cleanup_depth, ir.no_value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_value_block proc(state @mut State, block, owner ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
span :: ast.node_span(state.tree, block)
|
||||
runtime :: lower_type(state, type)
|
||||
shape :: try array_shape(state, type)
|
||||
slot := u32(0)
|
||||
if (runtime != .void) slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
||||
join :: try add_block(state)
|
||||
reached :: try lower_value_body(state, block, owner, type, slot, join)
|
||||
state.block = join
|
||||
state.terminated = !reached
|
||||
if (!reached) state.module.functions.items[state.function].blocks.items[usize(join)].terminator = ir.Terminator{ kind = .unreachable }
|
||||
if (!reached or runtime == .void) return ir.no_value
|
||||
return try emit(state, ir.Instruction{ op = .load_local, type = runtime, array_type = shape, slot = slot, span = span })
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_value_body proc(state @mut State, block, owner ast.NodeId, type typecheck.Type, slot u32, join ir.BlockId) bool ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (trap_type(state, block)) return false
|
||||
target := ValueTarget{ join = join, slot = slot, type = type, cleanup_depth = state.cleanups.items.len }
|
||||
previous_target :: state.value_target
|
||||
state.value_target = &target
|
||||
defer state.value_target = previous_target
|
||||
previous_fallthrough :: state.fallthrough_block
|
||||
previous_diagnostic :: state.fallthrough_diagnostic
|
||||
state.fallthrough_block = block
|
||||
state.fallthrough_diagnostic = typecheck.fallthrough_diagnostic(state.checked, owner)
|
||||
defer state.fallthrough_block = previous_fallthrough
|
||||
defer state.fallthrough_diagnostic = previous_diagnostic
|
||||
try predeclare_locals(state, block)
|
||||
try lower_block(state, block, true)
|
||||
if (!state.terminated) {
|
||||
# checking permits an implicit result only for void blocks.
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = ast.node_span(state.tree, block),
|
||||
}
|
||||
target.reached = true
|
||||
state.terminated = true
|
||||
}
|
||||
return target.reached
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_cleanups proc(state @mut State, depth usize, error_value ir.ValueId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
previous_target :: state.value_target
|
||||
state.value_target = null
|
||||
defer state.value_target = previous_target
|
||||
index := state.cleanups.items.len
|
||||
while (index > depth) {
|
||||
index -= 1
|
||||
@@ -932,6 +988,23 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
span = declaration.span,
|
||||
})
|
||||
}
|
||||
.yield_stmt: {
|
||||
statement :: ast.yield_stmt(tree, id)
|
||||
if (state.value_target) |target| {
|
||||
value :: try lower_as(state, statement.value?, target.type)
|
||||
if (state.terminated or trap_type(state, id)) return
|
||||
if (lower_type(state, target.type) != .void) _ = try emit(state, ir.Instruction{
|
||||
op = .store_local, left = value, slot = target.slot, span = statement.span,
|
||||
})
|
||||
try lower_cleanups(state, target.cleanup_depth, ir.no_value)
|
||||
if (state.terminated) return
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = target.join, span = statement.span,
|
||||
}
|
||||
target.reached = true
|
||||
state.terminated = true
|
||||
} else if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.return_stmt: {
|
||||
statement :: ast.return_stmt(tree, id)
|
||||
value := ir.no_value
|
||||
@@ -1067,7 +1140,6 @@ lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
local :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, capture_type), try array_shape(state, capture_type))
|
||||
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(local) }
|
||||
}
|
||||
if (!arm_view.expression) try predeclare_locals(state, arm_view.body)
|
||||
patterns :: ast.match_pattern_count(tree, arm)
|
||||
if (arm_view.catch_all) {
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
@@ -1119,7 +1191,10 @@ lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
||||
op = .store_local, left = value, slot = result_slot, span = span,
|
||||
})
|
||||
} else try lower_block(state, arm_view.body, true)
|
||||
} else {
|
||||
reached :: try lower_value_body(state, arm_view.body, arm_view.body, type, result_slot, join)
|
||||
if (reached) falls = true
|
||||
}
|
||||
if (!state.terminated) {
|
||||
falls = true
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
@@ -1602,8 +1677,7 @@ lower_catch proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
}
|
||||
if (!trap_type(state, expression.handler)) {
|
||||
if (expression.handler_is_block) {
|
||||
try predeclare_locals(state, expression.handler)
|
||||
try lower_block(state, expression.handler, true)
|
||||
_ = try lower_value_body(state, expression.handler, expression.handler, type, slot, join)
|
||||
} else {
|
||||
fallback :: try lower_as(state, expression.handler, type)
|
||||
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
||||
@@ -1934,6 +2008,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
.enum_literal: {
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.block_expr: {
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
return try lower_value_block(state, ast.block_expr(tree, id), id, typecheck.type_of(state.checked, id))
|
||||
}
|
||||
.match_expr: return try lower_match(state, id)
|
||||
.try_expr: return try lower_try(state, id)
|
||||
.catch_expr: return try lower_catch(state, id)
|
||||
|
||||
+68
-3
@@ -321,6 +321,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.while: return try parse_while(state, allow_else)
|
||||
.for, .inline: return try parse_for(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.yield: return try parse_yield(state, allow_else)
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.defer, .errdefer: return try parse_cleanup(state, allow_else)
|
||||
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
|
||||
@@ -385,6 +386,31 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_yield proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .yield)
|
||||
value :: if (
|
||||
next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .eof)
|
||||
or (state.match_arm_body and next_is(state, .comma))
|
||||
or (allow_else and next_is(state, .else))
|
||||
) {
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, keyword.span)
|
||||
yield try ast.add_invalid_expr(state.builder, keyword.span, diagnostic)
|
||||
} else {
|
||||
yield try parse_expr(state)
|
||||
}
|
||||
if (
|
||||
!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) return .unexpected_token
|
||||
return try ast.add_yield_stmt(state.builder, ast.Span{
|
||||
start = keyword.span.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = keyword.span.source,
|
||||
}, value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: current(state)
|
||||
@@ -925,7 +951,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
||||
@hide:file
|
||||
member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .break, .continue,
|
||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .yield, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
|
||||
else: false
|
||||
@@ -1158,7 +1184,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, .inline, .defer, .errdefer: true
|
||||
.open_curly, .return, .yield, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
body ast.NodeId := undefined
|
||||
@@ -1701,6 +1727,41 @@ named_binding_starts proc(state @State) bool {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
brace_starts_block proc(state @State) bool {
|
||||
first := state.next_token + 1
|
||||
while (first < state.tokens.len and state.tokens[first].kind == .newline) first += 1
|
||||
# empty records and braces without statement markers retain record syntax.
|
||||
if (first >= state.tokens.len or state.tokens[first].kind == .close_curly) return false
|
||||
depth := usize(0)
|
||||
for (state.next_token)..(state.tokens.len) |index| {
|
||||
kind :: state.tokens[index].kind
|
||||
if (kind == .eof) return false
|
||||
if (depth > 0
|
||||
and (state.tokens[first].kind == .if or state.tokens[first].kind == .open_curly)
|
||||
and (kind == .yield or kind == .return or kind == .defer or kind == .errdefer)
|
||||
and state.tokens[index - 1].kind != .dot
|
||||
and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) return true
|
||||
if (depth == 1) {
|
||||
if (kind == .double_colon or kind == .colon_equal) return true
|
||||
previous :: if (index > first) state.tokens[index - 1].kind else lexer.TokenKind.open_curly
|
||||
if (previous != .dot and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) {
|
||||
match kind {
|
||||
.yield, .return, .break, .continue, .while, .for, .inline, .defer, .errdefer: return true
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kind == .open_curly or kind == .open_paren or kind == .open_bracket) depth += 1
|
||||
if (kind == .close_curly or kind == .close_paren or kind == .close_bracket) {
|
||||
if (depth == 0) return false
|
||||
depth -= 1
|
||||
if (depth == 0) return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: current(state)
|
||||
@@ -1736,6 +1797,10 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
}, member_id)
|
||||
}
|
||||
.open_curly: {
|
||||
if (state.allow_record_literals and brace_starts_block(state)) {
|
||||
body :: try parse_block(state)
|
||||
return try ast.add_block_expr(state.builder, ast.builder_span(state.builder, body), body)
|
||||
}
|
||||
if (state.allow_record_literals) return try parse_record_literal(state, null, token.span.start)
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
|
||||
@@ -1808,7 +1873,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, .inline, .defer, .errdefer, .eof: true
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .yield, .if, .while, .for, .inline, .defer, .errdefer, .eof: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2040,3 +2040,205 @@ keeps_match_default_arm_after_newline_single_statement_if test {
|
||||
try testing.expect(branch.else_block == null)
|
||||
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, expression, 1)).catch_all)
|
||||
}
|
||||
|
||||
parses_value_blocks_and_clones_yield_children 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(flag bool) int {
|
||||
` result :: {
|
||||
` local :: 3
|
||||
` { if (flag) yield local }
|
||||
` yield { yield 4 }
|
||||
` }
|
||||
` return if (flag) { yield result } else { yield 5 }
|
||||
`}
|
||||
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| {
|
||||
function :: ast.function_decl(&tree, ast.child(&tree, package, 0))
|
||||
value :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, value))
|
||||
try testing.expect_equal(ast.NodeClass.expression, ast.node_class(&tree, value))
|
||||
block :: ast.block_expr(&tree, value)
|
||||
try testing.expect_equal(3, ast.block_statement_count(&tree, block))
|
||||
ordinary :: ast.statement_block(&tree, ast.block_statement(&tree, block, 1))
|
||||
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, ordinary, 0))
|
||||
first :: ast.block_statement(&tree, branch.then_block, 0)
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, first))
|
||||
try testing.expect_equal(ast.NodeClass.statement, ast.node_class(&tree, first))
|
||||
try testing.expect_equal("yield local", ast.source_text(&tree, ast.yield_stmt(&tree, first).span))
|
||||
last :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 2))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, last.value?))
|
||||
nested :: ast.block_statement(&tree, ast.block_expr(&tree, last.value?), 0)
|
||||
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, ast.yield_stmt(&tree, nested).value?))
|
||||
conditional :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 1)).value?
|
||||
for (0..ast.match_arm_count(&tree, conditional)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, conditional, index))
|
||||
try testing.expect(arm.expression)
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, arm.body))
|
||||
}
|
||||
}
|
||||
first_function :: ast.function_decl(&tree, ast.child(&tree, root, 0))
|
||||
first_value :: ast.statement_decl(&tree, ast.block_statement(&tree, first_function.body, 0)).initializer
|
||||
copied_value :: mapping[usize(first_value)]
|
||||
try testing.expect(copied_value != first_value)
|
||||
try testing.expect_equal(mapping[usize(ast.block_expr(&tree, first_value))], ast.block_expr(&tree, copied_value))
|
||||
}
|
||||
|
||||
parses_value_blocks_starting_with_named_assignments test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`main proc() i16 {
|
||||
` value ?i16 :: 42
|
||||
` return if value |payload| { payload = 7
|
||||
` yield payload
|
||||
` } else 0
|
||||
`}
|
||||
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))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
conditional :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 1)).value?
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, conditional, 0))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, arm.body))
|
||||
block :: ast.block_expr(&tree, arm.body)
|
||||
try testing.expect_equal(2, ast.block_statement_count(&tree, block))
|
||||
first :: ast.block_statement(&tree, block, 0)
|
||||
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, first))
|
||||
assignment :: ast.assignment_stmt(&tree, first)
|
||||
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, assignment.target)))
|
||||
try testing.expect_equal("7", ast.source_text(&tree, ast.node_span(&tree, assignment.value)))
|
||||
last :: ast.block_statement(&tree, block, 1)
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, last))
|
||||
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, ast.yield_stmt(&tree, last).value?)))
|
||||
}
|
||||
|
||||
preserves_record_literals_alongside_value_blocks test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`empty :: {}
|
||||
`single :: {1}
|
||||
`pair :: {1, 2}
|
||||
`keyed :: { value = { yield 3 } }
|
||||
`keyword :: { yield = 4 }
|
||||
`choose :: if (true) { value = 7 } else { value = 8 }
|
||||
`conditional :: {if (true) 1 else 2}
|
||||
`block :: { if (true) yield 9 }
|
||||
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))
|
||||
try testing.expect_equal(8, ast.package_decl_count(&tree))
|
||||
for (usize(0)..usize(5)) |index| {
|
||||
value :: ast.statement_decl(&tree, ast.package_decl(&tree, index)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, value))
|
||||
}
|
||||
choose :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
|
||||
for (usize(0)..usize(2)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, choose, index))
|
||||
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, arm.body))
|
||||
}
|
||||
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 6)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, conditional))
|
||||
block :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, block))
|
||||
}
|
||||
|
||||
requires_yield_values_and_recovers_following_statements 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() int {
|
||||
` value :: {
|
||||
` yield
|
||||
` after :: 2
|
||||
` if (true) yield else yield 3
|
||||
` yield 4 5
|
||||
` yield after
|
||||
` }
|
||||
` return value
|
||||
`}
|
||||
`tail :: 7
|
||||
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(3, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
value :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
|
||||
block :: ast.block_expr(&tree, value)
|
||||
try testing.expect_equal(5, ast.block_statement_count(&tree, block))
|
||||
missing :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 0))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.value?))
|
||||
try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, block, 1)))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, block, 3)))
|
||||
last :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 4))
|
||||
try testing.expect_equal("after", ast.source_text(&tree, ast.node_span(&tree, last.value?)))
|
||||
}
|
||||
|
||||
keeps_yielding_match_and_catch_bodies_as_raw_blocks 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() int {
|
||||
` selected :: match 1 { 1: { yield 2 }, else: yield 3 }
|
||||
` recovered :: selected catch |err| { yield err }
|
||||
` return use({ yield recovered }, $({ yield 4 }))
|
||||
`}
|
||||
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))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
selected :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
|
||||
for (usize(0)..usize(2)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, selected, index))
|
||||
try testing.expect(!arm.expression)
|
||||
try testing.expect_equal(ast.NodeClass.block, ast.node_class(&tree, arm.body))
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, arm.body, 0)))
|
||||
}
|
||||
recovered :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)).initializer
|
||||
handler :: ast.catch_expr(&tree, recovered)
|
||||
try testing.expect(handler.handler_is_block)
|
||||
try testing.expect_equal(ast.NodeClass.block, ast.node_class(&tree, handler.handler))
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, handler.handler, 0)))
|
||||
call :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 2)).value?
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.call_arg(&tree, call, 0)))
|
||||
constant :: ast.call_arg(&tree, call, 1)
|
||||
try testing.expect_equal(ast.ExprKind.comptime_expr, ast.expr_kind(&tree, constant))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.comptime_operand(&tree, constant)))
|
||||
}
|
||||
|
||||
+208
-43
@@ -122,7 +122,7 @@ Storage :: struct {
|
||||
allocator mem.Allocator
|
||||
}
|
||||
|
||||
#! owns node-indexed types, aggregate descriptors, initializer mappings, and function fallthrough obligations; release with deinit.
|
||||
#! owns node-indexed types, aggregate descriptors, initializer mappings, and function/value-block fallthrough obligations; release with deinit.
|
||||
#! borrows the caller-owned AST for signature comparisons; diagnostic IDs refer to the caller-owned store.
|
||||
Result :: struct { storage Storage }
|
||||
|
||||
@@ -140,6 +140,8 @@ Role :: enum {
|
||||
invalid,
|
||||
body,
|
||||
return_stmt,
|
||||
yield_stmt,
|
||||
value_body,
|
||||
if_stmt,
|
||||
assignment_stmt,
|
||||
while_stmt,
|
||||
@@ -186,6 +188,9 @@ Cell :: struct {
|
||||
enclosing_arm ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
match_owner ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
narrowed_type Type = Type{}
|
||||
yield_owner ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
first_yield ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
next_yield ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -226,6 +231,7 @@ State :: struct {
|
||||
collecting_assignment bool = false
|
||||
has_assignments bool = false
|
||||
pointer_depth usize = 0
|
||||
yield_owner ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
}
|
||||
|
||||
#! borrows `resolution` and `diagnostic_store` during checking; Result retains the AST borrow for signature comparisons.
|
||||
@@ -599,6 +605,28 @@ check_elaborated proc(
|
||||
}
|
||||
}
|
||||
}
|
||||
for 0..count |i| {
|
||||
id :: ast.NodeId(i)
|
||||
explicit :: cells[i].role == .expression and ast.expr_kind(tree, id) == .block_expr
|
||||
if (!explicit and cells[i].role != .value_body) continue
|
||||
result :: types[i]
|
||||
if (result.kind == .none or result.kind == .void or result.kind == .invalid) continue
|
||||
body :: if (explicit) ast.block_expr(tree, id) else id
|
||||
if (!block_falls_through(&state, body)) continue
|
||||
diagnostic :: try diagnostics.add(diagnostic_store, .missing_yield, ast.node_span(tree, body))
|
||||
hashmap.put(&fallthroughs, id, diagnostic) catch |err| {
|
||||
match err {
|
||||
.key_exists: unreachable
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
if (explicit) hashmap.put(&fallthroughs, body, diagnostic) catch |err| {
|
||||
match err {
|
||||
.key_exists: unreachable
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result{ storage = Storage{
|
||||
tree = tree,
|
||||
types = types,
|
||||
@@ -622,10 +650,10 @@ type_of proc(result @Result, id ast.NodeId) Type {
|
||||
return result.storage.types[usize(id)]
|
||||
}
|
||||
|
||||
#! returns the original diagnostic for a function's nonvoid fallthrough, independent of body errors.
|
||||
#! null means the function has no fallthrough obligation; lowering still owns trap placement.
|
||||
fallthrough_diagnostic proc(result @Result, function ast.NodeId) ?diagnostics.DiagnosticId {
|
||||
return hashmap.get(&result.storage.fallthroughs, function)
|
||||
#! returns the original diagnostic for a function or value block's nonvoid fallthrough.
|
||||
#! null means there is no fallthrough obligation; lowering still owns trap placement.
|
||||
fallthrough_diagnostic proc(result @Result, owner ast.NodeId) ?diagnostics.DiagnosticId {
|
||||
return hashmap.get(&result.storage.fallthroughs, owner)
|
||||
}
|
||||
|
||||
array_type_count proc(result @Result) usize {
|
||||
@@ -824,6 +852,8 @@ join proc(state @mut State, left, right ast.NodeId) void {
|
||||
|
||||
@hide:file
|
||||
depend proc(state @mut State, source, dependent ast.NodeId) void ! mem.AllocError {
|
||||
# a value block keeps its result type when only an unexecuted path is invalid.
|
||||
if (state.cells[usize(dependent)].role == .value_body) return
|
||||
next :: state.cells[usize(source)].first_edge
|
||||
try arraylist.append(&state.edges, Edge{ dependent = dependent, next = next })
|
||||
state.cells[usize(source)].first_edge = state.edges.items.len - 1
|
||||
@@ -1035,6 +1065,17 @@ collect_capture proc(state @mut State, id, body ast.NodeId) void ! mem.AllocErro
|
||||
try depend(state, id, body)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
collect_value_block proc(state @mut State, body, owner ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
saved_owner :: state.yield_owner
|
||||
state.yield_owner = owner
|
||||
defer state.yield_owner = saved_owner
|
||||
# both explicit and implicit bodies preserve errors on their executed paths.
|
||||
state.cells[usize(body)].role = .value_body
|
||||
state.cells[usize(body)].assignment_value = state.collecting_assignment
|
||||
try collect_block(state, body, state.return_annotation, state.in_loop)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
saved_annotation :: state.return_annotation
|
||||
@@ -1046,7 +1087,7 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
state.in_loop = saved_loop
|
||||
}
|
||||
tree :: state.tree
|
||||
state.cells[usize(id)].role = .body
|
||||
if (state.cells[usize(id)].role == .none) state.cells[usize(id)].role = .body
|
||||
for (0..ast.block_statement_count(tree, id)) |i| {
|
||||
statement :: ast.block_statement(tree, id, i)
|
||||
match ast.stmt_kind(tree, statement) {
|
||||
@@ -1067,6 +1108,23 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
try depend(state, value, statement)
|
||||
}
|
||||
}
|
||||
.yield_stmt: {
|
||||
state.cells[usize(statement)].role = .yield_stmt
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
owner :: state.yield_owner
|
||||
state.cells[usize(statement)].yield_owner = owner
|
||||
if (owner == ast.NodeId(maxval!(u32))) {
|
||||
code :: if (state.in_cleanup) diagnostics.Code.cleanup_control_flow else diagnostics.Code.yield_outside_block
|
||||
try fail(state, statement, code, ast.node_span(tree, statement))
|
||||
} else {
|
||||
state.cells[usize(statement)].next_yield = state.cells[usize(owner)].first_yield
|
||||
state.cells[usize(owner)].first_yield = statement
|
||||
}
|
||||
if (ast.yield_stmt(tree, statement).value) |value| {
|
||||
try collect_expression(state, value)
|
||||
try depend(state, value, statement)
|
||||
}
|
||||
}
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
state.cells[usize(statement)].role = .if_stmt
|
||||
@@ -1165,7 +1223,7 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
cleanup :: ast.cleanup_stmt(tree, statement)
|
||||
state.cells[usize(statement)].role = .cleanup
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
result :: state.types[usize(return_annotation)]
|
||||
result :: if (return_annotation == ast.NodeId(maxval!(u32))) Type{} else state.types[usize(return_annotation)]
|
||||
if (ast.stmt_kind(tree, statement) == .errdefer_stmt and result.kind != .fallible and result.kind != .invalid) {
|
||||
state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
|
||||
state.diagnostics, .errdefer_outside_fallible, cleanup.span,
|
||||
@@ -1177,9 +1235,12 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
state.arrays.items[usize(result.array_type)].error_type else result
|
||||
}
|
||||
saved_cleanup :: state.in_cleanup
|
||||
saved_owner :: state.yield_owner
|
||||
state.yield_owner = ast.NodeId(maxval!(u32))
|
||||
state.in_cleanup = true
|
||||
try collect_block(state, cleanup.body, return_annotation, false)
|
||||
state.in_cleanup = saved_cleanup
|
||||
state.yield_owner = saved_owner
|
||||
}
|
||||
.break_stmt, .continue_stmt: {
|
||||
state.cells[usize(statement)].role = .loop_control
|
||||
@@ -1198,7 +1259,7 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
.invalid: collect_invalid(state, statement)
|
||||
}
|
||||
source :: if (ast.stmt_kind(tree, statement) == .expression) ast.statement_expr(tree, statement) else statement
|
||||
try depend(state, source, id)
|
||||
if (state.cells[usize(id)].role != .value_body) try depend(state, source, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1212,6 +1273,9 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
state.cells[usize(id)].in_cleanup = state.in_cleanup
|
||||
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
|
||||
match ast.expr_kind(tree, id) {
|
||||
.block_expr: {
|
||||
try collect_value_block(state, ast.block_expr(tree, id), id)
|
||||
}
|
||||
.comptime_expr: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
|
||||
.typed_constant: {
|
||||
value :: ast.typed_constant(tree, id)
|
||||
@@ -1235,7 +1299,8 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
state.cells[usize(capture)].type = Type{}
|
||||
}
|
||||
if (expression.handler_is_block) {
|
||||
try collect_block(state, expression.handler, state.return_annotation, state.in_loop)
|
||||
try collect_value_block(state, expression.handler, expression.handler)
|
||||
try depend(state, expression.handler, id)
|
||||
} else try collect_expression(state, expression.handler)
|
||||
}
|
||||
.literal_int: {
|
||||
@@ -1615,11 +1680,20 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
|
||||
return
|
||||
}
|
||||
}
|
||||
if (
|
||||
state.cells[usize(id)].role == .value_body
|
||||
or (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .block_expr)
|
||||
) {
|
||||
state.cells[index].expected = expected
|
||||
try demand_yields(state, id, expected, code)
|
||||
seed(state, id, expected)
|
||||
return
|
||||
}
|
||||
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
|
||||
state.cells[index].expected = expected
|
||||
for 0..ast.match_arm_count(state.tree, id) |i| {
|
||||
arm :: ast.match_arm_view(state.tree, ast.match_arm(state.tree, id, i))
|
||||
if (arm.expression) try demand(state, arm.body, expected, code)
|
||||
try demand(state, arm.body, expected, code)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1981,9 +2055,23 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
try validate_match(state, id)
|
||||
return
|
||||
}
|
||||
if (state.cells[usize(id)].role == .yield_stmt) {
|
||||
owner :: state.cells[usize(id)].yield_owner
|
||||
if (owner == ast.NodeId(maxval!(u32)) or state.types[usize(id)].kind == .invalid) return
|
||||
expected :: state.types[usize(owner)]
|
||||
if (expected.kind == .none or expected.kind == .invalid) return
|
||||
if (ast.yield_stmt(tree, id).value) |value| {
|
||||
actual :: state.types[usize(value)]
|
||||
if (actual.kind != .invalid and !assignable_value(state, value, actual, expected)) {
|
||||
try fail(state, value, .yield_type_mismatch, ast.node_span(tree, value))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
match state.cells[usize(id)].role {
|
||||
.declaration: {
|
||||
declaration :: ast.statement_decl(tree, id)
|
||||
if (!expression_falls_through(state, declaration.initializer)) return
|
||||
if (declaration.type == null and state.types[usize(declaration.initializer)].kind == .null) {
|
||||
try fail(state, declaration.initializer, .cannot_infer_type, ast.node_span(tree, declaration.initializer))
|
||||
}
|
||||
@@ -2013,6 +2101,10 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
}
|
||||
.return_stmt: {
|
||||
statement :: ast.return_stmt(tree, id)
|
||||
if (state.cells[usize(id)].return_annotation == ast.NodeId(maxval!(u32))) {
|
||||
try fail(state, id, .return_type_mismatch, statement.span)
|
||||
return
|
||||
}
|
||||
expected :: state.types[usize(state.cells[usize(id)].return_annotation)]
|
||||
if (expected.kind == .invalid) return
|
||||
if (state.types[usize(id)].kind == .invalid) return
|
||||
@@ -2194,38 +2286,85 @@ validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
||||
}
|
||||
|
||||
|
||||
@hide:file
|
||||
BlockFlow :: struct {
|
||||
falls bool = true
|
||||
yields bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
block_falls_through proc(state @State, id ast.NodeId) bool {
|
||||
return block_flow(state, id).falls
|
||||
}
|
||||
|
||||
@hide:file
|
||||
value_block_completes proc(state @State, id ast.NodeId) bool {
|
||||
flow :: block_flow(state, id)
|
||||
return flow.falls or flow.yields
|
||||
}
|
||||
|
||||
@hide:file
|
||||
expression_falls_through proc(state @State, id ast.NodeId) bool {
|
||||
tree :: state.tree
|
||||
match ast.expr_kind(tree, id) {
|
||||
.block_expr: return value_block_completes(state, ast.block_expr(tree, id))
|
||||
.match_expr: {
|
||||
if (!state.exhaustive[usize(id)]) return true
|
||||
for 0..ast.match_arm_count(tree, id) |i| {
|
||||
arm :: ast.match_arm_view(tree, ast.match_arm(tree, id, i))
|
||||
if (arm.expression) {
|
||||
if (expression_falls_through(state, arm.body)) return true
|
||||
} else if (value_block_completes(state, arm.body)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else: return true
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
block_flow proc(state @State, id ast.NodeId) BlockFlow {
|
||||
tree :: state.tree
|
||||
result := BlockFlow{}
|
||||
for (0..ast.block_statement_count(tree, id)) |i| {
|
||||
statement :: ast.block_statement(tree, id, i)
|
||||
kind :: ast.stmt_kind(tree, statement)
|
||||
source :: if (kind == .expression) ast.statement_expr(tree, statement) else statement
|
||||
if (state.types[usize(source)].kind == .invalid) return false
|
||||
if (state.types[usize(source)].kind == .invalid) {
|
||||
result.falls = false
|
||||
return result
|
||||
}
|
||||
match kind {
|
||||
.return_stmt, .break_stmt, .continue_stmt, .invalid: return false
|
||||
.block_stmt: if (!block_falls_through(state, ast.statement_block(tree, statement))) return false
|
||||
.yield_stmt: {
|
||||
result.yields = true
|
||||
result.falls = false
|
||||
}
|
||||
.return_stmt, .break_stmt, .continue_stmt, .invalid: result.falls = false
|
||||
.block_stmt: {
|
||||
nested :: block_flow(state, ast.statement_block(tree, statement))
|
||||
result.yields = result.yields or nested.yields
|
||||
result.falls = nested.falls
|
||||
}
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
then_falls :: block_falls_through(state, conditional.then_block)
|
||||
then_flow :: block_flow(state, conditional.then_block)
|
||||
result.yields = result.yields or then_flow.yields
|
||||
if (conditional.else_block) |other| {
|
||||
if (!then_falls and !block_falls_through(state, other)) return false
|
||||
}
|
||||
}
|
||||
.expression: {
|
||||
if (ast.expr_kind(tree, source) == .match_expr and state.exhaustive[usize(source)]) {
|
||||
falls := false
|
||||
for 0..ast.match_arm_count(tree, source) |j| {
|
||||
arm :: ast.match_arm_view(tree, ast.match_arm(tree, source, j))
|
||||
if (arm.expression or block_falls_through(state, arm.body)) falls = true
|
||||
}
|
||||
if (!falls) return false
|
||||
else_flow :: block_flow(state, other)
|
||||
result.yields = result.yields or else_flow.yields
|
||||
result.falls = then_flow.falls or else_flow.falls
|
||||
}
|
||||
}
|
||||
.while_stmt: result.yields = result.yields or block_flow(state, ast.while_stmt(tree, statement).body).yields
|
||||
.for_stmt: result.yields = result.yields or block_flow(state, ast.for_stmt(tree, statement).body).yields
|
||||
.expression: result.falls = expression_falls_through(state, source)
|
||||
.declaration: result.falls = expression_falls_through(state, ast.statement_decl(tree, statement).initializer)
|
||||
.assignment_stmt: result.falls = expression_falls_through(state, ast.assignment_stmt(tree, statement).value)
|
||||
else: {}
|
||||
}
|
||||
if (!result.falls) return result
|
||||
}
|
||||
return true
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -2802,6 +2941,37 @@ unify_elements proc(state @mut State, left, right ast.NodeId) void ! (mem.AllocE
|
||||
if (right_type.kind == .array or right_type.kind == .record) try demand(state, left, right_type, .type_mismatch)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
demand_yields proc(state @mut State, owner ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
yield_id := state.cells[usize(owner)].first_yield
|
||||
while (yield_id != ast.NodeId(maxval!(u32))) {
|
||||
if (ast.yield_stmt(state.tree, yield_id).value) |value| try demand(state, value, expected, code)
|
||||
yield_id = state.cells[usize(yield_id)].next_yield
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
infer_value_block proc(state @mut State, owner ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
expected := state.cells[root(state, usize(owner))].expected
|
||||
yield_id := state.cells[usize(owner)].first_yield
|
||||
while (yield_id != ast.NodeId(maxval!(u32))) {
|
||||
if (ast.yield_stmt(state.tree, yield_id).value) |value| {
|
||||
if (expected.kind != .none) try demand(state, value, expected, .yield_type_mismatch)
|
||||
actual :: try infer_shape(state, value, defaults)
|
||||
if (expected.kind == .none and actual.kind != .none and actual.kind != .invalid) expected = actual
|
||||
}
|
||||
yield_id = state.cells[usize(yield_id)].next_yield
|
||||
}
|
||||
if (expected.kind == .none and defaults and state.cells[usize(owner)].first_yield == ast.NodeId(maxval!(u32))) {
|
||||
expected = Type{ kind = .void }
|
||||
}
|
||||
if (expected.kind != .none) {
|
||||
try demand_yields(state, owner, expected, .yield_type_mismatch)
|
||||
seed(state, owner, expected)
|
||||
}
|
||||
return group_type(state, owner)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
index :: root(state, usize(id))
|
||||
@@ -2809,6 +2979,10 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
|
||||
state.cells[index].visiting = true
|
||||
defer state.cells[index].visiting = false
|
||||
if (
|
||||
state.cells[usize(id)].role == .value_body
|
||||
or (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .block_expr)
|
||||
) return try infer_value_block(state, id, defaults)
|
||||
if (state.cells[usize(id)].role == .loop_capture and state.cells[usize(id)].for_loop != ast.NodeId(maxval!(u32))) {
|
||||
loop :: ast.for_stmt(state.tree, state.cells[usize(id)].for_loop)
|
||||
actual :: try infer_shape(state, loop.iterable, defaults)
|
||||
@@ -3186,7 +3360,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
||||
view :: ast.statement_decl(tree, id)
|
||||
if (view.type) |annotation| try context_demand(state, view.initializer, state.types[usize(annotation)], .type_mismatch, early)
|
||||
}
|
||||
if (!assignment_values and state.cells[i].role == .return_stmt) {
|
||||
if (!assignment_values and state.cells[i].role == .return_stmt and state.cells[i].return_annotation != ast.NodeId(maxval!(u32))) {
|
||||
if (ast.return_stmt(tree, id).value) |value| {
|
||||
try context_demand(state, value, state.types[usize(state.cells[i].return_annotation)], .return_type_mismatch, early)
|
||||
}
|
||||
@@ -4130,7 +4304,7 @@ collect_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
||||
if (arm.expression) {
|
||||
try collect_expression(state, arm.body)
|
||||
} else {
|
||||
try collect_block(state, arm.body, state.return_annotation, state.in_loop)
|
||||
try collect_value_block(state, arm.body, arm.body)
|
||||
}
|
||||
state.narrowing_arm = saved_arm
|
||||
}
|
||||
@@ -4173,7 +4347,6 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
result :: root(state, usize(id))
|
||||
expected := state.cells[result].expected
|
||||
has_block := false
|
||||
has_value := false
|
||||
for 0..ast.match_arm_count(state.tree, id) |i| {
|
||||
arm_id :: ast.match_arm(state.tree, id, i)
|
||||
arm :: ast.match_arm_view(state.tree, arm_id)
|
||||
@@ -4221,20 +4394,16 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arm.expression) {
|
||||
has_value = true
|
||||
if (arm.expression or state.cells[usize(arm.body)].first_yield != ast.NodeId(maxval!(u32))) {
|
||||
actual :: try infer_shape(state, arm.body, defaults)
|
||||
if (expected.kind == .none and actual.kind != .none and actual.kind != .invalid) expected = actual
|
||||
} else has_block = true
|
||||
}
|
||||
if (has_block and !has_value) expected = Type{ kind = .void }
|
||||
if (has_block and has_value and defaults) {
|
||||
try fail(state, id, .type_mismatch, ast.node_span(state.tree, id))
|
||||
}
|
||||
if (expected.kind == .none and has_block and defaults) expected = Type{ kind = .void }
|
||||
if (expected.kind != .none) {
|
||||
for 0..ast.match_arm_count(state.tree, id) |i| {
|
||||
arm :: ast.match_arm_view(state.tree, ast.match_arm(state.tree, id, i))
|
||||
if (arm.expression) try demand(state, arm.body, expected, .type_mismatch)
|
||||
try demand(state, arm.body, expected, .type_mismatch)
|
||||
}
|
||||
seed(state, id, expected)
|
||||
}
|
||||
@@ -4685,10 +4854,8 @@ infer_fallible_expression proc(state @mut State, id ast.NodeId, defaults bool) T
|
||||
if (catching) {
|
||||
expression :: ast.catch_expr(state.tree, id)
|
||||
if (expression.capture) |capture| seed_capture(state, capture, descriptor.error_type)
|
||||
if (!expression.handler_is_block) {
|
||||
try demand(state, expression.handler, descriptor.element, .catch_type_mismatch)
|
||||
_ = try infer_shape(state, expression.handler, defaults)
|
||||
}
|
||||
try demand(state, expression.handler, descriptor.element, .catch_type_mismatch)
|
||||
_ = try infer_shape(state, expression.handler, defaults)
|
||||
}
|
||||
return group_type(state, id)
|
||||
}
|
||||
@@ -4706,9 +4873,7 @@ validate_fallible_expression proc(state @mut State, id ast.NodeId) void ! (mem.A
|
||||
descriptor :: state.arrays.items[usize(source.array_type)]
|
||||
if (catching) {
|
||||
expression :: ast.catch_expr(state.tree, id)
|
||||
if (expression.handler_is_block) {
|
||||
if (descriptor.element.kind != .void and block_falls_through(state, expression.handler)) try fail(state, expression.handler, .catch_type_mismatch, ast.node_span(state.tree, expression.handler))
|
||||
} else {
|
||||
if (!expression.handler_is_block) {
|
||||
actual :: state.types[usize(expression.handler)]
|
||||
if (actual.kind != .invalid and !assignable_value(state, expression.handler, actual, descriptor.element)) try fail(state, expression.handler, .catch_type_mismatch, ast.node_span(state.tree, expression.handler))
|
||||
}
|
||||
|
||||
@@ -411,6 +411,7 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
|
||||
.return_stmt: {
|
||||
if (ast.return_stmt(tree, statement).value) |value| try resolve_expr(state, scope, value)
|
||||
}
|
||||
.yield_stmt: try resolve_expr(state, scope, ast.yield_stmt(tree, statement).value?)
|
||||
.break_stmt, .continue_stmt: {}
|
||||
.block_stmt: try resolve_arm(state, scope, ast.statement_block(tree, statement))
|
||||
.defer_stmt, .errdefer_stmt: {
|
||||
@@ -539,6 +540,7 @@ resolve_expr proc(
|
||||
tree :: state.tree
|
||||
match ast.expr_kind(tree, id) {
|
||||
.comptime_expr: try resolve_expr(state, scope, ast.comptime_operand(tree, id))
|
||||
.block_expr: try resolve_arm(state, scope, ast.block_expr(tree, id))
|
||||
.typed_constant: {
|
||||
value :: ast.typed_constant(tree, id)
|
||||
try resolve_expr(state, scope, value.type)
|
||||
|
||||
@@ -98,6 +98,7 @@ State :: struct {
|
||||
evaluation_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||
type_execution_depth usize = 0
|
||||
type_execution_remaining usize = 0
|
||||
value_expansion_depth usize = 0
|
||||
}
|
||||
|
||||
#! elaborates templates into cached concrete declarations in the caller-owned `builder` arena.
|
||||
@@ -121,7 +122,9 @@ elaborate proc(
|
||||
for (0..ast.node_count(&original)) |i| {
|
||||
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 == .expression
|
||||
and (ast.expr_kind(&original, id) == .comptime_expr or ast.expr_kind(&original, id) == .block_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 == .match_arm and ast.match_arm_view(&original, id).is_inline) needed = true
|
||||
@@ -237,6 +240,42 @@ 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 == .expression and ast.expr_kind(&state.tree, id) == .block_expr) {
|
||||
body :: ast.block_expr(&state.tree, id)
|
||||
annotation :: try value_block_type(state, body, 0)
|
||||
if (is_type(state, annotation) or type_record_storage(state, annotation, 0)) {
|
||||
selected :: try evaluate_type_value_block(state, id, annotation)
|
||||
if (selected == no_node) {
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
try replace(state, id, selected)
|
||||
try refresh(state)
|
||||
state.bindings.items[usize(id)] = binding(state, selected)
|
||||
return
|
||||
}
|
||||
state.value_expansion_depth += 1
|
||||
defer state.value_expansion_depth -= 1
|
||||
try expand(state, body)
|
||||
return
|
||||
}
|
||||
if (class == .block and state.value_expansion_depth > 0) {
|
||||
statements := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&statements)
|
||||
count :: ast.block_statement_count(&state.tree, id)
|
||||
for (0..count) |i| {
|
||||
statement :: ast.block_statement(&state.tree, id, i)
|
||||
try expand(state, statement)
|
||||
try arraylist.append(&statements, statement)
|
||||
if (value_block_terminal(state, statement)) break
|
||||
}
|
||||
if (statements.items.len != count) {
|
||||
replacement :: try ast.add_block(state.builder, ast.node_span(&state.tree, id), statements.items)
|
||||
try replace(state, id, replacement)
|
||||
try refresh(state)
|
||||
}
|
||||
return
|
||||
}
|
||||
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)
|
||||
@@ -437,6 +476,29 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
value_block_terminal proc(state @State, id ast.NodeId) bool {
|
||||
class :: ast.node_class(&state.tree, id)
|
||||
if (class == .block) {
|
||||
count :: ast.block_statement_count(&state.tree, id)
|
||||
return count > 0 and value_block_terminal(state, ast.block_statement(&state.tree, id, count - 1))
|
||||
}
|
||||
if (class != .statement) return false
|
||||
match ast.stmt_kind(&state.tree, id) {
|
||||
.yield_stmt, .return_stmt, .break_stmt, .continue_stmt: return true
|
||||
.block_stmt: return value_block_terminal(state, ast.statement_block(&state.tree, id))
|
||||
.if_stmt: {
|
||||
view :: ast.if_stmt(&state.tree, id)
|
||||
return (
|
||||
view.else_block != null
|
||||
and value_block_terminal(state, view.then_block)
|
||||
and value_block_terminal(state, view.else_block?)
|
||||
)
|
||||
}
|
||||
else: return false
|
||||
}
|
||||
}
|
||||
|
||||
# reflection values are owned AST snapshots. only ordinary leaves and selected
|
||||
# runtime bodies enter IR; metadata slices never retain interpreter addresses.
|
||||
@hide:file
|
||||
@@ -1369,16 +1431,18 @@ specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
|
||||
return true
|
||||
}
|
||||
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, index))
|
||||
selected :: try type_match_body(state, id, arm)
|
||||
if (selected == no_node or (statement == no_node and !arm.expression)) {
|
||||
selected := try type_match_body(state, id, arm)
|
||||
if (selected != no_node and !arm.expression) {
|
||||
selected = try ast.add_block_expr(state.builder, ast.node_span(&state.tree, selected), selected)
|
||||
try refresh(state)
|
||||
}
|
||||
if (selected == no_node) {
|
||||
try reject_evaluation(state, id)
|
||||
return true
|
||||
}
|
||||
try expand(state, selected)
|
||||
if (statement != no_node) {
|
||||
replacement := no_node
|
||||
if (arm.expression) replacement = try ast.add_expression_stmt(state.builder, selected)
|
||||
else replacement = try ast.add_block_stmt(state.builder, selected)
|
||||
replacement :: try ast.add_expression_stmt(state.builder, selected)
|
||||
try replace(state, statement, replacement)
|
||||
} else {
|
||||
resolved :: binding(state, selected)
|
||||
@@ -1891,7 +1955,7 @@ contextual_literal proc(state @State, id ast.NodeId) bool {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
if (kind == .builtin_call)
|
||||
return mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "some")
|
||||
return kind == .record_literal or kind == .array_literal or kind == .enum_literal
|
||||
return kind == .record_literal or kind == .array_literal or kind == .enum_literal or kind == .block_expr
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -1943,7 +2007,9 @@ evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Erro
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .array_type
|
||||
and !try type_array_initialized(state, id, id)) return Value{}
|
||||
if (type != no_node and (contextual_literal(state, id) or ast.expr_kind(&state.tree, type) == .fallible_type))
|
||||
if (type != no_node and (contextual_literal(state, id)
|
||||
or ast.expr_kind(&state.tree, id) == .block_expr
|
||||
or ast.expr_kind(&state.tree, type) == .fallible_type))
|
||||
return try evaluate_ir(state, id, type)
|
||||
}
|
||||
return try evaluate(state, id)
|
||||
@@ -2264,13 +2330,21 @@ TypeExecution :: struct {
|
||||
first_owned ast.NodeId
|
||||
returned ast.NodeId = no_node
|
||||
result_type ast.NodeId
|
||||
yielded ast.NodeId = no_node
|
||||
yield_type ast.NodeId = no_node
|
||||
yield_depth usize = 0
|
||||
cleanup_yield_depth usize = 0
|
||||
cleanup_depth usize = 0
|
||||
cleanup_loop_depth usize = 0
|
||||
evaluate_values bool = false
|
||||
pending_flow TypeFlow = .next
|
||||
loop_depth usize = 0
|
||||
inline_body ast.NodeId = no_node
|
||||
inline_blocks ?@mut arraylist.ArrayList(ast.NodeId) = null
|
||||
}
|
||||
|
||||
@hide:file
|
||||
TypeFlow :: enum { next, returned, break, continue, invalid }
|
||||
TypeFlow :: enum { next, returned, yielded, break, continue, invalid }
|
||||
|
||||
# type values cannot enter runtime IR. execute their control flow here, substituting
|
||||
# owned constants into snapshots; ordinary value operations still use checked IR.
|
||||
@@ -2283,6 +2357,7 @@ return_type_value proc(state @mut State, block, first_owned, result_type ast.Nod
|
||||
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
||||
first_owned = first_owned,
|
||||
result_type = result_type,
|
||||
evaluate_values = true,
|
||||
}
|
||||
defer arraylist.deinit(&execution.locals)
|
||||
defer mem.free(state.allocator, execution.mapping)
|
||||
@@ -2291,6 +2366,56 @@ return_type_value proc(state @mut State, block, first_owned, result_type ast.Nod
|
||||
return execution.returned
|
||||
}
|
||||
|
||||
@hide:file
|
||||
evaluate_type_value_block proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||
state.type_execution_depth += 1
|
||||
defer state.type_execution_depth -= 1
|
||||
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,
|
||||
evaluate_values = true,
|
||||
}
|
||||
defer arraylist.deinit(&execution.locals)
|
||||
defer mem.free(state.allocator, execution.mapping)
|
||||
mem.set(execution.mapping, no_node)
|
||||
return try execute_type_value_block(state, &execution, ast.block_expr(&state.tree, expression), annotation)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
execute_type_value_block proc(
|
||||
state @mut State, execution @mut TypeExecution, block, annotation ast.NodeId,
|
||||
) ast.NodeId ! Error {
|
||||
saved_value :: execution.yielded
|
||||
saved_type :: execution.yield_type
|
||||
execution.yielded = no_node
|
||||
execution.yield_type = annotation
|
||||
execution.yield_depth += 1
|
||||
defer {
|
||||
execution.yielded = saved_value
|
||||
execution.yield_type = saved_type
|
||||
execution.yield_depth -= 1
|
||||
}
|
||||
flow :: try execute_type_block(state, execution, block)
|
||||
if (flow == .yielded) return execution.yielded
|
||||
if (flow != .next) execution.pending_flow = flow
|
||||
else {
|
||||
inferred :: if (annotation == no_node) try value_block_type(state, block, 0) else annotation
|
||||
result :: binding(state, inferred)
|
||||
if (result.kind == .builtin and result.data.builtin == .void) {
|
||||
value :: try ast.add_literal(state.builder, .void, ast.node_span(&state.tree, block))
|
||||
try refresh(state)
|
||||
return value
|
||||
}
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.missing_yield, ast.node_span(&state.tree, block))
|
||||
execution.pending_flow = .invalid
|
||||
}
|
||||
return no_node
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_step proc(state @mut State, at ast.NodeId) bool ! Error {
|
||||
if (state.type_execution_remaining == 0) {
|
||||
@@ -2303,8 +2428,8 @@ type_step proc(state @mut State, at ast.NodeId) bool ! Error {
|
||||
}
|
||||
|
||||
@hide:file
|
||||
prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void {
|
||||
if (usize(id) >= execution.mapping.len) return
|
||||
prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void ! Error {
|
||||
if (id == no_node or usize(id) >= execution.mapping.len or execution.pending_flow != .next) return
|
||||
# substituted arguments belong to the caller, including nominal type identity.
|
||||
if (id < execution.first_owned) {
|
||||
execution.mapping[usize(id)] = id
|
||||
@@ -2320,8 +2445,73 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as
|
||||
}
|
||||
}
|
||||
}
|
||||
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .field) {
|
||||
field :: ast.field(&state.tree, id)
|
||||
if (field.type != null and field.value != null) {
|
||||
annotation :: try type_snapshot(state, execution, field.type?)
|
||||
if (annotation == no_node) return
|
||||
value :: try type_snapshot_expected(state, execution, field.value?, annotation)
|
||||
execution.mapping[usize(field.value?)] = value
|
||||
if (value == no_node and execution.pending_flow == .next) execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
}
|
||||
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .expression) {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
if (kind == .block_expr) {
|
||||
execution.mapping[usize(id)] = try execute_type_value_block(state, execution,
|
||||
ast.block_expr(&state.tree, id), no_node)
|
||||
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
if (kind == .match_expr) {
|
||||
execution.mapping[usize(id)] = try type_snapshot_match(state, execution, id, no_node)
|
||||
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
if (kind == .binary) {
|
||||
view :: ast.binary_expr(&state.tree, id)
|
||||
if (view.operator == .logical_and or view.operator == .logical_or) {
|
||||
boolean :: try builtin(state, id, .bool)
|
||||
left :: try type_snapshot_expected(state, execution, view.left, boolean)
|
||||
if (left == no_node) return
|
||||
value :: try evaluate_ir(state, left, boolean)
|
||||
if (value.kind != .boolean) {
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
right := no_node
|
||||
if (value.boolean == (view.operator == .logical_or)) {
|
||||
saved :: execution.evaluate_values
|
||||
execution.evaluate_values = false
|
||||
right = try type_snapshot(state, execution, view.right)
|
||||
if (right != no_node and !try skip_type_value_blocks(state, right)) {
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
execution.evaluate_values = saved
|
||||
} else right = try type_snapshot_expected(state, execution, view.right, boolean)
|
||||
if (right == no_node and execution.pending_flow == .next) {
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
# retain the operator and skipped operand for ordinary type validation.
|
||||
execution.mapping[usize(view.left)] = left
|
||||
execution.mapping[usize(view.right)] = right
|
||||
return
|
||||
}
|
||||
}
|
||||
if (kind == .catch_expr) {
|
||||
execution.mapping[usize(id)] = try type_snapshot_catch(state, execution, id, no_node)
|
||||
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
}
|
||||
for (0..ast.child_count(&state.tree, id)) |i|
|
||||
prepare_type_snapshot(state, execution, ast.child(&state.tree, id, i))
|
||||
try prepare_type_snapshot(state, execution, ast.child(&state.tree, id, i))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -2348,9 +2538,40 @@ bind_type_snapshot proc(state @mut State, execution @TypeExecution, id, start as
|
||||
try bind_type_snapshot(state, execution, ast.child(&state.tree, id, i), start)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
skip_type_value_blocks proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .block_expr) {
|
||||
annotation :: try expression_type(state, id, 0)
|
||||
if (annotation == no_node) return false
|
||||
span :: ast.node_span(&state.tree, id)
|
||||
# skipped type-control bodies retain only their result type for checked IR.
|
||||
placeholder :: try ast.add_literal(state.builder, .undefined, span)
|
||||
local :: try ast.add_statement_decl(state.builder, span, span, strpool.no_str,
|
||||
annotation, .variable, placeholder)
|
||||
try refresh(state)
|
||||
yielded :: try ast.add_yield_stmt(state.builder, span, try reference(state, id, local))
|
||||
statements [2]ast.NodeId :: [local, yielded]
|
||||
body :: try ast.add_block(state.builder, span, statements[..])
|
||||
replacement :: try ast.add_block_expr(state.builder, span, body)
|
||||
ast.replace_node_from_source(state.builder, id, replacement)
|
||||
try refresh(state)
|
||||
return true
|
||||
}
|
||||
for (0..ast.child_count(&state.tree, id)) |i|
|
||||
if (!try skip_type_value_blocks(state, ast.child(&state.tree, id, i))) return false
|
||||
return true
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
||||
prepare_type_snapshot(state, execution, id)
|
||||
if (id == no_node or execution.pending_flow != .next) return no_node
|
||||
if (execution.mapping.len < ast.node_count(&state.tree)) {
|
||||
previous :: execution.mapping.len
|
||||
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
||||
mem.set(execution.mapping[previous..], no_node)
|
||||
}
|
||||
try prepare_type_snapshot(state, execution, id)
|
||||
if (execution.pending_flow != .next) return no_node
|
||||
start :: ast.NodeId(ast.node_count(&state.tree))
|
||||
result :: try ast.clone_subtree(state.builder, id, execution.mapping)
|
||||
try refresh(state)
|
||||
@@ -2358,6 +2579,123 @@ type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_snapshot_expected proc(
|
||||
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
||||
) ast.NodeId ! Error {
|
||||
if (id == no_node or execution.pending_flow != .next) return no_node
|
||||
if (execution.evaluate_values and ast.expr_kind(&state.tree, id) == .block_expr)
|
||||
return try execute_type_value_block(state, execution, ast.block_expr(&state.tree, id), annotation)
|
||||
if (execution.evaluate_values and ast.expr_kind(&state.tree, id) == .match_expr)
|
||||
return try type_snapshot_match(state, execution, id, annotation)
|
||||
if (execution.evaluate_values and ast.expr_kind(&state.tree, id) == .catch_expr)
|
||||
return try type_snapshot_catch(state, execution, id, annotation)
|
||||
return try type_snapshot(state, execution, id)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_snapshot_catch proc(
|
||||
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
||||
) ast.NodeId ! Error {
|
||||
view :: ast.catch_expr(&state.tree, id)
|
||||
operand :: try type_snapshot(state, execution, view.operand)
|
||||
if (operand == no_node) return no_node
|
||||
operand_type :: try expression_type(state, operand, 0)
|
||||
if (operand_type == no_node or ast.expr_kind(&state.tree, operand_type) != .fallible_type) return no_node
|
||||
channels :: ast.binary_expr(&state.tree, operand_type)
|
||||
owned :: try type_execution_value(state, operand, operand_type)
|
||||
if (owned == no_node) return no_node
|
||||
span :: ast.node_span(&state.tree, id)
|
||||
boolean :: try builtin(state, id, .bool)
|
||||
yes :: try ast.add_boolean(state.builder, span, true)
|
||||
no :: try ast.add_boolean(state.builder, span, false)
|
||||
failed :: try ast.add_return_stmt(state.builder, span, yes)
|
||||
failure_statements [1]ast.NodeId :: [failed]
|
||||
handler :: try ast.add_block(state.builder, span, failure_statements[..])
|
||||
caught :: try ast.add_catch_expr(state.builder, span, owned, null, handler, true)
|
||||
discarded :: try ast.add_expression_stmt(state.builder, caught)
|
||||
success :: try ast.add_return_stmt(state.builder, span, no)
|
||||
statements [2]ast.NodeId :: [discarded, success]
|
||||
body :: try ast.add_block(state.builder, span, statements[..])
|
||||
try refresh(state)
|
||||
guard :: try evaluate_ir_body(state, id, body, boolean)
|
||||
if (guard.kind != .boolean) return no_node
|
||||
payload :: ast.typed_constant(&state.tree, owned).value
|
||||
if (!guard.boolean) return try type_execution_value(state, payload, channels.left)
|
||||
saved :: execution.locals.items.len
|
||||
defer execution.locals.items = execution.locals.items[..saved]
|
||||
if (view.capture) |capture| {
|
||||
error :: try type_execution_value(state, payload, channels.right)
|
||||
if (error == no_node) return no_node
|
||||
try arraylist.append(&execution.locals, TypeLocal{
|
||||
declaration = capture, value = error, annotation = channels.right,
|
||||
})
|
||||
}
|
||||
result_type := channels.left
|
||||
if (annotation != no_node and !is_type(state, annotation)) {
|
||||
contextual :: try canonical_type(state, annotation)
|
||||
if (contextual != no_node) {
|
||||
kind :: ast.expr_kind(&state.tree, contextual)
|
||||
if (kind == .optional_type
|
||||
and try same_type(state, ast.type_operand(&state.tree, contextual), channels.left))
|
||||
result_type = annotation
|
||||
if (kind == .fallible_type
|
||||
and try same_type(state, ast.binary_expr(&state.tree, contextual).left, channels.left))
|
||||
result_type = annotation
|
||||
}
|
||||
}
|
||||
if (view.handler_is_block)
|
||||
return try execute_type_value_block(state, execution, view.handler, result_type)
|
||||
selected :: try type_snapshot_expected(state, execution, view.handler, result_type)
|
||||
return try type_execution_value(state, selected, result_type)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_dispatch_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
||||
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, id).subject)
|
||||
if (subject == no_node) return no_node
|
||||
saved :: execution.evaluate_values
|
||||
execution.evaluate_values = false
|
||||
defer execution.evaluate_values = saved
|
||||
snapshot :: try type_snapshot(state, execution, id)
|
||||
if (snapshot == no_node) return no_node
|
||||
arms := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&arms)
|
||||
for (0..ast.match_arm_count(&state.tree, snapshot)) |i|
|
||||
try arraylist.append(&arms, ast.match_arm(&state.tree, snapshot, i))
|
||||
optional_capture :: ast.match_expr(&state.tree, id).optional_capture
|
||||
result :: try ast.add_match(state.builder, ast.node_span(&state.tree, id), subject, arms.items)
|
||||
if (optional_capture) ast.set_match_optional(state.builder, result)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_snapshot_match proc(
|
||||
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
||||
) ast.NodeId ! Error {
|
||||
snapshot :: try type_dispatch_snapshot(state, execution, id)
|
||||
if (snapshot == no_node) return no_node
|
||||
index :: try type_match_arm(state, snapshot)
|
||||
if (index == no_index) return no_node
|
||||
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, index))
|
||||
saved :: execution.locals.items.len
|
||||
defer execution.locals.items = execution.locals.items[..saved]
|
||||
if (arm.capture) |capture| {
|
||||
value :: try type_match_capture(state, snapshot)
|
||||
if (value == no_node) return no_node
|
||||
constant :: ast.typed_constant(&state.tree, value)
|
||||
payload :: if (is_type(state, constant.type)) constant.value else value
|
||||
try arraylist.append(&execution.locals, TypeLocal{
|
||||
declaration = capture,
|
||||
value = payload,
|
||||
annotation = constant.type,
|
||||
})
|
||||
}
|
||||
if (!arm.expression) return try execute_type_value_block(state, execution, arm.body, annotation)
|
||||
return try type_snapshot_expected(state, execution, arm.body, annotation)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
nominal_type_value proc(state @State, id ast.NodeId) bool {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
@@ -2386,9 +2724,14 @@ 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 == .yield_stmt or kind == .return_stmt) {
|
||||
try expand(state, id)
|
||||
return if (kind == .yield_stmt) TypeFlow.yielded else TypeFlow.returned
|
||||
}
|
||||
if (kind == .block_stmt)
|
||||
return try inline_block(state, ast.statement_block(&state.tree, id))
|
||||
selected := no_node
|
||||
value_source := false
|
||||
if (kind == .if_stmt) {
|
||||
conditional :: ast.if_stmt(&state.tree, id)
|
||||
explicit :: ast.expr_kind(&state.tree, conditional.condition) == .comptime_expr
|
||||
@@ -2414,8 +2757,10 @@ inline_statement proc(state @mut State, id ast.NodeId) TypeFlow ! Error {
|
||||
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 {
|
||||
if (!arm.expression) {
|
||||
selected = body
|
||||
value_source = true
|
||||
} 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[..])
|
||||
@@ -2425,8 +2770,13 @@ inline_statement proc(state @mut State, id ast.NodeId) TypeFlow ! Error {
|
||||
}
|
||||
if (selected != no_node) {
|
||||
try refresh(state)
|
||||
flow :: try inline_block(state, selected)
|
||||
replacement :: try ast.add_block_stmt(state.builder, selected)
|
||||
flow := try inline_block(state, selected)
|
||||
replacement := no_node
|
||||
if (value_source) {
|
||||
value :: try ast.add_block_expr(state.builder, ast.node_span(&state.tree, selected), selected)
|
||||
replacement = try ast.add_expression_stmt(state.builder, value)
|
||||
if (flow == .yielded) flow = .next
|
||||
} else replacement = try ast.add_block_stmt(state.builder, selected)
|
||||
try replace(state, id, replacement)
|
||||
try refresh(state)
|
||||
return flow
|
||||
@@ -2490,7 +2840,8 @@ expand_inline_for proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
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 ((flow != .next and flow != .yielded and flow != .returned)
|
||||
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)
|
||||
@@ -2664,23 +3015,33 @@ type_match_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.
|
||||
|
||||
@hide:file
|
||||
type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Error {
|
||||
if (input == no_node) return no_node
|
||||
expression := input
|
||||
while (ast.expr_kind(&state.tree, expression) == .match_expr) {
|
||||
if (!try type_step(state, expression)) return no_node
|
||||
index :: try type_match_arm(state, expression)
|
||||
if (index == no_index) return no_node
|
||||
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
|
||||
if (!arm.expression) return no_node
|
||||
expression = try type_match_body(state, expression, arm)
|
||||
if (!arm.expression and expression != no_node) {
|
||||
expression = try ast.add_block_expr(state.builder, ast.node_span(&state.tree, expression), expression)
|
||||
try refresh(state)
|
||||
}
|
||||
if (expression == no_node) return no_node
|
||||
}
|
||||
if (expression != no_node and ast.expr_kind(&state.tree, expression) == .block_expr) {
|
||||
annotation :: try value_block_type(state, ast.block_expr(&state.tree, expression), 0)
|
||||
if (annotation != no_node and (is_type(state, annotation) or type_record_storage(state, annotation, 0)))
|
||||
return try evaluate_type_value_block(state, expression, annotation)
|
||||
}
|
||||
return expression
|
||||
}
|
||||
|
||||
# normalize only selected expression arms before ordinary expansion can visit them.
|
||||
# normalize selected value sources before ordinary expansion can visit their bodies.
|
||||
# nominal results enter here after their instance declaration is cached.
|
||||
@hide:file
|
||||
expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
if (id == no_node) return false
|
||||
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
|
||||
selected :: try type_value_expression(state, id)
|
||||
if (selected == no_node) return false
|
||||
@@ -3258,7 +3619,10 @@ type_array_update proc(
|
||||
|
||||
@hide:file
|
||||
type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
if (annotation == no_node) return no_node
|
||||
if (input == no_node or annotation == no_node) return no_node
|
||||
if (ast.expr_kind(&state.tree, input) == .block_expr
|
||||
and (is_type(state, annotation) or type_record_storage(state, annotation, 0)))
|
||||
return try evaluate_type_value_block(state, input, annotation)
|
||||
expression :: try type_value_expression(state, input)
|
||||
if (expression == no_node) return no_node
|
||||
if (!try expand_type_matches(state, annotation)) return no_node
|
||||
@@ -3403,6 +3767,7 @@ type_assignment_value proc(
|
||||
local :: execution.locals.items[index]
|
||||
span :: ast.node_span(&state.tree, statement)
|
||||
value :: try type_snapshot(state, execution, assignment.value)
|
||||
if (value == no_node) return no_node
|
||||
mutability :: if (ast.node_class(&state.tree, local.declaration) == .statement)
|
||||
ast.statement_decl(&state.tree, local.declaration).mutability else ast.Mutability.constant
|
||||
temporary :: try ast.add_statement_decl(state.builder, span, span, strpool.no_str,
|
||||
@@ -3432,6 +3797,7 @@ type_assignment_value proc(
|
||||
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)
|
||||
if (snapshot == no_node) return .invalid
|
||||
flow :: try inline_block(state, snapshot)
|
||||
if (flow == .invalid) return flow
|
||||
try arraylist.append(execution.inline_blocks?, snapshot)
|
||||
@@ -3439,13 +3805,33 @@ execute_type_block proc(state @mut State, execution @mut TypeExecution, block as
|
||||
}
|
||||
saved :: execution.locals.items.len
|
||||
defer execution.locals.items = execution.locals.items[..saved]
|
||||
reached := usize(0)
|
||||
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 execute_type_statement(state, execution, statement)
|
||||
if (flow != .next) return flow
|
||||
reached = i + 1
|
||||
flow = try execute_type_statement(state, execution, statement)
|
||||
if (flow != .next) break
|
||||
}
|
||||
return .next
|
||||
if (flow == .invalid) return flow
|
||||
# yielded/returned values already own their snapshots before scope cleanup runs.
|
||||
while (reached > 0) {
|
||||
reached -= 1
|
||||
statement :: ast.block_statement(&state.tree, block, reached)
|
||||
if (ast.stmt_kind(&state.tree, statement) != .defer_stmt) continue
|
||||
saved_depth :: execution.cleanup_yield_depth
|
||||
saved_loop :: execution.cleanup_loop_depth
|
||||
execution.cleanup_loop_depth = execution.loop_depth
|
||||
execution.cleanup_yield_depth = execution.yield_depth
|
||||
execution.cleanup_depth += 1
|
||||
cleanup :: try execute_type_block(state, execution, ast.cleanup_stmt(&state.tree, statement).body)
|
||||
execution.cleanup_depth -= 1
|
||||
execution.cleanup_yield_depth = saved_depth
|
||||
execution.cleanup_loop_depth = saved_loop
|
||||
if (cleanup != .next) return .invalid
|
||||
}
|
||||
return flow
|
||||
}
|
||||
|
||||
# substitute selector names without snapshotting away the assignment's root place.
|
||||
@@ -3462,6 +3848,7 @@ type_assignment_target proc(state @mut State, execution @mut TypeExecution, id a
|
||||
base :: try type_assignment_target(state, execution, ast.call_arg(&state.tree, id, 0))
|
||||
if (base == no_node) return no_node
|
||||
argument :: try type_snapshot(state, execution, ast.call_arg(&state.tree, id, 1))
|
||||
if (argument == no_node) return no_node
|
||||
arguments [2]ast.NodeId :: [base, argument]
|
||||
result :: try ast.add_builtin_call(state.builder, ast.node_span(&state.tree, id), name, arguments[..])
|
||||
try refresh(state)
|
||||
@@ -3485,10 +3872,56 @@ type_assignment_target proc(state @mut State, execution @mut TypeExecution, id a
|
||||
|
||||
@hide:file
|
||||
execute_type_statement proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
||||
if (execution.cleanup_depth > 0) {
|
||||
kind :: ast.stmt_kind(&state.tree, statement)
|
||||
if (kind == .return_stmt
|
||||
or ((kind == .break_stmt or kind == .continue_stmt) and execution.loop_depth <= execution.cleanup_loop_depth)) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.cleanup_control_flow, ast.node_span(&state.tree, statement))
|
||||
return .invalid
|
||||
}
|
||||
}
|
||||
flow :: try execute_type_statement_body(state, execution, statement)
|
||||
if (execution.pending_flow == .next) return flow
|
||||
pending :: execution.pending_flow
|
||||
execution.pending_flow = .next
|
||||
return pending
|
||||
}
|
||||
|
||||
@hide:file
|
||||
execute_type_statement_body proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
||||
match ast.stmt_kind(&state.tree, statement) {
|
||||
.defer_stmt: {}
|
||||
.errdefer_stmt: {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.errdefer_outside_fallible, ast.node_span(&state.tree, statement))
|
||||
return .invalid
|
||||
}
|
||||
.yield_stmt: {
|
||||
if (execution.yield_depth == 0) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.yield_outside_block, ast.node_span(&state.tree, statement))
|
||||
return .invalid
|
||||
}
|
||||
if (execution.cleanup_depth > 0 and execution.yield_depth <= execution.cleanup_yield_depth) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.cleanup_control_flow, ast.node_span(&state.tree, statement))
|
||||
return .invalid
|
||||
}
|
||||
value :: ast.yield_stmt(&state.tree, statement).value?
|
||||
expression :: try type_snapshot_expected(state, execution, value, execution.yield_type)
|
||||
if (expression == no_node) return .invalid
|
||||
annotation := execution.yield_type
|
||||
if (annotation == no_node) annotation = try expression_type(state, expression, 0)
|
||||
execution.yielded = try type_execution_value(state, expression, annotation)
|
||||
if (execution.yielded == no_node
|
||||
or !try type_array_initialized(state, execution.yielded, value)) return .invalid
|
||||
return .yielded
|
||||
}
|
||||
.return_stmt: {
|
||||
if (ast.return_stmt(&state.tree, statement).value) |value| {
|
||||
expression :: try type_snapshot(state, execution, value)
|
||||
expression :: try type_snapshot_expected(state, execution, value, execution.result_type)
|
||||
if (expression == no_node) return .invalid
|
||||
execution.returned = try type_execution_value(state, expression, execution.result_type)
|
||||
if (execution.returned != no_node
|
||||
and !try type_array_initialized(state, execution.returned, value)) return .invalid
|
||||
@@ -3498,13 +3931,14 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
}
|
||||
.declaration: {
|
||||
declaration :: ast.statement_decl(&state.tree, statement)
|
||||
snapshot :: try type_snapshot(state, execution, declaration.initializer)
|
||||
expression :: try type_value_expression(state, snapshot)
|
||||
if (expression == no_node) return .invalid
|
||||
annotation := no_node
|
||||
if (declaration.type) |type| annotation = try type_snapshot(state, execution, type)
|
||||
else if (nominal_type_value(state, expression)) annotation = try builtin(state, expression, .type)
|
||||
else {
|
||||
snapshot :: try type_snapshot_expected(state, execution, declaration.initializer, annotation)
|
||||
expression :: try type_value_expression(state, snapshot)
|
||||
if (expression == no_node) return .invalid
|
||||
if (annotation == no_node and nominal_type_value(state, expression))
|
||||
annotation = try builtin(state, expression, .type)
|
||||
else if (annotation == no_node) {
|
||||
if (!try expand_type_matches(state, expression)) return .invalid
|
||||
try expand(state, expression)
|
||||
annotation = try expression_type(state, expression, 0)
|
||||
@@ -3588,7 +4022,8 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
execution.locals.items[index].value = value
|
||||
return .next
|
||||
}
|
||||
expression :: try type_snapshot(state, execution, assignment.value)
|
||||
expression :: try type_snapshot_expected(state, execution,
|
||||
assignment.value, execution.locals.items[index].annotation)
|
||||
value :: try type_execution_value(state, expression, execution.locals.items[index].annotation)
|
||||
if (value == no_node) return .invalid
|
||||
execution.locals.items[index].value = value
|
||||
@@ -3621,6 +4056,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
if (!try type_step(state, condition)) return .invalid
|
||||
if (!invariant or value.kind == .invalid) {
|
||||
expression :: try type_snapshot(state, execution, condition)
|
||||
if (expression == no_node) return .invalid
|
||||
if (capture) |name| value = try type_capture_guard(state, expression, name)
|
||||
else {
|
||||
boolean :: try builtin(state, expression, .bool)
|
||||
@@ -3635,6 +4071,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
execution.locals.items = execution.locals.items[..saved]
|
||||
if (capture) |name| {
|
||||
expression :: try type_snapshot(state, execution, condition)
|
||||
if (expression == no_node) return .invalid
|
||||
unwrapped :: try ast.add_unary(state.builder,
|
||||
ast.node_span(&state.tree, condition), .unwrap, expression)
|
||||
try refresh(state)
|
||||
@@ -3646,18 +4083,25 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
})
|
||||
}
|
||||
flow :: try execute_type_block(state, execution, body)
|
||||
if (!looping or flow == .returned or flow == .invalid) return flow
|
||||
if (!looping or flow == .returned or flow == .yielded or flow == .invalid) return flow
|
||||
if (flow == .break) return .next
|
||||
}
|
||||
}
|
||||
.expression: {
|
||||
expression :: ast.statement_expr(&state.tree, statement)
|
||||
if (ast.expr_kind(&state.tree, expression) != .match_expr) return .invalid
|
||||
if (ast.expr_kind(&state.tree, expression) != .match_expr) {
|
||||
snapshot :: try type_snapshot(state, execution, expression)
|
||||
if (snapshot == no_node) return .invalid
|
||||
annotation :: try expression_type(state, snapshot, 0)
|
||||
if (try type_execution_value(state, snapshot, annotation) == no_node) return .invalid
|
||||
return .next
|
||||
}
|
||||
subject_type := no_node
|
||||
arms :: ast.match_arm_count(&state.tree, expression)
|
||||
if (arms > 0
|
||||
and ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, arms - 1)).is_inline) {
|
||||
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, expression).subject)
|
||||
if (subject == no_node) return .invalid
|
||||
subject_type = try expression_type(state, subject, 0)
|
||||
}
|
||||
before :: ast.node_count(&state.tree)
|
||||
@@ -3668,7 +4112,8 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
||||
mem.set(execution.mapping[previous..], no_node)
|
||||
}
|
||||
snapshot :: try type_snapshot(state, execution, expression)
|
||||
snapshot :: try type_dispatch_snapshot(state, execution, expression)
|
||||
if (snapshot == no_node) return .invalid
|
||||
index :: try type_match_arm(state, snapshot)
|
||||
if (index == no_index) return .invalid
|
||||
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
|
||||
@@ -3684,7 +4129,10 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
annotation = constant.type,
|
||||
})
|
||||
}
|
||||
if (!arm.expression) return try execute_type_block(state, execution, arm.body)
|
||||
if (!arm.expression) {
|
||||
if (try execute_type_value_block(state, execution, arm.body, no_node) == no_node) return .invalid
|
||||
return .next
|
||||
}
|
||||
body :: try type_snapshot(state, execution, arm.body)
|
||||
value :: try type_value_expression(state, body)
|
||||
if (value == no_node) return .invalid
|
||||
@@ -3729,6 +4177,7 @@ type_collection_place proc(state @mut State, execution @mut TypeExecution, id as
|
||||
place, ast.selection_expr(&state.tree, id).member)
|
||||
} else {
|
||||
index :: try type_snapshot(state, execution, ast.index_expr(&state.tree, id).index)
|
||||
if (index == no_node) return no_node
|
||||
value :: try type_execution_value(state, index, try builtin(state, index, .usize))
|
||||
if (value == no_node) return no_node
|
||||
result = try ast.add_index(state.builder, ast.node_span(&state.tree, id), place, value)
|
||||
@@ -3863,7 +4312,7 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta
|
||||
})
|
||||
}
|
||||
flow :: try execute_type_block(state, execution, loop.body)
|
||||
if (flow == .returned or flow == .invalid) return flow
|
||||
if (flow == .returned or flow == .yielded or flow == .invalid) return flow
|
||||
if (flow == .break) return .next
|
||||
}
|
||||
return .next
|
||||
@@ -3878,6 +4327,7 @@ execute_type_for proc(state @mut State, execution @mut TypeExecution, statement
|
||||
bounds :: ast.range_expr(&state.tree, loop.iterable)
|
||||
start :: try type_snapshot(state, execution, bounds.start)
|
||||
end :: try type_snapshot(state, execution, bounds.end)
|
||||
if (start == no_node or end == no_node) return .invalid
|
||||
typed_bound :: if (flexible(state, start)) end else start
|
||||
annotation :: try expression_type(state, typed_bound, 0)
|
||||
if (annotation == no_node or !try integer_type(state, annotation)) return .invalid
|
||||
@@ -3902,7 +4352,7 @@ execute_type_for proc(state @mut State, execution @mut TypeExecution, statement
|
||||
if (!keep.boolean) return .next
|
||||
if (loop.capture != null) execution.locals.items[saved].value = current
|
||||
flow :: try execute_type_block(state, execution, loop.body)
|
||||
if (flow == .returned or flow == .invalid) return flow
|
||||
if (flow == .returned or flow == .yielded or flow == .invalid) return flow
|
||||
if (flow == .break) return .next
|
||||
if (try value_equal(state, try evaluate(state, current), try evaluate(state, limit))) return .next
|
||||
one :: try ast.add_integer(state.builder, span, 1)
|
||||
@@ -3920,6 +4370,7 @@ mix proc(left, right usize) usize {
|
||||
|
||||
@hide:file
|
||||
evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
if (id == no_node) return Value{}
|
||||
if (state.depth >= 128) return Value{}
|
||||
state.depth += 1
|
||||
defer state.depth -= 1
|
||||
@@ -4001,7 +4452,29 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
}
|
||||
.binary: return try evaluate_binary(state, id)
|
||||
.call: return try evaluate_call(state, id)
|
||||
.record_literal, .array_literal, .selection, .index, .match_expr, .builtin_call, .catch_expr:
|
||||
.block_expr: {
|
||||
annotation :: try expression_type(state, id, 0)
|
||||
if (annotation == no_node) return Value{}
|
||||
if (is_type(state, annotation) or type_record_storage(state, annotation, 0)) {
|
||||
result :: try evaluate_type_value_block(state, id, annotation)
|
||||
if (result == no_node) return Value{}
|
||||
return try evaluate_expected(state, result, annotation)
|
||||
}
|
||||
return try evaluate_ir(state, id, annotation)
|
||||
}
|
||||
.match_expr: {
|
||||
annotation :: try expression_type(state, id, 0)
|
||||
if (annotation != no_node and (is_type(state, annotation) or type_record_storage(state, annotation, 0))) {
|
||||
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||
state.type_execution_depth += 1
|
||||
defer state.type_execution_depth -= 1
|
||||
selected :: try type_value_expression(state, id)
|
||||
if (selected == no_node or selected == id) return Value{}
|
||||
return try evaluate_expected(state, selected, annotation)
|
||||
}
|
||||
return try evaluate_ir(state, id, annotation)
|
||||
}
|
||||
.record_literal, .array_literal, .selection, .index, .builtin_call, .catch_expr:
|
||||
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
||||
else: return Value{}
|
||||
}
|
||||
@@ -4322,8 +4795,39 @@ type_hash_context proc(state @mut State, id ast.NodeId, metadata bool) usize ! E
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
value_block_yield proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId ! Error {
|
||||
if (depth >= 128) return no_node
|
||||
class :: ast.node_class(&state.tree, id)
|
||||
if (class != .block and class != .statement) return no_node
|
||||
if (class == .statement) {
|
||||
kind :: ast.stmt_kind(&state.tree, id)
|
||||
if (kind == .yield_stmt)
|
||||
return ast.yield_stmt(&state.tree, id).value?
|
||||
if (kind != .block_stmt and kind != .if_stmt and kind != .while_stmt and kind != .for_stmt)
|
||||
return no_node
|
||||
}
|
||||
result := no_node
|
||||
for (0..ast.child_count(&state.tree, id)) |i| {
|
||||
child :: ast.child(&state.tree, id, i)
|
||||
value :: try value_block_yield(state, child, depth + 1)
|
||||
if (value == no_node or try expression_type(state, value, depth + 1) == no_node) continue
|
||||
result = value
|
||||
if (!flexible(state, value)) return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
value_block_type proc(state @mut State, block ast.NodeId, depth usize) ast.NodeId ! Error {
|
||||
value :: try value_block_yield(state, block, depth)
|
||||
return if (value != no_node) try expression_type(state, value, depth + 1)
|
||||
else try builtin(state, block, .void)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId ! Error {
|
||||
if (id == no_node) return no_node
|
||||
if (depth >= 128) return no_node
|
||||
try expand(state, id)
|
||||
resolved :: binding(state, id)
|
||||
@@ -4367,15 +4871,16 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
.literal_bool: return try builtin(state, id, .bool)
|
||||
.literal_string: return try builtin(state, id, .string)
|
||||
.literal_void: return try builtin(state, id, .void)
|
||||
.block_expr: return try value_block_type(state, ast.block_expr(&state.tree, id), depth + 1)
|
||||
.match_expr: {
|
||||
result := no_node
|
||||
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
||||
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
||||
if (!arm.expression) return no_node
|
||||
annotation :: try expression_type(state, arm.body, depth + 1)
|
||||
annotation :: if (arm.expression) try expression_type(state, arm.body, depth + 1)
|
||||
else try value_block_type(state, arm.body, depth + 1)
|
||||
if (annotation == no_node) continue
|
||||
result = annotation
|
||||
if (!flexible(state, arm.body)) return result
|
||||
if (!arm.expression or !flexible(state, arm.body)) return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -4394,8 +4899,10 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
}
|
||||
return no_node
|
||||
}
|
||||
.catch_expr: {
|
||||
type :: try expression_type(state, ast.catch_expr(&state.tree, id).operand, depth + 1)
|
||||
.catch_expr, .try_expr: {
|
||||
operand :: if (kind == .catch_expr) ast.catch_expr(&state.tree, id).operand
|
||||
else ast.type_operand(&state.tree, id)
|
||||
type :: try expression_type(state, operand, depth + 1)
|
||||
if (type == no_node or ast.expr_kind(&state.tree, type) != .fallible_type) return no_node
|
||||
return try canonical_type(state, ast.binary_expr(&state.tree, type).left)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user