report global cycle endpoints

This commit is contained in:
2026-06-30 22:20:35 +02:00
parent 5161e99af5
commit eb06b2ee62
3 changed files with 64 additions and 12 deletions
+6 -10
View File
@@ -1,6 +1,10 @@
# "quick" / "easy" fixes # general todos
- for global initialization cycles, report also starting and ending lines - sum-type ABI/layout polish
- consider dynamic tag-width shrinking after the fixed-`u16` ABI has real pressure
- consider all-void channel collapse after fallible channels are otherwise stable
- define cross-module/global-id ABI determinism before multi-module builds depend on it
- keep backed/C enum composition and must-consume fallible linting as later policy work
# milestones # milestones
@@ -590,17 +594,9 @@
spelling for void-payload variants spelling for void-payload variants
- named fallible signatures can use inline unbacked enum and `union(enum)` error types - named fallible signatures can use inline unbacked enum and `union(enum)` error types
after `!` after `!`
- qualified same-name disambiguation remains deferred; existing ambiguous fallible return
diagnostics cover the current surface
23.7. anonymous struct payloads and keyed payload sugar 23.7. anonymous struct payloads and keyed payload sugar
23.8. sum-type ABI/layout polish
- consider dynamic tag-width shrinking after the fixed-`u16` ABI has real pressure
- consider all-void channel collapse after fallible channels are otherwise stable
- define cross-module/global-id ABI determinism before multi-module builds depend on it
- keep backed/C enum composition and must-consume fallible linting as later policy work
24. dynamic heap allocation 24. dynamic heap allocation
- see below for direction - see below for direction
- notes below are too big in scope for a first pass and the language is not mature enough to support it yet - notes below are too big in scope for a first pass and the language is not mature enough to support it yet
+31 -2
View File
@@ -7088,6 +7088,28 @@ Cycle_Frame :: struct {
next_dependency: int, next_dependency: int,
} }
cycle_global_location :: proc(checker: ^Checker, global_id: hir.Global_Id) -> (name, path: string, line: int) {
name = "<unknown>"
path = "<unknown>"
line = 1
index := int(global_id)
if global_id == hir.INVALID_GLOBAL || index >= len(checker.module.globals) {
return
}
name = symbol_text(checker, checker.module.globals[global_id].name)
if index >= len(checker.ast_module.globals) {
return
}
span := checker.ast_module.globals[global_id].span
source_file := source.source_for_span(checker.diagnostics, span)
if source_file == nil {
return
}
path = source_file.path
line, _ = source.line_and_column(source_file, span.start)
return
}
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id, states: []u8) { detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id, states: []u8) {
if states[global_id] == 2 { if states[global_id] == 2 {
return return
@@ -7120,11 +7142,18 @@ detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id,
continue continue
} }
if states[dependency] == 1 { if states[dependency] == 1 {
start_name, start_path, start_line := cycle_global_location(checker, dependency)
end_name, end_path, end_line := cycle_global_location(checker, frame.global)
id := source.addf( id := source.addf(
checker.diagnostics, checker.diagnostics,
checker.ast_module.globals[dependency].span, checker.ast_module.globals[dependency].span,
"global initialization cycle involving '%s'", "global initialization cycle from '%s' at %s:%d to '%s' at %s:%d",
symbol_text(checker, checker.module.globals[dependency].name), start_name,
start_path,
start_line,
end_name,
end_path,
end_line,
) )
checker.module.globals[dependency].diagnostic = id checker.module.globals[dependency].diagnostic = id
checker.module.globals[dependency].problematic = true checker.module.globals[dependency].problematic = true
+27
View File
@@ -4684,6 +4684,33 @@ hundred_thousand_term_runtime_addition_uses_iterative_pipeline :: proc(t: ^testi
testing.expect(t, instruction_count > 100_000) testing.expect(t, instruction_count > 100_000)
} }
@(test)
global_cycle_diagnostic_reports_endpoint_lines :: proc(t: ^testing.T) {
text := `a i32 :: b
b i32 :: a
main func() void {}
`
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)
found := false
for _, diagnostic_index in diagnostics.items {
message := source.format(&diagnostics, source.diagnostic_id(diagnostic_index))
found = found || strings.contains(message, "global initialization cycle from 'a' at test.bro:1 to 'b' at test.bro:2")
delete(message)
}
testing.expect(t, found)
}
@(test) @(test)
deep_global_cycle_detection_uses_iterative_dfs :: proc(t: ^testing.T) { deep_global_cycle_detection_uses_iterative_dfs :: proc(t: ^testing.T) {
count := 50_000 count := 50_000