fix: track imports used by forced comptime initializers

This commit is contained in:
2026-10-24 02:46:35 +02:00
parent 81531208b2
commit a6e688db71
3 changed files with 36 additions and 1 deletions
+1 -1
View File
@@ -259,7 +259,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
- direct bodyful value calls with no runtime parameters, including parameterless and all-`$` functions, are evaluated at comptime when their resolved result can materialize; otherwise they retain their zero-argument runtime specialization, while a reached `compile_error!` remains a diagnostic
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`; imports referenced only by these initializers count as used, including when the initialized global is read from another package
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, exact type `==`/`!=`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
- `struct_type!(layout, names, types, defaults)` constructs a nominal record type from comptime fixed arrays or tuples and is valid in every type position; layout is `.auto` or `.c`, bare `null` means a required field, and `some!(null)` installs an optional `null` default
+5
View File
@@ -6284,6 +6284,11 @@ build_comptime_expr :: proc(
pkg: ast.Package_Id,
file: ast.File_Id,
) -> hir.Expr_Id {
if expr.left != ast.INVALID_EXPR {
mark_expr_imports_used(checker, expr.left, file)
} else {
mark_block_imports_used(checker, expr.body, file)
}
state := ct_state_make(checker, pkg, file)
defer ct_state_destroy(&state)
value := INVALID_CT_VALUE
+30
View File
@@ -10998,6 +10998,36 @@ unused_import_is_a_warning :: proc(t: ^testing.T) {
testing.expect(t, found)
}
@(test)
imports_used_only_by_comptime_global_initializers_are_not_warned :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-comptime-global-import"
output := "/tmp/brolang-test-comptime-global-import-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
packages := [3]string{"app", "table", "helper"}
for name in packages {
testing.expect(t, os2.make_directory_all(fmt.tprintf("%s/%s", directory, name)) == nil)
}
helper_text := `make func() [1]i32 { return [42] }
`
table_sources := [2]string{
"helper :: import \"../helper\"\nvalues :: $helper.make()\n",
"helper :: import \"../helper\"\nvalues :: ${ yield helper.make() }\n",
}
app_text := `table :: import "../table"
main func() i32 { return table.values[0] }
`
testing.expect(t, os.write_entire_file(fmt.tprintf("%s/helper/main.bro", directory), transmute([]byte)helper_text))
testing.expect(t, os.write_entire_file(fmt.tprintf("%s/app/main.bro", directory), transmute([]byte)app_text))
for table_text in table_sources {
testing.expect(t, os.write_entire_file(fmt.tprintf("%s/table/main.bro", directory), transmute([]byte)table_text))
testing.expect_value(t, compiler_core.compile_package(fmt.tprintf("%s/app", directory), output), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
}
@(test)
unused_missing_package_does_not_trap :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-missing-unused"