diff --git a/docs/language.md b/docs/language.md index 23b7e52..e2ec7a0 100644 --- a/docs/language.md +++ b/docs/language.md @@ -235,6 +235,31 @@ i32[1] arr = { }; ``` +### Zero Initialization + +`zero` zero-initializes aggregates such as arrays, structures, vectors, and +matrices. `nil` is used in pointer-like contexts. + +``` +type Buffer = struct { + i8* ptr; + u64 length; +}; + +let Buffer buffer = zero; +let mut u32[16] words = zero; +pub state Buffer[4] buffers = zero; +``` + +Numeric elements and fields become `0` or `0.0`, pointer-like fields become +`nil`, and nested aggregates are zero-initialized recursively. `zero` is +contextually typed, so the surrounding declaration or expression must provide +the complete aggregate type. For example, `let value = zero;` is invalid. + +`zero` is not a partial aggregate initializer. A brace initializer must still +provide every required element or field. Scalar and enum targets are rejected; +use an ordinary numeric literal or `nil` when initializing those values. + ### Boxed Types > [!CAUTION] @@ -392,9 +417,17 @@ state i32 x = 1234; // mutable, local pub state i32 y = 5678; // mutable, global linkage ``` -For `pub` data and state, an initializer may be ommitted to create a reference to be resolved by the linker. +For `pub` data and state, an initializer may be omitted to create a reference to be resolved by the linker. Non-constant global initializers are lowered to program startup initialization before user code runs. +An explicit `zero` initializer creates a definition rather than an external +reference. For example: + +``` +pub state i32[4] supplied_elsewhere; // external declaration +pub state i32[4] owned_here = zero; // zero-filled definition +``` + #### Function Scope Inside function definitions, variable declarations take a different form: @@ -686,7 +719,8 @@ let node tail = { 1, nil }; let node head = { 0, ref tail }; ``` -`nil` may be used in lieu of a reference to indicate `NULL`. +`nil` may be used in lieu of a reference to indicate `NULL`. To zero-initialize +an aggregate, use [`zero`](#zero-initialization) instead. ### Load diff --git a/src/bin/lsp/inlay_hints.ml b/src/bin/lsp/inlay_hints.ml index ded5cf2..9b234a0 100644 --- a/src/bin/lsp/inlay_hints.ml +++ b/src/bin/lsp/inlay_hints.ml @@ -42,7 +42,7 @@ let rec walk_expression typing type_env query_range acc (expr : Core.expression) walk_expression typing type_env query_range acc unary.value.inner | Block block -> walk_block typing type_env query_range acc block - | Literal _ | Identifier _ | Nil -> + | Literal _ | Identifier _ | Nil | Zero -> acc | ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner -> walk_expression typing type_env query_range acc inner diff --git a/src/bin/lsp/symbol_resolution.ml b/src/bin/lsp/symbol_resolution.ml index ae915f2..d19060e 100644 --- a/src/bin/lsp/symbol_resolution.ml +++ b/src/bin/lsp/symbol_resolution.ml @@ -473,7 +473,7 @@ and walk_expression state env (expr : Core.expression) = match literal.value with | Core.Enum enum_lit -> enum_literal_hover state expr enum_lit | _ -> ()) - | Nil -> () + | Nil | Zero -> () | ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner -> walk_expression state env inner | BoxConstruct box -> @@ -774,7 +774,7 @@ let rec highlight_expression state env (expr : Core.expression) = Analysis.lookup_enum_variant state.type_env expr.loc resolved enum_lit.value.enum_variant.value)) | _ -> ()) - | Nil -> () + | Nil | Zero -> () | ToBool inner | SizeExpr inner | BoxExpr inner | Unbox inner | Ref inner | Load inner -> highlight_expression state env inner | BoxConstruct box -> diff --git a/src/lib/ast/analysis_asserts.ml b/src/lib/ast/analysis_asserts.ml index 33aa414..2b89e22 100644 --- a/src/lib/ast/analysis_asserts.ml +++ b/src/lib/ast/analysis_asserts.ml @@ -139,6 +139,7 @@ module Assert = struct (render_expression write.value.value) | Core.SizeType _ -> "size" | Core.Nil -> "nil" + | Core.Zero -> "zero" in if self_prec < ctx_prec then "(" ^ rendered ^ ")" else rendered @@ -207,7 +208,7 @@ module Assert = struct (fun expr -> render_specialized_expression state expr) box.value.args)) | (Core.Literal _ | Core.Identifier _ | Core.Block _ | Core.Initializer _ | Core.Match _ - | Core.BoxType _ | Core.SizeType _ | Core.Nil) -> + | Core.BoxType _ | Core.SizeType _ | Core.Nil | Core.Zero) -> render_expression ~ctx_prec expr let assert_context (compile_assert : Core.compile_assert) suffix = diff --git a/src/lib/ast/analysis_cfold.ml b/src/lib/ast/analysis_cfold.ml index 78564f0..a25ffd6 100644 --- a/src/lib/ast/analysis_cfold.ml +++ b/src/lib/ast/analysis_cfold.ml @@ -285,13 +285,14 @@ module ConstantFold = struct | Core.Assign write -> Core.Assign (fold_write write) | Core.Mutate write -> Core.Mutate (fold_write write) | Core.Literal literal -> Core.Literal (fold_literal literal) - | (Core.Identifier _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as value -> + | (Core.Identifier _ | Core.SizeType _ | Core.Nil | Core.Zero | Core.BoxType _) as + value -> value in let expr = { expr with value } in match expr.value with - | Core.Literal _ | Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _ - | Core.BoxConstruct _ -> + | Core.Literal _ | Core.Identifier _ | Core.Nil | Core.Zero | Core.SizeType _ + | Core.BoxType _ | Core.BoxConstruct _ -> expr | Core.Block block when block.value.statements = [] -> ( match block.value.result with Some result -> result | None -> expr) diff --git a/src/lib/ast/analysis_cleanup.ml b/src/lib/ast/analysis_cleanup.ml index 1c031c3..d3445fc 100644 --- a/src/lib/ast/analysis_cleanup.ml +++ b/src/lib/ast/analysis_cleanup.ml @@ -128,8 +128,8 @@ module Cleanup = struct } | Core.Assign write -> clean_write_like typed (fun write -> Core.Assign write) write | Core.Mutate write -> clean_write_like typed (fun write -> Core.Mutate write) write - | (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.BoxType _) as - value -> + | (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.Zero + | Core.BoxType _) as value -> value in { expr with value = cleaned_value } diff --git a/src/lib/ast/analysis_ownership.ml b/src/lib/ast/analysis_ownership.ml index 816f063..d8f4a85 100644 --- a/src/lib/ast/analysis_ownership.ml +++ b/src/lib/ast/analysis_ownership.ml @@ -198,7 +198,7 @@ module Ownership = struct if not (resolved_contains_ownership state expr.loc expected) then () else match expr.value with - | Core.Nil -> () + | Core.Nil | Core.Zero -> () | Core.BoxExpr _ | Core.BoxType _ | Core.BoxConstruct _ -> () | Core.Call call -> emit_retains_for_expected_enum_call state reason expected call | Core.As cast -> emit_retains_for_transfer state reason expected cast.value.inner @@ -263,7 +263,7 @@ module Ownership = struct let rec visit_expression state scopes (expr : Core.expression) = match expr.value with - | Core.Identifier _ | Core.Nil | Core.SizeType _ | Core.BoxType _ -> () + | Core.Identifier _ | Core.Nil | Core.Zero | Core.SizeType _ | Core.BoxType _ -> () | Core.BoxConstruct box -> List.iter (visit_expression state scopes) box.value.args | Core.Literal literal -> ( diff --git a/src/lib/ast/analysis_purity.ml b/src/lib/ast/analysis_purity.ml index 4da5459..3b6748b 100644 --- a/src/lib/ast/analysis_purity.ml +++ b/src/lib/ast/analysis_purity.ml @@ -90,7 +90,9 @@ module Purity = struct let rec visit_expression state current env (expr : Core.expression) = let visit = visit_expression state current in match expr.value with - | Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.BoxType _ | Core.Nil -> () + | Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.BoxType _ | Core.Nil + | Core.Zero -> + () | Core.BoxConstruct box -> List.iter (visit env) box.value.args | Core.ToBool inner diff --git a/src/lib/ast/analysis_semantic.ml b/src/lib/ast/analysis_semantic.ml index f05f22f..714f282 100644 --- a/src/lib/ast/analysis_semantic.ml +++ b/src/lib/ast/analysis_semantic.ml @@ -379,7 +379,7 @@ module Semantic = struct | _ -> () in match expr.value with - | Core.Identifier _ | Core.Literal _ | Core.Nil | Core.SizeType _ -> () + | Core.Identifier _ | Core.Literal _ | Core.Nil | Core.Zero | Core.SizeType _ -> () | Core.ToBool inner -> check_expression state env loop_depth inner; check_scalar_truthy inner diff --git a/src/lib/ast/analysis_specialize.ml b/src/lib/ast/analysis_specialize.ml index 1f5653e..8073aa8 100644 --- a/src/lib/ast/analysis_specialize.ml +++ b/src/lib/ast/analysis_specialize.ml @@ -320,7 +320,8 @@ module Specialize = struct }; }; } - | (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.BoxType _) -> + | (Core.Identifier _ | Core.Literal _ | Core.SizeType _ | Core.Nil | Core.Zero + | Core.BoxType _) -> expr and rewrite_call state annotations (expr : Core.expression) (call : Core.call) = diff --git a/src/lib/ast/analysis_typing.ml b/src/lib/ast/analysis_typing.ml index 5b2ca74..5b7f36c 100644 --- a/src/lib/ast/analysis_typing.ml +++ b/src/lib/ast/analysis_typing.ml @@ -387,6 +387,7 @@ module Typing = struct integer = None; }; } + | Core.Zero -> infer_zero state ~expected_type expr.loc | Core.Match match_expr -> infer_match state env ~expected_type expr.loc match_expr | Core.BoxExpr inner -> ( let inner_expected = @@ -866,6 +867,19 @@ module Typing = struct | Some expected -> annotation_of_resolved loc (Some expected) | None -> unknown_expr_annotation + and infer_zero state ~(expected_type : resolved_ty option) loc : expr_annotation = + let valid = + match expected_type with + | Some (ResolvedArray _ | ResolvedVec _ | ResolvedMatrix _) -> true + | Some (ResolvedNamed _ as struct_ty) -> + Option.is_some (lookup_struct_fields state.type_env loc struct_ty) + | Some _ | None -> false + in + if not valid then + add_diagnostic state Error loc + "zero requires an explicit array, struct, vector, or matrix target type"; + annotation_of_resolved loc expected_type + and infer_unary state env loc (unary : Core.unary) : expr_annotation = let inner_ann = infer_value_expression state env unary.value.inner in match unary.value.op with diff --git a/src/lib/ast/analysis_verify.ml b/src/lib/ast/analysis_verify.ml index 4b08b01..6dee99d 100644 --- a/src/lib/ast/analysis_verify.ml +++ b/src/lib/ast/analysis_verify.ml @@ -86,7 +86,7 @@ module Verify = struct and verify_expression state (expr : Core.expression) = let verify_subexprs () = match expr.value with - | Core.Identifier _ | Core.Literal _ | Core.Nil -> () + | Core.Identifier _ | Core.Literal _ | Core.Nil | Core.Zero -> () | Core.ToBool inner | Core.SizeExpr inner | Core.BoxExpr inner diff --git a/src/lib/ast/cimport.ml b/src/lib/ast/cimport.ml index 835b73f..0f675c6 100644 --- a/src/lib/ast/cimport.ml +++ b/src/lib/ast/cimport.ml @@ -242,6 +242,7 @@ let is_haven_keyword = "Mat"; "mut"; "nil"; + "zero"; "pub"; "ref"; "ret"; diff --git a/src/lib/ast/convert.ml b/src/lib/ast/convert.ml index 362dec6..cca4364 100644 --- a/src/lib/ast/convert.ml +++ b/src/lib/ast/convert.ml @@ -378,6 +378,7 @@ and cst_expr_to_surface (expr : Cst.expression) : Surface.expression = | Cst.SizeExpr inner -> Surface.SizeExpr (cst_expr_to_surface inner) | Cst.SizeType ty -> Surface.SizeType (cst_type_to_surface ty) | Cst.Nil -> Surface.Nil + | Cst.Zero -> Surface.Zero | Cst.If ifx -> Surface.If (cst_if_expr_to_surface ifx) | Cst.Match m -> Surface.Match (cst_match_expr_to_surface m) | Cst.BoxExpr inner -> Surface.BoxExpr (cst_expr_to_surface inner) @@ -1278,6 +1279,7 @@ and surface_expr_to_core st (expr : Surface.expression) : Core.expression = | Surface.SizeExpr inner -> Core.SizeExpr (surface_expr_to_core st inner) | Surface.SizeType ty -> Core.SizeType (surface_type_to_core ty) | Surface.Nil -> Core.Nil + | Surface.Zero -> Core.Zero | Surface.If ifx -> lower_if_expr st expr.loc ifx | Surface.Match m -> Core.Match diff --git a/src/lib/ast/core_ast.ml b/src/lib/ast/core_ast.ml index 5f33289..c148cae 100644 --- a/src/lib/ast/core_ast.ml +++ b/src/lib/ast/core_ast.ml @@ -183,6 +183,7 @@ and expression_desc = | SizeExpr of expression | SizeType of haven_type | Nil + | Zero | Match of match_expr | BoxExpr of expression | BoxType of haven_type diff --git a/src/lib/ast/llvm_ir.ml b/src/lib/ast/llvm_ir.ml index 63142af..d7cec08 100644 --- a/src/lib/ast/llvm_ir.ml +++ b/src/lib/ast/llvm_ir.ml @@ -664,6 +664,7 @@ let rec constant_of_expr t (expr : Core.expression) = | Core.Literal lit -> constant_of_literal t expr.loc resolved lit | Core.Nil when Analysis.resolved_is_pointerish resolved -> Some (Llvm.const_null (llvm_type_of_resolved t resolved)) + | Core.Zero -> Some (zero_constant t resolved) | Core.As cast -> Option.bind (constant_of_expr t cast.value.inner) @@ -2000,6 +2001,7 @@ and emit_expr t (expr : Core.expression) = | None -> fail ~loc:expr.loc "failed to lower size expression") | Core.Nil -> Llvm.const_null (llvm_type_of_resolved t (expr_resolved_type t expr)) + | Core.Zero -> zero_constant t (expr_resolved_type t expr) | Core.Match match_expr -> emit_match t expr match_expr | Core.BoxExpr inner -> emit_box_expr t expr inner | Core.BoxType ty -> emit_box_type_expr t expr ty diff --git a/src/lib/ast/pretty.ml b/src/lib/ast/pretty.ml index 9575a20..0b70d9a 100644 --- a/src/lib/ast/pretty.ml +++ b/src/lib/ast/pretty.ml @@ -135,6 +135,7 @@ and pp_surface_expression fmt (expr : Surface.expression) = | SizeExpr inner -> fprintf fmt "SizeExpr(%a)" pp_surface_expression inner | SizeType ty -> fprintf fmt "SizeType(%a)" pp_surface_type ty | Nil -> fprintf fmt "Nil" + | Zero -> fprintf fmt "Zero" | If ifx -> fprintf fmt "@[If(@,cond=%a,@ then=%a,@ else=%a@,)@]" pp_surface_expression ifx.value.cond pp_surface_block ifx.value.then_branch @@ -387,6 +388,7 @@ and pp_core_expression fmt (expr : Core.expression) = | SizeExpr inner -> fprintf fmt "SizeExpr(%a)" pp_core_expression inner | SizeType ty -> fprintf fmt "SizeType(%a)" pp_core_type ty | Nil -> fprintf fmt "Nil" + | Zero -> fprintf fmt "Zero" | Match m -> fprintf fmt "@[Match(@,expr=%a,@ arms=%a@,)@]" pp_core_expression m.value.expr diff --git a/src/lib/ast/surface_ast.ml b/src/lib/ast/surface_ast.ml index a549736..723395d 100644 --- a/src/lib/ast/surface_ast.ml +++ b/src/lib/ast/surface_ast.ml @@ -195,6 +195,7 @@ and expression_desc = | SizeExpr of expression | SizeType of haven_type | Nil + | Zero | If of if_expr | Match of match_expr | BoxExpr of expression diff --git a/src/lib/cst/cst.ml b/src/lib/cst/cst.ml index 3ea3d92..0822723 100644 --- a/src/lib/cst/cst.ml +++ b/src/lib/cst/cst.ml @@ -246,6 +246,7 @@ and expression_desc = | SizeExpr of expression | SizeType of haven_type | Nil + | Zero | If of if_expr | Match of match_expr | BoxExpr of expression diff --git a/src/lib/cst/emit.ml b/src/lib/cst/emit.ml index 58b4e46..7267744 100644 --- a/src/lib/cst/emit.ml +++ b/src/lib/cst/emit.ml @@ -161,6 +161,7 @@ let rec emit_expression ?(ctx_prec = 0) ~indent ~comments fmt expr = fprintf fmt "size(%a)" (emit_expression ~ctx_prec:0 ~indent ~comments) e | SizeType t -> fprintf fmt "size<%a>" emit_type t | Nil -> fprintf fmt "nil" + | Zero -> fprintf fmt "zero" | If i -> emit_if_expr ~indent ~comments fmt i | Match m -> emit_match_expr ~indent ~comments fmt m | BoxExpr e -> diff --git a/src/lib/cst/locate.ml b/src/lib/cst/locate.ml index 916c9b4..6fb84b8 100644 --- a/src/lib/cst/locate.ml +++ b/src/lib/cst/locate.ml @@ -196,6 +196,7 @@ and walk_expression predicate acc expr = | SizeExpr e -> walk_expression predicate acc e | SizeType t -> walk_haven_type predicate acc t | Nil -> acc + | Zero -> acc | If i -> walk_if_expr predicate acc i | Match m -> let acc = add_if predicate (MatchExpr m) acc in diff --git a/src/lib/cst/pretty.ml b/src/lib/cst/pretty.ml index 987ac97..23e4931 100644 --- a/src/lib/cst/pretty.ml +++ b/src/lib/cst/pretty.ml @@ -114,6 +114,7 @@ and pp_expression fmt expr = | SizeExpr e -> fprintf fmt "SizeExpr(%a)" pp_expression e | SizeType t -> fprintf fmt "SizeType(%a)" pp_type t | Nil -> fprintf fmt "Nil" + | Zero -> fprintf fmt "Zero" | If i -> pp_if_expr fmt i | Match m -> pp_match_expr fmt m | BoxExpr e -> fprintf fmt "Box(expr=%a)" pp_expression e diff --git a/src/lib/parser/grammar.mly b/src/lib/parser/grammar.mly index afa30c9..1d255e5 100644 --- a/src/lib/parser/grammar.mly +++ b/src/lib/parser/grammar.mly @@ -32,7 +32,7 @@ (* Main keywords *) %token PUB FN MUT IF ELSE LET WHILE UNTIL BREAK CONTINUE MATCH AS ITER -%token LOAD RET STRUCT TYPE NIL DEFER IMPURE ENUM IMPORT CIMPORT SIZE +%token LOAD RET STRUCT TYPE NIL ZERO DEFER IMPURE ENUM IMPORT CIMPORT SIZE %token BOX UNBOX INTRINSIC FOREIGN DATA STATE VEC MAT FUNCTION %token VAFUNCTION CELL REF EXTEND WITH CONSTRUCT DESTRUCT @@ -261,6 +261,7 @@ primary: | SIZE LT t=haven_type GT { mk_expr $startpos $endpos (SizeType t) } | SIZE LPAREN e=expr RPAREN { mk_expr $startpos $endpos (SizeExpr e) } | NIL { mk_expr $startpos $endpos Nil } + | ZERO { mk_expr $startpos $endpos Zero } ; init: LBRACE exprs=separated_nonempty_list(COMMA, expr) RBRACE { mk_loc $startpos $endpos { exprs } } ; diff --git a/src/lib/parser/parser.ml b/src/lib/parser/parser.ml index 597975a..bb5b431 100644 --- a/src/lib/parser/parser.ml +++ b/src/lib/parser/parser.ml @@ -34,6 +34,7 @@ let keywords = ("void", Grammar.VOID_TYPE); ("type", Grammar.TYPE); ("nil", Grammar.NIL); + ("zero", Grammar.ZERO); ("defer", Grammar.DEFER); ("impure", Grammar.IMPURE); ("enum", Grammar.ENUM); diff --git a/src/test/test_llvm_ir.ml b/src/test/test_llvm_ir.ml index e1953e1..b147260 100644 --- a/src/test/test_llvm_ir.ml +++ b/src/test/test_llvm_ir.ml @@ -135,6 +135,27 @@ let run () = assert_true "external declarations should not synthesize startup initialization" (not (string_contains external_ir "@__haven_global_init")); + let zero_aggregate_ir = + emit_ir "pub state i32[4] values = zero;\npub fn read() -> i32 { values[3] }" + in + assert_true "aggregate zero initializers should emit zero-filled definitions" + (string_contains zero_aggregate_ir "@values = global [4 x i32] zeroinitializer"); + + let zero_struct_ir = + emit_ir + "type Buffer = struct { i8* ptr; u64 length; };\npub state Buffer buffer = zero;" + in + assert_true "struct zero initializers should recursively include pointer fields" + (string_contains zero_struct_ir + "@buffer = global %haven.struct.Buffer zeroinitializer"); + + let local_zero_ir = + emit_ir + "pub impure fn read() -> i32 { let mut i32[4] values = zero; values[3] = 7; values[0] }" + in + assert_true "local aggregate zero initializers should store a zero-filled value" + (string_contains local_zero_ir "store [4 x i32] zeroinitializer"); + let constant_cast_ir = emit_ir "data u32 ZERO = as(0);\npub fn main() -> u32 { ZERO }" diff --git a/src/test/test_parser.ml b/src/test/test_parser.ml index 19c40ab..8f5d2b1 100644 --- a/src/test/test_parser.ml +++ b/src/test/test_parser.ml @@ -37,6 +37,9 @@ let run () = assert_parse_ok "initializer without trailing comma" "type Thing = struct { i32 value; }; pub fn main() -> i32 { let Thing thing = { 1 }; thing.value }"; + assert_parse_ok "aggregate zero initializer" + "pub state i32[4] values = zero;"; + assert_parse_ok "extend lifecycle block" {| type Buffer = struct { diff --git a/src/test/test_typing_semantics.ml b/src/test/test_typing_semantics.ml index f5b7d31..dd66d44 100644 --- a/src/test/test_typing_semantics.ml +++ b/src/test/test_typing_semantics.ml @@ -529,4 +529,52 @@ pub fn sut() -> i32 { |> Analysis.Pipeline.run_core in assert_has_diagnostics "invalid vector arithmetic should fail" - bad_vector_binary_pipeline.semantic.diagnostics + bad_vector_binary_pipeline.semantic.diagnostics; + + let aggregate_zero_pipeline = + parse_to_core + {| +type Buffer = struct { + i8* ptr; + u64 length; +}; + +pub state Buffer buffer = zero; +pub state i32[4] values = zero; +pub state fvec3 vector = zero; +pub state mat2x3 matrix = zero; + +fn make_buffer() -> Buffer { + zero +} +|} + |> Analysis.Pipeline.run_core + in + assert_no_diagnostics "zero should type-check for aggregate targets" + aggregate_zero_pipeline.typing.diagnostics; + assert_no_diagnostics "zero aggregates should pass semantic analysis" + aggregate_zero_pipeline.semantic.diagnostics; + + let scalar_zero_pipeline = + parse_to_core "pub state i32 value = zero;" |> Analysis.Pipeline.run_core + in + assert_has_diagnostics "zero initializer should reject scalar targets" + scalar_zero_pipeline.typing.diagnostics; + assert_any_diagnostic_message_contains "scalar zero diagnostic wording" + "zero requires an explicit array, struct, vector, or matrix target type" + scalar_zero_pipeline.typing.diagnostics; + + let enum_zero_pipeline = + parse_to_core + "type Choice = enum { First, Second }; pub state Choice value = zero;" + |> Analysis.Pipeline.run_core + in + assert_has_diagnostics "zero initializer should reject enum targets" + enum_zero_pipeline.typing.diagnostics; + + let untyped_zero_pipeline = + parse_to_core "pub fn main() -> void { let value = zero; }" + |> Analysis.Pipeline.run_core + in + assert_has_diagnostics "zero should require a contextual aggregate type" + untyped_zero_pipeline.typing.diagnostics