fix: bug hunt

This commit is contained in:
2026-07-24 22:38:36 +02:00
parent 8b50eb7606
commit 96275121be
6 changed files with 334 additions and 70 deletions
+97 -45
View File
@@ -5407,10 +5407,15 @@ infer_expr :: proc(
}
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
if available && distinct_ok && distinct_item.kind == .Distinct && len(expr.args) == 1 {
stack[frame_index].left = distinct_type
named_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
named_item, named_ok := types.node(&checker.module.types, named_type)
constructor_type := types.resolve_alias(named_type, &checker.module.types)
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
scalar_alias := named_ok && named_item.kind == .Alias &&
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
distinct_constructor := constructor_ok && constructor_item.kind == .Distinct
if available && len(expr.args) == 1 && (scalar_alias || distinct_constructor) {
stack[frame_index].left = constructor_type
stack[frame_index].stage = 7
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
continue
@@ -7633,6 +7638,41 @@ enum_member_hir :: proc(
})
}
build_scalar_cast :: proc(
checker: ^Checker,
value: hir.Expr_Id,
target: types.Type,
span: source.Span,
) -> hir.Expr_Id {
store := &checker.module.types
actual := checker.module.exprs[value].type
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
actual_repr := types.runtime_representation(actual, store)
actual_item, actual_item_ok := types.node(store, actual)
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
if !valid_target || !valid_actual {
id := source.addf(
checker.diagnostics,
span,
"scalar cast requires numeric scalar types, got %s to %s",
types.name(actual),
types.name(target),
)
return invalid_hir_expr(checker, span, id, target)
}
return add_hir_expr(checker, hir.Expr{
kind=.Scalar_Cast,
span=span,
type=target,
left=value,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
build_function_value :: proc(
checker: ^Checker,
template: ast.Function_Id,
@@ -8041,17 +8081,22 @@ build_compound_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, expected)
case .Enum_Literal:
if types.is_tagged_union(expected, store) {
index, field, found := find_struct_field(checker, expected, expr.name)
literal_expected := expected
optional_expected := types.is_optional(expected, store)
if optional_expected {
literal_expected = types.child_type(expected, store)
}
if types.is_tagged_union(literal_expected, store) {
index, field, found := find_struct_field(checker, literal_expected, expr.name)
if !found {
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, literal_expected))
return invalid_hir_expr(checker, expr.span, id, expected)
}
values := make([]hir.Expr_Id, 1, checker.allocator)
if expr.left == ast.INVALID_EXPR {
if !types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
symbol_text(checker, expr.name), type_label(checker, literal_expected), symbol_text(checker, expr.name))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, expected)
}
@@ -8065,11 +8110,12 @@ build_compound_expr :: proc(
values[0] = build_nested_expr(checker, expr.left, locals, global_reads, calls, field.type, pkg, file)
values[0] = coerce_expr(checker, values[0], field.type, checker.module.exprs[values[0]].span)
}
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
result := add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=literal_expected, args=values, integer=i64(index),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return coerce_expr(checker, result, expected, expr.span) if optional_expected else result
}
if expr.left != ast.INVALID_EXPR {
id := source.addf(
@@ -8080,7 +8126,7 @@ build_compound_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
if !types.is_enum(expected, store) {
if !types.is_enum(literal_expected, store) {
id := source.addf(
checker.diagnostics,
expr.span,
@@ -8089,36 +8135,12 @@ build_compound_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
return enum_member_hir(checker, expected, expr.name, expr.span)
result := enum_member_hir(checker, literal_expected, expr.name, expr.span)
return coerce_expr(checker, result, expected, expr.span) if optional_expected else result
case .Cast:
target := type_from_syntax(checker, expr.type, pkg, file)
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
actual := checker.module.exprs[value].type
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
actual_repr := types.runtime_representation(actual, store)
actual_item, actual_item_ok := types.node(store, actual)
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
if !valid_target || !valid_actual {
id := source.addf(
checker.diagnostics,
expr.span,
"scalar cast requires numeric scalar types, got %s to %s",
types.name(actual),
types.name(target),
)
return invalid_hir_expr(checker, expr.span, id, target)
}
return add_hir_expr(checker, hir.Expr{
kind=.Scalar_Cast,
span=expr.span,
type=target,
left=value,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return build_scalar_cast(checker, value, target, expr.span)
case .Address:
// `&<array literal>` (Zig's `&.{...}`): the operand is an rvalue with no
// address, so promote it to an anonymous global constant and take *its*
@@ -9369,10 +9391,36 @@ build_expr :: proc(
}
}
if callee == hir.INVALID_EXPR {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
if distinct_ok && distinct_item.kind == .Distinct {
if !is_runtime_type(checker, distinct_type) {
named_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
named_item, named_ok := types.node(&checker.module.types, named_type)
constructor_type := types.resolve_alias(named_type, &checker.module.types)
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
scalar_alias := named_ok && named_item.kind == .Alias &&
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
if scalar_alias {
if len(expr.args) != 1 {
id := source.addf(
checker.diagnostics,
expr.span,
"type alias '%s' expects 1 argument, got %d",
symbol_text(checker, expr.name),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id, constructor_type)
_ = pop(&stack)
continue
}
stack[frame_index].target_type = constructor_type
stack[frame_index].stage = 11
append(&stack, Build_Expr_Frame{
expr=expr.args[0],
expected=types.INVALID,
template=ast.INVALID_FUNCTION,
})
continue
}
if constructor_ok && constructor_item.kind == .Distinct {
if !is_runtime_type(checker, constructor_type) {
id := source.addf(
checker.diagnostics,
expr.span,
@@ -9391,15 +9439,15 @@ build_expr :: proc(
symbol_text(checker, expr.name),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id, distinct_type)
last = invalid_hir_expr(checker, expr.span, id, constructor_type)
_ = pop(&stack)
continue
}
stack[frame_index].target_type = distinct_type
stack[frame_index].target_type = constructor_type
stack[frame_index].stage = 8
append(&stack, Build_Expr_Frame{
expr=expr.args[0],
expected=distinct_item.child,
expected=constructor_item.child,
template=ast.INVALID_FUNCTION,
})
continue
@@ -9988,6 +10036,10 @@ build_expr :: proc(
}
_ = pop(&stack)
}
if frame.stage == 11 {
last = build_scalar_cast(checker, last, frame.target_type, expr.span)
_ = pop(&stack)
}
}
return last
}
+76 -15
View File
@@ -1499,15 +1499,37 @@ ct_eval_expr :: proc(
case .Catch:
return ct_eval_catch_expr(state, expr, expected, depth+1)
case .Range:
left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
child_hint := types.INVALID
if types.is_range(expected, store) {
child_hint = types.child_type(expected, store)
}
right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1)
if !right_ok || right_flow.kind != .Normal {
return INVALID_CT_VALUE, right_flow, right_ok
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
left, right: Ct_Value_Id
flow: Ct_Flow
ok: bool
if left_const && !right_const && !types.is_valid(child_hint) {
right, flow, ok = ct_eval_expr(state, expr.right, types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
left, flow, ok = ct_eval_expr(state, expr.left, state.values[right].type, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
} else {
left, flow, ok = ct_eval_expr(state, expr.left, child_hint, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right_hint := child_hint if types.is_valid(child_hint) else state.values[left].type
right, flow, ok = ct_eval_expr(state, expr.right, right_hint, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
}
child_type := types.widest(state.values[left].type, state.values[right].type)
child_type := child_hint if types.is_valid(child_hint) else
types.widest(state.values[left].type, state.values[right].type)
if !types.is_concrete_integer(child_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "range bounds must be compatible concrete integers")
}
@@ -1905,10 +1927,15 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
store := &checker.module.types
if types.is_tagged_union(expected, store) {
index, field, found := find_struct_field(checker, expected, expr.name)
literal_expected := expected
optional_expected := types.is_optional(expected, store)
if optional_expected {
literal_expected = types.child_type(expected, store)
}
if types.is_tagged_union(literal_expected, store) {
index, field, found := find_struct_field(checker, literal_expected, expr.name)
if !found {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, literal_expected))
}
payload := INVALID_CT_VALUE
if expr.left != ast.INVALID_EXPR {
@@ -1921,23 +1948,31 @@ ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.T
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
} else if !types.is_void(field.type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "variant '.%s' on '%s' needs a payload", symbol_text(checker, expr.name), type_label(checker, expected))
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "variant '.%s' on '%s' needs a payload", symbol_text(checker, expr.name), type_label(checker, literal_expected))
}
start := u32(len(state.children))
append(&state.children, payload)
return ct_add_value(state, Ct_Value{kind=.Struct, type=expected, start=start, count=1, active=i64(index)}), ct_flow(.Normal), true
result := ct_add_value(state, Ct_Value{kind=.Struct, type=literal_expected, start=start, count=1, active=i64(index)})
if optional_expected {
return ct_coerce_expr_value(state, result, expected, expr.span)
}
return result, ct_flow(.Normal), true
}
if expr.left != ast.INVALID_EXPR {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s{...}' requires a tagged-union context", symbol_text(checker, expr.name))
}
if !types.is_enum(expected, store) {
if !types.is_enum(literal_expected, store) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s' requires an enum context", symbol_text(checker, expr.name))
}
member, ok := find_enum_member(checker, expected, expr.name)
member, ok := find_enum_member(checker, literal_expected, expr.name)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name))
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=expected, integer=member.value}), ct_flow(.Normal), true
result := ct_add_value(state, Ct_Value{kind=.Integer, type=literal_expected, integer=member.value})
if optional_expected {
return ct_coerce_expr_value(state, result, expected, expr.span)
}
return result, ct_flow(.Normal), true
}
ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol.Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
@@ -3550,6 +3585,32 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
}
}
}
named_type := types.find_named(
&checker.module.types,
u32(target_pkg),
u32(expr.name),
file=u32(expr_lookup_file(expr, state.file)),
)
named_item, named_ok := types.node(&checker.module.types, named_type)
target := types.resolve_alias(named_type, &checker.module.types)
if named_ok && named_item.kind == .Alias &&
types.is_concrete_scalar(target) && !types.is_bool(target) {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state,
.Not_Comptime,
expr.span,
"type alias '%s' expects 1 argument, got %d",
symbol_text(checker, expr.name),
len(expr.args),
)
}
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
return ct_scalar_cast(state, value, target, expr.span)
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
}
if runtime_param_count(checker.ast_module.functions[template]) != 0 {