diagnostics upgrade
This commit is contained in:
+222
-15
@@ -917,8 +917,8 @@ multi_source_diagnostics_report_the_originating_file :: proc(t: ^testing.T) {
|
||||
found := false
|
||||
for _, diagnostic_index in diagnostics.items {
|
||||
message := source.format(&diagnostics, source.diagnostic_id(diagnostic_index))
|
||||
if strings.contains(message, "/b.bro:2:9:") &&
|
||||
strings.contains(message, "unknown package alias 'math'") {
|
||||
if strings.contains(message, "--> ") && strings.contains(message, "/b.bro:2:9") &&
|
||||
strings.contains(message, "unknown symbol 'math'") {
|
||||
found = true
|
||||
}
|
||||
delete(message)
|
||||
@@ -1956,7 +1956,7 @@ old_intrinsic_spellings_are_not_recognized :: proc(t: ^testing.T) {
|
||||
}
|
||||
found := [len(names)]bool{}
|
||||
for diagnostic in diagnostics.items {
|
||||
if !strings.contains(diagnostic.message, "unresolved function") {
|
||||
if !strings.contains(diagnostic.message, "unknown symbol") {
|
||||
continue
|
||||
}
|
||||
for name, index in names {
|
||||
@@ -4306,6 +4306,34 @@ main func() i32 {
|
||||
testing.expect(t, indirect_calls > 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
qualified_value_calls_do_not_imply_package_resolution :: proc(t: ^testing.T) {
|
||||
text := `Callbacks :: struct {
|
||||
call @func() void
|
||||
}
|
||||
main func() void {
|
||||
callbacks Callbacks = Callbacks{call = func() void {}}
|
||||
callbacks.call()
|
||||
missing.call()
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.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 'missing'")
|
||||
testing.expect(t, !strings.contains(diagnostics.items[0].message, "package"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
function_literals_do_not_capture_locals :: proc(t: ^testing.T) {
|
||||
text := `main func() i32 {
|
||||
@@ -4330,7 +4358,7 @@ function_literals_do_not_capture_locals :: proc(t: ^testing.T) {
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "unresolved global 'offset'")
|
||||
found = found || strings.contains(diagnostic.message, "unknown symbol 'offset'")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
@@ -7210,7 +7238,7 @@ source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t:
|
||||
testing.expect_value(t, len(store.items[source_id].line_starts), 3)
|
||||
testing.expect_value(t, first, second)
|
||||
testing.expect_value(t, len(diagnostics.items), 2)
|
||||
testing.expect(t, strings.contains(formatted, "owned.bro:2:1:"))
|
||||
testing.expect(t, strings.contains(formatted, "--> owned.bro:2:1"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -7233,8 +7261,92 @@ diagnostic_warnings_format_and_dedupe_by_severity :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, len(diagnostics.items), 2)
|
||||
testing.expect_value(t, diagnostics.items[warning].severity, source.Severity.Warning)
|
||||
testing.expect_value(t, diagnostics.items[err].severity, source.Severity.Error)
|
||||
testing.expect(t, strings.contains(formatted_warning, "test.bro:1:1: warning: same"))
|
||||
testing.expect(t, strings.contains(formatted_error, "test.bro:1:1: error: same"))
|
||||
testing.expect(t, strings.contains(formatted_warning, "warning: same\n --> test.bro:1:1"))
|
||||
testing.expect(t, strings.contains(formatted_error, "error: same\n --> test.bro:1:1"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
rich_diagnostics_render_labels_notes_help_and_tabs :: proc(t: ^testing.T) {
|
||||
store := source.init_store()
|
||||
defer source.destroy_store(&store)
|
||||
primary_file := source.add_source(&store, "main.bro", "\tmissing()\nnext()\n")
|
||||
definition_file := source.add_source(&store, "dep.bro", "value :: 1\n")
|
||||
diagnostics := source.init_store_diagnostics(&store)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
id := source.add(&diagnostics, source.Span{file=primary_file, start=1, end=8}, "unknown symbol 'missing'")
|
||||
source.set_primary_label(&diagnostics, id, "unknown symbol")
|
||||
source.set_primary_label(&diagnostics, id, "unknown symbol")
|
||||
source.add_secondary_label(
|
||||
&diagnostics, id,
|
||||
source.Span{file=definition_file, start=0, end=5},
|
||||
"related declaration",
|
||||
)
|
||||
source.add_note(&diagnostics, id, "names resolve in the current scope")
|
||||
source.add_note(&diagnostics, id, "names resolve in the current scope")
|
||||
source.add_help(&diagnostics, id, "declare 'missing' before using it")
|
||||
formatted := source.format(&diagnostics, id)
|
||||
defer delete(formatted)
|
||||
multiline := source.add(
|
||||
&diagnostics,
|
||||
source.Span{file=primary_file, start=1, end=17},
|
||||
"multiline failure",
|
||||
)
|
||||
multiline_formatted := source.format(&diagnostics, multiline)
|
||||
defer delete(multiline_formatted)
|
||||
unknown := source.add(&diagnostics, source.Span{}, "no location")
|
||||
unknown_formatted := source.format(&diagnostics, unknown)
|
||||
defer delete(unknown_formatted)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.annotations), 4)
|
||||
testing.expect(t, strings.contains(formatted, "error: unknown symbol 'missing'"))
|
||||
testing.expect(t, strings.contains(formatted, "--> main.bro:1:2"))
|
||||
testing.expect(t, strings.contains(formatted, "^^^^^^^ unknown symbol"))
|
||||
testing.expect(t, strings.contains(formatted, "::: dep.bro:1:1"))
|
||||
testing.expect(t, strings.contains(formatted, "----- related declaration"))
|
||||
testing.expect(t, strings.contains(formatted, "note: names resolve in the current scope"))
|
||||
testing.expect(t, strings.contains(formatted, "help: declare 'missing' before using it"))
|
||||
testing.expect(t, strings.contains(multiline_formatted, "1 | missing()"))
|
||||
testing.expect(t, !strings.contains(multiline_formatted, "next()"))
|
||||
testing.expect_value(t, unknown_formatted, "main.bro: error: no location")
|
||||
}
|
||||
|
||||
@(test)
|
||||
poisoned_expressions_preserve_independent_root_diagnostics :: proc(t: ^testing.T) {
|
||||
text := `bad :: missing_global
|
||||
sink func(value i32) void { _ = value }
|
||||
broken func(value int) int { return missing_return + value }
|
||||
main func() void {
|
||||
_ = missing_add + 1
|
||||
_ = try missing_try()
|
||||
missing_catch() catch |_| {}
|
||||
sink(missing_arg)
|
||||
missing_stmt()
|
||||
missing_target.field = 1
|
||||
value i32 = 0
|
||||
value += missing_rhs
|
||||
_ = broken(1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.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), 9)
|
||||
for diagnostic in diagnostics.items {
|
||||
testing.expect(t, strings.has_prefix(diagnostic.message, "unknown symbol 'missing_"))
|
||||
testing.expect(t, !strings.contains(diagnostic.message, "fallible expression"))
|
||||
testing.expect(t, !strings.contains(diagnostic.message, "must be consumed"))
|
||||
testing.expect(t, !strings.contains(diagnostic.message, "compatible numeric operands"))
|
||||
testing.expect(t, !strings.contains(diagnostic.message, "implicitly convert"))
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -7923,7 +8035,7 @@ referenced_missing_package_traps_at_reference :: proc(t: ^testing.T) {
|
||||
defer delete(stdout)
|
||||
defer delete(stderr)
|
||||
testing.expect(t, !state.success)
|
||||
testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6:"))
|
||||
testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -7991,8 +8103,8 @@ file_hidden_declarations_are_not_package_members :: proc(t: ^testing.T) {
|
||||
found_import := false
|
||||
found_collision := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_sibling = found_sibling || strings.contains(diagnostic.message, "unresolved function 'sibling'")
|
||||
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unresolved global 'sibling_value'")
|
||||
found_sibling = found_sibling || strings.contains(diagnostic.message, "unknown symbol 'sibling'")
|
||||
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unknown symbol 'sibling_value'")
|
||||
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type 'Sibling'")
|
||||
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member 'secret'")
|
||||
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function 'collision'")
|
||||
@@ -8221,7 +8333,7 @@ main func() void {}
|
||||
wants := []string{
|
||||
"has no member 'missing'",
|
||||
"is file-hidden",
|
||||
"unknown package alias 'nope'",
|
||||
"unknown symbol 'nope'",
|
||||
"unavailable imported package 'gone'",
|
||||
"package member 'dep.ambiguous' is ambiguous",
|
||||
"duplicate declaration alias 'duplicate'",
|
||||
@@ -8937,10 +9049,10 @@ conditional_unwrap_diagnostics_cover_counts_guards_and_capture_scope :: proc(t:
|
||||
duplicate = duplicate || strings.contains(diagnostic.message, "unwrap captures must have distinct names")
|
||||
non_optional = non_optional || strings.contains(diagnostic.message, "unwrap requires an optional value")
|
||||
guard = guard || strings.contains(diagnostic.message, "unwrap guard must be a bool")
|
||||
outer_operand_scope = outer_operand_scope || strings.contains(diagnostic.message, "unresolved global 'earlier'")
|
||||
outer_operand_scope = outer_operand_scope || strings.contains(diagnostic.message, "unknown symbol 'earlier'")
|
||||
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'value'")
|
||||
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'value'")
|
||||
capture_scope += 1 if strings.contains(diagnostic.message, "unresolved global 'value'") else 0
|
||||
capture_scope += 1 if strings.contains(diagnostic.message, "unknown symbol 'value'") else 0
|
||||
}
|
||||
testing.expect_value(t, count_mismatches, 2)
|
||||
testing.expect(t, duplicate)
|
||||
@@ -8978,7 +9090,7 @@ if_unwrap_binding_is_scoped_to_then_block :: proc(t: ^testing.T) {
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "unresolved global 'v'")
|
||||
found = found || strings.contains(diagnostic.message, "unknown symbol 'v'")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
@@ -9433,7 +9545,7 @@ main func() void {
|
||||
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'item'")
|
||||
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'item'")
|
||||
immutable_pointer = immutable_pointer || strings.contains(diagnostic.message, "assignment target is not writable")
|
||||
scope = scope || strings.contains(diagnostic.message, "unresolved global 'item'")
|
||||
scope = scope || strings.contains(diagnostic.message, "unknown symbol 'item'")
|
||||
integer_bounds += 1 if strings.contains(diagnostic.message, "range bounds must be compatible concrete integers") else 0
|
||||
}
|
||||
testing.expect(t, unsupported)
|
||||
@@ -11920,6 +12032,101 @@ main func() void {
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-root-diagnostic"
|
||||
defer _ = os2.remove_all(directory)
|
||||
_ = os2.remove_all(directory)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
token_text := `Token :: struct { start int }
|
||||
`
|
||||
lexer_text := `scan func(cursor usize) void ! missing.Error {
|
||||
token Token = Token{start = cursor}
|
||||
_ = token
|
||||
}
|
||||
`
|
||||
main_text := `main func() void {
|
||||
scan(1) catch |_| { return }
|
||||
}
|
||||
`
|
||||
testing.expect(t, os.write_entire_file(
|
||||
"/tmp/brolang-test-root-diagnostic/token.bro",
|
||||
transmute([]byte)token_text,
|
||||
))
|
||||
testing.expect(t, os.write_entire_file(
|
||||
"/tmp/brolang-test-root-diagnostic/lexer.bro",
|
||||
transmute([]byte)lexer_text,
|
||||
))
|
||||
testing.expect(t, os.write_entire_file(
|
||||
"/tmp/brolang-test-root-diagnostic/main.bro",
|
||||
transmute([]byte)main_text,
|
||||
))
|
||||
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
|
||||
defer ast.destroy_module(&module)
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect(t, loaded)
|
||||
testing.expect_value(t, len(diagnostics.items), 1)
|
||||
testing.expect_value(t, diagnostics.items[0].message, "unknown symbol 'missing'")
|
||||
formatted := source.format(&diagnostics, source.Diagnostic_Id(0))
|
||||
defer delete(formatted)
|
||||
testing.expect(t, strings.contains(formatted, "/lexer.bro:1:32"))
|
||||
testing.expect(t, strings.contains(formatted, "^^^^^^^ unknown symbol"))
|
||||
testing.expect(t, !strings.contains(formatted, "fallible expression"))
|
||||
testing.expect(t, !strings.contains(formatted, "must be consumed"))
|
||||
testing.expect(t, !strings.contains(formatted, "could not resolve the 'int' constraint"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
poisoned_global_and_local_types_do_not_create_inference_fallbacks :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-poisoned-declarations"
|
||||
defer _ = os2.remove_all(directory)
|
||||
_ = os2.remove_all(directory)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
text := `bad missing.Global :: 1
|
||||
main func() void {
|
||||
value absent.Local = 1
|
||||
_ = value
|
||||
}
|
||||
`
|
||||
testing.expect(t, os.write_entire_file(
|
||||
"/tmp/brolang-test-poisoned-declarations/main.bro",
|
||||
transmute([]byte)text,
|
||||
))
|
||||
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
|
||||
defer ast.destroy_module(&module)
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect(t, loaded)
|
||||
testing.expect_value(t, len(diagnostics.items), 2)
|
||||
unknown_missing := false
|
||||
unknown_absent := false
|
||||
for diagnostic in diagnostics.items {
|
||||
unknown_missing = unknown_missing || diagnostic.message == "unknown symbol 'missing'"
|
||||
unknown_absent = unknown_absent || diagnostic.message == "unknown symbol 'absent'"
|
||||
testing.expect(t, !strings.contains(diagnostic.message, "could not resolve"))
|
||||
testing.expect(t, !strings.contains(diagnostic.message, "could not infer"))
|
||||
}
|
||||
testing.expect(t, unknown_missing)
|
||||
testing.expect(t, unknown_absent)
|
||||
}
|
||||
|
||||
named_record_field_type :: proc(
|
||||
module: ^hir.Module,
|
||||
symbols: ^symbol.Table,
|
||||
|
||||
Reference in New Issue
Block a user