diagnostics upgrade

This commit is contained in:
2026-07-15 22:55:31 +02:00
parent c4fa8e930f
commit 7de0b7f268
11 changed files with 857 additions and 75 deletions
+277 -35
View File
@@ -208,6 +208,7 @@ Checker :: struct {
record_field_conflicts: []types.Type,
record_field_conflict_spans: []source.Span,
record_field_demands_dirty: bool,
poisoned_packages: []bool,
external_global_canonical: []ast.Global_Id,
external_global_diagnostics: []source.Diagnostic_Id,
constants: []Constant,
@@ -1496,16 +1497,30 @@ expr_lookup_file :: proc(expr: ast.Expr, file: ast.File_Id) -> ast.File_Id {
return ast.INVALID_FILE if symbol.is_valid(expr.qualifier) else file
}
expr_symbol_span :: proc(checker: ^Checker, expr: ast.Expr, name: symbol.Id) -> source.Span {
span := expr.span
span.end = span.start+source.Offset(len(symbol_text(checker, name)))
return span
}
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
span := expr_symbol_span(checker, expr, expr.qualifier)
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.qualifier))
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
return id
}
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
}
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
if find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_FUNCTION {
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a function, not a global value", symbol_text(checker, expr.name))
if !checker.ast_module.functions[template].imported {
source.add_secondary_label(checker.diagnostics, id, checker.ast_module.functions[template].span, "function declared here")
}
return id
}
if symbol.is_valid(expr.qualifier) {
return source.addf(
@@ -1516,12 +1531,20 @@ add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
symbol_text(checker, expr.name),
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
span := expr_symbol_span(checker, expr, expr.name)
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name))
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
return id
}
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
if find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_GLOBAL {
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a global, not a function", symbol_text(checker, expr.name))
if !checker.ast_module.globals[global].external {
source.add_secondary_label(checker.diagnostics, id, checker.ast_module.globals[global].span, "global declared here")
}
return id
}
if symbol.is_valid(expr.qualifier) {
return source.addf(
@@ -1532,7 +1555,10 @@ add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
symbol_text(checker, expr.name),
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
span := expr_symbol_span(checker, expr, expr.name)
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name))
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
return id
}
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
@@ -2649,6 +2675,7 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
_ = add_package_resolution_diagnostic(checker, expr, file)
return types.INVALID
}
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
@@ -3145,6 +3172,7 @@ validate_declarations :: proc(checker: ^Checker) {
continue
}
has_comptime := function_has_comptime_params(function)
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
for param in function.params {
@@ -3152,7 +3180,7 @@ validate_declarations :: proc(checker: ^Checker) {
if param.comptime_value || !has_comptime {
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
if param.comptime_value {
if param.comptime_value && !signature_poisoned {
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
@@ -3169,7 +3197,7 @@ validate_declarations :: proc(checker: ^Checker) {
symbol_text(checker, param.name),
)
}
} else if !has_comptime {
} else if !has_comptime && !signature_poisoned {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
@@ -3197,7 +3225,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
append(&locals, param.name)
if !has_comptime && types.contains_c_struct_by_value(param_type, &checker.module.types) {
if !has_comptime && !signature_poisoned && types.contains_c_struct_by_value(param_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -3206,7 +3234,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if !has_comptime {
if !has_comptime && !signature_poisoned {
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
diagnostic != source.INVALID_DIAGNOSTIC {
@@ -3221,7 +3249,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if types.is_valid(function.error) {
if types.is_valid(function.error) && !signature_poisoned {
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
error_sum := types.is_enum(error_type, &checker.module.types) ||
types.is_tagged_union(error_type, &checker.module.types)
@@ -3239,7 +3267,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if !function.has_body && !function.c_abi {
if !function.has_body && !function.c_abi && !signature_poisoned {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
@@ -3247,7 +3275,7 @@ validate_declarations :: proc(checker: ^Checker) {
symbol_text(checker, function.name),
)
}
if function.variadic && (!function.c_abi || function.has_body) {
if function.variadic && (!function.c_abi || function.has_body) && !signature_poisoned {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
@@ -3255,7 +3283,7 @@ validate_declarations :: proc(checker: ^Checker) {
symbol_text(checker, function.name),
)
}
if !function.has_body && function.c_abi {
if !function.has_body && function.c_abi && !signature_poisoned {
for param in function.params {
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
@@ -3351,7 +3379,7 @@ is_inferred_record_field :: proc(checker: ^Checker, slot: int) -> bool {
types.is_constraint(checker.record_field_constraints[slot])
}
record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, bool) {
record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, ast.Package_Id, bool) {
for item in checker.module.types.nodes {
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
@@ -3360,10 +3388,10 @@ record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.I
start := int(item.field_start)
if slot >= start && slot < start+int(item.field_count) &&
slot >= 0 && slot < len(checker.module.types.fields) {
return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), true
return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), ast.Package_Id(item.pkg), true
}
}
return symbol.INVALID, symbol.INVALID, false
return symbol.INVALID, symbol.INVALID, ast.INVALID_PACKAGE, false
}
record_field_conflict :: proc(checker: ^Checker, slot: int, actual: types.Type, span: source.Span) {
@@ -3484,7 +3512,7 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
if !types.is_constraint(constraint) {
continue
}
record_name, field_name, ok := record_field_owner(checker, slot)
record_name, field_name, record_pkg, ok := record_field_owner(checker, slot)
if !ok {
continue
}
@@ -3509,7 +3537,8 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
symbol_text(checker, record_name), symbol_text(checker, field_name),
)
}
} else if types.is_constraint(current) {
} else if types.is_constraint(current) &&
!(int(record_pkg) < len(checker.poisoned_packages) && checker.poisoned_packages[record_pkg]) {
source.addf(
checker.diagnostics,
source.Span{},
@@ -5622,6 +5651,10 @@ infer_all :: proc(checker: ^Checker) {
// Demands accumulate in global_demands so the default never blocks a later
// cross-family demand (e.g. integer literal -> unsigned or float).
for global, index in checker.ast_module.globals {
if global.diagnostic != source.INVALID_DIAGNOSTIC {
checker.global_types[index] = types.I64
continue
}
declared := resolve_inferred_array(
checker,
type_from_syntax(checker, global.type, global.pkg, global.file),
@@ -5675,7 +5708,7 @@ infer_all :: proc(checker: ^Checker) {
// Backward demands: a global pushes its own (declared or already-resolved) type
// onto open numeric slots reachable through names and numeric arithmetic.
for global, index in checker.ast_module.globals {
if global.external {
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
continue
}
demand := checker.global_types[index]
@@ -5689,7 +5722,7 @@ infer_all :: proc(checker: ^Checker) {
// concrete-typed ones) for its side effect of specializing called functions and
// recording demands from call arguments in their initializers.
for global, index in checker.ast_module.globals {
if global.external {
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
continue
}
declared := resolve_inferred_array(
@@ -5765,7 +5798,8 @@ infer_all :: proc(checker: ^Checker) {
// 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]) {
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC ||
is_runtime_type(checker, checker.global_types[index]) {
continue
}
if checker.global_open_const[index] {
@@ -5807,7 +5841,7 @@ prune_specs :: proc(checker: ^Checker) {
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
}
for global in checker.ast_module.globals {
if global.external {
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
continue
}
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
@@ -5859,6 +5893,23 @@ invalid_hir_expr :: proc(
)
}
invalid_expr_diagnostic :: proc(checker: ^Checker, id: hir.Expr_Id) -> (source.Diagnostic_Id, bool) {
if id == hir.INVALID_EXPR || int(id) >= len(checker.module.exprs) {
return source.INVALID_DIAGNOSTIC, false
}
expr := checker.module.exprs[id]
return expr.diagnostic, expr.kind == .Invalid && expr.diagnostic != source.INVALID_DIAGNOSTIC
}
propagate_invalid_expr :: proc(checker: ^Checker, span: source.Span, values: ..hir.Expr_Id) -> (hir.Expr_Id, bool) {
for value in values {
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
return invalid_hir_expr(checker, span, diagnostic), true
}
}
return hir.INVALID_EXPR, false
}
add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) {
for existing in values {
if existing == value {
@@ -5935,6 +5986,9 @@ coerce_expr :: proc(
if expr_id == hir.INVALID_EXPR {
return expr_id
}
if _, invalid := invalid_expr_diagnostic(checker, expr_id); invalid {
return expr_id
}
actual := checker.module.exprs[expr_id].type
if types.equal(actual, expected) {
return expr_id
@@ -6918,6 +6972,9 @@ build_compound_expr :: proc(
})
}
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, value); propagated {
return invalid
}
if !hir_is_location(checker, value) {
id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location")
return invalid_hir_expr(checker, expr.span, id)
@@ -6936,6 +6993,9 @@ build_compound_expr :: proc(
})
case .Deref:
pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, pointer); propagated {
return invalid
}
pointer_type := checker.module.exprs[pointer].type
if !types.is_pointer(pointer_type, store) {
id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer")
@@ -6949,6 +7009,9 @@ build_compound_expr :: proc(
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
index = coerce_expr(checker, index, types.USIZE, expr.span)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container, index); propagated {
return invalid
}
container_type := checker.module.exprs[container].type
item, ok := types.container(container_type, store)
if !ok {
@@ -6970,6 +7033,9 @@ build_compound_expr :: proc(
})
}
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container); propagated {
return invalid
}
container_type := checker.module.exprs[container].type
item, ok := types.container(container_type, store)
if !ok {
@@ -6987,6 +7053,10 @@ build_compound_expr :: proc(
if bound != ast.INVALID_EXPR {
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file)
bounds[index] = coerce_expr(checker, bounds[index], types.USIZE, checker.ast_module.exprs[bound].span)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, bounds[index]); propagated {
delete(bounds, checker.allocator)
return invalid
}
}
}
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
@@ -7000,11 +7070,17 @@ build_compound_expr :: proc(
return enum_member_hir(checker, enum_type, expr.name, expr.span)
}
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, base); propagated {
return invalid
}
base_type := checker.module.exprs[base].type
result, _ := build_field_from_value(checker, expr, base, base_type)
return result
case .Unwrap:
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, optional); propagated {
return invalid
}
optional_type := checker.module.exprs[optional].type
if !types.is_optional(optional_type, store) {
id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional")
@@ -7016,6 +7092,9 @@ build_compound_expr :: proc(
})
case .Orelse:
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, optional); propagated {
return invalid
}
optional_type := checker.module.exprs[optional].type
if !types.is_optional(optional_type, store) {
id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand")
@@ -7035,6 +7114,9 @@ build_compound_expr :: proc(
}
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
if diagnostic, invalid := invalid_expr_diagnostic(checker, channel); invalid {
return invalid_hir_expr(checker, expr.span, diagnostic)
}
channel_type := checker.module.exprs[channel].type
success := types.fallible_success(channel_type, store)
if !types.is_valid(success) {
@@ -7076,6 +7158,9 @@ build_compound_expr :: proc(
case .Catch:
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
if diagnostic, invalid := invalid_expr_diagnostic(checker, channel); invalid {
return invalid_hir_expr(checker, expr.span, diagnostic)
}
channel_type := checker.module.exprs[channel].type
success := types.fallible_success(channel_type, store)
if !types.is_valid(success) {
@@ -7145,6 +7230,9 @@ build_compound_expr :: proc(
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
}
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
child := expected_child
if !types.is_valid(child) {
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
@@ -7174,6 +7262,9 @@ build_compound_expr :: proc(
})
case .Not:
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
return invalid
}
operand_type := checker.module.exprs[operand].type
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
@@ -7186,6 +7277,9 @@ build_compound_expr :: proc(
case .And, .Or:
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
@@ -7226,11 +7320,11 @@ build_compound_expr :: proc(
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
}
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
if checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
}
operand_type := types.INVALID
if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
@@ -7420,6 +7514,9 @@ build_binary_arith :: proc(
left, right: hir.Expr_Id,
span: source.Span,
) -> hir.Expr_Id {
if invalid, propagated := propagate_invalid_expr(checker, span, left, right); propagated {
return invalid
}
// Pointer arithmetic is only defined for `+` (many-pointer + usize).
if op == .Add &&
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
@@ -7960,6 +8057,9 @@ build_expr :: proc(
checker.diagnostics, expr.span, message,
symbol_text(checker, expr.name), len(function.params), len(expr.args),
)
if !function.imported {
source.add_secondary_label(checker.diagnostics, id, function.span, "function declared here")
}
} else if len(mapping_failure) > 0 {
id = source.addf(
checker.diagnostics, expr.span,
@@ -8009,6 +8109,11 @@ build_expr :: proc(
}
if frame.stage == 5 {
operand := last
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
last = invalid
_ = pop(&stack)
continue
}
operand_type := checker.module.exprs[operand].type
if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) {
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float")
@@ -8071,6 +8176,17 @@ build_expr :: proc(
continue
}
}
if invalid, propagated := propagate_invalid_expr(checker, expr.span, ..stack[frame_index].built_args); propagated {
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
delete(stack[frame_index].mapping, checker.allocator)
stack[frame_index].mapping = nil
last = invalid
_ = pop(&stack)
continue
}
function := checker.ast_module.functions[frame.template]
comptime_values: []Comptime_Value
comptime_ok := false
@@ -8236,6 +8352,11 @@ build_expr :: proc(
}
if frame.stage == 6 {
callee := last
if invalid, propagated := propagate_invalid_expr(checker, expr.span, callee); propagated {
last = invalid
_ = pop(&stack)
continue
}
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
@@ -8279,6 +8400,17 @@ build_expr :: proc(
continue
}
}
invalid, propagated := propagate_invalid_expr(checker, expr.span, frame.left)
if !propagated {
invalid, propagated = propagate_invalid_expr(checker, expr.span, ..stack[frame_index].built_args)
}
if propagated {
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid
_ = pop(&stack)
continue
}
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
@@ -8897,6 +9029,22 @@ build_block :: proc(
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
if statement.diagnostic != source.INVALID_DIAGNOSTIC {
if statement.kind == .Declaration && symbol.is_valid(statement.name) {
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); !found {
_ = append_build_local(
ctx, statement.name, types.I64, !statement.immutable, statement.span,
)
}
}
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic,
})
ctx.problematic^ = true
continue
}
switch statement.kind {
case .Declaration:
// A value block (`x :: { ... yield v }` / `x T = { ... }`): the parser
@@ -9070,6 +9218,15 @@ build_block :: proc(
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
types.INVALID, ctx.pkg, ctx.file,
)
if diagnostic, invalid := invalid_expr_diagnostic(checker, target_expr); invalid {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=diagnostic,
})
ctx.problematic^ = true
continue
}
target_type := checker.module.exprs[target_expr].type
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
@@ -9092,6 +9249,15 @@ build_block :: proc(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
rhs_expected, ctx.pkg, ctx.file,
)
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=diagnostic,
})
ctx.problematic^ = true
continue
}
if types.is_many_pointer(target_type, &checker.module.types) {
assignment_op = .Pointer_Add
if statement.assignment_op != .Add {
@@ -9208,6 +9374,15 @@ build_block :: proc(
continue
}
target_expr := build_global_reference(checker, global, statement.span, ctx.global_reads)
if diagnostic, invalid := invalid_expr_diagnostic(checker, target_expr); invalid {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=diagnostic,
})
ctx.problematic^ = true
continue
}
target_type := checker.module.exprs[target_expr].type
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
@@ -9425,7 +9600,14 @@ build_block :: proc(
})
case .Expression:
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
if !types.is_void(checker.module.exprs[value].type) {
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = diagnostic,
})
ctx.problematic^ = true
} else if !types.is_void(checker.module.exprs[value].type) {
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
@@ -11504,15 +11686,10 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
checker.current_comptime_values = spec.comptime_values
defer checker.current_comptime_values = previous_comptime
signature_diagnostic := source.INVALID_DIAGNOSTIC
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
unresolved_result := !types.is_void(spec.result) && !is_runtime_type(checker, spec.result)
if unresolved_result {
checker.specs[id].result = types.I64
spec.result = types.I64
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete result type for '%s'",
symbol_text(checker, function.name),
)
}
for arg in spec.args {
if !is_runtime_type(checker, arg) {
@@ -11582,6 +11759,16 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
native_main := function.pkg == 0 && function.name == checker.main_symbol && checker.entry_point == .Plain
if !function.has_body {
if unresolved_result && signature_diagnostic == source.INVALID_DIAGNOSTIC &&
checker.template_diagnostics[spec.template] == source.INVALID_DIAGNOSTIC {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete result type for '%s'",
symbol_text(checker, function.name),
)
problematic = true
}
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
&checker.module.functions,
@@ -11665,6 +11852,22 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
append(&body, block_stmt)
}
delete(block, checker.allocator)
if unresolved_result && signature_diagnostic == source.INVALID_DIAGNOSTIC &&
checker.template_diagnostics[spec.template] == source.INVALID_DIAGNOSTIC && !problematic {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete result type for '%s'",
symbol_text(checker, function.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=function.span, expr=hir.INVALID_EXPR,
local=hir.INVALID_LOCAL, diagnostic=signature_diagnostic,
})
problematic = true
returns = true
}
fallible_void := types.kind(spec.result, &checker.module.types) == .Fallible &&
types.is_void(types.fallible_success(spec.result, &checker.module.types))
@@ -11773,6 +11976,28 @@ static_integer_value :: proc(module: ^hir.Module, expr_id: hir.Expr_Id) -> (i64,
build_globals :: proc(checker: ^Checker) {
for global, global_index in checker.ast_module.globals {
if global.diagnostic != source.INVALID_DIAGNOSTIC {
global_type := checker.global_types[global_index]
if !is_runtime_type(checker, global_type) {
global_type = types.I64
}
expr := hir.INVALID_EXPR
if !global.external {
expr = invalid_hir_expr(checker, global.span, global.diagnostic, global_type)
}
append(&checker.module.globals, hir.Global{
name=global.name,
link_name=strings.clone(global.link_name, checker.allocator),
type=global_type,
expr=expr,
external=global.external,
writable=global.writable || !global.immutable,
direct_problem=true,
problematic=true,
diagnostic=global.diagnostic,
})
continue
}
if global.external {
global_type := checker.global_types[global_index]
writable := global.writable
@@ -11841,6 +12066,12 @@ build_globals :: proc(checker: ^Checker) {
global_type = checker.module.exprs[expr].type
}
diagnostic := source.INVALID_DIAGNOSTIC
if root, invalid := invalid_expr_diagnostic(checker, expr); invalid {
diagnostic = root
if !is_runtime_type(checker, global_type) {
global_type = types.I64
}
}
if !global.immutable && is_undefined_expr(checker, global.expr) {
diagnostic = source.add(
checker.diagnostics,
@@ -12188,6 +12419,7 @@ check :: proc(
checker.record_field_defaults = make([]types.Type, len(checker.module.types.fields), allocator)
checker.record_field_conflicts = make([]types.Type, len(checker.module.types.fields), allocator)
checker.record_field_conflict_spans = make([]source.Span, len(checker.module.types.fields), allocator)
checker.poisoned_packages = make([]bool, max(len(ast_module.packages), 1), allocator)
init_record_field_inference(&checker)
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
@@ -12199,8 +12431,17 @@ check :: proc(
}
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
for &diagnostic in checker.template_diagnostics {
diagnostic = source.INVALID_DIAGNOSTIC
for &diagnostic, index in checker.template_diagnostics {
diagnostic = ast_module.functions[index].diagnostic
pkg := ast_module.functions[index].pkg
if diagnostic != source.INVALID_DIAGNOSTIC && int(pkg) < len(checker.poisoned_packages) {
checker.poisoned_packages[pkg] = true
}
}
for global in ast_module.globals {
if global.diagnostic != source.INVALID_DIAGNOSTIC && int(global.pkg) < len(checker.poisoned_packages) {
checker.poisoned_packages[global.pkg] = true
}
}
defer {
for spec in checker.specs {
@@ -12220,6 +12461,7 @@ check :: proc(
delete(checker.record_field_defaults, allocator)
delete(checker.record_field_conflicts, allocator)
delete(checker.record_field_conflict_spans, allocator)
delete(checker.poisoned_packages, allocator)
delete(checker.external_global_canonical, allocator)
delete(checker.external_global_diagnostics, allocator)
delete(checker.constants, allocator)