diff --git a/LANGUAGE.md b/LANGUAGE.md index bc41e75..eaf2798 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -9,7 +9,7 @@ roadmap and milestone history. - newline-terminated statements and `#` comments - 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` - directory packages with merged declarations - file-local relative imports, import aliases, and qualified member access diff --git a/TODO.md b/TODO.md index 0c1ba20..670a3fd 100644 --- a/TODO.md +++ b/TODO.md @@ -177,7 +177,7 @@ - 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): - - allow mutable local declarations with `undefined` + - allow mutable local and global declarations with `undefined` - undefined values are assigned a poison value (0xaa...) - allows for something like: ``` @@ -696,6 +696,7 @@ and inferred array counts may resolve through the existing inference fixpoint, but the final type must be concrete runtime storage - 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 the same mutability rules as other writable locations - keep mutable globals invalid in comptime evaluation; `$global_var` and writes from diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 5364605..fbdbda6 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -7891,7 +7891,7 @@ build_compound_expr :: proc( id := source.add( checker.diagnostics, 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) case .Enum_Literal: @@ -13686,7 +13686,20 @@ build_globals :: proc(checker: ^Checker) { is_runtime_type(checker, 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] if is_runtime_type(checker, checker.global_types[global_index]) { 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) 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 types.is_comptime_only(global_type, &checker.module.types) || types.is_comptime_only(declared, &checker.module.types) { diff --git a/compiler_tests.odin b/compiler_tests.odin index bde4c46..5894bbe 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -9211,6 +9211,7 @@ counter int = 0 ratio float = 1 span range = 0..2 point Point = Point { x = 1 } +unset Point = undefined values [_]mut i32 = [10, 20] main func() void { counter = 1 @@ -9239,8 +9240,9 @@ main func() void { found_counter := false found_ratio := false found_span := false + found_unset := false found_values := false - for global in hir_module.globals { + for global, global_index in hir_module.globals { name := symbol.resolve(&symbols, global.name) if name == "counter" { 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) } else if name == "span" { 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" { item, ok := types.node(&hir_module.types, global.type) 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_ratio) testing.expect(t, found_span) + testing.expect(t, found_unset) testing.expect(t, found_values) testing.expect(t, strings.contains(llvm_text, "internal global")) 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 ID i32 = 0 counter = 0 -bad i32 = undefined runtime i32 = 1 immutable :: 1 foo func(foo i32) void {} @@ -9304,7 +9312,6 @@ main func() void { defer hir.destroy_module(&hir_module) missing_type := false - undefined_global := false not_comptime := false immutable_write := false global_type_shadow := false @@ -9317,7 +9324,6 @@ main func() void { value_label_shadow := false for diagnostic in diagnostics.items { 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") 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") @@ -9330,7 +9336,6 @@ main func() void { value_label_shadow = value_label_shadow || strings.contains(diagnostic.message, "local 'value_label' shadows visible label") } testing.expect(t, missing_type) - testing.expect(t, undefined_global) testing.expect(t, not_comptime) testing.expect(t, immutable_write) testing.expect(t, global_type_shadow) @@ -11914,7 +11919,7 @@ main func() void { for diagnostic in diagnostics.items { 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_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") }