remove undefined global constraint

This commit is contained in:
2026-07-22 02:43:30 +02:00
parent 63f000dd42
commit 0d04925b3a
4 changed files with 29 additions and 21 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ roadmap and milestone history.
- newline-terminated statements and `#` comments - newline-terminated statements and `#` comments
- immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks - immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
- immutable package globals, mutable runtime globals, function-local mutable locals, and mutable local declarations initialized with `undefined` - immutable package globals, mutable runtime globals, function-local mutable locals, and mutable declarations initialized with `undefined`
- package-level functions, globals, native type declarations, and `Name :: alias T` - package-level functions, globals, native type declarations, and `Name :: alias T`
- directory packages with merged declarations - directory packages with merged declarations
- file-local relative imports, import aliases, and qualified member access - file-local relative imports, import aliases, and qualified member access
+2 -1
View File
@@ -177,7 +177,7 @@
- future direction: generalize toward Zig-style arbitrary pointer-result casts once casts have a broader result-type story - future direction: generalize toward Zig-style arbitrary pointer-result casts once casts have a broader result-type story
12. `undefined` as inspired by zig (implemented): 12. `undefined` as inspired by zig (implemented):
- allow mutable local declarations with `undefined` - allow mutable local and global declarations with `undefined`
- undefined values are assigned a poison value (0xaa...) - undefined values are assigned a poison value (0xaa...)
- allows for something like: - allows for something like:
``` ```
@@ -696,6 +696,7 @@
and inferred array counts may resolve through the existing inference fixpoint, but and inferred array counts may resolve through the existing inference fixpoint, but
the final type must be concrete runtime storage the final type must be concrete runtime storage
- emit source-defined mutable globals as writable globals, not constants - emit source-defined mutable globals as writable globals, not constants
- allow `undefined` initializers for runtime storage initialized explicitly by a function
- allow assignment, address-taking, field/index mutation, and pointer passing under - allow assignment, address-taking, field/index mutation, and pointer passing under
the same mutability rules as other writable locations the same mutability rules as other writable locations
- keep mutable globals invalid in comptime evaluation; `$global_var` and writes from - keep mutable globals invalid in comptime evaluation; `$global_var` and writes from
+15 -13
View File
@@ -7891,7 +7891,7 @@ build_compound_expr :: proc(
id := source.add( id := source.add(
checker.diagnostics, checker.diagnostics,
expr.span, expr.span,
"'undefined' is only valid as a mutable local declaration initializer", "'undefined' is only valid as a mutable declaration initializer",
) )
return invalid_hir_expr(checker, expr.span, id, expected) return invalid_hir_expr(checker, expr.span, id, expected)
case .Enum_Literal: case .Enum_Literal:
@@ -13686,7 +13686,20 @@ build_globals :: proc(checker: ^Checker) {
is_runtime_type(checker, checker.global_types[global_index]) { is_runtime_type(checker, checker.global_types[global_index]) {
expected = checker.global_types[global_index] expected = checker.global_types[global_index]
} }
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file) expr := hir.INVALID_EXPR
if !global.immutable && is_undefined_expr(checker, global.expr) {
expr = add_hir_expr(checker, hir.Expr{
kind=.Undefined,
span=checker.ast_module.exprs[global.expr].span,
type=checker.global_types[global_index],
target=hir.INVALID_REF,
left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
expr = build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
}
global_type := checker.global_types[global_index] global_type := checker.global_types[global_index]
if is_runtime_type(checker, checker.global_types[global_index]) { if is_runtime_type(checker, checker.global_types[global_index]) {
expr = coerce_expr(checker, expr, checker.global_types[global_index], global.span) expr = coerce_expr(checker, expr, checker.global_types[global_index], global.span)
@@ -13713,17 +13726,6 @@ build_globals :: proc(checker: ^Checker) {
global_type = constraint_recovery_type(checker, declared) global_type = constraint_recovery_type(checker, declared)
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type) expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
} }
if !global.immutable && is_undefined_expr(checker, global.expr) {
diagnostic = source.add(
checker.diagnostics,
global.span,
"'undefined' is only valid as a mutable local declaration initializer",
)
if !is_runtime_type(checker, global_type) {
global_type = types.I64
}
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}
if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) { if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) {
if types.is_comptime_only(global_type, &checker.module.types) || if types.is_comptime_only(global_type, &checker.module.types) ||
types.is_comptime_only(declared, &checker.module.types) { types.is_comptime_only(declared, &checker.module.types) {
+11 -6
View File
@@ -9211,6 +9211,7 @@ counter int = 0
ratio float = 1 ratio float = 1
span range = 0..2 span range = 0..2
point Point = Point { x = 1 } point Point = Point { x = 1 }
unset Point = undefined
values [_]mut i32 = [10, 20] values [_]mut i32 = [10, 20]
main func() void { main func() void {
counter = 1 counter = 1
@@ -9239,8 +9240,9 @@ main func() void {
found_counter := false found_counter := false
found_ratio := false found_ratio := false
found_span := false found_span := false
found_unset := false
found_values := false found_values := false
for global in hir_module.globals { for global, global_index in hir_module.globals {
name := symbol.resolve(&symbols, global.name) name := symbol.resolve(&symbols, global.name)
if name == "counter" { if name == "counter" {
found_counter = global.writable && !global.is_static && types.equal(global.type, types.I32) found_counter = global.writable && !global.is_static && types.equal(global.type, types.I32)
@@ -9248,6 +9250,12 @@ main func() void {
found_ratio = global.writable && !global.is_static && types.equal(global.type, types.F64) found_ratio = global.writable && !global.is_static && types.equal(global.type, types.F64)
} else if name == "span" { } else if name == "span" {
found_span = global.writable && !global.is_static && types.is_range(global.type, &hir_module.types) found_span = global.writable && !global.is_static && types.is_range(global.type, &hir_module.types)
} else if name == "unset" {
found_unset = global.writable &&
!global.is_static &&
hir_module.exprs[global.expr].kind == .Undefined &&
len(ir_module.globals[global_index].initializer) > 0 &&
ir_module.globals[global_index].initializer[0].op == .Poison
} else if name == "values" { } else if name == "values" {
item, ok := types.node(&hir_module.types, global.type) item, ok := types.node(&hir_module.types, global.type)
found_values = global.writable && !global.is_static && ok && item.kind == .Array && item.count == 2 found_values = global.writable && !global.is_static && ok && item.kind == .Array && item.count == 2
@@ -9256,6 +9264,7 @@ main func() void {
testing.expect(t, found_counter) testing.expect(t, found_counter)
testing.expect(t, found_ratio) testing.expect(t, found_ratio)
testing.expect(t, found_span) testing.expect(t, found_span)
testing.expect(t, found_unset)
testing.expect(t, found_values) testing.expect(t, found_values)
testing.expect(t, strings.contains(llvm_text, "internal global")) testing.expect(t, strings.contains(llvm_text, "internal global"))
testing.expect(t, !strings.contains(llvm_text, "internal constant i32 0")) testing.expect(t, !strings.contains(llvm_text, "internal constant i32 0"))
@@ -9267,7 +9276,6 @@ mutable_global_diagnostics_and_no_shadowing :: proc(t: ^testing.T) {
OtherID :: distinct i32 OtherID :: distinct i32
ID i32 = 0 ID i32 = 0
counter = 0 counter = 0
bad i32 = undefined
runtime i32 = 1 runtime i32 = 1
immutable :: 1 immutable :: 1
foo func(foo i32) void {} foo func(foo i32) void {}
@@ -9304,7 +9312,6 @@ main func() void {
defer hir.destroy_module(&hir_module) defer hir.destroy_module(&hir_module)
missing_type := false missing_type := false
undefined_global := false
not_comptime := false not_comptime := false
immutable_write := false immutable_write := false
global_type_shadow := false global_type_shadow := false
@@ -9317,7 +9324,6 @@ main func() void {
value_label_shadow := false value_label_shadow := false
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
missing_type = missing_type || strings.contains(diagnostic.message, "mutable global 'counter' requires a type annotation") missing_type = missing_type || strings.contains(diagnostic.message, "mutable global 'counter' requires a type annotation")
undefined_global = undefined_global || strings.contains(diagnostic.message, "'undefined' is only valid as a mutable local declaration initializer")
not_comptime = not_comptime || strings.contains(diagnostic.message, "global 'runtime' is not comptime-known") not_comptime = not_comptime || strings.contains(diagnostic.message, "global 'runtime' is not comptime-known")
immutable_write = immutable_write || strings.contains(diagnostic.message, "assignment target is not writable") immutable_write = immutable_write || strings.contains(diagnostic.message, "assignment target is not writable")
global_type_shadow = global_type_shadow || strings.contains(diagnostic.message, "global 'ID' shadows visible type") global_type_shadow = global_type_shadow || strings.contains(diagnostic.message, "global 'ID' shadows visible type")
@@ -9330,7 +9336,6 @@ main func() void {
value_label_shadow = value_label_shadow || strings.contains(diagnostic.message, "local 'value_label' shadows visible label") value_label_shadow = value_label_shadow || strings.contains(diagnostic.message, "local 'value_label' shadows visible label")
} }
testing.expect(t, missing_type) testing.expect(t, missing_type)
testing.expect(t, undefined_global)
testing.expect(t, not_comptime) testing.expect(t, not_comptime)
testing.expect(t, immutable_write) testing.expect(t, immutable_write)
testing.expect(t, global_type_shadow) testing.expect(t, global_type_shadow)
@@ -11914,7 +11919,7 @@ main func() void {
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
immutable_count += 1 if strings.contains(diagnostic.message, "'undefined' requires a mutable local declaration") else 0 immutable_count += 1 if strings.contains(diagnostic.message, "'undefined' requires a mutable local declaration") else 0
found_unresolved = found_unresolved || strings.contains(diagnostic.message, "could not infer a concrete type for local 'unresolved'") found_unresolved = found_unresolved || strings.contains(diagnostic.message, "could not infer a concrete type for local 'unresolved'")
found_assignment = found_assignment || strings.contains(diagnostic.message, "'undefined' is only valid as a mutable local declaration initializer") found_assignment = found_assignment || strings.contains(diagnostic.message, "'undefined' is only valid as a mutable declaration initializer")
found_incompatible = found_incompatible || strings.contains(diagnostic.message, "cannot implicitly convert f64 to i8") found_incompatible = found_incompatible || strings.contains(diagnostic.message, "cannot implicitly convert f64 to i8")
} }