bug fixes
This commit is contained in:
+131
-30
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user