feat: execute existing hash maps through Honey

This commit is contained in:
2026-10-30 11:46:49 +01:00
parent 31a1bf1995
commit e90d4f0829
19 changed files with 761 additions and 47 deletions
+50 -6
View File
@@ -1101,6 +1101,7 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
try collect_declaration(state, statement)
}
.expression: try collect_expression(state, ast.statement_expr(tree, statement))
.unreachable_stmt: state.cells[usize(statement)].type = Type{ kind = .void }
.return_stmt: {
state.cells[usize(statement)].role = .return_stmt
state.cells[usize(statement)].type = Type{ kind = .void }
@@ -2335,6 +2336,7 @@ validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
BlockFlow :: struct {
falls bool = true
yields bool = false
breaks bool = false
}
@hide:file
@@ -2367,6 +2369,16 @@ expression_falls_through proc(state @State, id ast.NodeId) bool {
}
}
@hide:file
expression_breaks proc(state @State, id ast.NodeId) bool {
tree :: state.tree
if (ast.node_class(tree, id) == .block) return block_flow(state, id).breaks
for 0..ast.child_count(tree, id) |i| {
if (expression_breaks(state, ast.child(tree, id, i))) return true
}
return false
}
@hide:file
block_flow proc(state @State, id ast.NodeId) BlockFlow {
tree :: state.tree
@@ -2381,30 +2393,62 @@ block_flow proc(state @State, id ast.NodeId) BlockFlow {
}
match kind {
.yield_stmt: {
if (ast.yield_stmt(tree, statement).value) |value|
result.breaks = result.breaks or expression_breaks(state, value)
result.yields = true
result.falls = false
}
.return_stmt, .break_stmt, .continue_stmt, .invalid: result.falls = false
.return_stmt: {
if (ast.return_stmt(tree, statement).value) |value|
result.breaks = result.breaks or expression_breaks(state, value)
result.falls = false
}
.break_stmt: {
result.breaks = true
result.falls = false
}
.continue_stmt, .unreachable_stmt, .invalid: result.falls = false
.block_stmt: {
nested :: block_flow(state, ast.statement_block(tree, statement))
result.yields = result.yields or nested.yields
result.breaks = result.breaks or nested.breaks
result.falls = nested.falls
}
.if_stmt: {
conditional :: ast.if_stmt(tree, statement)
then_flow :: block_flow(state, conditional.then_block)
result.yields = result.yields or then_flow.yields
result.breaks = result.breaks or then_flow.breaks or expression_breaks(state, conditional.condition)
if (conditional.else_block) |other| {
else_flow :: block_flow(state, other)
result.yields = result.yields or else_flow.yields
result.breaks = result.breaks or else_flow.breaks
result.falls = then_flow.falls or else_flow.falls
}
}
.while_stmt: result.yields = result.yields or block_flow(state, ast.while_stmt(tree, statement).body).yields
.for_stmt: result.yields = result.yields or block_flow(state, ast.for_stmt(tree, statement).body).yields
.expression: result.falls = expression_falls_through(state, source)
.declaration: result.falls = expression_falls_through(state, ast.statement_decl(tree, statement).initializer)
.assignment_stmt: result.falls = expression_falls_through(state, ast.assignment_stmt(tree, statement).value)
.while_stmt: {
loop :: ast.while_stmt(tree, statement)
body :: block_flow(state, loop.body)
result.yields = result.yields or body.yields
result.breaks = result.breaks or expression_breaks(state, loop.condition)
if (loop.capture == null and ast.expr_kind(tree, loop.condition) == .literal_bool
and mem.eql(ast.literal_text(tree, loop.condition), "true"))
result.falls = body.breaks
}
.for_stmt: {
loop :: ast.for_stmt(tree, statement)
result.yields = result.yields or block_flow(state, loop.body).yields
result.breaks = result.breaks or expression_breaks(state, loop.iterable)
}
.expression, .declaration, .assignment_stmt: {
value :: if (kind == .expression) source
else if (kind == .declaration) ast.statement_decl(tree, statement).initializer
else ast.assignment_stmt(tree, statement).value
result.breaks = result.breaks or expression_breaks(state, value)
if (kind == .assignment_stmt)
result.breaks = result.breaks or expression_breaks(state, ast.assignment_stmt(tree, statement).target)
result.falls = expression_falls_through(state, value)
}
else: {}
}
if (!result.falls) return result
+10 -11
View File
@@ -684,7 +684,7 @@ return_errors_preserve_identity_check_unreachable_code_and_suppress_missing_retu
`void_value proc() void { return 1 }
`unknown proc() int { return absent }
`syntax proc() int { return (1 }
`unreachable proc() int {
`after_return proc() int {
` return 7
` -"bad"
`}
@@ -910,7 +910,7 @@ analyzes_return_paths_after_trap_propagation_without_poisoning_signatures test {
`syntax_trap proc() int {
` return 1 stray
`}
`unreachable proc() int {
`after_return proc() int {
` return 1
` -"bad"
`}
@@ -1198,7 +1198,7 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`missing proc() int { while true { return 1 } }
`terminal proc() int { while true { return 1 } }
`bad_body proc() int { while false { -"bad" } }
`continues proc() int {
` while false { -"bad" }
@@ -1226,12 +1226,11 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
defer deinit(&result)
for usize(0)..usize(2) |index| {
function :: ast.package_decl(&tree, index)
body :: ast.function_decl(&tree, function).body
_ = try expect_fallthrough(&result, &diagnostic_store, function, ast.node_span(&tree, body))
try expect_kind(&result, ast.block_statement(&tree, body, 0), .void)
}
terminal :: ast.package_decl(&tree, 0)
try testing.expect(fallthrough_diagnostic(&result, terminal) == null)
body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
_ = try expect_fallthrough(&result, &diagnostic_store, ast.package_decl(&tree, 1), ast.node_span(&tree, body))
try expect_kind(&result, ast.block_statement(&tree, body, 0), .void)
continues_body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
loop_statement :: ast.block_statement(&tree, continues_body, 0)
loop :: ast.while_stmt(&tree, loop_statement)
@@ -1261,7 +1260,7 @@ while_checks_nested_bodies_without_claiming_a_zero_iteration_return test {
try expect_kind(&result, assignment.target, .int)
try expect_kind(&result, assignment.value, .int)
}
try testing.expect_equal(2, code_count(&diagnostic_store, .missing_return))
try testing.expect_equal(1, code_count(&diagnostic_store, .missing_return))
try testing.expect_equal(1, code_count(&diagnostic_store, .condition_not_bool))
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
}
@@ -1518,7 +1517,7 @@ short_circuit_skip_paths_do_not_guarantee_a_return test {
`call proc() int { take(true or 1) }
`conditional proc() int { if false and missing_condition { return 1 } }
`loop proc() int { while true or missing_guard { return 1 } }
`loop_body proc() int { while true { true or 1 } }
`loop_body proc(flag bool) int { while flag { true or 1 } }
`arms proc(flag bool) int {
` if flag { false and missing_arm } else { return 1 }
`}
+1 -1
View File
@@ -412,7 +412,7 @@ 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)
}
.yield_stmt: try resolve_expr(state, scope, ast.yield_stmt(tree, statement).value?)
.break_stmt, .continue_stmt: {}
.break_stmt, .continue_stmt, .unreachable_stmt: {}
.block_stmt: try resolve_arm(state, scope, ast.statement_block(tree, statement))
.defer_stmt, .errdefer_stmt: {
cleanup :: ast.cleanup_stmt(tree, statement)
+83 -5
View File
@@ -134,7 +134,8 @@ elaborate proc(
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
name :: ast.builtin_call(&original, id).name
text :: ast.source_text(&original, ast.node_span(&original, name))
if (mem.eql(text, "field") or mem.eql(text, "struct_type") or mem.eql(text, "typeinfo")) needed = true
if (mem.eql(text, "field") or mem.eql(text, "struct_type") or mem.eql(text, "typeinfo")
or mem.eql(text, "compile_error")) needed = true
}
if (class == .function and ast.function_generic(&original, id)) needed = true
if (class == .function) {
@@ -237,6 +238,35 @@ reject proc(state @mut State, id ast.NodeId, code diagnostics.Code) void ! Error
state.bindings.items[usize(id)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
}
@hide:file
expand_compile_error proc(state @mut State, id ast.NodeId) void ! Error {
if (ast.call_arg_count(&state.tree, id) != 1) {
try reject(state, id, .invalid_compile_error)
return
}
state.evaluation_diagnostic = diagnostics.no_diagnostic
value :: try evaluate(state, ast.call_arg(&state.tree, id, 0))
if (value.kind != .string) {
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
else try reject(state, id, .invalid_compile_error)
return
}
text :: ast.literal_text(&state.tree, value.node)
bytes := arraylist.init(u8, state.allocator)
defer arraylist.deinit(&bytes)
cursor := usize(1)
while (cursor < text.len - 1) {
byte :: ast.string_next(text, &cursor) catch {
try reject(state, id, .invalid_escape)
return
}
try arraylist.append(&bytes, byte)
}
state.evaluation_diagnostic = try diagnostics.add_message(state.diagnostics,
.compile_error, ast.node_span(&state.tree, id), bytes.items)
try reject_evaluation(state, id)
}
@hide:file
expand proc(state @mut State, id ast.NodeId) void ! Error {
try refresh(state)
@@ -287,6 +317,15 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt) {
loop :: ast.for_stmt(&state.tree, id)
try expand(state, loop.iterable)
if (loop.index) |capture| {
annotation :: try builtin(state, id, .usize)
hashmap.put(&state.capture_types, capture, annotation) catch |err| {
match err {
.key_exists: {}
else: return err
}
}
}
if (!loop.pointer_capture and loop.capture != null) {
iterable :: try expression_type(state, loop.iterable, 0)
element := no_node
@@ -417,6 +456,10 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
}
if (class == .expression and ast.expr_kind(&state.tree, id) == .builtin_call) {
name :: ast.builtin_call(&state.tree, id).name
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "compile_error")) {
try expand_compile_error(state, id)
return
}
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "field")) {
try expand_field(state, id)
return
@@ -496,7 +539,7 @@ value_block_terminal proc(state @State, id ast.NodeId) bool {
}
if (class != .statement) return false
match ast.stmt_kind(&state.tree, id) {
.yield_stmt, .return_stmt, .break_stmt, .continue_stmt: return true
.yield_stmt, .return_stmt, .break_stmt, .continue_stmt, .unreachable_stmt: return true
.block_stmt: return value_block_terminal(state, ast.statement_block(&state.tree, id))
.if_stmt: {
view :: ast.if_stmt(&state.tree, id)
@@ -2300,7 +2343,11 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
diagnostic_store := diagnostics.init(state.allocator)
defer diagnostics.deinit(&diagnostic_store)
# Resolution's existing diagnostic IDs remain valid in this temporary store.
for (state.diagnostics.list.items) |diagnostic| try arraylist.append(&diagnostic_store.list, diagnostic)
for (state.diagnostics.list.items) |diagnostic| {
if (diagnostic.custom_message) |text| {
_ = try diagnostics.add_message(&diagnostic_store, diagnostic.code, diagnostic.span, text)
} else _ = try diagnostics.add(&diagnostic_store, diagnostic.code, diagnostic.span)
}
original_diagnostics :: diagnostics.count(&diagnostic_store)
checked := try typecheck.check(state.allocator, &tree, state.resolution, &diagnostic_store)
defer typecheck.deinit(&checked)
@@ -2744,6 +2791,7 @@ inline_statement proc(state @mut State, id ast.NodeId) TypeFlow ! Error {
kind :: ast.stmt_kind(&state.tree, id)
if (kind == .break_stmt) return .break
if (kind == .continue_stmt) return .continue
if (kind == .unreachable_stmt) return .returned
if (kind == .yield_stmt or kind == .return_stmt) {
try expand(state, id)
return if (kind == .yield_stmt) TypeFlow.yielded else TypeFlow.returned
@@ -3911,6 +3959,11 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
@hide:file
execute_type_statement_body proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
match ast.stmt_kind(&state.tree, statement) {
.unreachable_stmt: {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.unreachable_executed, ast.node_span(&state.tree, statement))
return .invalid
}
.defer_stmt: {}
.errdefer_stmt: {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
@@ -4465,6 +4518,18 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
view :: ast.unary_expr(&state.tree, id)
if (view.operator == .bit_not)
return try evaluate_ir(state, id, try expression_type(state, id, 0))
if (view.operator == .unwrap) {
# source-known callbacks already own declaration identities, not interpreter handles.
value := try evaluate(state, view.operand)
annotation :: try expression_type(state, view.operand, 0)
if (value.kind == .function and annotation != no_node
and ast.expr_kind(&state.tree, annotation) == .optional_type) {
try arraylist.append(&state.rewrites.constraints, id)
value.annotation = ast.type_operand(&state.tree, annotation)
return value
}
return try evaluate_ir(state, id, try expression_type(state, id, 0))
}
value := try evaluate(state, view.operand)
if (view.operator == .negate and value.kind == .integer) value.negative = value.magnitude != 0 and !value.negative
else if (view.operator == .negate and value.kind == .real) value.magnitude = value.magnitude xor (u64(1) << 63)
@@ -4917,6 +4982,8 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
return try builtin(state, id, .usize)
if ((mem.eql(name, "maxval") or mem.eql(name, "minval")) and count == 1)
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
if ((mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) and count == 2)
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
if (mem.eql(name, "divtrunc") and count == 2) {
left :: ast.call_arg(&state.tree, id, 0)
right :: ast.call_arg(&state.tree, id, 1)
@@ -4974,6 +5041,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
if (destination.kind == .builtin) return callee
if (destination.kind == .declaration and ast.node_class(&state.tree, destination.data.declaration) == .function) return try canonical_type(state, ast.function_decl(&state.tree, destination.data.declaration).return_type)
signature :: try expression_type(state, callee, depth + 1)
if (signature != no_node and is_type(state, signature)) return try canonical_type(state, callee)
if (signature != no_node and ast.expr_kind(&state.tree, signature) == .function_type)
return try canonical_type(state, ast.callable_result(&state.tree, signature))
return no_node
@@ -5121,8 +5189,18 @@ infer_value proc(state @mut State, function, pattern ast.NodeId, actual Value, a
@hide:file
infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []mut Value, contextual bool) bool ! Error {
resolved :: binding(state, pattern)
if (resolved.kind == .declaration and parameter_index(state, function, resolved.data.declaration) != no_index)
return try infer_value(state, function, pattern, Value{ kind = .type, node = actual }, arguments, contextual)
if (resolved.kind == .declaration) {
index :: parameter_index(state, function, resolved.data.declaration)
if (index != no_index) {
if (arguments[index].kind == .type) {
expected :: try canonical_type(state, arguments[index].node)
# a fixed slice type supplies borrowing/string context; ordinary
# argument checking owns element, mutability and sentinel validation.
if (expected != no_node and ast.expr_kind(&state.tree, expected) == .slice_type) return true
}
return try infer_value(state, function, pattern, Value{ kind = .type, node = actual }, arguments, contextual)
}
}
# only normalize the actual type: the pattern may still contain unbound
# parameters or a type-generator call whose arguments must be inferred.
concrete :: try canonical_type(state, actual)