bug fixes

This commit is contained in:
2026-07-12 10:13:19 +02:00
parent cff9e9500f
commit b0c716537e
14 changed files with 847 additions and 106 deletions
+131 -30
View File
@@ -265,14 +265,100 @@ record_unused_locals :: proc(
}
}
// type_label renders a type for a diagnostic, resolving a named type (enum/union/struct/
// distinct) to its declared source name; primitives and unnamed types fall back to
// `types.name` (which prints `<type N>` for anonymous nodes).
type_label :: proc(checker: ^Checker, value: types.Type) -> string {
if node, ok := types.node(&checker.module.types, value); ok && node.name != 0 {
return symbol_text(checker, symbol.Id(node.name))
write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: types.Type) {
store := &checker.module.types
item, ok := types.node(store, value)
if !ok {
strings.write_string(builder, types.name(value))
return
}
return types.name(value)
if item.name != 0 {
strings.write_string(builder, symbol_text(checker, symbol.Id(item.name)))
return
}
switch item.kind {
case .Array:
strings.write_byte(builder, '[')
if item.inferred_count {
strings.write_byte(builder, '_')
} else {
fmt.sbprintf(builder, "%d", item.count)
}
if item.has_sentinel {
fmt.sbprintf(builder, ";%d", item.sentinel)
}
strings.write_byte(builder, ']')
if item.mutable {
strings.write_string(builder, "mut ")
}
write_type_label(checker, builder, item.child)
case .Pointer:
if item.has_sentinel {
fmt.sbprintf(builder, "[*;%d]", item.sentinel)
} else {
strings.write_byte(builder, '*' if item.many else '@')
}
if item.mutable {
strings.write_string(builder, "mut ")
}
write_type_label(checker, builder, item.child)
case .Slice:
if item.has_sentinel {
fmt.sbprintf(builder, "[;%d]", item.sentinel)
} else {
strings.write_string(builder, "[]")
}
if item.mutable {
strings.write_string(builder, "mut ")
}
write_type_label(checker, builder, item.child)
case .Range:
strings.write_string(builder, "range(")
write_type_label(checker, builder, item.child)
strings.write_byte(builder, ')')
case .Optional:
strings.write_byte(builder, '?')
write_type_label(checker, builder, item.child)
case .Function:
strings.write_string(builder, "c_func(" if item.c_abi else "func(")
for param, index in types.params_for(store, value) {
if index > 0 {
strings.write_string(builder, ", ")
}
write_type_label(checker, builder, param.type)
}
if item.variadic {
if item.field_count > 0 {
strings.write_string(builder, ", ")
}
strings.write_string(builder, "...")
}
strings.write_string(builder, ") ")
write_type_label(checker, builder, item.child)
case .Fallible:
write_type_label(checker, builder, item.child)
strings.write_string(builder, " ! ")
write_type_label(checker, builder, item.extra)
case .Struct:
strings.write_string(builder, "struct")
case .Union:
strings.write_string(builder, "union")
case .Enum:
strings.write_string(builder, "enum")
case .Alias, .Distinct, .Named:
write_type_label(checker, builder, item.child)
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
strings.write_string(builder, types.name(value))
}
}
// Render dynamic types using source syntax so diagnostics never expose internal
// type-store ids such as `<type 230>`.
type_label :: proc(checker: ^Checker, value: types.Type) -> string {
builder := strings.builder_make(context.temp_allocator)
write_type_label(checker, &builder, value)
return strings.to_string(builder)
}
is_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
@@ -460,6 +546,8 @@ type_from_syntax :: proc(
store := &checker.module.types
changed := false
#partial switch item.kind {
case .Alias:
return type_from_syntax(checker, item.child, pkg, file, depth+1)
case .Array:
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
changed = changed || child != item.child
@@ -3246,6 +3334,7 @@ infer_all :: proc(checker: ^Checker) {
ensure_spec(checker, main_template, nil)
}
defaults_applied := false
for {
changed := false
checker.global_demands_dirty = false
@@ -3334,22 +3423,30 @@ infer_all :: proc(checker: ^Checker) {
changed = true
}
if !changed {
if !defaults_applied {
defaults_applied = true
defaulted := false
// No authoritative demand can still arrive. Assign final defaults, then
// continue the same fixpoint so dependent globals/specs observe them.
for global, index in checker.ast_module.globals {
if global.external || is_runtime_type(checker, checker.global_types[index]) {
continue
}
if checker.global_open_const[index] {
checker.global_types[index] = types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
defaulted = true
} else if checker.global_open_float[index] {
checker.global_types[index] = types.F64
defaulted = true
}
}
if defaulted {
continue
}
}
break
}
}
// Any open constant that no use ever demanded now takes its default: an integer
// constant the smallest signed type that holds its value, a float constant f64.
for global, index in checker.ast_module.globals {
if global.external || is_runtime_type(checker, checker.global_types[index]) {
continue
}
if checker.global_open_const[index] {
checker.global_types[index] = types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
} else if checker.global_open_float[index] {
checker.global_types[index] = types.F64
}
}
}
prune_specs :: proc(checker: ^Checker) {
@@ -3604,8 +3701,8 @@ coerce_expr :: proc(
checker.diagnostics,
span,
"cannot implicitly convert %s to %s",
types.name(actual),
types.name(expected),
type_label(checker, actual),
type_label(checker, expected),
)
return invalid_hir_expr(checker, span, id, expected)
}
@@ -4533,7 +4630,7 @@ build_compound_expr :: proc(
}
handler: [dynamic]hir.Stmt_Id
handler.allocator = checker.allocator
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span)
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span, allow_exit=true)
body = handler[:]
resize(ctx.locals, capture_start)
}
@@ -6683,18 +6780,17 @@ build_block :: proc(
}
// build_value_block builds a `{ ... yield v }` value block whose final statement
// must be a `yield`: it builds the leading statements inline (their own scope and
// defers), evaluates the yield expression in that scope, then capturing the value
// first, like a function return runs the block's defers and closes the scope. The
// resulting `value`/`value_type` are spliced into the enclosing declaration or
// assignment. `expected` is the binding's type (INVALID for an untyped `::`, where
// the yield's natural type is taken). Statements are appended to `body`.
// must be a `yield`. Catch handlers may instead exit on every path, in which case
// `allow_exit` leaves the fallback expression invalid. Otherwise it builds the leading
// statements inline, evaluates the yield in their scope, then captures the value before
// running defers. `expected` is the binding's type (INVALID for an untyped `::`).
build_value_block :: proc(
ctx: ^Build_Ctx,
body: ^[dynamic]hir.Stmt_Id,
body_stmts: []ast.Stmt_Id,
expected: types.Type,
span: source.Span,
allow_exit := false,
) -> (value: hir.Expr_Id, value_type: types.Type) {
checker := ctx.checker
n := len(body_stmts)
@@ -6705,6 +6801,10 @@ build_value_block :: proc(
for s in inner {
append(body, s)
}
if allow_exit && all_paths_exit(&checker.module, inner) {
delete(inner, checker.allocator)
return hir.INVALID_EXPR, expected
}
delete(inner, checker.allocator)
id := source.add(checker.diagnostics, span, "a value block must end with an explicit 'yield'")
ctx.problematic^ = true
@@ -6782,6 +6882,7 @@ build_value_source :: proc(
span: source.Span,
label := symbol.INVALID,
value_control_flow := false,
allow_exit := false,
) -> (value: hir.Expr_Id, value_type: types.Type) {
checker := ctx.checker
if symbol.is_valid(label) {
@@ -6797,7 +6898,7 @@ build_value_source :: proc(
return build_value_match(ctx, body, body_stmts[0], expected, span)
}
}
return build_value_block(ctx, body, body_stmts, expected, span)
return build_value_block(ctx, body, body_stmts, expected, span, allow_exit)
}
// new_value_slot allocates a fresh, un-nameable mutable local to hold a value-if/loop