feat: support fallible results and scoped cleanup
This commit is contained in:
+124
-6
@@ -1,17 +1,17 @@
|
||||
#! grammar:
|
||||
#! * type ..... -> identifier {'.' member} | '?' type | ('@' | '*') ['mut'] type | '@proc' '(' parameters ')' type | '[' (expr | '_') ']' ['mut'] type | '[' [';' expr] ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
|
||||
#! * type ..... -> type_sum ['!' type]; type_sum -> type_atom {'|' type_atom}; type_atom includes '(' type ')', identifier {'.' member}, '?' type_atom, ('@' | '*') ['mut'] type_atom, '@proc' '(' parameters ')' type, '[' (expr | '_') ']' ['mut'] type_atom, '[' [';' expr] ']' ['mut'] type_atom, record/enum/union declarations, and ('distinct' | 'alias') type.
|
||||
#! * binding ... -> :: | :=
|
||||
#! * literal .. -> int | float | string | 'true' | 'false' | 'null'
|
||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
|
||||
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
|
||||
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
|
||||
#! * 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 | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! braceless guards/iterables require parentheses unless they are calls or optional captures; else binds to the nearest if.
|
||||
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/& higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
|
||||
#! 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.
|
||||
|
||||
import "@std/debug"
|
||||
@@ -63,6 +63,10 @@ ExprKind :: enum {
|
||||
slice
|
||||
builtin_call
|
||||
literal_null
|
||||
fallible_type
|
||||
sum_type
|
||||
try_expr
|
||||
catch_expr
|
||||
}
|
||||
|
||||
DeclKind :: enum {
|
||||
@@ -82,6 +86,9 @@ StmtKind :: enum {
|
||||
if_stmt
|
||||
while_stmt
|
||||
for_stmt
|
||||
defer_stmt
|
||||
errdefer_stmt
|
||||
block_stmt
|
||||
invalid
|
||||
}
|
||||
|
||||
@@ -155,6 +162,13 @@ NodeKind :: enum {
|
||||
expr_slice
|
||||
expr_builtin_call
|
||||
literal_null
|
||||
expr_fallible_type
|
||||
expr_sum_type
|
||||
expr_try
|
||||
expr_catch
|
||||
stmt_defer
|
||||
stmt_errdefer
|
||||
stmt_block
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -239,6 +253,20 @@ BinaryExprView :: struct {
|
||||
right NodeId
|
||||
}
|
||||
|
||||
CatchExprView :: struct {
|
||||
span Span
|
||||
operand NodeId
|
||||
capture ?NodeId
|
||||
handler NodeId
|
||||
handler_is_block bool
|
||||
}
|
||||
|
||||
CleanupView :: struct {
|
||||
span Span
|
||||
body NodeId
|
||||
capture ?NodeId
|
||||
}
|
||||
|
||||
CallExprView :: struct {
|
||||
span Span
|
||||
callee NodeId
|
||||
@@ -485,6 +513,41 @@ add_binary proc(
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_binary_type proc(
|
||||
builder @mut Builder, span Span, left, right NodeId, fallible bool,
|
||||
) NodeId ! mem.AllocError {
|
||||
kind NodeKind :: if (fallible) .expr_fallible_type else .expr_sum_type
|
||||
return try add_node(builder, Node{
|
||||
kind = kind,
|
||||
main = NodeData{ binary_operator = .add },
|
||||
data0 = NodeData{ node_id = left },
|
||||
data1 = NodeData{ node_id = right },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_try_expr proc(builder @mut Builder, span Span, operand NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_try,
|
||||
main = NodeData{ node_id = operand },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_catch_expr proc(
|
||||
builder @mut Builder, span Span, operand NodeId, capture ?NodeId,
|
||||
handler NodeId, handler_is_block bool,
|
||||
) NodeId ! mem.AllocError {
|
||||
capture_id :: if (capture) |id| u32(id) else u32(no_node)
|
||||
block_flag :: if (handler_is_block) u32(1) else u32(0)
|
||||
extra :: try add_extra(builder, capture_id)
|
||||
_ = try add_extra(builder, block_flag)
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_catch,
|
||||
main = NodeData{ extra = extra },
|
||||
data0 = NodeData{ node_id = operand },
|
||||
data1 = NodeData{ node_id = handler },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_range proc(
|
||||
builder @mut Builder, span Span, start NodeId, end NodeId, inclusive bool,
|
||||
) NodeId ! mem.AllocError {
|
||||
@@ -735,6 +798,25 @@ add_expression_stmt proc(builder @mut Builder, expression NodeId) NodeId ! mem.A
|
||||
}, builder_span(builder, expression))
|
||||
}
|
||||
|
||||
add_cleanup_stmt proc(
|
||||
builder @mut Builder, span Span, body NodeId, capture ?NodeId, error_only bool,
|
||||
) NodeId ! mem.AllocError {
|
||||
kind NodeKind :: if (error_only) .stmt_errdefer else .stmt_defer
|
||||
capture_id :: if (capture) |id| id else no_node
|
||||
return try add_node(builder, Node{
|
||||
kind = kind,
|
||||
main = NodeData{ node_id = body },
|
||||
data0 = NodeData{ node_id = capture_id },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_block_stmt proc(builder @mut Builder, body NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .stmt_block,
|
||||
main = NodeData{ node_id = body },
|
||||
}, builder_span(builder, body))
|
||||
}
|
||||
|
||||
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{
|
||||
@@ -1041,6 +1123,9 @@ stmt_kind proc(tree @Tree, id NodeId) StmtKind {
|
||||
.stmt_assignment: .assignment_stmt
|
||||
.stmt_while: .while_stmt
|
||||
.stmt_for: .for_stmt
|
||||
.stmt_defer: .defer_stmt
|
||||
.stmt_errdefer: .errdefer_stmt
|
||||
.stmt_block: .block_stmt
|
||||
.invalid_stmt: .invalid
|
||||
else: unreachable
|
||||
}
|
||||
@@ -1052,6 +1137,22 @@ statement_expr proc(tree @Tree, id NodeId) NodeId {
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
cleanup_stmt proc(tree @Tree, id NodeId) CleanupView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .stmt_defer or node.kind == .stmt_errdefer)
|
||||
return CleanupView{
|
||||
span = get_span(&tree.storage, id),
|
||||
body = node.main.node_id,
|
||||
capture = maybe_node_id(u32(node.data0.node_id)),
|
||||
}
|
||||
}
|
||||
|
||||
statement_block proc(tree @Tree, id NodeId) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .stmt_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)
|
||||
@@ -1234,6 +1335,10 @@ expression_kind proc(kind NodeKind) ExprKind {
|
||||
.expr_slice: .slice
|
||||
.expr_builtin_call: .builtin_call
|
||||
.literal_null: .literal_null
|
||||
.expr_fallible_type: .fallible_type
|
||||
.expr_sum_type: .sum_type
|
||||
.expr_try: .try_expr
|
||||
.expr_catch: .catch_expr
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
@@ -1250,7 +1355,7 @@ unary_expr proc(tree @Tree, id NodeId) UnaryExprView {
|
||||
|
||||
binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_binary)
|
||||
debug.assert(node.kind == .expr_binary or node.kind == .expr_fallible_type or node.kind == .expr_sum_type)
|
||||
return BinaryExprView{
|
||||
span = get_span(&tree.storage, id),
|
||||
operator = node.main.binary_operator,
|
||||
@@ -1259,6 +1364,19 @@ binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
|
||||
}
|
||||
}
|
||||
|
||||
catch_expr proc(tree @Tree, id NodeId) CatchExprView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_catch)
|
||||
extra :: usize(node.main.extra)
|
||||
return CatchExprView{
|
||||
span = get_span(&tree.storage, id),
|
||||
operand = node.data0.node_id,
|
||||
capture = maybe_node_id(tree.storage.extra.items[extra]),
|
||||
handler = node.data1.node_id,
|
||||
handler_is_block = tree.storage.extra.items[extra + 1] != 0,
|
||||
}
|
||||
}
|
||||
|
||||
range_expr proc(tree @Tree, id NodeId) RangeView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_range)
|
||||
@@ -1403,7 +1521,7 @@ union_type proc(tree @Tree, id NodeId) UnionTypeView {
|
||||
|
||||
type_operand proc(tree @Tree, id NodeId) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type)
|
||||
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type or node.kind == .expr_try)
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,14 @@ Code :: enum {
|
||||
sentinel_mismatch
|
||||
overlapping_memory_copy
|
||||
unsupported_foreign_call
|
||||
invalid_fallible_type
|
||||
invalid_sum_type
|
||||
invalid_fallible_operand
|
||||
try_outside_fallible
|
||||
incompatible_propagated_error
|
||||
catch_type_mismatch
|
||||
errdefer_outside_fallible
|
||||
cleanup_control_flow
|
||||
}
|
||||
|
||||
Diagnostic :: struct {
|
||||
@@ -219,5 +227,13 @@ messages :: $enummap.init(Code, []u8, {
|
||||
sentinel_mismatch = "slice sentinel does not match the expected value",
|
||||
overlapping_memory_copy = "memory copy source and destination overlap",
|
||||
unsupported_foreign_call = "foreign function calls are unsupported by the interpreter",
|
||||
invalid_fallible_type = "fallible type requires a concrete success type and an enum, struct, or tagged union error channel",
|
||||
invalid_sum_type = "sum composition requires compatible named enum, struct, or tagged union variants",
|
||||
invalid_fallible_operand = "try and catch require a fallible operand",
|
||||
try_outside_fallible = "try requires an enclosing fallible function",
|
||||
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",
|
||||
})
|
||||
|
||||
|
||||
@@ -219,6 +219,7 @@ Function :: struct {
|
||||
locals arraylist.ArrayList(Type)
|
||||
result Type = .void
|
||||
result_array_type ArrayId = no_array
|
||||
result_fallible bool = false
|
||||
array_storage arraylist.ArrayList(ArrayStorage)
|
||||
aggregate_cells usize = 0
|
||||
has_addresses bool = false
|
||||
|
||||
@@ -26,6 +26,10 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "return", .return },
|
||||
{ "break", .break },
|
||||
{ "continue", .continue },
|
||||
{ "try", .try },
|
||||
{ "catch", .catch },
|
||||
{ "defer", .defer },
|
||||
{ "errdefer", .errdefer },
|
||||
{ "if", .if },
|
||||
{ "for", .for },
|
||||
{ "else", .else },
|
||||
|
||||
@@ -23,6 +23,8 @@ TokenKind :: enum {
|
||||
match
|
||||
proc, return
|
||||
break, continue
|
||||
try, catch
|
||||
defer, errdefer
|
||||
true, false
|
||||
and, or
|
||||
struct, enum, union
|
||||
|
||||
+26
-2
@@ -118,8 +118,32 @@ emit proc(
|
||||
try io.write_all(output, " ")
|
||||
try function_symbol(output, module, entry)
|
||||
try io.write_all(output, "()\n")
|
||||
if (result.kind != .void and result.kind != .invalid) {
|
||||
try print_call(output, module, result, 0)
|
||||
printable := result
|
||||
operand usize := 0
|
||||
if (function.result_fallible) {
|
||||
shape :: module.arrays.items[usize(result.shape)]
|
||||
success :: shape.fields[0]
|
||||
try io.write_all(output, " %result = alloca ")
|
||||
try write_type(output, module, result)
|
||||
try io.write_all(output, "\n store ")
|
||||
try write_type(output, module, result)
|
||||
try io.write_all(output, " %r0, ptr %result\n %tag = load ")
|
||||
try write_type(output, module, Type{ kind = shape.tag_type })
|
||||
try io.write_all(output, ", ptr %result\n %success = icmp eq ")
|
||||
try write_type(output, module, Type{ kind = shape.tag_type })
|
||||
try io.write_all(output, " %tag, ")
|
||||
try write_constant(output, module, success.tag)
|
||||
try io.write_all(output, "\n br i1 %success, label %ok, label %error\nerror:\n call void @report(i64 -1)\n ret i32 1\nok:\n")
|
||||
printable = Type{ kind = success.type, shape = success.array_type }
|
||||
operand = 1
|
||||
if (printable.kind != .void) {
|
||||
try io.print(" %payload = getelementptr i8, ptr %result, i64 {}\n %r1 = load ", output, {shape.payload_offset})
|
||||
try write_type(output, module, printable)
|
||||
try io.write_all(output, ", ptr %payload\n")
|
||||
}
|
||||
}
|
||||
if (printable.kind != .void and printable.kind != .invalid) {
|
||||
try print_call(output, module, printable, operand)
|
||||
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
|
||||
}
|
||||
try io.write_all(output, " call void @report(i64 -1)\n")
|
||||
|
||||
+383
-10
@@ -21,6 +21,7 @@ Storage :: struct {
|
||||
LoopTargets :: struct {
|
||||
continue_target ir.BlockId
|
||||
exit ir.BlockId
|
||||
cleanup_depth usize
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -33,12 +34,15 @@ State :: struct {
|
||||
storage []mut Storage
|
||||
arrays []mut ir.ArrayId
|
||||
arguments arraylist.ArrayList(ir.ValueId)
|
||||
cleanups arraylist.ArrayList(ast.NodeId)
|
||||
records []mut ir.ArrayId
|
||||
function usize = 0
|
||||
block ir.BlockId = ir.BlockId(0)
|
||||
terminated bool = false
|
||||
loop ?LoopTargets = null
|
||||
return_type typecheck.Type = typecheck.Type{}
|
||||
fallthrough_block ?ast.NodeId = null
|
||||
fallthrough_diagnostic ?diagnostics.DiagnosticId = null
|
||||
}
|
||||
|
||||
#! lowers checked source into caller-owned executable storage; release with ir.deinit.
|
||||
@@ -90,8 +94,10 @@ lower_package proc(
|
||||
arrays = arrays,
|
||||
records = records,
|
||||
arguments = arraylist.init(ir.ValueId, allocator),
|
||||
cleanups = arraylist.init(ast.NodeId, allocator),
|
||||
}
|
||||
defer arraylist.deinit(&state.arguments)
|
||||
defer arraylist.deinit(&state.cleanups)
|
||||
|
||||
# allocate every package identity before lowering any initializer or body.
|
||||
for (0..ast.package_decl_count(tree)) |i| {
|
||||
@@ -138,6 +144,7 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
|
||||
return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
|
||||
}
|
||||
if (type.kind == .slice) return .record
|
||||
if (type.kind == .fallible) return .record
|
||||
if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element)
|
||||
return match type.kind {
|
||||
.int: .int
|
||||
@@ -160,7 +167,7 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
|
||||
.function: .function
|
||||
.enumeration: unreachable
|
||||
.none, .invalid, .range, .type_value, .null, .anyopaque: .invalid
|
||||
.slice, .optional: unreachable
|
||||
.slice, .optional, .fallible: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +180,7 @@ lower_optional_type proc(state @State, child typecheck.Type) ir.Type {
|
||||
@hide:file
|
||||
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (type.kind == .record) return try prepare_record(state, type.record_type)
|
||||
if (type.kind == .slice or type.kind == .optional) return try prepare_container(state, type)
|
||||
if (type.kind == .slice or type.kind == .optional or type.kind == .fallible) return try prepare_container(state, type)
|
||||
if (type.kind != .array) return ir.no_array
|
||||
# checker history includes unresolved shapes; materialize only final used types.
|
||||
return try prepare_array(state, type.array_type)
|
||||
@@ -202,6 +209,23 @@ prepare_container proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.
|
||||
if (type.kind == .optional and (descriptor.element.kind == .pointer or descriptor.element.kind == .function)) return ir.no_array
|
||||
index :: usize(type.array_type)
|
||||
if (state.arrays[index] != ir.no_array) return state.arrays[index]
|
||||
if (type.kind == .fallible) {
|
||||
fields [2]ir.RecordField :: [
|
||||
{
|
||||
type = lower_type(state, descriptor.element),
|
||||
array_type = try array_shape(state, descriptor.element),
|
||||
tag = ir.Value{ type = .int, data = ir.ValueData{ integer = 0 } },
|
||||
},
|
||||
{
|
||||
type = lower_type(state, descriptor.error_type),
|
||||
array_type = try array_shape(state, descriptor.error_type),
|
||||
tag = ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } },
|
||||
},
|
||||
]
|
||||
shape :: try ir.add_union(state.module, fields[..], true)
|
||||
state.arrays[index] = shape
|
||||
return shape
|
||||
}
|
||||
fields [2]mut ir.RecordField := [{ type = .pointer }, { type = .usize }]
|
||||
if (type.kind == .optional) {
|
||||
fields[0] = ir.RecordField{ type = .bool }
|
||||
@@ -354,9 +378,13 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
||||
state.function = usize(state.storage[usize(id)].index)
|
||||
state.block = ir.BlockId(0)
|
||||
state.terminated = false
|
||||
state.loop = 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)
|
||||
state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||
state.module.functions.items[state.function].result_fallible = state.return_type.kind == .fallible
|
||||
for (0..ast.function_param_count(tree, id)) |i| {
|
||||
parameter :: ast.function_param(tree, id, i)
|
||||
type :: typecheck.type_of(state.checked, parameter)
|
||||
@@ -389,7 +417,9 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
||||
trap(state, diagnostic, ast.node_span(tree, declaration.body))
|
||||
return
|
||||
}
|
||||
finish(state, ir.no_value, ast.node_span(tree, declaration.body))
|
||||
value := ir.no_value
|
||||
if (state.return_type.kind == .fallible) value = try wrap_fallible(state, value, state.return_type, false, ast.node_span(tree, declaration.body))
|
||||
finish(state, value, ast.node_span(tree, declaration.body))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -433,6 +463,12 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
||||
}
|
||||
try predeclare_locals(state, loop.body)
|
||||
}
|
||||
.block_stmt: try predeclare_locals(state, ast.statement_block(state.tree, statement))
|
||||
.defer_stmt, .errdefer_stmt: {
|
||||
cleanup :: ast.cleanup_stmt(state.tree, statement)
|
||||
if (cleanup.capture) |capture| try predeclare_capture(state, capture)
|
||||
try predeclare_locals(state, cleanup.body)
|
||||
}
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
@@ -453,6 +489,8 @@ store_capture proc(state @mut State, capture ast.NodeId, value ir.ValueId, optio
|
||||
|
||||
@hide:file
|
||||
lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
cleanup_depth :: state.cleanups.items.len
|
||||
defer state.cleanups.items = state.cleanups.items.ptr[..cleanup_depth]
|
||||
# reset every direct declaration before control can return or trap within the scope.
|
||||
if (fresh_scope) {
|
||||
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
||||
@@ -471,6 +509,76 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
|
||||
if (state.terminated) return
|
||||
try lower_statement(state, ast.block_statement(state.tree, block, i))
|
||||
}
|
||||
if (!state.terminated) {
|
||||
if (state.fallthrough_block) |function_body| {
|
||||
if (function_body == block) {
|
||||
if (state.fallthrough_diagnostic) |diagnostic| {
|
||||
trap(state, diagnostic, ast.node_span(state.tree, block))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!state.terminated) try lower_cleanups(state, cleanup_depth, ir.no_value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_cleanups proc(state @mut State, depth usize, error_value ir.ValueId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
index := state.cleanups.items.len
|
||||
while (index > depth) {
|
||||
index -= 1
|
||||
id :: state.cleanups.items[index]
|
||||
if (ast.stmt_kind(state.tree, id) == .errdefer_stmt and error_value == ir.no_value) continue
|
||||
cleanup :: ast.cleanup_stmt(state.tree, id)
|
||||
if (cleanup.capture) |capture| {
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .store_local, left = error_value, slot = state.storage[usize(capture)].index, span = cleanup.span,
|
||||
})
|
||||
}
|
||||
try lower_block(state, cleanup.body, true)
|
||||
if (state.terminated) return
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
wrap_fallible proc(state @mut State, value ir.ValueId, type typecheck.Type, failed bool, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
slot :: if (failed) u32(1) else u32(0)
|
||||
shape :: try array_shape(state, type)
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .union_literal, type = .record, array_type = shape, slot = slot,
|
||||
left = value, constant = state.module.arrays.items[usize(shape)].fields[usize(slot)].tag, span = span,
|
||||
})
|
||||
}
|
||||
|
||||
@hide:file
|
||||
fallible_failed proc(state @mut State, value ir.ValueId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tag :: try emit(state, ir.Instruction{ op = .union_tag, type = .int, left = value, span = span })
|
||||
error_tag :: try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, span)
|
||||
return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = tag, right = error_tag, span = span })
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_return proc(state @mut State, value ir.ValueId, span diagnostics.Span) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.return_type.kind != .fallible) {
|
||||
try lower_cleanups(state, 0, ir.no_value)
|
||||
if (!state.terminated) finish(state, value, span)
|
||||
return
|
||||
}
|
||||
failed :: try fallible_failed(state, value, span)
|
||||
success :: try add_block(state)
|
||||
failure :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
||||
}
|
||||
state.block = failure
|
||||
error_type :: typecheck.array_type(state.checked, state.return_type.array_type).error_type
|
||||
error_value :: try project(state, value, lower_type(state, error_type), try array_shape(state, error_type), 1, span)
|
||||
try lower_cleanups(state, 0, error_value)
|
||||
if (!state.terminated) finish(state, value, span)
|
||||
state.block = success
|
||||
state.terminated = false
|
||||
try lower_cleanups(state, 0, ir.no_value)
|
||||
if (!state.terminated) finish(state, value, span)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -551,7 +659,7 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
|
||||
span = ast.node_span(state.tree, loop.condition),
|
||||
}
|
||||
previous_loop :: state.loop
|
||||
state.loop = LoopTargets{ continue_target = guard, exit = exit }
|
||||
state.loop = LoopTargets{ continue_target = guard, exit = exit, cleanup_depth = state.cleanups.items.len }
|
||||
defer state.loop = previous_loop
|
||||
state.block = body
|
||||
if (loop.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, loop.condition), loop.span)
|
||||
@@ -598,7 +706,7 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
||||
kind = .branch, value = condition_value, target = body, alternative = exit, span = interval.span,
|
||||
}
|
||||
previous_loop :: state.loop
|
||||
state.loop = LoopTargets{ continue_target = update, exit = exit }
|
||||
state.loop = LoopTargets{ continue_target = update, exit = exit, cleanup_depth = state.cleanups.items.len }
|
||||
defer state.loop = previous_loop
|
||||
state.block = body
|
||||
if (loop.capture) |capture| _ = trap_binding(state, capture)
|
||||
@@ -633,6 +741,11 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
||||
lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
match ast.stmt_kind(tree, id) {
|
||||
.block_stmt: try lower_block(state, ast.statement_block(tree, id), true)
|
||||
.defer_stmt, .errdefer_stmt: {
|
||||
if (trap_type(state, id)) return
|
||||
try arraylist.append(&state.cleanups, id)
|
||||
}
|
||||
.if_stmt: try lower_if(state, id)
|
||||
.while_stmt: try lower_while(state, id)
|
||||
.for_stmt: try lower_for(state, id)
|
||||
@@ -642,6 +755,8 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
target := targets.exit
|
||||
# each loop supplies its own guard or guarded-update entry.
|
||||
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.continue_target
|
||||
try lower_cleanups(state, targets.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, span = ast.node_span(tree, id),
|
||||
}
|
||||
@@ -712,7 +827,10 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
value := ir.no_value
|
||||
if (statement.value) |expression| value = try lower_as(state, expression, state.return_type)
|
||||
if (state.terminated or trap_type(state, id)) return
|
||||
finish(state, value, statement.span)
|
||||
if (statement.value == null and state.return_type.kind == .fallible) {
|
||||
value = try wrap_fallible(state, value, state.return_type, false, statement.span)
|
||||
}
|
||||
try lower_return(state, value, statement.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1127,6 +1245,14 @@ lower_as proc(state @mut State, id ast.NodeId, expected typecheck.Type) ir.Value
|
||||
actual :: typecheck.type_of(state.checked, id)
|
||||
span :: ast.node_span(state.tree, id)
|
||||
if (actual.kind == .invalid or expected.kind == .invalid) return try lower_expression(state, id)
|
||||
if (expected.kind == .fallible and actual.kind != .fallible) {
|
||||
descriptor :: typecheck.array_type(state.checked, expected.array_type)
|
||||
failed :: !typecheck.value_assignable(state.checked, actual, descriptor.element)
|
||||
channel :: if (failed) descriptor.error_type else descriptor.element
|
||||
payload :: try lower_as(state, id, channel)
|
||||
if (state.terminated) return ir.no_value
|
||||
return try wrap_fallible(state, payload, expected, failed, span)
|
||||
}
|
||||
if (expected.kind == .optional and actual.kind != .optional) {
|
||||
child :: typecheck.array_type(state.checked, expected.array_type).element
|
||||
runtime :: lower_type(state, child)
|
||||
@@ -1144,7 +1270,245 @@ lower_as proc(state @mut State, id ast.NodeId, expected typecheck.Type) ir.Value
|
||||
if (state.terminated) return ir.no_value
|
||||
return try record_pair(state, try array_shape(state, expected), parts.pointer, parts.bound, span)
|
||||
}
|
||||
return try lower_expression(state, id)
|
||||
value :: try lower_expression(state, id)
|
||||
if (state.terminated) return ir.no_value
|
||||
return try convert_value(state, value, actual, expected, span)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
convert_value proc(state @mut State, value ir.ValueId, actual, expected typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (actual.kind == .fallible and expected.kind == .fallible) {
|
||||
if (actual.array_type == expected.array_type) return value
|
||||
source :: typecheck.array_type(state.checked, actual.array_type)
|
||||
destination :: typecheck.array_type(state.checked, expected.array_type)
|
||||
shape :: try array_shape(state, expected)
|
||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
||||
failed :: try fallible_failed(state, value, span)
|
||||
success :: try add_block(state)
|
||||
failure :: try add_block(state)
|
||||
join :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
||||
}
|
||||
for (0..2) |i| {
|
||||
state.block = if (i == 0) success else failure
|
||||
child :: if (i == 0) source.element else source.error_type
|
||||
target :: if (i == 0) destination.element else destination.error_type
|
||||
payload := ir.no_value
|
||||
if (child.kind != .void) payload = try project(state, value, lower_type(state, child), try array_shape(state, child), u32(i), span)
|
||||
converted :: try convert_value(state, payload, child, target, span)
|
||||
wrapped :: try wrap_fallible(state, converted, expected, i == 1, span)
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
}
|
||||
state.block = join
|
||||
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
||||
}
|
||||
if (expected.kind == .optional and actual.kind != .optional) {
|
||||
child :: typecheck.array_type(state.checked, expected.array_type).element
|
||||
runtime :: lower_type(state, child)
|
||||
nullable :: child.kind == .pointer or child.kind == .function
|
||||
if (actual.kind == .null and nullable) return try null_constant(state, runtime, span)
|
||||
payload := ir.no_value
|
||||
if (actual.kind == .null) payload = try zero_value(state, runtime, try array_shape(state, child), span)
|
||||
else payload = try convert_value(state, value, actual, child, span)
|
||||
if (nullable) return payload
|
||||
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = actual.kind != .null } }, span)
|
||||
return try record_pair(state, try array_shape(state, expected), present, payload, span)
|
||||
}
|
||||
if (actual.kind == .optional and expected.kind == .optional and actual.array_type != expected.array_type) {
|
||||
source :: typecheck.array_type(state.checked, actual.array_type).element
|
||||
child :: typecheck.array_type(state.checked, expected.array_type).element
|
||||
if (source.kind == .pointer or source.kind == .function) return value
|
||||
shape :: try array_shape(state, expected)
|
||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
||||
present :: try optional_present(state, value, actual, span)
|
||||
some :: try add_block(state)
|
||||
none :: try add_block(state)
|
||||
join :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = present, target = some, alternative = none, span = span,
|
||||
}
|
||||
state.block = some
|
||||
payload :: try optional_payload(state, value, actual, span)
|
||||
converted :: try convert_value(state, payload, source, child, span)
|
||||
wrapped :: try record_pair(state, shape, present, converted, span)
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
state.block = none
|
||||
absent :: try zero_value(state, .record, shape, span)
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = absent, slot = slot, span = span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
state.block = join
|
||||
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
||||
}
|
||||
if (expected.kind == .slice and actual.kind == .string) {
|
||||
pointer :: try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
|
||||
length :: try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
|
||||
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
|
||||
}
|
||||
if (expected.kind == .slice and actual.kind == .array) {
|
||||
source_shape :: try array_shape(state, actual)
|
||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .array, source_shape))
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = value, slot = slot, span = span })
|
||||
address :: try emit(state, ir.Instruction{
|
||||
op = .address_local, type = .address, pointee = .array, array_type = source_shape, slot = slot, span = span,
|
||||
})
|
||||
pointer :: try emit(state, ir.Instruction{
|
||||
op = .pointer_from_address, type = .pointer, left = address, pointee = .array, array_type = source_shape, span = span,
|
||||
})
|
||||
length :: try usize_constant(state, typecheck.array_type(state.checked, actual.array_type).len, span)
|
||||
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
|
||||
}
|
||||
if (!typecheck.error_assignable(state.checked, actual, expected)
|
||||
and !typecheck.error_assignable(state.checked, expected, actual)) return value
|
||||
source_union := false
|
||||
if (actual.kind == .record) source_union = typecheck.record_type(state.checked, actual.record_type).is_union
|
||||
if (expected.kind == .enumeration) {
|
||||
if (!source_union) return value
|
||||
return try emit(state, ir.Instruction{ op = .union_tag, type = lower_type(state, expected), left = value, span = span })
|
||||
}
|
||||
if (expected.kind != .record) return value
|
||||
destination :: typecheck.record_type(state.checked, expected.record_type)
|
||||
if (!destination.is_union) return value
|
||||
if (actual.kind == .record and actual.record_type == expected.record_type) return value
|
||||
shape :: try array_shape(state, expected)
|
||||
if (actual.kind == .record and !source_union) {
|
||||
for destination.fields |field, i| {
|
||||
if (field.type.kind != .record or field.type.record_type != actual.record_type or field.type.nominal != actual.nominal) continue
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .union_literal, type = .record, array_type = shape, slot = u32(i), left = value,
|
||||
constant = state.module.arrays.items[usize(shape)].fields[i].tag, span = span,
|
||||
})
|
||||
}
|
||||
unreachable
|
||||
}
|
||||
discriminant := value
|
||||
source_shape := ir.no_array
|
||||
if (source_union) {
|
||||
source_shape = try array_shape(state, actual)
|
||||
tag_type :: typecheck.record_type(state.checked, actual.record_type).tag_type
|
||||
discriminant = try emit(state, ir.Instruction{ op = .union_tag, type = lower_type(state, tag_type), left = value, span = span })
|
||||
}
|
||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
||||
join :: try add_block(state)
|
||||
for destination.fields |field, i| {
|
||||
selected :: try add_block(state)
|
||||
next :: try add_block(state)
|
||||
tag :: state.module.arrays.items[usize(shape)].fields[i].tag
|
||||
expected_tag :: try constant(state, tag, span)
|
||||
condition :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = discriminant, right = expected_tag, span = span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = condition, target = selected, alternative = next, span = span,
|
||||
}
|
||||
state.block = selected
|
||||
payload := ir.no_value
|
||||
if (source_union and field.type.kind != .void) {
|
||||
source_fields :: state.module.arrays.items[usize(source_shape)].fields
|
||||
for source_fields |source_field, j| {
|
||||
if (source_field.tag.data.unsigned != tag.data.unsigned) continue
|
||||
payload = try project(state, value, source_field.type, source_field.array_type, u32(j), span)
|
||||
break
|
||||
}
|
||||
}
|
||||
wrapped :: try emit(state, ir.Instruction{
|
||||
op = .union_literal, type = .record, array_type = shape, slot = u32(i), left = payload, constant = tag, span = span,
|
||||
})
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
state.block = next
|
||||
}
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .unreachable, span = span }
|
||||
state.block = join
|
||||
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_try proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
operand :: ast.type_operand(state.tree, id)
|
||||
value :: try lower_expression(state, operand)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
span :: ast.node_span(state.tree, id)
|
||||
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, operand).array_type)
|
||||
failed :: try fallible_failed(state, value, span)
|
||||
success :: try add_block(state)
|
||||
failure :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
||||
}
|
||||
state.block = failure
|
||||
payload :: try project(state, value, lower_type(state, descriptor.error_type), try array_shape(state, descriptor.error_type), 1, span)
|
||||
target_error :: typecheck.array_type(state.checked, state.return_type.array_type).error_type
|
||||
converted :: try convert_value(state, payload, descriptor.error_type, target_error, span)
|
||||
result :: try wrap_fallible(state, converted, state.return_type, true, span)
|
||||
try lower_cleanups(state, 0, converted)
|
||||
if (!state.terminated) finish(state, result, span)
|
||||
state.block = success
|
||||
state.terminated = false
|
||||
if (descriptor.element.kind == .void) return ir.no_value
|
||||
return try project(state, value, lower_type(state, descriptor.element), try array_shape(state, descriptor.element), 0, span)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_catch proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
expression :: ast.catch_expr(state.tree, id)
|
||||
value :: try lower_expression(state, expression.operand)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
span :: expression.span
|
||||
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, expression.operand).array_type)
|
||||
type :: typecheck.type_of(state.checked, id)
|
||||
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))
|
||||
failed :: try fallible_failed(state, value, span)
|
||||
success :: try add_block(state)
|
||||
failure :: try add_block(state)
|
||||
join :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
||||
}
|
||||
state.block = success
|
||||
if (runtime != .void) {
|
||||
payload :: try project(state, value, lower_type(state, descriptor.element), try array_shape(state, descriptor.element), 0, span)
|
||||
converted :: try convert_value(state, payload, descriptor.element, type, span)
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = converted, slot = slot, span = span })
|
||||
}
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
state.block = failure
|
||||
if (expression.capture) |capture| {
|
||||
try predeclare_capture(state, capture)
|
||||
payload :: try project(state, value, lower_type(state, descriptor.error_type), try array_shape(state, descriptor.error_type), 1, span)
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = payload, slot = state.storage[usize(capture)].index, span = span })
|
||||
}
|
||||
if (!trap_type(state, expression.handler)) {
|
||||
if (expression.handler_is_block) {
|
||||
try predeclare_locals(state, expression.handler)
|
||||
try lower_block(state, expression.handler, true)
|
||||
} else {
|
||||
fallback :: try lower_as(state, expression.handler, type)
|
||||
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
||||
op = .store_local, left = fallback, slot = slot, span = span,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!state.terminated) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
state.block = join
|
||||
state.terminated = false
|
||||
if (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
|
||||
@@ -1412,7 +1776,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
# ranges have no physical value; only lower_for decomposes an iterable.
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .slice_type, .optional_type: {
|
||||
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
|
||||
if (!trap_type(state, id)) {
|
||||
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
||||
}
|
||||
@@ -1422,6 +1786,8 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.match_expr: return try lower_match(state, id)
|
||||
.try_expr: return try lower_try(state, id)
|
||||
.catch_expr: return try lower_catch(state, id)
|
||||
.literal_null: return try null_constant(state, .pointer, span)
|
||||
.slice: return try lower_slice(state, id)
|
||||
.builtin_call: return try lower_builtin(state, id)
|
||||
@@ -1544,8 +1910,15 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
type = .function,
|
||||
data = ir.ValueData{ function = ir.FunctionId(location.index) },
|
||||
}, span)
|
||||
.global: return try emit(state, ir.Instruction{ op = .load_global, type = type, array_type = shape, slot = location.index, span = span })
|
||||
.local: return try emit(state, ir.Instruction{ op = .load_local, type = type, array_type = shape, slot = location.index, span = span })
|
||||
.global, .local: {
|
||||
declaration_type :: typecheck.type_of(state.checked, binding.data.declaration)
|
||||
operation :: if (location.kind == .global) ir.Op.load_global else ir.Op.load_local
|
||||
value :: try emit(state, ir.Instruction{
|
||||
op = operation, type = lower_type(state, declaration_type),
|
||||
array_type = try array_shape(state, declaration_type), slot = location.index, span = span,
|
||||
})
|
||||
return try convert_value(state, value, declaration_type, typecheck.type_of(state.checked, id), span)
|
||||
}
|
||||
.none: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1924,3 +1924,36 @@ empty_array_entry_results_need_no_escaped_allocation test {
|
||||
interpreter.deinit(&outcome)
|
||||
try testing.expect_equal(usize(0), state.live)
|
||||
}
|
||||
|
||||
fallible_source_handlers_do_not_catch_runtime_allocation_failures test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Error :: enum { out_of_memory }
|
||||
`fail proc() int ! Error { return .out_of_memory }
|
||||
`main proc() int { return fail() catch 42 }
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(usize(0), diagnostics.count(&diagnostic_store))
|
||||
succeeded := false
|
||||
failures := usize(0)
|
||||
for (usize(0)..usize(128)) |limit| {
|
||||
state := AllocationState{ remaining = limit }
|
||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||
outcome := interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
||||
try testing.expect(error == .out_of_memory)
|
||||
failures += 1
|
||||
try testing.expect_equal(usize(0), state.live)
|
||||
try testing.expect_equal(usize(0), diagnostics.count(&diagnostic_store))
|
||||
continue
|
||||
}
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
interpreter.deinit(&outcome)
|
||||
try testing.expect_equal(usize(0), state.live)
|
||||
succeeded = true
|
||||
break
|
||||
}
|
||||
try testing.expect(succeeded and failures > 0)
|
||||
}
|
||||
|
||||
+12
-2
@@ -187,9 +187,19 @@ run_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverE
|
||||
defer ir.deinit(&module)
|
||||
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
if (outcome.trap == null) try print_result(io.stdout(runtime), &module, outcome.value)
|
||||
value := outcome.value
|
||||
failed := false
|
||||
if (outcome.trap == null) {
|
||||
if (module.entry) |entry| {
|
||||
if (module.functions.items[usize(entry)].result_fallible) {
|
||||
failed = ir.union_active(&module, value, 1)
|
||||
if (!failed) value = ir.aggregate_item(&module, value, 0)
|
||||
}
|
||||
}
|
||||
if (!failed) try print_result(io.stdout(runtime), &module, value)
|
||||
}
|
||||
try report_diagnostics(allocator, io.stderr(runtime), compilation.source_files(&session), &diagnostic_store, outcome.trap)
|
||||
if (outcome.trap != null or diagnostics.count(&diagnostic_store) != 0) return 1
|
||||
if (failed or outcome.trap != null or diagnostics.count(&diagnostic_store) != 0) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
+158
-33
@@ -30,6 +30,7 @@ State :: struct {
|
||||
next_token usize
|
||||
paren_depth usize = 0
|
||||
allow_record_literals bool = true
|
||||
allow_sum_types bool = true
|
||||
match_arm_body bool = false
|
||||
}
|
||||
|
||||
@@ -176,6 +177,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
parse_parameters proc(
|
||||
state @mut State, params @mut arraylist.ArrayList(ast.NodeId),
|
||||
) void ! ParseError {
|
||||
skip_newlines(state)
|
||||
names arraylist.ArrayList(lexer.Token) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&names)
|
||||
while !next_is(state, .close_paren) {
|
||||
@@ -187,24 +189,42 @@ parse_parameters proc(
|
||||
_ = try consume(state, .comma)
|
||||
}
|
||||
type_id :: try parse_type(state)
|
||||
type_span :: ast.builder_span(state.builder, type_id)
|
||||
type_end :: state.tokens[state.next_token - 1].span.end
|
||||
for (names.items) |parameter_name| {
|
||||
param_id :: try ast.add_param(
|
||||
state.builder,
|
||||
ast.Span{ start = parameter_name.span.start, end = type_span.end, source = parameter_name.span.source },
|
||||
ast.Span{ start = parameter_name.span.start, end = type_end, source = parameter_name.span.source },
|
||||
parameter_name.span,
|
||||
parameter_name.data.str_id,
|
||||
type_id,
|
||||
)
|
||||
try arraylist.append(params, param_id)
|
||||
}
|
||||
if next_is(state, .comma) _ = try consume(state, .comma)
|
||||
skip_newlines(state)
|
||||
if next_is(state, .comma) {
|
||||
state.next_token += 1
|
||||
skip_newlines(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
||||
open :: try consume(state, .open_curly)
|
||||
previous_depth :: state.paren_depth
|
||||
previous_record_literals :: state.allow_record_literals
|
||||
previous_sum_types :: state.allow_sum_types
|
||||
previous_match_arm_body :: state.match_arm_body
|
||||
state.paren_depth = 0
|
||||
state.allow_record_literals = true
|
||||
state.allow_sum_types = true
|
||||
state.match_arm_body = false
|
||||
defer {
|
||||
state.paren_depth = previous_depth
|
||||
state.allow_record_literals = previous_record_literals
|
||||
state.allow_sum_types = previous_sum_types
|
||||
state.match_arm_body = previous_match_arm_body
|
||||
}
|
||||
statements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&statements)
|
||||
|
||||
@@ -249,6 +269,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
or next_n_is(state, 1, .distinct)
|
||||
or next_n_is(state, 1, .at)
|
||||
or next_n_is(state, 1, .question)
|
||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
||||
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
|
||||
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
|
||||
or next_n_is(state, 1, .double_colon)
|
||||
@@ -263,8 +284,10 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.for: return try parse_for(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
|
||||
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null: {
|
||||
.defer, .errdefer: return try parse_cleanup(state, allow_else)
|
||||
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct,
|
||||
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .try: {
|
||||
expression :: try parse_expr(state)
|
||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||
return try ast.add_expression_stmt(state.builder, expression)
|
||||
@@ -273,6 +296,30 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_error_capture proc(state @mut State) ?ast.NodeId ! ParseError {
|
||||
capture ?ast.NodeId := null
|
||||
if (!next_is(state, .pipe)) return capture
|
||||
state.next_token += 1
|
||||
capture = try parse_capture_name(state)
|
||||
_ = try consume(state, .pipe)
|
||||
return capture
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_cleanup proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: current(state)
|
||||
state.next_token += 1
|
||||
error_only :: keyword.kind == .errdefer
|
||||
capture ?ast.NodeId :: if (error_only) try parse_error_capture(state) else null
|
||||
body :: try parse_arm(state, allow_else)
|
||||
return try ast.add_cleanup_stmt(state.builder, ast.Span{
|
||||
start = keyword.span.start,
|
||||
end = ast.builder_span(state.builder, body).end,
|
||||
source = keyword.span.source,
|
||||
}, body, capture, error_only)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .return)
|
||||
@@ -517,8 +564,14 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
@hide:file
|
||||
parse_guard_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||
previous :: state.allow_record_literals
|
||||
previous_sum_types :: state.allow_sum_types
|
||||
state.allow_record_literals = false
|
||||
defer state.allow_record_literals = previous
|
||||
# capture pipes stay boundaries even when an index/call permits record literals.
|
||||
state.allow_sum_types = false
|
||||
defer {
|
||||
state.allow_record_literals = previous
|
||||
state.allow_sum_types = previous_sum_types
|
||||
}
|
||||
return try parse_expr(state)
|
||||
}
|
||||
|
||||
@@ -630,12 +683,49 @@ parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
|
||||
@hide:file
|
||||
parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
skip_operand_newlines(state)
|
||||
start :: current(state).span
|
||||
left := try parse_type_atom(state)
|
||||
while true {
|
||||
skip_continuation_newlines(state)
|
||||
if (!next_is(state, .pipe)) break
|
||||
state.next_token += 1
|
||||
right :: try parse_type_atom(state)
|
||||
left = try ast.add_binary_type(state.builder, ast.Span{
|
||||
start = start.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = start.source,
|
||||
}, left, right, false)
|
||||
}
|
||||
if (next_is(state, .bang)) {
|
||||
state.next_token += 1
|
||||
right :: try parse_type(state)
|
||||
left = try ast.add_binary_type(state.builder, ast.Span{
|
||||
start = start.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = start.source,
|
||||
}, left, right, true)
|
||||
}
|
||||
return left
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
|
||||
skip_operand_newlines(state)
|
||||
if (next_is(state, .open_paren)) {
|
||||
state.next_token += 1
|
||||
state.paren_depth += 1
|
||||
defer state.paren_depth -= 1
|
||||
child :: try parse_type(state)
|
||||
_ = try consume(state, .close_paren)
|
||||
return child
|
||||
}
|
||||
if (next_is(state, .question)) {
|
||||
token :: current(state)
|
||||
state.next_token += 1
|
||||
child :: try parse_type(state)
|
||||
child :: try parse_type_atom(state)
|
||||
return try ast.add_optional_type(state.builder, ast.Span{
|
||||
start = token.span.start, end = ast.builder_span(state.builder, child).end, source = token.span.source,
|
||||
start = token.span.start, end = state.tokens[state.next_token - 1].span.end, source = token.span.source,
|
||||
}, child)
|
||||
}
|
||||
if (next_is(state, .at) or next_is(state, .star)) {
|
||||
@@ -653,7 +743,7 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
result :: try parse_type(state)
|
||||
span :: ast.Span{
|
||||
start = token.span.start,
|
||||
end = ast.builder_span(state.builder, result).end,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = token.span.source,
|
||||
}
|
||||
if (mutable or token.kind == .star) {
|
||||
@@ -662,10 +752,10 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
}
|
||||
return try ast.add_function_type(state.builder, span, result, params.items)
|
||||
}
|
||||
element :: try parse_type(state)
|
||||
element :: try parse_type_atom(state)
|
||||
return try ast.add_pointer_type(state.builder, ast.Span{
|
||||
start = token.span.start,
|
||||
end = ast.builder_span(state.builder, element).end,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = token.span.source,
|
||||
}, element, mutable, token.kind == .star)
|
||||
}
|
||||
@@ -680,9 +770,9 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .close_bracket)
|
||||
mutable :: next_is(state, .mut)
|
||||
if (mutable) state.next_token += 1
|
||||
element :: try parse_type(state)
|
||||
element :: try parse_type_atom(state)
|
||||
return try ast.add_slice_type(state.builder, ast.Span{
|
||||
start = open.span.start, end = ast.builder_span(state.builder, element).end, source = open.span.source,
|
||||
start = open.span.start, end = state.tokens[state.next_token - 1].span.end, source = open.span.source,
|
||||
}, element, mutable, sentinel)
|
||||
}
|
||||
count ?ast.NodeId := null
|
||||
@@ -698,12 +788,12 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .close_bracket)
|
||||
mutable :: next_is(state, .mut)
|
||||
if (mutable) state.next_token += 1
|
||||
element :: try parse_type(state)
|
||||
element :: try parse_type_atom(state)
|
||||
return try ast.add_array_type(
|
||||
state.builder,
|
||||
ast.Span{
|
||||
start = open.span.start,
|
||||
end = ast.builder_span(state.builder, element).end,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = open.span.source,
|
||||
},
|
||||
count, element, mutable,
|
||||
@@ -733,7 +823,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .ident or kind == .open_bracket or kind == .struct
|
||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
|
||||
or kind == .at or kind == .star or kind == .question
|
||||
or kind == .at or kind == .star or kind == .question or kind == .open_paren
|
||||
)
|
||||
}
|
||||
|
||||
@@ -742,7 +832,7 @@ member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test, .c_func, .null: true
|
||||
.alias, .match, .import, .test, .c_func, .null, .try, .catch, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
@@ -961,7 +1051,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: true
|
||||
.open_curly, .return, .break, .continue, .if, .while, .for, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
if (statement_body or named_binding_starts(state)) {
|
||||
@@ -1144,21 +1234,21 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
state.next_token += 1
|
||||
operand :: try parse_expr_bp(state, right_bp)
|
||||
operand_span :: ast.builder_span(state.builder, operand)
|
||||
span :: ast.Span{
|
||||
start = token.span.start,
|
||||
end = operand_span.end,
|
||||
source = token.span.source,
|
||||
}
|
||||
operator ast.UnaryOperator :: match token.kind {
|
||||
.bang: .logical_not
|
||||
.ampersand: .address_of
|
||||
else: .negate
|
||||
}
|
||||
yield try ast.add_unary(
|
||||
state.builder,
|
||||
ast.Span{
|
||||
start = token.span.start,
|
||||
end = operand_span.end,
|
||||
source = token.span.source,
|
||||
},
|
||||
operator,
|
||||
operand,
|
||||
)
|
||||
prefixed :: if (token.kind == .try)
|
||||
try ast.add_try_expr(state.builder, span, operand)
|
||||
else
|
||||
try ast.add_unary(state.builder, span, operator, operand)
|
||||
yield prefixed
|
||||
} else {
|
||||
yield try parse_primary(state)
|
||||
}
|
||||
@@ -1178,7 +1268,11 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .bang and next_n_is(state, 1, .open_paren)) {
|
||||
# adjacent bang calls stay intrinsics; spaced 'T ! (E)' is a fallible type.
|
||||
if (
|
||||
token.kind == .bang and next_n_is(state, 1, .open_paren)
|
||||
and state.tokens[state.next_token - 1].span.end == token.span.start
|
||||
) {
|
||||
state.next_token += 1
|
||||
left = try parse_call(state, left, state.tokens[expression_start].span.start, true)
|
||||
left_grouped = false
|
||||
@@ -1218,6 +1312,34 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .catch) {
|
||||
if (min_bp > BindingPowerScalar(0)) break
|
||||
state.next_token += 1
|
||||
capture :: try parse_error_capture(state)
|
||||
skip_operand_newlines(state)
|
||||
handler_is_block :: next_is(state, .open_curly)
|
||||
handler :: if (handler_is_block) try parse_block(state) else try parse_expr_bp(state, BindingPowerScalar(0))
|
||||
left = try ast.add_catch_expr(state.builder, ast.Span{
|
||||
start = state.tokens[expression_start].span.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = token.span.source,
|
||||
}, left, capture, handler, handler_is_block)
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .bang or (token.kind == .pipe and state.allow_sum_types)) {
|
||||
power :: if (token.kind == .bang) BindingPowerScalar(1) else BindingPowerScalar(2)
|
||||
if (power < min_bp) break
|
||||
state.next_token += 1
|
||||
right :: try parse_expr_bp(state, BindingPowerScalar(u32(power) + 1))
|
||||
left = try ast.add_binary_type(state.builder, ast.Span{
|
||||
start = state.tokens[expression_start].span.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = token.span.source,
|
||||
}, left, right, token.kind == .bang)
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .range or token.kind == .range_inclusive) {
|
||||
if (min_bp > BindingPowerScalar(0)) break
|
||||
left = try validate_range_bound(state, left, left_grouped)
|
||||
@@ -1421,7 +1543,7 @@ invalid_array_literal proc(state @mut State, start, end u32) ast.NodeId ! ParseE
|
||||
@hide:file
|
||||
pointer_binding_starts proc(state @State) bool {
|
||||
depth := usize(0)
|
||||
for (state.next_token + 2)..(state.tokens.len) |i| {
|
||||
for (state.next_token + 1)..(state.tokens.len) |i| {
|
||||
kind :: state.tokens[i].kind
|
||||
if (kind == .open_curly or kind == .open_bracket or kind == .open_paren) depth += 1
|
||||
if (kind == .close_curly or kind == .close_bracket or kind == .close_paren) {
|
||||
@@ -1444,6 +1566,7 @@ named_binding_starts proc(state @State) bool {
|
||||
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc)
|
||||
or next_n_is(state, 1, .at)
|
||||
or next_n_is(state, 1, .question)
|
||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
||||
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
|
||||
)
|
||||
)
|
||||
@@ -1538,7 +1661,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, .eof: true
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .if, .while, .for, .defer, .errdefer, .eof: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
@@ -1560,6 +1683,7 @@ is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
|
||||
or kind == .and
|
||||
or kind == .or
|
||||
or kind == .pipe
|
||||
or kind == .catch
|
||||
or is_expression_boundary(kind)
|
||||
)
|
||||
}
|
||||
@@ -1588,6 +1712,7 @@ skip_continuation_newlines proc(state @mut State) void {
|
||||
kind != .close_paren and kind != .comma and kind != .open_paren
|
||||
and kind != .close_bracket and kind != .open_bracket
|
||||
and kind != .range and kind != .range_inclusive
|
||||
and kind != .pipe and kind != .bang and kind != .catch
|
||||
and infix_binding_power(kind) == null
|
||||
) state.next_token = start
|
||||
}
|
||||
@@ -1667,7 +1792,7 @@ recovery_closes_delimiters proc(state @State, paren_depth, square_depth usize) b
|
||||
kind :: state.tokens[i].kind
|
||||
match kind {
|
||||
.open_curly, .close_curly, .eof, .if, .else, .while, .for,
|
||||
.return, .break, .continue: return false
|
||||
.return, .break, .continue, .defer, .errdefer: return false
|
||||
.ident: {
|
||||
if (i + 1 < state.tokens.len) {
|
||||
next :: state.tokens[i + 1].kind
|
||||
@@ -1739,7 +1864,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
|
||||
@hide:file
|
||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||
return match kind {
|
||||
.minus, .bang, .ampersand: BindingPowerScalar(50)
|
||||
.minus, .bang, .ampersand, .try: BindingPowerScalar(50)
|
||||
else: null
|
||||
}
|
||||
}
|
||||
|
||||
+580
-15
@@ -28,6 +28,7 @@ TypeKind :: enum {
|
||||
optional
|
||||
null
|
||||
anyopaque
|
||||
fallible
|
||||
}
|
||||
|
||||
RecordId :: distinct u32
|
||||
@@ -47,6 +48,7 @@ EnumType :: struct {
|
||||
backing TypeKind
|
||||
explicit_backing bool = false
|
||||
members []EnumMember
|
||||
composed bool = false
|
||||
}
|
||||
|
||||
RecordField :: struct {
|
||||
@@ -61,6 +63,7 @@ RecordType :: struct {
|
||||
is_union bool = false
|
||||
tag_type Type = Type{}
|
||||
declared_tag_type Type = Type{}
|
||||
composed bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -77,6 +80,7 @@ ArrayType :: struct {
|
||||
mutable bool = false
|
||||
kind TypeKind = .array
|
||||
sentinel ?i64 = null
|
||||
error_type Type = Type{}
|
||||
}
|
||||
|
||||
PointerId :: distinct u32
|
||||
@@ -103,6 +107,7 @@ Type :: struct {
|
||||
|
||||
@hide:file
|
||||
Storage :: struct {
|
||||
tree @ast.Tree
|
||||
types []mut Type
|
||||
arrays arraylist.ArrayList(ArrayType)
|
||||
pointers arraylist.ArrayList(PointerType)
|
||||
@@ -118,7 +123,7 @@ Storage :: struct {
|
||||
}
|
||||
|
||||
#! owns node-indexed types, aggregate descriptors, initializer mappings, and function fallthrough obligations; release with deinit.
|
||||
#! function IDs refer to the caller-owned AST; diagnostic IDs refer to the caller-owned store.
|
||||
#! borrows the caller-owned AST for signature comparisons; diagnostic IDs refer to the caller-owned store.
|
||||
Result :: struct { storage Storage }
|
||||
|
||||
@hide:file
|
||||
@@ -141,6 +146,7 @@ Role :: enum {
|
||||
for_stmt,
|
||||
loop_capture,
|
||||
loop_control,
|
||||
cleanup,
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -174,6 +180,11 @@ Cell :: struct {
|
||||
match_pattern bool = false
|
||||
first_edge usize = no_index
|
||||
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
in_cleanup bool = false
|
||||
narrowing_arm ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
enclosing_arm ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
match_owner ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
narrowed_type Type = Type{}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -208,13 +219,15 @@ State :: struct {
|
||||
variant_slots []mut usize
|
||||
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
in_loop bool = false
|
||||
in_cleanup bool = false
|
||||
narrowing_arm ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
slots []mut usize
|
||||
collecting_assignment bool = false
|
||||
has_assignments bool = false
|
||||
pointer_depth usize = 0
|
||||
}
|
||||
|
||||
#! borrows the AST, resolution, and diagnostic store only during checking.
|
||||
#! borrows resolution and diagnostics during checking; Result retains the AST borrow for signature comparisons.
|
||||
#! temporary constraints are freed on every exit; Result owns types, aggregate descriptors, initializer mappings, and fallthrough obligations.
|
||||
check proc(
|
||||
allocator mem.Allocator,
|
||||
@@ -327,7 +340,8 @@ check proc(
|
||||
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
||||
if (cells[i].type.kind == .invalid) continue
|
||||
binding :: sema.binding(resolution, id)
|
||||
if (binding.kind == .declaration and !cells[i].assignment_value and !cells[usize(binding.data.declaration)].fixed) {
|
||||
if (binding.kind == .declaration and !cells[i].assignment_value
|
||||
and !cells[usize(binding.data.declaration)].fixed and !has_narrowing(&state, id)) {
|
||||
join(&state, id, binding.data.declaration)
|
||||
}
|
||||
}
|
||||
@@ -535,6 +549,7 @@ check proc(
|
||||
function :: ast.function_decl(tree, id)
|
||||
result :: types[usize(function.return_type)]
|
||||
if (result.kind == .void or result.kind == .invalid or types[usize(id)].kind == .invalid) continue
|
||||
if (result.kind == .fallible and state.arrays.items[usize(result.array_type)].element.kind == .void) continue
|
||||
if (block_falls_through(&state, function.body)) {
|
||||
# fallthrough is independent of errors in paths that may not execute.
|
||||
diagnostic :: try diagnostics.add(diagnostic_store, .missing_return, ast.node_span(tree, function.body))
|
||||
@@ -547,6 +562,7 @@ check proc(
|
||||
}
|
||||
}
|
||||
return Result{ storage = Storage{
|
||||
tree = tree,
|
||||
types = types,
|
||||
arrays = state.arrays,
|
||||
pointers = state.pointers,
|
||||
@@ -968,6 +984,9 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
state.cells[usize(statement)].role = .return_stmt
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
state.cells[usize(statement)].return_annotation = return_annotation
|
||||
if (state.in_cleanup) state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
|
||||
state.diagnostics, .cleanup_control_flow, ast.node_span(tree, statement),
|
||||
))
|
||||
if (ast.return_stmt(tree, statement).value) |value| {
|
||||
try collect_expression(state, value)
|
||||
try depend(state, value, statement)
|
||||
@@ -1051,11 +1070,38 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
||||
try collect_block(state, loop.body, return_annotation, true)
|
||||
try depend(state, loop.body, id)
|
||||
}
|
||||
.block_stmt: {
|
||||
body :: ast.statement_block(tree, statement)
|
||||
state.cells[usize(statement)].role = .body
|
||||
try collect_block(state, body, return_annotation, in_loop)
|
||||
try depend(state, body, statement)
|
||||
}
|
||||
.defer_stmt, .errdefer_stmt: {
|
||||
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)]
|
||||
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,
|
||||
))
|
||||
}
|
||||
if (cleanup.capture) |capture| {
|
||||
try collect_capture(state, capture, cleanup.body)
|
||||
state.cells[usize(capture)].type = if (result.kind == .fallible)
|
||||
state.arrays.items[usize(result.array_type)].error_type else result
|
||||
}
|
||||
saved_cleanup :: state.in_cleanup
|
||||
state.in_cleanup = true
|
||||
try collect_block(state, cleanup.body, return_annotation, false)
|
||||
state.in_cleanup = saved_cleanup
|
||||
}
|
||||
.break_stmt, .continue_stmt: {
|
||||
state.cells[usize(statement)].role = .loop_control
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
if (!in_loop) {
|
||||
code :: if (ast.stmt_kind(tree, statement) == .break_stmt)
|
||||
code :: if (state.in_cleanup) diagnostics.Code.cleanup_control_flow
|
||||
else if (ast.stmt_kind(tree, statement) == .break_stmt)
|
||||
diagnostics.Code.break_outside_loop
|
||||
else
|
||||
diagnostics.Code.continue_outside_loop
|
||||
@@ -1077,7 +1123,27 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
tree :: state.tree
|
||||
state.cells[usize(id)].role = .expression
|
||||
state.cells[usize(id)].assignment_value = state.collecting_assignment
|
||||
state.cells[usize(id)].return_annotation = state.return_annotation
|
||||
state.cells[usize(id)].in_cleanup = state.in_cleanup
|
||||
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
|
||||
match ast.expr_kind(tree, id) {
|
||||
.try_expr: {
|
||||
operand :: ast.type_operand(tree, id)
|
||||
try collect_expression(state, operand)
|
||||
try depend(state, operand, id)
|
||||
}
|
||||
.catch_expr: {
|
||||
expression :: ast.catch_expr(tree, id)
|
||||
try collect_expression(state, expression.operand)
|
||||
try depend(state, expression.operand, id)
|
||||
if (expression.capture) |capture| {
|
||||
try collect_capture(state, capture, expression.handler)
|
||||
state.cells[usize(capture)].type = Type{}
|
||||
}
|
||||
if (expression.handler_is_block) {
|
||||
try collect_block(state, expression.handler, state.return_annotation, state.in_loop)
|
||||
} else try collect_expression(state, expression.handler)
|
||||
}
|
||||
.literal_int: {
|
||||
state.cells[usize(id)].numeric = true
|
||||
state.cells[usize(id)].anchored = true
|
||||
@@ -1203,7 +1269,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||
}
|
||||
.record_type, .union_type, .enum_type, .distinct_type,
|
||||
.pointer_type, .function_type, .slice_type, .optional_type: {
|
||||
.pointer_type, .function_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
|
||||
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
|
||||
if (type.kind != .invalid) {
|
||||
type.value_kind = type.kind
|
||||
@@ -1437,6 +1503,35 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
|
||||
return
|
||||
}
|
||||
}
|
||||
if (expected.kind == .fallible) {
|
||||
descriptor :: state.arrays.items[usize(expected.array_type)]
|
||||
actual := group_type(state, id)
|
||||
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)
|
||||
and has_narrowing(state, id)) actual = try infer_shape(state, id, false)
|
||||
if (actual.kind == .fallible) return
|
||||
target := descriptor.element
|
||||
literal :: state.cells[index].literal
|
||||
if (actual.kind == .none and state.cells[index].expected.kind != .none) actual = state.cells[index].expected
|
||||
if (literal != ast.NodeId(maxval!(u32))) {
|
||||
name := ast.NodeId(maxval!(u32))
|
||||
if (ast.expr_kind(state.tree, literal) == .enum_literal) name = ast.enum_literal(state.tree, literal)
|
||||
if (ast.expr_kind(state.tree, literal) == .record_literal) {
|
||||
if (ast.record_literal(state.tree, literal).type) |annotation| {
|
||||
if (ast.expr_kind(state.tree, annotation) == .enum_literal) name = ast.enum_literal(state.tree, annotation)
|
||||
}
|
||||
if (name == ast.NodeId(maxval!(u32)) and actual.kind == .none
|
||||
and descriptor.element.kind != .record and descriptor.error_type.kind == .record
|
||||
and !state.records.items[usize(descriptor.error_type.record_type)].is_union) target = descriptor.error_type
|
||||
}
|
||||
if (name != ast.NodeId(maxval!(u32)) and type_has_variant(state, descriptor.error_type, name)
|
||||
and !type_has_variant(state, descriptor.element, name)) target = descriptor.error_type
|
||||
else if (actual.kind != .none and !assignable_type(state, actual, target)
|
||||
and assignable_type(state, actual, descriptor.error_type)) target = descriptor.error_type
|
||||
} else if (actual.kind != .none and !assignable_type(state, actual, target)
|
||||
and assignable_type(state, actual, descriptor.error_type)) target = descriptor.error_type
|
||||
try demand(state, id, target, code)
|
||||
return
|
||||
}
|
||||
if (expected.kind == .optional) {
|
||||
if (group_type(state, id).kind != .optional and group_type(state, id).kind != .null) {
|
||||
try demand(state, id, state.arrays.items[usize(expected.array_type)].element, code)
|
||||
@@ -1661,6 +1756,22 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
|
||||
@hide:file
|
||||
assignable_type proc(state @State, actual, expected Type) bool {
|
||||
if (same_type(state, actual, expected)) return true
|
||||
if (expected.kind == .fallible) {
|
||||
target :: state.arrays.items[usize(expected.array_type)]
|
||||
if (actual.kind == .fallible) {
|
||||
source :: state.arrays.items[usize(actual.array_type)]
|
||||
return (
|
||||
assignable_type(state, source.element, target.element)
|
||||
and error_subset(state.records.items, state.enums.items, source.error_type, target.error_type)
|
||||
)
|
||||
}
|
||||
return (
|
||||
assignable_type(state, actual, target.element)
|
||||
or error_subset(state.records.items, state.enums.items, actual, target.error_type)
|
||||
)
|
||||
}
|
||||
if ((composed_type(state, actual) or composed_type(state, expected))
|
||||
and error_subset(state.records.items, state.enums.items, actual, expected)) return true
|
||||
if (expected.kind == .optional) {
|
||||
child :: state.arrays.items[usize(expected.array_type)].element
|
||||
if (actual.kind == .null) return true
|
||||
@@ -1691,6 +1802,13 @@ assignable_type proc(state @State, actual, expected Type) bool {
|
||||
return assignable_type(state, a.element, b.element)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
composed_type proc(state @State, type Type) bool {
|
||||
if (type.kind == .enumeration) return state.enums.items[usize(type.enum_type)].composed
|
||||
if (type.kind == .record) return state.records.items[usize(type.record_type)].composed
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
actual :: state.types[usize(value)]
|
||||
@@ -1752,6 +1870,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
statement :: ast.return_stmt(tree, id)
|
||||
expected :: state.types[usize(state.cells[usize(id)].return_annotation)]
|
||||
if (expected.kind == .invalid) return
|
||||
if (state.types[usize(id)].kind == .invalid) return
|
||||
if (statement.value) |value| {
|
||||
actual :: state.types[usize(value)]
|
||||
if (actual.kind == .invalid) {
|
||||
@@ -1760,12 +1879,14 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
|
||||
state.types[usize(id)] = state.types[usize(value)]
|
||||
}
|
||||
} else if (expected.kind != .void) {
|
||||
} else if (expected.kind != .void
|
||||
and !(expected.kind == .fallible and state.arrays.items[usize(expected.array_type)].element.kind == .void)) {
|
||||
try fail(state, id, .return_type_mismatch, statement.span)
|
||||
}
|
||||
}
|
||||
.expression: {
|
||||
match ast.expr_kind(tree, id) {
|
||||
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
|
||||
.unary: {
|
||||
expression :: ast.unary_expr(tree, id)
|
||||
if (expression.operator == .logical_not) {
|
||||
@@ -1952,6 +2073,7 @@ block_falls_through proc(state @State, id ast.NodeId) bool {
|
||||
if (state.types[usize(source)].kind == .invalid) return false
|
||||
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
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
then_falls :: block_falls_through(state, conditional.then_block)
|
||||
@@ -2128,7 +2250,8 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
|
||||
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
|
||||
for state.arrays.items |existing, i| {
|
||||
if (existing.kind == descriptor.kind and existing.len == descriptor.len and existing.mutable == descriptor.mutable
|
||||
and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element)) {
|
||||
and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element)
|
||||
and same_type(state, existing.error_type, descriptor.error_type)) {
|
||||
return Type{ kind = descriptor.kind, array_type = ArrayId(i) }
|
||||
}
|
||||
}
|
||||
@@ -2341,7 +2464,14 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
|
||||
same_type proc(state @State, left, right Type) bool {
|
||||
if (left.kind != right.kind) return false
|
||||
if (left.nominal != right.nominal) return false
|
||||
if (left.kind == .enumeration) return left.enum_type == right.enum_type
|
||||
if (left.kind == .enumeration) {
|
||||
if (left.enum_type == right.enum_type) return true
|
||||
if (!state.enums.items[usize(left.enum_type)].composed and !state.enums.items[usize(right.enum_type)].composed) return false
|
||||
return (
|
||||
error_subset(state.records.items, state.enums.items, left, right)
|
||||
and error_subset(state.records.items, state.enums.items, right, left)
|
||||
)
|
||||
}
|
||||
if (left.kind == .pointer) {
|
||||
a :: state.pointers.items[usize(left.pointer_type)]
|
||||
b :: state.pointers.items[usize(right.pointer_type)]
|
||||
@@ -2359,6 +2489,10 @@ same_type proc(state @State, left, right Type) bool {
|
||||
if (left.record_type == right.record_type) return true
|
||||
a :: state.records.items[usize(left.record_type)]
|
||||
b :: state.records.items[usize(right.record_type)]
|
||||
if (a.composed or b.composed) return (
|
||||
error_subset(state.records.items, state.enums.items, left, right)
|
||||
and error_subset(state.records.items, state.enums.items, right, left)
|
||||
)
|
||||
if (a.is_union or b.is_union) return false
|
||||
if (a.declaration != ast.NodeId(maxval!(u32)) or b.declaration != ast.NodeId(maxval!(u32))
|
||||
or a.fields.len != b.fields.len) return false
|
||||
@@ -2372,10 +2506,13 @@ same_type proc(state @State, left, right Type) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (left.kind != .array and left.kind != .slice and left.kind != .optional) return true
|
||||
if (left.kind != .array and left.kind != .slice and left.kind != .optional and left.kind != .fallible) return true
|
||||
a :: state.arrays.items[usize(left.array_type)]
|
||||
b :: state.arrays.items[usize(right.array_type)]
|
||||
return a.len == b.len and a.mutable == b.mutable and same_sentinel(a.sentinel, b.sentinel) and same_type(state, a.element, b.element)
|
||||
return (
|
||||
a.len == b.len and a.mutable == b.mutable and same_sentinel(a.sentinel, b.sentinel)
|
||||
and same_type(state, a.element, b.element) and same_type(state, a.error_type, b.error_type)
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -2397,6 +2534,16 @@ sentinel_bits proc(value ConstantInteger) i64 {
|
||||
@hide:file
|
||||
array_cells proc(state @State, type Type) ?usize {
|
||||
if (type.kind == .void) return usize(0)
|
||||
if (type.kind == .fallible) {
|
||||
descriptor :: state.arrays.items[usize(type.array_type)]
|
||||
success := usize(0)
|
||||
failure := usize(0)
|
||||
if (array_cells(state, descriptor.element)) |count| success = count else return null
|
||||
if (array_cells(state, descriptor.error_type)) |count| failure = count else return null
|
||||
payload :: if (success > failure) success else failure
|
||||
if (payload == maxval!(usize)) return null
|
||||
return payload + 1
|
||||
}
|
||||
if (type.kind == .slice) return usize(2)
|
||||
if (type.kind == .optional) {
|
||||
child :: state.arrays.items[usize(type.array_type)].element
|
||||
@@ -2480,6 +2627,14 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
defer state.cells[index].visiting = false
|
||||
if (state.cells[usize(id)].role == .expression) {
|
||||
kind :: ast.expr_kind(state.tree, id)
|
||||
if (declaration_reference(state.tree, id) and has_narrowing(state, id)) {
|
||||
narrowed :: try narrow_reference(state, id)
|
||||
if (narrowed.kind != .none) {
|
||||
seed(state, id, narrowed)
|
||||
return narrowed
|
||||
}
|
||||
}
|
||||
if (kind == .try_expr or kind == .catch_expr) return try infer_fallible_expression(state, id, defaults)
|
||||
if (kind == .builtin_call) return try infer_builtin(state, id, defaults)
|
||||
if (kind == .slice) return try infer_slice(state, id, defaults)
|
||||
if (kind == .unary and ast.unary_expr(state.tree, id).operator == .unwrap) {
|
||||
@@ -2727,6 +2882,11 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
|
||||
@hide:file
|
||||
prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
for 0..(state.cells.len) |i| {
|
||||
if (state.cells[i].role == .expression and ast.expr_kind(state.tree, ast.NodeId(i)) == .catch_expr) {
|
||||
_ = try infer_shape(state, ast.NodeId(i), defaults)
|
||||
}
|
||||
}
|
||||
for (0..(state.cells.len)) |i| {
|
||||
capture :: state.cells[i].capture
|
||||
if (capture == ast.NodeId(maxval!(u32)) or state.cells[i].role != .expression) continue
|
||||
@@ -2738,7 +2898,8 @@ prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.Allo
|
||||
for (0..(state.cells.len)) |j| {
|
||||
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
|
||||
binding :: sema.binding(state.resolution, ast.NodeId(j))
|
||||
if (binding.kind == .declaration and binding.data.declaration == capture) seed(state, ast.NodeId(j), child)
|
||||
if (binding.kind == .declaration and binding.data.declaration == capture
|
||||
and !has_narrowing(state, ast.NodeId(j))) seed(state, ast.NodeId(j), child)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2832,7 +2993,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
||||
@hide:file
|
||||
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (early and expected.kind != .array and expected.kind != .record
|
||||
and expected.kind != .pointer and expected.kind != .slice and expected.kind != .optional
|
||||
and expected.kind != .pointer and expected.kind != .slice and expected.kind != .optional and expected.kind != .fallible
|
||||
and expected.kind != .enumeration and !integer_kind(expected.kind)) return
|
||||
try demand(state, id, expected, code)
|
||||
}
|
||||
@@ -2887,7 +3048,8 @@ prepare_ranges proc(state @mut State, assignment_values, constrain bool) void !
|
||||
or !declaration_reference(tree, id)
|
||||
) continue
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture) {
|
||||
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture
|
||||
and !has_narrowing(state, id)) {
|
||||
seed(state, id, group_type(state, binding.data.declaration))
|
||||
}
|
||||
}
|
||||
@@ -2948,7 +3110,8 @@ specialize_annotations proc(state @mut State) void ! (mem.AllocError | diagnosti
|
||||
or !declaration_reference(state.tree, id)
|
||||
) continue
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].fixed) {
|
||||
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].fixed
|
||||
and !has_narrowing(state, id)) {
|
||||
seed(state, id, state.cells[usize(binding.data.declaration)].type)
|
||||
}
|
||||
}
|
||||
@@ -3217,6 +3380,7 @@ type_syntax proc(kind ast.ExprKind) bool {
|
||||
kind == .record_type or kind == .union_type or kind == .enum_type
|
||||
or kind == .distinct_type or kind == .alias_expr
|
||||
or kind == .pointer_type or kind == .function_type or kind == .slice_type or kind == .optional_type
|
||||
or kind == .fallible_type or kind == .sum_type
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3248,6 +3412,8 @@ check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type !
|
||||
if (kind == .pointer_type) return try check_pointer_type(state, id)
|
||||
if (kind == .function_type) return try check_function_type(state, id)
|
||||
if (kind == .slice_type or kind == .optional_type) return try check_container_type(state, id)
|
||||
if (kind == .fallible_type) return try check_fallible_type(state, id)
|
||||
if (kind == .sum_type) return try check_sum_type(state, id)
|
||||
if (kind == .union_type) {
|
||||
type :: try check_union(state, id, nominal)
|
||||
state.cells[usize(id)].type = type
|
||||
@@ -3662,11 +3828,16 @@ collect_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
||||
if (binding.kind == .invalid) state.cells[usize(capture)].type = invalid_type(binding.data.diagnostic)
|
||||
try depend(state, capture, arm.body)
|
||||
}
|
||||
saved_arm :: state.narrowing_arm
|
||||
state.cells[usize(arm_id)].enclosing_arm = saved_arm
|
||||
state.cells[usize(arm_id)].match_owner = id
|
||||
state.narrowing_arm = arm_id
|
||||
if (arm.expression) {
|
||||
try collect_expression(state, arm.body)
|
||||
} else {
|
||||
try collect_block(state, arm.body, state.return_annotation, state.in_loop)
|
||||
}
|
||||
state.narrowing_arm = saved_arm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3733,7 +3904,8 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
for 0..(state.cells.len) |j| {
|
||||
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
|
||||
binding :: sema.binding(state.resolution, ast.NodeId(j))
|
||||
if (binding.kind == .declaration and binding.data.declaration == capture) seed(state, ast.NodeId(j), payload)
|
||||
if (binding.kind == .declaration and binding.data.declaration == capture
|
||||
and !has_narrowing(state, ast.NodeId(j))) seed(state, ast.NodeId(j), payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3940,3 +4112,396 @@ global_variant proc(state @mut State, name ast.NodeId, payload Type) u64 ! mem.A
|
||||
try arraylist.append(&state.variants, Variant{ name = name, payload = payload })
|
||||
return u64(state.variants.items.len)
|
||||
}
|
||||
|
||||
#! applies the same value-type coercion rules used for storage, arguments, and returns.
|
||||
value_assignable proc(result @Result, actual, expected Type) bool {
|
||||
# assignability reads only descriptors, signature annotations, and the borrowed AST.
|
||||
state State := undefined
|
||||
state.tree = result.storage.tree
|
||||
state.types = result.storage.types
|
||||
state.arrays = result.storage.arrays
|
||||
state.pointers = result.storage.pointers
|
||||
state.records = result.storage.records
|
||||
state.enums = result.storage.enums
|
||||
return assignable_type(&state, actual, expected)
|
||||
}
|
||||
|
||||
#! tests source-error subset membership using the descriptors retained by checking.
|
||||
error_assignable proc(result @Result, actual, expected Type) bool {
|
||||
return error_subset(result.storage.records.items, result.storage.enums.items, actual, expected)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
error_subset proc(records []RecordType, enums []EnumType, actual, expected Type) bool {
|
||||
if (actual.nominal != expected.nominal) return false
|
||||
if (actual.kind == .record and expected.kind == .record and actual.record_type == expected.record_type) return true
|
||||
if (actual.kind == .enumeration and expected.kind == .enumeration and actual.enum_type == expected.enum_type) return true
|
||||
target := expected
|
||||
if (expected.kind == .record) {
|
||||
descriptor :: records[usize(expected.record_type)]
|
||||
if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) return false
|
||||
if (descriptor.composed and actual.kind == .record and !records[usize(actual.record_type)].is_union) {
|
||||
for descriptor.fields |field| {
|
||||
if (field.type.kind == .record and field.type.record_type == actual.record_type
|
||||
and field.type.nominal == actual.nominal) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
target = descriptor.tag_type
|
||||
}
|
||||
if (target.kind != .enumeration) return false
|
||||
destination :: enums[usize(target.enum_type)]
|
||||
source := actual
|
||||
if (actual.kind == .record) {
|
||||
descriptor :: records[usize(actual.record_type)]
|
||||
if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) return false
|
||||
source = descriptor.tag_type
|
||||
}
|
||||
if (source.kind != .enumeration) return false
|
||||
origin :: enums[usize(source.enum_type)]
|
||||
if (origin.explicit_backing or destination.explicit_backing) {
|
||||
if (!origin.explicit_backing or !destination.explicit_backing
|
||||
or origin.declaration != destination.declaration or origin.backing != destination.backing) return false
|
||||
}
|
||||
for origin.members |member| {
|
||||
found := false
|
||||
for destination.members |other| {
|
||||
if (member.magnitude == other.magnitude and member.negative == other.negative) found = true
|
||||
}
|
||||
if (!found) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_has_variant proc(state @State, type Type, name ast.NodeId) bool {
|
||||
if (type.kind == .enumeration) return enum_member_slot(state, state.enums.items[usize(type.enum_type)], name) != no_index
|
||||
if (type.kind != .record) return false
|
||||
descriptor :: state.records.items[usize(type.record_type)]
|
||||
return (
|
||||
descriptor.is_union and descriptor.tag_type.kind == .enumeration
|
||||
and named_field(state.tree, descriptor.fields, name) != no_index
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
check_fallible_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
cached := state.cells[usize(id)].type
|
||||
if (cached.kind == .type_value) cached.kind = cached.value_kind
|
||||
if (cached.kind != .none) return cached
|
||||
expression :: ast.binary_expr(state.tree, id)
|
||||
success :: try check_annotation(state, expression.left, true)
|
||||
failure :: try check_annotation(state, expression.right, false)
|
||||
if (success.kind == .invalid) return success
|
||||
if (failure.kind == .invalid) return failure
|
||||
valid_error :: failure.kind == .enumeration or (failure.kind == .record
|
||||
and (!state.records.items[usize(failure.record_type)].is_union
|
||||
or state.records.items[usize(failure.record_type)].tag_type.kind == .enumeration))
|
||||
if (!valid_error or success.kind == .none or success.kind == .range or success.kind == .type_value
|
||||
or success.kind == .anyopaque or has_inferred_count(state, success)) {
|
||||
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_fallible_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
type :: try intern_array(state, ArrayType{ kind = .fallible, element = success, error_type = failure })
|
||||
state.cells[usize(id)].type = type
|
||||
return type
|
||||
}
|
||||
|
||||
@hide:file
|
||||
SumVariant :: struct {
|
||||
name ast.NodeId
|
||||
payload Type
|
||||
tag u64
|
||||
}
|
||||
|
||||
@hide:file
|
||||
append_sum_variants proc(state @mut State, variants @mut arraylist.ArrayList(SumVariant), type Type) bool ! mem.AllocError {
|
||||
if (type.kind == .enumeration) {
|
||||
descriptor :: state.enums.items[usize(type.enum_type)]
|
||||
if (descriptor.explicit_backing) return false
|
||||
for (descriptor.members) |member| try arraylist.append(variants, SumVariant{
|
||||
name = member.name, payload = Type{ kind = .void }, tag = member.magnitude,
|
||||
})
|
||||
return true
|
||||
}
|
||||
if (type.kind != .record or type.nominal != ast.NodeId(maxval!(u32))) return false
|
||||
descriptor :: state.records.items[usize(type.record_type)]
|
||||
if (descriptor.is_union) {
|
||||
if (descriptor.tag_type.kind != .enumeration) return false
|
||||
members :: state.enums.items[usize(descriptor.tag_type.enum_type)].members
|
||||
for (descriptor.fields) |field, i| try arraylist.append(variants, SumVariant{
|
||||
name = field.name?, payload = field.type, tag = members[i].magnitude,
|
||||
})
|
||||
return true
|
||||
}
|
||||
name := ast.NodeId(maxval!(u32))
|
||||
for 0..ast.package_decl_count(state.tree) |i| {
|
||||
declaration :: ast.package_decl(state.tree, i)
|
||||
if (ast.decl_kind(state.tree, declaration) == .statement
|
||||
and ast.statement_decl(state.tree, declaration).initializer == descriptor.declaration) name = declaration
|
||||
}
|
||||
if (name == ast.NodeId(maxval!(u32))) {
|
||||
for 0..(state.cells.len) |i| {
|
||||
if (state.cells[i].role != .declaration) continue
|
||||
if (ast.statement_decl(state.tree, ast.NodeId(i)).initializer == descriptor.declaration) name = ast.NodeId(i)
|
||||
}
|
||||
}
|
||||
if (name == ast.NodeId(maxval!(u32))) return false
|
||||
try arraylist.append(variants, SumVariant{ name = name, payload = type, tag = try global_variant(state, name, type) })
|
||||
return true
|
||||
}
|
||||
|
||||
@hide:file
|
||||
check_sum_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
cached := state.cells[usize(id)].type
|
||||
if (cached.kind == .type_value) cached.kind = cached.value_kind
|
||||
if (cached.kind != .none) return cached
|
||||
if (state.cells[usize(id)].record_checking) {
|
||||
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
|
||||
}
|
||||
state.cells[usize(id)].record_checking = true
|
||||
defer state.cells[usize(id)].record_checking = false
|
||||
expression :: ast.binary_expr(state.tree, id)
|
||||
left :: try check_annotation(state, expression.left, false)
|
||||
right :: try check_annotation(state, expression.right, false)
|
||||
if (left.kind == .invalid) return left
|
||||
if (right.kind == .invalid) return right
|
||||
variants arraylist.ArrayList(SumVariant) := arraylist.init(state.records.allocator)
|
||||
defer arraylist.deinit(&variants)
|
||||
valid := try append_sum_variants(state, &variants, left)
|
||||
if (valid) valid = try append_sum_variants(state, &variants, right)
|
||||
if (!valid) {
|
||||
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_sum_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
count := usize(0)
|
||||
all_void := true
|
||||
for 0..(variants.items.len) |i| {
|
||||
variant :: variants.items[i]
|
||||
duplicate := false
|
||||
for variants.items[0..count] |previous| {
|
||||
if (ast.name_id(state.tree, previous.name) != ast.name_id(state.tree, variant.name)) continue
|
||||
if (previous.tag != variant.tag) {
|
||||
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_sum_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
duplicate = true
|
||||
}
|
||||
if (duplicate) continue
|
||||
variants.items[count] = variant
|
||||
count += 1
|
||||
if (variant.payload.kind != .void) all_void = false
|
||||
}
|
||||
members :: try mem.alloc(EnumMember, state.enums.allocator, count)
|
||||
members_owned := true
|
||||
defer if (members_owned) mem.free(state.enums.allocator, members)
|
||||
for variants.items[0..count] |variant, i| {
|
||||
if (variant.tag > u64(maxval!(u16))) {
|
||||
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_sum_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
members[i] = EnumMember{ name = variant.name, magnitude = variant.tag }
|
||||
}
|
||||
try arraylist.append(&state.enums, EnumType{ declaration = id, backing = .u16, members = members, composed = true })
|
||||
members_owned = false
|
||||
tag :: Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
|
||||
type := tag
|
||||
if (!all_void) {
|
||||
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||
errdefer mem.free(state.records.allocator, fields)
|
||||
for (variants.items[0..count]) |variant, i| fields[i] = RecordField{
|
||||
name = variant.name, type = variant.payload, default = null,
|
||||
}
|
||||
try arraylist.append(&state.records, RecordType{
|
||||
declaration = id, fields = fields, is_union = true, tag_type = tag, composed = true,
|
||||
})
|
||||
type = Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
|
||||
}
|
||||
state.cells[usize(id)].type = type
|
||||
return type
|
||||
}
|
||||
|
||||
@hide:file
|
||||
seed_capture proc(state @mut State, capture ast.NodeId, type Type) void {
|
||||
seed(state, capture, type)
|
||||
for 0..(state.cells.len) |i| {
|
||||
if (state.cells[i].role != .expression or !declaration_reference(state.tree, ast.NodeId(i))) continue
|
||||
binding :: sema.binding(state.resolution, ast.NodeId(i))
|
||||
if (binding.kind == .declaration and binding.data.declaration == capture
|
||||
and !has_narrowing(state, ast.NodeId(i))) seed(state, ast.NodeId(i), type)
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
infer_fallible_expression proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
catching :: ast.expr_kind(state.tree, id) == .catch_expr
|
||||
operand :: if (catching) ast.catch_expr(state.tree, id).operand else ast.type_operand(state.tree, id)
|
||||
source :: try infer_shape(state, operand, defaults)
|
||||
if (source.kind == .invalid) {
|
||||
seed(state, id, source)
|
||||
return source
|
||||
}
|
||||
if (source.kind != .fallible) {
|
||||
if (defaults and source.kind != .none) {
|
||||
try fail(state, id, .invalid_fallible_operand, ast.node_span(state.tree, operand))
|
||||
seed(state, id, state.types[usize(id)])
|
||||
}
|
||||
return group_type(state, id)
|
||||
}
|
||||
descriptor :: state.arrays.items[usize(source.array_type)]
|
||||
seed(state, id, descriptor.element)
|
||||
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)
|
||||
}
|
||||
}
|
||||
return group_type(state, id)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
validate_fallible_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
catching :: ast.expr_kind(state.tree, id) == .catch_expr
|
||||
operand :: if (catching) ast.catch_expr(state.tree, id).operand else ast.type_operand(state.tree, id)
|
||||
source :: state.types[usize(operand)]
|
||||
if (source.kind == .invalid) return
|
||||
if (source.kind != .fallible) {
|
||||
try fail(state, id, .invalid_fallible_operand, ast.node_span(state.tree, operand))
|
||||
return
|
||||
}
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (state.cells[usize(id)].in_cleanup) {
|
||||
try fail(state, id, .cleanup_control_flow, ast.node_span(state.tree, id))
|
||||
return
|
||||
}
|
||||
annotation :: state.cells[usize(id)].return_annotation
|
||||
if (annotation == ast.NodeId(maxval!(u32))) {
|
||||
try fail(state, id, .try_outside_fallible, ast.node_span(state.tree, id))
|
||||
return
|
||||
}
|
||||
result :: state.types[usize(annotation)]
|
||||
if (result.kind == .invalid) return
|
||||
if (result.kind != .fallible) {
|
||||
try fail(state, id, .try_outside_fallible, ast.node_span(state.tree, id))
|
||||
} else if (!error_subset(state.records.items, state.enums.items, descriptor.error_type,
|
||||
state.arrays.items[usize(result.array_type)].error_type)) {
|
||||
try fail(state, id, .incompatible_propagated_error, ast.node_span(state.tree, id))
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
matching_narrowing_arm proc(state @State, id ast.NodeId) ast.NodeId {
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind != .declaration) return ast.NodeId(maxval!(u32))
|
||||
target :: binding.data.declaration
|
||||
if (state.cells[usize(target)].role == .declaration
|
||||
and ast.statement_decl(state.tree, target).mutability == .variable) return ast.NodeId(maxval!(u32))
|
||||
arm := state.cells[usize(id)].narrowing_arm
|
||||
while (arm != ast.NodeId(maxval!(u32))) {
|
||||
owner :: state.cells[usize(arm)].match_owner
|
||||
subject :: ast.match_expr(state.tree, owner).subject
|
||||
original :: sema.binding(state.resolution, subject)
|
||||
if (original.kind == .declaration and original.data.declaration == binding.data.declaration) return arm
|
||||
arm = state.cells[usize(arm)].enclosing_arm
|
||||
}
|
||||
return ast.NodeId(maxval!(u32))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
has_narrowing proc(state @State, id ast.NodeId) bool {
|
||||
return matching_narrowing_arm(state, id) != ast.NodeId(maxval!(u32))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
arm_names_variant proc(state @State, arm, name ast.NodeId) bool {
|
||||
for 0..ast.match_pattern_count(state.tree, arm) |i| {
|
||||
pattern :: ast.match_pattern(state.tree, arm, i)
|
||||
if (ast.expr_kind(state.tree, pattern) == .enum_literal
|
||||
and ast.name_id(state.tree, ast.enum_literal(state.tree, pattern)) == ast.name_id(state.tree, name)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
arm_keeps_variant proc(state @State, owner, arm, name ast.NodeId) bool {
|
||||
if (!ast.match_arm_view(state.tree, arm).catch_all) return arm_names_variant(state, arm, name)
|
||||
for 0..ast.match_arm_count(state.tree, owner) |i| {
|
||||
previous :: ast.match_arm(state.tree, owner, i)
|
||||
if (previous == arm) break
|
||||
if (ast.match_arm_view(state.tree, previous).catch_all or arm_names_variant(state, previous, name)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@hide:file
|
||||
narrow_reference proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
arm :: matching_narrowing_arm(state, id)
|
||||
if (arm == ast.NodeId(maxval!(u32))) return Type{}
|
||||
cached :: state.cells[usize(arm)].narrowed_type
|
||||
if (cached.kind != .none) return cached
|
||||
owner :: state.cells[usize(arm)].match_owner
|
||||
subject :: ast.match_expr(state.tree, owner).subject
|
||||
source :: try infer_shape(state, subject, false)
|
||||
key := source
|
||||
if (source.kind == .record) {
|
||||
descriptor :: state.records.items[usize(source.record_type)]
|
||||
if (!descriptor.is_union) return source
|
||||
key = descriptor.tag_type
|
||||
}
|
||||
if (key.kind != .enumeration) return source
|
||||
descriptor :: state.enums.items[usize(key.enum_type)]
|
||||
count := usize(0)
|
||||
for descriptor.members |member| {
|
||||
if (arm_keeps_variant(state, owner, arm, member.name)) count += 1
|
||||
}
|
||||
if (count == descriptor.members.len) {
|
||||
state.cells[usize(arm)].narrowed_type = source
|
||||
return source
|
||||
}
|
||||
members :: try mem.alloc(EnumMember, state.enums.allocator, count)
|
||||
members_owned := true
|
||||
defer if (members_owned) mem.free(state.enums.allocator, members)
|
||||
next := usize(0)
|
||||
for descriptor.members |member| {
|
||||
if (!arm_keeps_variant(state, owner, arm, member.name)) continue
|
||||
members[next] = member
|
||||
next += 1
|
||||
}
|
||||
try arraylist.append(&state.enums, EnumType{
|
||||
declaration = descriptor.declaration, backing = descriptor.backing,
|
||||
explicit_backing = descriptor.explicit_backing, members = members, composed = true,
|
||||
})
|
||||
members_owned = false
|
||||
tag :: Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
|
||||
narrowed := tag
|
||||
if (source.kind == .record) {
|
||||
original :: state.records.items[usize(source.record_type)]
|
||||
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||
errdefer mem.free(state.records.allocator, fields)
|
||||
next = 0
|
||||
for original.fields |field| {
|
||||
if (!arm_keeps_variant(state, owner, arm, field.name?)) continue
|
||||
fields[next] = field
|
||||
next += 1
|
||||
}
|
||||
try arraylist.append(&state.records, RecordType{
|
||||
declaration = ast.NodeId(maxval!(u32)), fields = fields, is_union = true,
|
||||
tag_type = tag, composed = true,
|
||||
})
|
||||
narrowed = Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
|
||||
}
|
||||
state.cells[usize(arm)].narrowed_type = narrowed
|
||||
return narrowed
|
||||
}
|
||||
|
||||
+18
-2
@@ -382,6 +382,11 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
|
||||
if (ast.return_stmt(tree, statement).value) |value| try resolve_expr(state, scope, value)
|
||||
}
|
||||
.break_stmt, .continue_stmt: {}
|
||||
.block_stmt: try resolve_arm(state, scope, ast.statement_block(tree, statement))
|
||||
.defer_stmt, .errdefer_stmt: {
|
||||
cleanup :: ast.cleanup_stmt(tree, statement)
|
||||
try resolve_capture_arm(state, scope, cleanup.body, cleanup.capture)
|
||||
}
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
try resolve_expr(state, scope, conditional.condition)
|
||||
@@ -511,7 +516,7 @@ resolve_expr proc(
|
||||
match ast.expr_kind(tree, id) {
|
||||
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null: {}
|
||||
.enum_literal: {}
|
||||
.distinct_type, .alias_expr, .optional_type: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||
.distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||
.enum_type: {
|
||||
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
|
||||
for (0..ast.enum_member_count(tree, id)) |i| {
|
||||
@@ -534,6 +539,17 @@ resolve_expr proc(
|
||||
else try resolve_block(state, &arm_scope, arm.body)
|
||||
}
|
||||
}
|
||||
.catch_expr: {
|
||||
expression :: ast.catch_expr(tree, id)
|
||||
try resolve_expr(state, scope, expression.operand)
|
||||
handler_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
||||
defer hashmap.deinit(&handler_scope.names)
|
||||
if (expression.capture) |capture| {
|
||||
try predeclare(state, &handler_scope, capture, ast.node_span(tree, capture))
|
||||
}
|
||||
if (expression.handler_is_block) try resolve_block(state, &handler_scope, expression.handler)
|
||||
else try resolve_expr(state, &handler_scope, expression.handler)
|
||||
}
|
||||
.identifier: {
|
||||
span :: ast.node_span(tree, id)
|
||||
name :: ast.name_id(tree, id)
|
||||
@@ -577,7 +593,7 @@ resolve_expr proc(
|
||||
state.bindings[usize(expression.member)] = value
|
||||
}
|
||||
.unary: try resolve_expr(state, scope, ast.unary_expr(tree, id).operand)
|
||||
.binary: {
|
||||
.binary, .fallible_type, .sum_type: {
|
||||
expression :: ast.binary_expr(tree, id)
|
||||
try resolve_expr(state, scope, expression.left)
|
||||
try resolve_expr(state, scope, expression.right)
|
||||
|
||||
Reference in New Issue
Block a user