improve error messages

This commit is contained in:
2026-08-09 22:41:40 +02:00
parent 8785912b73
commit 5244bfbf1b
2 changed files with 108 additions and 2 deletions
+79 -1
View File
@@ -3905,17 +3905,83 @@ runtime_write_declaration_matches :: proc(checker: ^Checker, function: ast.Funct
type_from_syntax(checker, function.result, function.pkg, function.file) == types.C_LONG
}
signature_type_diagnostic :: proc(
checker: ^Checker,
function: ast.Function,
value: types.Type,
depth := 0,
) -> source.Diagnostic_Id {
if depth > 64 || is_type_metatype_syntax(checker, value) {
return source.INVALID_DIAGNOSTIC
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return source.INVALID_DIAGNOSTIC
}
if item.kind == .Named && !item.declared && item.qualifier == 0 {
name := symbol.Id(item.name)
if param, found := comptime_param_for_name(function, name);
found && is_comptime_type_param(checker, param) {
return source.INVALID_DIAGNOSTIC
}
span := function.span
for type_use in checker.ast_module.type_uses {
if type_use.file == function.file && type_use.type == value &&
type_use.span.start >= function.span.start && type_use.span.end <= function.span.end {
span = type_use.span
break
}
}
diagnostic := source.addf(
checker.diagnostics,
span,
"unknown symbol '%s'",
symbol_text(checker, name),
)
source.set_primary_label(checker.diagnostics, diagnostic, "unknown symbol")
return diagnostic
}
if diagnostic := signature_type_diagnostic(
checker, function, item.child, depth+1,
); diagnostic != source.INVALID_DIAGNOSTIC {
return diagnostic
}
if diagnostic := signature_type_diagnostic(
checker, function, item.extra, depth+1,
); diagnostic != source.INVALID_DIAGNOSTIC {
return diagnostic
}
if item.kind == .Function {
for param in types.params_for(&checker.module.types, value) {
if diagnostic := signature_type_diagnostic(
checker, function, param.type, depth+1,
); diagnostic != source.INVALID_DIAGNOSTIC {
return diagnostic
}
}
}
return source.INVALID_DIAGNOSTIC
}
validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions {
if len(function.unsupported_reason) > 0 {
continue
}
has_comptime := function_has_comptime_params(function)
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
signature_poisoned := checker.template_diagnostics[function_id] != source.INVALID_DIAGNOSTIC
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
comptime_prefix := 0
for param in function.params {
if !signature_poisoned {
if diagnostic := signature_type_diagnostic(
checker, function, param.type,
); diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
signature_poisoned = true
}
}
dependent := param.comptime_value && type_pattern_mentions_comptime(
checker, function, comptime_prefix, param.type,
)
@@ -3994,6 +4060,18 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if !signature_poisoned {
signature_types := [2]types.Type{function.result, function.error}
for value in signature_types {
if diagnostic := signature_type_diagnostic(
checker, function, value,
); diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
signature_poisoned = true
break
}
}
}
if !has_comptime && !signature_poisoned {
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
if function.c_abi && types.is_noreturn(result_type) {
+29 -1
View File
@@ -10432,7 +10432,7 @@ package_and_file_local_visibility_is_enforced :: proc(t: ^testing.T) {
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function 'collision'")
found_file_sibling = found_file_sibling || strings.contains(diagnostic.message, "unknown symbol 'file_sibling'")
found_file_sibling_value = found_file_sibling_value || strings.contains(diagnostic.message, "unknown symbol 'file_sibling_value'")
found_file_sibling_type = found_file_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type 'File_Sibling'")
found_file_sibling_type = found_file_sibling_type || strings.contains(diagnostic.message, "unknown symbol 'File_Sibling'")
}
testing.expect(t, !found_sibling)
testing.expect(t, !found_sibling_value)
@@ -15042,6 +15042,34 @@ missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T)
testing.expect(t, !strings.contains(formatted, "could not resolve the 'int' constraint"))
}
@(test)
missing_unqualified_signature_type_reports_one_root_error :: proc(t: ^testing.T) {
text := `render_stmt func(node NodeId) void {}
main func() void {
render_stmt(0)
}
`
source_file := source.Source{path="renderer.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 1)
testing.expect_value(t, diagnostics.items[0].message, "unknown symbol 'NodeId'")
formatted := source.format(&diagnostics, source.Diagnostic_Id(0))
defer delete(formatted)
testing.expect(t, strings.contains(formatted, "renderer.bro:1:23"))
testing.expect(t, strings.contains(formatted, "^^^^^^ unknown symbol"))
testing.expect(t, !strings.contains(formatted, "could not resolve specialization"))
}
@(test)
poisoned_global_and_local_types_do_not_create_inference_fallbacks :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-poisoned-declarations"