const std = @import("std"); const Allocator = std.mem.Allocator; const ast = @import("../ast.zig"); const Node = ast.Node; const types = @import("types.zig"); const inst_mod = @import("inst.zig"); const mod_mod = @import("module.zig"); const type_bridge = @import("type_bridge.zig"); const unescape = @import("../unescape.zig"); const parser_mod = @import("../parser.zig"); const interp_mod = @import("interp.zig"); const errors = @import("../errors.zig"); const jni_descriptor = @import("jni_descriptor.zig"); const program_index_mod = @import("program_index.zig"); const resolver_mod = @import("resolver.zig"); const imports_mod = @import("../imports.zig"); const ProgramIndex = program_index_mod.ProgramIndex; const GlobalInfo = program_index_mod.GlobalInfo; const StructTemplate = program_index_mod.StructTemplate; const TemplateParam = program_index_mod.TemplateParam; const ProtocolDeclInfo = program_index_mod.ProtocolDeclInfo; const ProtocolMethodInfo = program_index_mod.ProtocolMethodInfo; const ModuleConstInfo = program_index_mod.ModuleConstInfo; const TypeResolver = @import("type_resolver.zig").TypeResolver; const ResolveEnv = @import("type_resolver.zig").ResolveEnv; const PackResolver = @import("packs.zig").PackResolver; const ExprTyper = @import("expr_typer.zig").ExprTyper; const CallResolver = @import("calls.zig").CallResolver; const GenericResolver = @import("generics.zig").GenericResolver; const ProtocolResolver = @import("protocols.zig").ProtocolResolver; const CoercionResolver = @import("conversions.zig").CoercionResolver; const ErrorAnalysis = @import("error_analysis.zig").ErrorAnalysis; const ErrorFlow = @import("error_flow.zig").ErrorFlow; const ObjcLowering = @import("ffi_objc.zig").ObjcLowering; const semantic_diagnostics = @import("semantic_diagnostics.zig"); const lower_error = @import("lower/error.zig"); const lower_comptime = @import("lower/comptime.zig"); const lower_stmt = @import("lower/stmt.zig"); const lower_control_flow = @import("lower/control_flow.zig"); const lower_decl = @import("lower/decl.zig"); const lower_nominal = @import("lower/nominal.zig"); const lower_protocol = @import("lower/protocol.zig"); const lower_coerce = @import("lower/coerce.zig"); const lower_ffi = @import("lower/ffi.zig"); const lower_objc_class = @import("lower/objc_class.zig"); const lower_call = @import("lower/call.zig"); const lower_pack = @import("lower/pack.zig"); const lower_generic = @import("lower/generic.zig"); const lower_expr = @import("lower/expr.zig"); const lower_closure = @import("lower/closure.zig"); const TypeId = types.TypeId; const StringId = types.StringId; const Ref = inst_mod.Ref; const BlockId = inst_mod.BlockId; const FuncId = inst_mod.FuncId; const Function = inst_mod.Function; const Module = mod_mod.Module; const Builder = mod_mod.Builder; /// One frame in the chain of module-const names currently being folded by the /// SOURCE-AWARE const evaluator (`Lowering.foldSourceConstInt` and its float /// twins). Stack-allocated per recursive frame, so cycle detection needs no /// allocation — the source-aware analogue of `program_index.ModuleConstFrame`, /// which guards the GLOBAL-map fold (`moduleConstInt`). The frame keys on the /// const's (name, author-source) pair, NOT name alone: same-name nested consts /// across modules (`a.M` ≠ `b.M`) must NOT trip a false cycle (F3). A pair /// already on the chain is a cyclic definition (`N :: N`; `N :: M + 1; M :: N`) /// with no compile-time value → folds to null. pub const ConstFoldFrame = struct { name: []const u8, source: ?[]const u8, parent: ?*const ConstFoldFrame, }; pub fn constFoldFrameContains(frame: ?*const ConstFoldFrame, name: []const u8, source: ?[]const u8) bool { var cur = frame; while (cur) |c| : (cur = c.parent) { if (std.mem.eql(u8, c.name, name) and sourcesEql(c.source, source)) return true; } return false; } fn sourcesEql(a: ?[]const u8, b: ?[]const u8) bool { if (a == null and b == null) return true; if (a == null or b == null) return false; return std.mem.eql(u8, a.?, b.?); } /// Folding context for a SOURCE-AWARE module-const EXPRESSION RHS (E2/F2/R1). /// The leaf-resolution twin of `program_index.ModuleConstCtx`, but every leaf /// name resolves through the querying source's OWN const author /// (`selectModuleConst`, own-wins / ambiguous) instead of the GLOBAL last-wins /// `module_const_map`. This is what makes a same-name shadow's RHS chain /// (`K :: M + 1`, with `M` a same-name shadow too) fold `M` to the SELECTED /// author's `M` — coherently for a const used as a value AND as an array /// dimension / count. `frame` is the cyclic-definition guard. pub const SourceConstCtx = struct { lowering: *Lowering, frame: ?*const ConstFoldFrame, pub fn lookupDimName(self: SourceConstCtx, name: []const u8) ?i64 { return self.lowering.foldSourceConstInt(name, self.frame); } pub fn lookupPackLen(self: SourceConstCtx, name: []const u8) ?i64 { return self.lowering.lookupPackLen(name); } pub fn lookupFloatName(self: SourceConstCtx, name: []const u8) ?f64 { return self.lowering.foldSourceConstFloat(name, self.frame); } pub fn nameIsFloatTyped(self: SourceConstCtx, name: []const u8) bool { return self.lowering.sourceConstIsFloatTyped(name, self.frame); } }; // ── Scope ─────────────────────────────────────────────────────────────── pub const Binding = struct { ref: Ref, ty: TypeId, is_alloca: bool, // true if ref is a pointer that needs load is_ref_capture: bool = false, // `for xs: (*x)` — `ref` is `*elem`; auto-deref in value positions }; // `init` / `deinit` / `put` are pub so collaborator unit tests (e.g. // calls.test.zig) can stand up a lexical scope and exercise the // scope-dependent call forms (closure / fn-pointer callees) without // driving a full function lowering. pub const Scope = struct { map: std.StringHashMap(Binding), fn_names: std.StringHashMap([]const u8), // bare name → mangled name for local functions parent: ?*Scope, pub fn init(alloc: Allocator, parent: ?*Scope) Scope { return .{ .map = std.StringHashMap(Binding).init(alloc), .fn_names = std.StringHashMap([]const u8).init(alloc), .parent = parent, }; } pub fn deinit(self: *Scope) void { self.map.deinit(); self.fn_names.deinit(); } pub fn put(self: *Scope, name: []const u8, binding: Binding) void { self.map.put(name, binding) catch unreachable; } pub fn lookup(self: *const Scope, name: []const u8) ?Binding { if (self.map.get(name)) |b| return b; if (self.parent) |p| return p.lookup(name); return null; } pub fn lookupFn(self: *const Scope, name: []const u8) ?[]const u8 { if (self.fn_names.get(name)) |mangled| return mangled; if (self.parent) |p| return p.lookupFn(name); return null; } }; /// A pending block-scoped cleanup: `defer` (runs on every block exit) or /// `onfail` (runs only when an error leaves the block, binding the in-flight /// tag). Both share one declaration-ordered stack so error-exit cleanup runs /// them interleaved in reverse order (ERR E1.7). const CleanupEntry = struct { body: *const Node, is_onfail: bool, binding: ?[]const u8 = null, }; /// Pure non-transitive visibility walk: `name` is visible from `source` when /// it's in `source`'s own scope or in any module reachable over one `graph` /// edge. The core of the lowering visibility predicate, exposed so a unit test /// can exercise the edge-walk without standing up a whole `Lowering`. Falls open /// (true) when `scopes`/`graph` are null (scoping infra unwired). pub fn nameVisibleOverEdges( scopes: ?*std.StringHashMap(std.StringHashMap(void)), graph: ?*std.StringHashMap(std.StringHashMap(void)), source: []const u8, name: []const u8, ) bool { const sc = scopes orelse return true; const own_scope = sc.get(source) orelse return true; if (own_scope.contains(name)) return true; const g = graph orelse return true; const direct = g.get(source) orelse return true; var it = direct.iterator(); while (it.next()) |kv| { const dep = sc.get(kv.key_ptr.*) orelse continue; if (dep.contains(name)) return true; } return false; } // ── Lowering ──────────────────────────────────────────────────────────── pub const Lowering = struct { module: *Module, builder: Builder, alloc: Allocator, scope: ?*Scope = null, break_target: ?BlockId = null, continue_target: ?BlockId = null, loop_defer_base: usize = 0, // defer-stack height at the innermost loop's body start (break/continue drain to here) suppress_int_fit_check: bool = false, // inside an explicit `xx` cast operand: truncation is requested, skip the literal fits-check block_counter: u32 = 0, comptime_counter: u32 = 0, main_file: ?[]const u8 = null, // path of the main file; imported functions are declared extern resolved_root: ?*const Node = null, // full AST root (for building comptime modules) comptime_param_nodes: ?std.StringHashMap(*const Node) = null, // active comptime substitutions target_type: ?TypeId = null, // target type for struct/enum literals without explicit names lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered /// Identity map: authoring `*const ast.FnDecl` → the FuncId `declareFunction` /// created for it. The name-keyed function table (`resolveFuncByName`) returns /// the FIRST author of a name, so two same-name authors collide there; this /// map addresses each author's OWN slot by decl identity, letting /// a SHADOWED author lower its body into a distinct FuncId. fn_decl_fids: std.AutoHashMap(*const ast.FnDecl, FuncId), /// FuncId-keyed lowered tracking — the identity twin of `lowered_functions` /// (which keys by name). A shadowed same-name author shares the winner's name /// but not its FuncId, so name-keyed tracking can't tell them apart; this /// records which specific FuncIds have had a real body lowered. lowered_fids: std.AutoHashMap(FuncId, void), local_fn_counter: u32 = 0, // unique counter for mangling local function names /// Per-declaration nominal identity bookkeeping (E2). The FIRST source to /// register a given top-level type NAME keeps `nominal_id = 0` (structural — /// byte-identical to pre-E2 single-author registration); a later registration /// of the same name from a DIFFERENT source is a same-name SHADOW and gets a /// fresh id from `next_nominal_id`, so the two authors intern to DISTINCT /// TypeIds (closing the last-wins collapse). `nominal_name_authors` /// records each name's first author source to make that decision. nominal_name_authors: std.AutoHashMap(types.StringId, []const u8), next_nominal_id: u32 = 0, /// Declaration-name / import / visibility facts (architecture phase A1, /// `ProgramIndex`). Owns `import_flags`; borrows `module_scopes` / /// `import_graph` from the compilation driver. Reached via /// `self.program_index.`; populated by scan/registration code. program_index: ProgramIndex, current_source_file: ?[]const u8 = null, // source file of function currently being lowered // Implicit Context parameter machinery. When the program imports // `std.sx` (and therefore declares `Context :: struct {...}`), every // default-conv sx function gains a synthetic `__sx_ctx: *void` param // at slot 0, and `current_ctx_ref` is bound to that param on each // function-body entry. `lowerCall` / `call_indirect` prepend this ref // to the args of every sx-to-sx call. push Context.{...} rebinds it // to a stack-allocated Context for the lexical body. See // `~/.claude/plans/lets-see-options-for-merry-dijkstra.md`. implicit_ctx_enabled: bool = false, current_ctx_ref: Ref = Ref.none, sel_register_name_fid: ?FuncId = null, // lazily-declared `sel_registerName` extern (non-literal selector fallback) jni_env_stack: std.ArrayList(Ref) = std.ArrayList(Ref).empty, // lexical `#jni_env(env)` Ref stack — top is current scope's env for omitted-env `#jni_call` jni_env_stack_base: usize = 0, // index above which the currently-lowering fn's `#jni_env` scopes live; outer-fn Refs aren't valid in this fn's instruction stream jni_env_tl_get_fid: ?FuncId = null, // extern `sx_jni_env_tl_get` (from library/vendors/sx_jni_runtime/sx_jni_env_tl.c) jni_env_tl_set_fid: ?FuncId = null, // extern `sx_jni_env_tl_set` needs_jni_env_tl_runtime: bool = false, // set when lowering touches the JNI env TL; signals Compilation to auto-link the runtime .c trace_push_fid: ?FuncId = null, // extern `sx_trace_push` (ERR E3.1, from library/vendors/sx_trace_runtime/sx_trace.c) trace_clear_fid: ?FuncId = null, // extern `sx_trace_clear` needs_trace_runtime: bool = false, // set when lowering emits a trace push/clear; signals Compilation to auto-link sx_trace.c chain_fail_target: ?ChainFailTarget = null, // ERR E2.4: when set, a failable `or` chain routes its TOTAL failure here (an absorbing consumer like `catch`) instead of propagating to the function current_foreign_class: ?*const ast.ForeignClassDecl = null, // set while lowering a `#jni_main` (or any sx-defined `#jni_class`) bodied method — `super.method(args)` dispatch resolves the parent class against this fcd's `#extends` current_foreign_method: ?ast.ForeignMethodDecl = null, // the specific method whose body is being lowered; `super.(...)` reuses its signature type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId) current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch) force_block_value: bool = false, // set by lowerBlockValue to extract if-else values block_terminated: bool = false, // set when constant-folded if emits a return/br into current block in_lambda_body: bool = false, // true while lowering a closure-literal body; sharpens the `raise`-not-failable diagnostic (ERR E5.1: tell the user to annotate `-> (T, !)`) defer_stack: std.ArrayList(CleanupEntry) = std.ArrayList(CleanupEntry).empty, // block-scoped defer + onfail cleanup stack func_defer_base: usize = 0, // defer stack base for current function (lowerReturn drains to this) deferred_type_fns: std.ArrayList([]const u8) = std.ArrayList([]const u8).empty, // functions deferred until all types registered processing_deferred: bool = false, // true when processing deferred functions (prevents re-deferral) /// True while emitting the compiler-synthesized default-Context global /// (`emitDefaultContextGlobal`). The built-in allocator infrastructure /// (`CAllocator`/`Allocator`/`Context`) is resolved as compiler internals, /// independent of the user program's import STYLE (a `std :: #import` puts /// `CAllocator` behind a namespace edge from `main`, so the user-visibility /// gate would reject it) — so the bare TYPE leaf falls open here (F1). emitting_default_context: bool = false, /// Names declared as a BLOCK-LOCAL type (a `Foo :: struct/enum/union/error_set` /// or bare type-decl statement inside a fn / init body), keyed by the DECLARING /// source. A local type registers into the global type table and CLOBBERS a /// same-name top-level entry (`registerStructDecl`'s `findByName … orelse intern` /// + `updatePreservingKey`), so after it lowers the name IS the local type /// program-wide (single-author, pre-E2). The source-aware bare-TYPE gate consults /// this so a legitimately block-local type resolves in ITS OWN source (never /// mistaken for a namespaced-only leak, even when a namespaced-only import authors /// a same-name top-level type — R2). It is keyed by source because a local is /// visible ONLY within the source that declares it: an imported template's field /// resolution (run in the template's source context, E3 attempt-4) must NOT bind a /// name the CALLER declared block-local (E3 attempt-5). local_type_names: std.StringHashMap(std.StringHashMap(void)), struct_defaults_map: std.StringHashMap([]const ?*const Node), // struct name → field defaults struct_instance_bindings: std.StringHashMap(std.StringHashMap(TypeId)), // mangled struct name → type param bindings struct_instance_template: std.StringHashMap([]const u8), // mangled struct name → template name struct_instance_author: std.StringHashMap(*const ast.StructDecl), // mangled struct name → authoring StructDecl (CP-2: body-author ≡ layout-author) comptime_value_bindings: ?std.StringHashMap(i64) = null, // comptime value bindings ($N → integer value) protocol_thunk_map: std.StringHashMap([]const FuncId), // "Proto\x00Type" → thunk FuncIds protocol_vtable_type_map: std.StringHashMap(TypeId), // protocol name → vtable struct TypeId protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId), // "Proto\x00Type" → vtable GlobalId param_impl_map: std.StringHashMap(std.ArrayList(ParamImplEntry)), // "Proto\x00\x00" → impl entries (parameterised protocols only; list lets Phase 4/5 detect cross-module overlap) /// Pack-variadic impl entries — separate map keyed by `"Proto\x00"` /// (NO source suffix) so a single impl `Closure(..$args) -> $R` can be /// matched against many concrete source shapes. Concrete impls in /// `param_impl_map` win when both match (specificity rule). param_impl_pack_map: std.StringHashMap(std.ArrayList(PackParamImplEntry)), /// Active pack bindings during monomorphisation. Mirrors `type_bindings` /// but for variadic pack names: `args → [T1, T2, ...]`. Read by /// `resolveTypeWithBindings` on closure_type_expr to substitute /// `Closure(..$args) -> $R` into a concrete closure type. pack_bindings: ?std.StringHashMap([]const TypeId) = null, /// Active when lowering an inlined comptime-call body. `return X;` /// inside the body must NOT emit a `ret` into the caller's LLVM /// function — instead it stores X into `.slot` (typed `.ret_ty`) /// and sets `block_terminated` so the inliner can load the slot /// once the body finishes. Without this, a body like /// `{ return 42; }` truncates the caller's basic block mid-flight /// and trips LLVM's "Terminator found in the middle of a basic /// block" verifier. inline_return_target: ?InlineReturnInfo = null, /// Active pack-arg-node bindings during a comptime call's body lowering. /// Maps the pack-param name (e.g. `args`) to the slice of call-site /// argument AST nodes. `lowerIndexExpr` (and `inferExprType`) check /// this map when the index expression's base is an identifier matching /// a pack name AND the index is a comptime int literal — substitutes /// with the i-th call arg's lowered value so the static type tracks /// the call arg's real type instead of `Any`. The `[]Any` slice path /// remains the runtime-indexed fallback for non-literal indices. pack_arg_nodes: ?std.StringHashMap([]const *const Node) = null, /// Active pack-arity bindings during a pack-fn mono's body lowering. /// Maps the pack-param name (e.g. `args`) to N. `lowerFieldAccess` /// uses this to resolve `args.len` to a compile-time constant Ref /// when no `args` slice is in scope (the mono path doesn't /// materialise the slice). pack_param_count: ?std.StringHashMap(u32) = null, /// Type-only pack binding consulted by `inferExprType` for /// `args[]` (parallel to `pack_arg_nodes` which carries the /// AST substitution used at lowering time). Holds the concrete /// call-site arg types in declaration order — same data the /// mono's pack-param signature uses. Lets generic-`$R` return /// inference resolve `args[i]` to the correct concrete type even /// before the mono's scope is set up. pack_arg_types: ?std.StringHashMap([]const TypeId) = null, /// Active during a protocol-pack mono's body lowering: pack-param name → /// constraint protocol name (`..xs: Box` ⇒ `xs` → `"Box"`). Lets /// `lowerFieldAccess` enforce the interface-only rule — a member access /// `xs[i].` is rejected unless `` is one of the protocol's methods. /// Null / absent for the comptime `..$args` pack (no constraint). pack_constraint: ?std.StringHashMap([]const u8) = null, struct_const_map: std.StringHashMap(StructConstInfo), // "Struct.CONST" → value info foreign_name_map: std.StringHashMap([]const u8), // sx name → C name for #foreign renames target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if) comptime_constants: std.StringHashMap(ComptimeValue), // compile-time known constants (e.g. OS, ARCH) diagnostics: ?*errors.DiagnosticList = null, // error reporting with source locations xx_reentrancy: std.AutoHashMap(u64, void), // (src_ty, dst_ty) pairs currently being resolved through user-space Into; prevents infinite monomorphisation when a convert body re-enters the same xx /// Whole-program-converged inferred error sets (ERR E1.4b): top-level /// bare-`!` function name → its sorted escape-tag ids (literal raises + /// pure-failable `try` edges, fix-pointed across the call graph). The /// shared `!` placeholder TypeId stays empty; this side map holds the real /// per-function sets (sidesteps the name-only error-set interning). Read by /// `lowerTry`'s named-caller widening and the empty-inferred warning. inferred_error_sets: std.StringHashMap([]const u32), /// Whole-program-converged inferred error sets keyed by closure/function /// VALUE-signature shape (ERR E5.1 sub-feature 2): every occurrence of /// `Closure() -> (T, !)` with a structurally identical value-signature /// shares one node; each bare-`!` closure literal of that shape unions its /// escape tags in. Read by `checkEscapeWidening` when a `try` operand is a /// closure/fn-type SLOT call (no static fn name). Key = `closureShapeKey`. shape_inferred_sets: std.StringHashMap([]const u32), pub const ComptimeValue = union(enum) { int_val: i64, enum_tag: struct { ty: TypeId, tag: u32 }, }; pub const StructConstInfo = struct { value: *const Node, ty: ?TypeId, // null if no type annotation (inferred) }; /// One impl block for a parameterised protocol (e.g. `impl Into(Block) for Closure() -> void`). /// Stored in `param_impl_map` keyed by (protocol_name, target_args_mangled, source_mangled). /// `defining_module` enables import-scoped visibility + cross-module duplicate diagnostics. pub const ParamImplEntry = struct { methods: []const *const ast.FnDecl, source_ty: TypeId, target_args: []const TypeId, defining_module: []const u8, span: ast.Span, }; const InlineReturnInfo = struct { slot: Ref, ret_ty: TypeId, done_bb: BlockId }; /// ERR E2.4 — where a failable `or` chain's TOTAL failure routes when the /// chain is the operand of an absorbing consumer (`catch`). `bb` is a block /// with a single parameter typed `set` (the error tag); the chain branches /// there with its final error instead of propagating to the function. const ChainFailTarget = struct { bb: BlockId, set: TypeId }; /// Pack-variadic impl entry — `impl Proto(Args...) for Closure(Prefix..., ..$pack) -> $ret`. /// Matches any concrete closure source whose first `prefix_len` param types /// equal `source_pack_ty`'s fixed prefix; the tail binds to `pack_var_name` /// (e.g. "args") and the source's return type binds to `ret_var_name` /// (e.g. "R") when the impl's return is generic. `ret_var_name == null` /// means the return type is concrete and must match exactly. pub const PackParamImplEntry = struct { methods: []const *const ast.FnDecl, source_pack_ty: TypeId, target_args: []const TypeId, defining_module: []const u8, span: ast.Span, pack_var_name: []const u8, ret_var_name: ?[]const u8, }; /// Caller-state protection for lowering a function body re-entrantly — a /// lazily lowered callee, a qualified `ns.fn` alias, or an out-of-line /// same-name author. `enter` snapshots the in-progress builder / scope / /// flag / pack / jni state and installs a fresh set for the nested body; /// `restore` puts the caller's state back. Lowering a callee must be /// transparent to the caller's own lowering — notably `block_terminated`, /// which leaking back would mark the caller's trailing statements /// dead-after-terminator. pub const FnBodyReentry = struct { l: *Lowering, func: ?FuncId, block: ?BlockId, counter: u32, scope: ?*Scope, defer_base: usize, block_terminated: bool, force_block_value: bool, source_file: ?[]const u8, jni_env_base: usize, pack_arg_nodes: ?std.StringHashMap([]const *const Node), pack_param_count: ?std.StringHashMap(u32), pack_arg_types: ?std.StringHashMap([]const TypeId), inline_return_target: ?InlineReturnInfo, pub fn enter(l: *Lowering) FnBodyReentry { const g = FnBodyReentry{ .l = l, .func = l.builder.func, .block = l.builder.current_block, .counter = l.builder.inst_counter, .scope = l.scope, .defer_base = l.func_defer_base, .block_terminated = l.block_terminated, .force_block_value = l.force_block_value, .source_file = l.current_source_file, .jni_env_base = l.jni_env_stack_base, .pack_arg_nodes = l.pack_arg_nodes, .pack_param_count = l.pack_param_count, .pack_arg_types = l.pack_arg_types, .inline_return_target = l.inline_return_target, }; // The `#jni_env` Ref stack is lexical to ONE function's instruction // stream; move the visible base to the current top. Pack-fn mono // state is likewise lexical to the pack-fn body — null it so a // callee sharing a param NAME with the active pack doesn't fold the // outer mono's arity into its own `.len`. l.jni_env_stack_base = l.jni_env_stack.items.len; l.pack_arg_nodes = null; l.pack_param_count = null; l.pack_arg_types = null; l.inline_return_target = null; l.func_defer_base = l.defer_stack.items.len; l.block_terminated = false; l.force_block_value = false; return g; } pub fn restore(g: FnBodyReentry) void { const l = g.l; l.setCurrentSourceFile(g.source_file); l.scope = g.scope; l.func_defer_base = g.defer_base; l.block_terminated = g.block_terminated; l.force_block_value = g.force_block_value; l.builder.func = g.func; l.builder.current_block = g.block; l.builder.inst_counter = g.counter; l.jni_env_stack_base = g.jni_env_base; l.pack_arg_nodes = g.pack_arg_nodes; l.pack_param_count = g.pack_param_count; l.pack_arg_types = g.pack_arg_types; l.inline_return_target = g.inline_return_target; } }; pub fn init(module: *Module) Lowering { return .{ .module = module, .builder = Builder.init(module), .alloc = module.alloc, .lowered_functions = std.StringHashMap(void).init(module.alloc), .fn_decl_fids = std.AutoHashMap(*const ast.FnDecl, FuncId).init(module.alloc), .lowered_fids = std.AutoHashMap(FuncId, void).init(module.alloc), .nominal_name_authors = std.AutoHashMap(types.StringId, []const u8).init(module.alloc), .local_type_names = std.StringHashMap(std.StringHashMap(void)).init(module.alloc), .struct_defaults_map = std.StringHashMap([]const ?*const Node).init(module.alloc), .struct_instance_bindings = std.StringHashMap(std.StringHashMap(TypeId)).init(module.alloc), .struct_instance_template = std.StringHashMap([]const u8).init(module.alloc), .struct_instance_author = std.StringHashMap(*const ast.StructDecl).init(module.alloc), .protocol_thunk_map = std.StringHashMap([]const FuncId).init(module.alloc), .protocol_vtable_type_map = std.StringHashMap(TypeId).init(module.alloc), .protocol_vtable_global_map = std.StringHashMap(inst_mod.GlobalId).init(module.alloc), .param_impl_map = std.StringHashMap(std.ArrayList(ParamImplEntry)).init(module.alloc), .param_impl_pack_map = std.StringHashMap(std.ArrayList(PackParamImplEntry)).init(module.alloc), .struct_const_map = std.StringHashMap(StructConstInfo).init(module.alloc), .foreign_name_map = std.StringHashMap([]const u8).init(module.alloc), .comptime_constants = std.StringHashMap(ComptimeValue).init(module.alloc), .xx_reentrancy = std.AutoHashMap(u64, void).init(module.alloc), .inferred_error_sets = std.StringHashMap([]const u32).init(module.alloc), .shape_inferred_sets = std.StringHashMap([]const u32).init(module.alloc), .program_index = ProgramIndex.init(module.alloc), }; } // ── Layout delegators ─────────────────────────────────────────── /// Byte size of an IR type matching LLVM's type layout. pub fn typeSizeBytes(self: *Lowering, ty: TypeId) usize { return self.module.types.typeSizeBytes(ty); } pub fn typeAlignBytes(self: *Lowering, ty: TypeId) usize { return self.module.types.typeAlignBytes(ty); } fn resolveReturnType2(self: *Lowering, rt: ?*const Node) TypeId { if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map); return .void; } // ── Type-resolution delegators ────────────────────────────────── pub fn resolveReturnType(self: *Lowering, fd: *const ast.FnDecl) TypeId { if (fd.return_type) |rt| { return self.resolveTypeWithBindings(rt); } // No explicit annotation — the type is inferred from the body, which // references the function's own parameters (`(x: s32) => x * 2`). Those // params aren't pushed into `self.scope` until body lowering, so bind // them into a temporary scope here; otherwise `inferExprType` can't // resolve `x`, the inference yields `.unresolved`, and that reaches LLVM // emission as `func.ret`. Whether it slipped through used to // depend on a same-named binding lingering from earlier lowering. var tmp_scope = Scope.init(self.alloc, self.scope); defer tmp_scope.deinit(); const saved_scope = self.scope; self.scope = &tmp_scope; defer self.scope = saved_scope; for (fd.params, 0..) |p, i| { // Bind only plain annotated value params — that's all the body's // return type can depend on by name. Skip variadic / pack / comptime // params (their concrete types come from per-call substitution) and // unannotated ones (no context here). Resolve the type directly via // resolveTypeWithBindings rather than resolveParamType: the latter // does variadic/pack bookkeeping that must run exactly once, at body // lowering — calling it here too corrupts that state. if (p.is_variadic or p.is_pack or p.is_comptime) continue; if (p.type_expr.data == .inferred_type) continue; const pty = self.resolveTypeWithBindings(p.type_expr); tmp_scope.put(p.name, .{ .ref = Ref.fromIndex(@intCast(i)), .ty = pty, .is_alloca = false }); } // Arrow functions without explicit return type: infer from body expression. if (fd.is_arrow) { return self.inferExprType(fd.body); } // Not arrow: an explicit `return ` statement wins. Otherwise // default to void — the body's tail expression is a side-effect // statement, not an implicit return. if (self.findReturnValueType(fd.body)) |ty| return ty; return .void; } /// Walk a function body and return the type of the first `return ;` /// statement encountered. Does not descend into nested function or lambda /// declarations (those have their own return types). pub fn findReturnValueType(self: *Lowering, node: *const Node) ?TypeId { return switch (node.data) { .return_stmt => |rs| if (rs.value) |v| self.inferExprType(v) else null, .block => |blk| blk: { for (blk.stmts) |s| { if (self.findReturnValueType(s)) |t| break :blk t; } break :blk null; }, .if_expr => |ie| blk: { if (self.findReturnValueType(ie.then_branch)) |t| break :blk t; if (ie.else_branch) |eb| { if (self.findReturnValueType(eb)) |t| break :blk t; } break :blk null; }, .while_expr => |we| self.findReturnValueType(we.body), .for_expr => |fe| self.findReturnValueType(fe.body), .match_expr => |me| blk: { for (me.arms) |arm| { if (self.findReturnValueType(arm.body)) |t| break :blk t; } break :blk null; }, else => null, }; } pub fn resolveParamType(self: *Lowering, p: *const ast.Param) TypeId { // A plain value param with no annotation can only be typed from // context (a lambda's target closure signature). When `resolveParamType` // is reached for one, there is no such context — so it's a genuine // "missing annotation" error, not an 8-byte-int guess. (Comptime/ // variadic pack params also carry `inferred_type` but get their types // from per-call substitution, so they're exempt here.) if (p.type_expr.data == .inferred_type and !p.is_comptime and !p.is_variadic and !p.is_pack) { if (self.diagnostics) |d| { d.addFmt(.err, p.type_expr.span, "parameter '{s}' has no type annotation", .{p.name}); } return .unresolved; } const declared_ty = self.resolveTypeWithBindings(p.type_expr); if (p.is_variadic) { // Two surface forms: // - legacy `name: ..T` — declared_ty is the element type; // wrap to receive a `[]T` slice. // - new `..name: []T` — declared_ty is already the slice // type; use it as-is. Wrapping here would double up to // `[][]T` and downstream LLVM emission crashes when the // caller's argument-marshal pack produces a `[]T` that // doesn't match the callee's stored param shape. if (!declared_ty.isBuiltin()) { const info = self.module.types.get(declared_ty); if (info == .slice) return declared_ty; } return self.module.types.sliceOf(declared_ty); } return declared_ty; } pub fn resolveType(self: *Lowering, type_ann: *const Node) TypeId { return self.resolveTypeWithBindings(type_ann); } /// Resolve a type node with the visibility context pinned to `src`, the /// DEFINING module of a namespaced callee, restoring the caller's context /// after. A namespaced callee's declared return type may name a type that is /// bare-visible only inside the callee's own module — namespaced-only from the /// call site's view. Post-E1 the bare leaf is source-aware, so resolving that /// return type in the CALL SITE's context would wrongly reject it (the type /// analog of the namespaced-fn-body source pin that lowers a namespaced fn body in /// its own module's context). `src == null` falls back to the call site's /// context unchanged. pub fn resolveTypeInSource(self: *Lowering, src: ?[]const u8, type_ann: *const Node) TypeId { const pinned = src orelse return self.resolveType(type_ann); const saved = self.current_source_file; defer self.setCurrentSourceFile(saved); self.setCurrentSourceFile(pinned); return self.resolveType(type_ann); } /// `resolveParamType` with the visibility context pinned to `src`, the /// DEFINING module of the param's function. An imported method's /// default-param type (`alloc: Allocator`) is bare-visible only inside its /// own module, so typing a cross-module call's args against it must resolve /// in that module's context, not the call site's (E4 — the param analog of /// `resolveTypeInSource`). `src == null` falls back unchanged. pub fn resolveParamTypeInSource(self: *Lowering, src: ?[]const u8, p: *const ast.Param) TypeId { const pinned = src orelse return self.resolveParamType(p); const saved = self.current_source_file; defer self.setCurrentSourceFile(saved); self.setCurrentSourceFile(pinned); return self.resolveParamType(p); } /// Construct a `TypeResolver` view over the current lowering state (borrows /// only; cheap by-value, reflects current `diagnostics` / `program_index`). pub fn typeResolver(self: *Lowering) TypeResolver { return .{ .alloc = self.alloc, .types = &self.module.types, .diagnostics = self.diagnostics, .index = &self.program_index, }; } /// Snapshot the active resolution context (Principle 2) for `TypeResolver`. /// A2.2 wires the type bindings + literal target; the pack/comptime fields /// are populated as A2.3 moves the cases that consume them. fn resolveEnv(self: *Lowering) ResolveEnv { return .{ .type_bindings = if (self.type_bindings) |*tb| tb else null, .target_type = self.target_type, }; } /// Inner-type recursion hook for `TypeResolver.resolveCompound`: resolves a /// child type node through the full stateful resolver, so generic structs / /// bindings / aliases in element position keep their resolution. pub fn resolveInner(self: *Lowering, node: *const Node) TypeId { return self.resolveTypeWithBindings(node); } /// Fixed-array dimension hook for `TypeResolver.resolveCompound`. A literal /// `[16]T` and a named-const `N :: 16; [N]T` must resolve to the SAME length: /// the dimension folds to a compile-time integer (looked up in the comptime / /// value / module-const tables the stateful lowering owns) and is narrowed to /// `u32` through the single range-checked `program_index.foldDimU32` — never a /// bare `@intCast`, so an oversized-but-valid `i64` dim (`[5_000_000_000]`) /// diagnoses instead of panicking the compiler. A dimension that /// isn't a compile-time integer (or doesn't fit a `u32`) is a hard error: /// emit a diagnostic so the driver aborts (`hasErrors()`), then return a /// harmless `0` so body lowering finishes without touching the `.unresolved` /// sentinel (which would `@panic` in `sizeOf` mid-lowering, before the /// diagnostic surfaces). The diagnostic — not the returned length — is what /// guarantees no garbage ships. pub fn resolveArrayLen(self: *Lowering, len_node: *const Node) ?u32 { const result = program_index_mod.foldDimU32(len_node, self, 0); if (result == .ok) return result.ok; // A non-const / oversized / negative dim is a hard error. Emit the // shared diagnostic (single wording source — `program_index.reportDimError`, // also used by the stateless alias path so the two cannot diverge) and // return null so `resolveCompound` yields the `.unresolved` sentinel — NO // fabricated length (a `0` here gives a 0-byte alloca and OOB // element access). Lowering the binding never computes the failed type's // size: `alloca` records the type but defers `sizeOf` to LLVM emission, // which the emitted diagnostic pre-empts via `hasErrors()`, and a // downstream use of the `.unresolved`-typed value is poison-suppressed (a // field access stays silent — `emitFieldError`). So the failure surfaces // as ONE clean diagnostic and never reaches the `sizeOf` panic. if (self.diagnostics) |d| program_index_mod.reportDimError(d, len_node.span, result); return null; } /// Leaf-name lookup for the shared dimension evaluator: a name bound to a /// compile-time integer across the three const tables. pub fn lookupDimName(self: *Lowering, name: []const u8) ?i64 { return self.comptimeIntNamed(name); } /// Pack-length leaf for the shared integer-expression evaluator: a pack /// name's monomorphised arity (e.g. an `inline for 0..xs.len` bound). /// Resolves through `pack_param_count`, which is populated when a comptime /// call binds a pack name. A name with no active pack binding is not a /// compile-time integer leaf here → null. pub fn lookupPackLen(self: *Lowering, name: []const u8) ?i64 { if (self.pack_param_count) |ppc| { if (ppc.get(name)) |n| return @intCast(n); } return null; } /// Float-valued leaf for the shared float-expression evaluator: a name bound /// to a NUMERIC module const whose compile-time value is a (non-integral) /// float — the FLOAT counterpart of `lookupDimName`, routed through the SAME /// `module_const_map` so the unified narrowing rule resolves a float-const /// leaf (`F : f64 : 2.5`) exactly as it resolves an int-const leaf. Integer / /// integral-float leaves and comptime int bindings are already resolved by the /// `evalConstIntExpr` delegation inside `evalConstFloatExpr`; this surfaces the /// non-integral float const so the rule can reject it. pub fn lookupFloatName(self: *Lowering, name: []const u8) ?f64 { return self.foldSourceConstFloat(name, null); } /// True iff `name` is a FLOAT-valued module const (`F : f64 : 2.5`, /// `K : f64 : 4.0`, untyped `M :: 4.0`, untyped-EXPR `ME :: 4.0 + 1.0`). The /// int folder's division arm consults this so a `/` with a float-const operand /// is recognised as float division. Comptime / generic /// value bindings are always integer-valued, so only the module-const table /// can name a float. pub fn nameIsFloatTyped(self: *Lowering, name: []const u8) bool { return self.sourceConstIsFloatTyped(name, null); } /// Resolve a type node, checking type_bindings first for generic type params. pub fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId { // Pack-index in a type position: `$[]` resolves to the // i-th element type of the active pack binding (step 3 of the // variadic heterogeneous type packs feature). Unblocks parametric // trampoline bodies (`(*void, $args[0]) -> $args[1]`) in stdlib's // generic Into(Block) impl. OOB indices / a missing binding emit a // diagnostic and return the `.unresolved` sentinel — never a plausible // `.s64`, which would silently fabricate an 8-byte int. if (node.data == .pack_index_type_expr) { const pi = node.data.pack_index_type_expr; if (self.pack_arg_types) |pat| { if (pat.get(pi.pack_name)) |arg_tys| { if (pi.index < arg_tys.len) return arg_tys[pi.index]; if (self.diagnostics) |diags| { diags.addFmt(.err, node.span, "pack-index type ${s}[{}] out of bounds: '{s}' has {} element{s}", .{ pi.pack_name, pi.index, pi.pack_name, arg_tys.len, if (arg_tys.len == 1) @as([]const u8, "") else @as([]const u8, "s"), }); } return .unresolved; } } if (self.diagnostics) |diags| { diags.addFmt(.err, node.span, "pack-index type ${s}[{}] used outside an active pack binding", .{ pi.pack_name, pi.index, }); } return .unresolved; } // `*Self` substitution inside foreign-class member declarations // — both foreign and sx-defined — resolves to the class's own // 0-field stub struct (i.e. the opaque Obj-C pointer type). // This matches the Obj-C idiom where `self` IS the object. // `self.field` access on sx-defined classes is rewritten by // lowerFieldAccess to go through the `__sx_state` ivar // (object_getIvar + struct_gep) when needed — see M1.2 A.3. if (node.data == .type_expr and std.mem.eql(u8, node.data.type_expr.name, "Self")) { if (self.current_foreign_class) |fcd| { if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) { return self.foreignClassStructType(fcd); } } } // Structural type shapes — `*T`, `[*]T`, `[]T`, `?T`, `[N]T`, functions, // PLAIN closures, and PLAIN tuples — are owned by // `TypeResolver.resolveCompound` (A2.3b). Element types recurse through // the full stateful resolver (`resolveInner` → here) so generic structs // / bindings keep their resolution. resolveCompound returns null only // for the pack-shaped forms (`Closure(..p)`, spread tuples) below. if (TypeResolver.resolveCompound(&self.module.types, node, self)) |t| return t; // Generic type-param binding (`$T`, or a bare return-type `T` without // the `$` prefix) — owned by TypeResolver via the explicit ResolveEnv. // The parameterized / call / closure / function arms that used to live // here were redundant with the unconditional handling just below (both // read the active bindings through the same resolvers), so they're gone. if (TypeResolver.resolveBinding(node, self.resolveEnv())) |t| return t; // Even without active type_bindings, handle parameterized types with struct templates if (node.data == .parameterized_type_expr) { return self.resolveParameterizedWithBindings(&node.data.parameterized_type_expr, node.span); } if (node.data == .call) { return self.resolveTypeCallWithBindings(&node.data.call); } // Plain structural shapes were handled by resolveCompound above. What // reaches here is the PACK-shaped subset, owned by `PackResolver` // (packs.zig): pack-shaped `Closure(..p)` and spread tuples. (Functions // are never pack-shaped at the type level — resolveCompound owns them // all, so there is no function arm here.) switch (node.data) { .closure_type_expr => |ct| { return self.packResolver().resolveClosureTypeWithBindings(&ct); }, .tuple_type_expr => |tt| { return self.packResolver().resolveTupleTypeWithBindings(&tt); }, // `(..$Ts)` in a type position (e.g. a struct field) parses as a // tuple LITERAL whose elements include a pack spread; PackResolver // expands it (returns null when no spread, so we fall through). .tuple_literal => |tl| { if (self.packResolver().resolveTupleLiteralType(&tl)) |t| return t; }, else => {}, } // An unbound generic type param (`$R` with no active binding) must not // fabricate an empty-struct stub — that surfaces as `R{}` downstream. // Return `.unresolved` so callers (e.g. lambda return-type inference, // call-site `$R` inference) treat it as not-yet-known. if (node.data == .type_expr and node.data.type_expr.is_generic) { // A VALUE param (`$N: u32`) named in a TYPE position (`x: N`) is bound // to a compile-time integer, not a type, so `resolveBinding` above // found no TYPE binding and it lands here. In the MAIN file the // `UnknownTypeChecker` owns this diagnostic (and halts before codegen); // an imported template's fields are resolved in the template's source // context (see `instantiateGenericStruct`) and are checker-trusted, so // this leaf is the sole guard — emit the tailored hint, mirroring the // imported `.undeclared` leaf. A genuinely-unbound type param (`$R`, // no value binding) stays a silent `.unresolved`. const nm = node.data.type_expr.name; const bound_value = if (self.comptime_value_bindings) |cvb| cvb.contains(nm) else false; if (bound_value) { const is_main = if (self.main_file) |mf| (if (self.current_source_file) |csf| std.mem.eql(u8, csf, mf) else true) else true; if (!is_main) { if (self.diagnostics) |d| d.addFmt(.err, node.span, "'{s}' is a value parameter, not a type; introduce a generic type parameter with `${s}: Type`", .{ nm, nm }); } } return .unresolved; } // Bare type names resolve through the source-aware `selectNominalLeaf` // (E1): the nominal author is selected over the ONE graph-walk collector // and resolved against the source-keyed caches, not the global // `findByName` first-match / global alias map. Other node kinds (inline // type decls, error types) still route through type_bridge, which reads // the global compat maps (cut over in a later phase). switch (node.data) { .type_expr => |te| { // Qualified `alias.Type` (incl. a carried alias): resolve the // base name pinned to the alias's target module. if (std.mem.lastIndexOfScalar(u8, te.name, '.')) |dot| { if (self.namespaceAliasTarget(te.name[0..dot], node.span)) |target| { const saved = self.current_source_file; self.setCurrentSourceFile(target.target_module_path); const ty = self.resolveNominalLeaf(te.name[dot + 1 ..], te.is_raw, node.span); self.setCurrentSourceFile(saved); return ty; } } return self.resolveNominalLeaf(te.name, te.is_raw, node.span); }, .identifier => |id| return self.resolveNominalLeaf(id.name, id.is_raw, node.span), // A non-spread tuple literal in a type position is a tuple-type // literal (`(s32, s32)`); validate its elements are types and reject // non-type elements loudly. .tuple_literal => return self.resolveTupleLiteralTypeArg(node), else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map), } } /// Bind a `PackResolver` to this Lowering for pack-aware TYPE-position /// resolution (`Closure(..p)` / `(Params...) -> R` / `(..xs)` tuples and /// their `..xs.T` projections). A2.3 moved that logic into `packs.zig`. pub fn packResolver(self: *Lowering) PackResolver { return .{ .l = self }; } /// Resolve a `Vector(N, T)` lane count to a positive compile-time integer /// through the shared `program_index.foldDimU32` folder (min 1) — so a literal /// (`Vector(4, f32)`), a module/generic const (`Vector(N, f32)`), and a const /// expression (`Vector(M + 1, f32)`) all resolve identically, and the i64→u32 /// narrowing is range-checked (an oversized lane diagnoses instead of /// panicking). A non-const lane (`Vector(get(), f32)`) or a /// non-positive one emits a clean diagnostic and returns null; the caller /// yields `.unresolved` rather than fabricating a `<0 x float>` lane count /// that crashes LLVM verification. pub fn resolveVectorLane(self: *Lowering, lane_node: *const Node) ?u32 { switch (program_index_mod.foldDimU32(lane_node, self, 1)) { .ok => |n| return n, .too_large => |v| { if (self.diagnostics) |d| d.addFmt(.err, lane_node.span, "Vector lane count {} does not fit in u32", .{v}); return null; }, .non_integral_float => |v| { if (self.diagnostics) |d| d.addFmt(.err, lane_node.span, "Vector lane count must be an integer, but '{d}' is a non-integral float", .{v}); return null; }, .not_const, .below_min => { if (self.diagnostics) |d| d.addFmt(.err, lane_node.span, "Vector lane count must be a positive compile-time integer constant", .{}); return null; }, } } /// Infer the type of an expression from its AST node (used for untyped var decls). pub fn inferExprType(self: *Lowering, node: *const Node) TypeId { return switch (node.data) { .call => |*c| self.callResolver().resultType(c), else => self.exprTyper().inferType(node), }; } fn exprTyper(self: *Lowering) ExprTyper { return .{ .l = self }; } pub fn callResolver(self: *Lowering) CallResolver { return .{ .l = self }; } /// A `Resolver` facade over the borrowed Phase A import facts (Phase B). Cheap /// by-value; `collectVisibleAuthors`'s `AuthorSet.flat` slice is backed by /// `self.alloc` and owned by the caller (`selectPlainCallableAuthor` frees it). pub fn resolver(self: *Lowering) resolver_mod.Resolver { return resolver_mod.Resolver.init(&self.program_index, self.alloc); } pub fn genericResolver(self: *Lowering) GenericResolver { return .{ .l = self }; } pub fn protocolResolver(self: *Lowering) ProtocolResolver { return .{ .l = self }; } pub fn coercionResolver(self: *Lowering) CoercionResolver { return .{ .l = self }; } pub fn errorAnalysis(self: *Lowering) ErrorAnalysis { return .{ .l = self }; } pub fn errorFlow(self: *Lowering) ErrorFlow { return .{ .l = self }; } pub fn objc(self: *Lowering) ObjcLowering { return .{ .l = self }; } /// Check if a name refers to a known type (primitive or registered struct/enum/union). /// Used to distinguish type-as-value (silent placeholder) from genuinely unresolved names. pub fn isKnownTypeName(self: *Lowering, name: []const u8) bool { if (type_bridge.resolveTypePrimitive(name) != null) return true; if (self.type_bindings) |bindings| { if (bindings.get(name) != null) return true; } if (self.program_index.type_alias_map.get(name) != null) return true; const name_id = self.module.types.internString(name); return self.module.types.findByName(name_id) != null; } /// Update `self.current_source_file` and mirror it onto `diags.current_source_file`, /// so any diagnostic emitted from inside a function lowered from another module is /// attributed to that module — not whichever file the diagnostics list was init'd with. pub fn setCurrentSourceFile(self: *Lowering, source_file: ?[]const u8) void { self.current_source_file = source_file; if (self.diagnostics) |d| d.current_source_file = source_file; } /// Stamp a caller-provided comptime `$`-arg node with the caller's source /// file. When the node is later substituted into the (defining-module-pinned) /// metaprogram body and lowered, lowerExpr's per-node source switch resolves /// its bare names in the CALLER's visibility context — not the callee's — so /// a caller-owned helper passed to an imported metaprogram stays visible. /// Only stamps a node with no source yet, and only when the caller context /// is known; an unknown caller source leaves the node's fall-open intact. pub fn stampCallerSource(self: *Lowering, node: *Node) void { if (node.source_file != null) return; if (self.current_source_file) |src| node.source_file = src; } pub fn emitError(self: *Lowering, name: []const u8, span: ?ast.Span) Ref { if (self.diagnostics) |diags| { // The literal message carries the lowering's `current_source_file` // and enclosing function name. The diagnostic renderer's // `source_file` -> `file:line:col` prefix can drift when a span is // offset into one source but the diagnostic falls back to another // (e.g. synthetic AST nodes inserted from `#insert` take their // span from the call site, not from the string being inserted). // Embedding the file + function in the message means a // misattributed span can never hide WHERE the lookup actually // failed. Setting SX_TRACE_UNRESOLVED=1 also dumps a Zig stack // trace at the emit site to surface the calling lowering path. const sf = self.current_source_file orelse ""; const fn_name: []const u8 = if (self.builder.func) |fid| self.module.types.getString(self.module.functions.items[@intFromEnum(fid)].name) else ""; if (std.c.getenv("SX_TRACE_UNRESOLVED") != null) { std.debug.print("\n== unresolved '{s}' (in {s} fn {s}) ==\n", .{ name, sf, fn_name }); std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); } diags.addFmt(.err, span, "unresolved '{s}' (in {s} fn {s})", .{ name, sf, fn_name }); } return self.emitPlaceholder(name); } pub fn emitFieldError(self: *Lowering, obj_ty: TypeId, field: []const u8, span: ast.Span) Ref { // A field access on an already-`.unresolved` object is a cascade from an // upstream type-resolution failure that was ALREADY diagnosed (e.g. an // unresolvable / oversized array dimension). The // `.unresolved` sentinel never exists without an accompanying error, so // piling a second "field not found on unresolved" onto the real one is // pure noise; stay silent and return a placeholder so lowering finishes // and `hasErrors()` aborts the build on the genuine diagnostic. if (obj_ty != .unresolved) { if (self.diagnostics) |diags| { const ty_name = self.formatTypeName(obj_ty); diags.addFmt(.err, span, "field '{s}' not found on type '{s}'", .{ field, ty_name }); } } return self.emitPlaceholder(field); } /// Get the alloca Ref for an expression, if it's a simple variable reference. /// Returns null for complex expressions (field access, function calls, etc.) pub fn getExprAlloca(self: *Lowering, node: *const Node) ?Ref { const name = switch (node.data) { .identifier => |id| id.name, .type_expr => |te| te.name, else => return null, }; if (self.scope) |scope| { if (scope.lookup(name)) |binding| { if (binding.is_alloca) return binding.ref; } } return null; } /// Get the element type for a slice/array/string type. A non-collection /// type has no element type — return `.unresolved` (asking for it is a bug) /// rather than a plausible `.s64`. pub fn getElementType(self: *Lowering, ty: TypeId) TypeId { if (ty == .string) return .u8; if (ty.isBuiltin()) return .unresolved; const info = self.module.types.get(ty); return switch (info) { .slice => |s| s.element, .array => |a| a.element, .vector => |v| v.element, .many_pointer => |p| p.element, else => .unresolved, }; } pub fn isFloat(ty: TypeId) bool { return ty == .f32 or ty == .f64; } /// Result type of an arithmetic / bitwise / shift binary op over two /// scalar operand types. This is the single promotion rule shared by the /// value path (`lowerBinaryOp`) and AST-level inference /// (`ExprTyper.inferType`'s binary-op arm), so static typing reports /// exactly the type the lowered value carries. An integer LHS with a /// floating-point RHS promotes to the float (`s64 + f64` → `f64`); every /// other pairing — including vectors / structs, whose `isInt` is false — /// takes the LHS type. Comparison / logical ops never reach here (they /// are `.bool` at both sites). pub fn arithResultType(lhs_ty: TypeId, rhs_ty: TypeId) TypeId { if (isInt(lhs_ty) and isFloat(rhs_ty)) return rhs_ty; return lhs_ty; } fn isInt(ty: TypeId) bool { return switch (ty) { .s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize => true, else => false, }; } /// Carry-rule resolution outcome for a namespace alias, diagnostic-free. pub const AliasVerdict = union(enum) { /// No edge anywhere visible from the current file binds this alias. none, /// ≥2 DIRECT flat imports carry the alias to DISTINCT targets. ambiguous, /// The alias resolves — own edge, or carried over one flat hop. target: imports_mod.NamespaceTarget, }; /// Resolve a namespace alias visible from the current source file under /// the carry rule: the file's OWN `ns :: #import` edge wins; otherwise an /// alias declared by a DIRECT flat import is carried (one level — flat /// edges of flat edges do not chain). Two distinct carried targets for /// the same alias are ambiguous. pub fn namespaceAliasVerdict(self: *Lowering, alias: []const u8) AliasVerdict { const edges = self.program_index.namespace_edges orelse return .none; const from = self.current_source_file orelse return .none; if (edges.getPtr(from)) |own| { if (own.get(alias)) |t| return .{ .target = t }; } const flat = self.program_index.flat_import_graph orelse return .none; const direct = flat.get(from) orelse return .none; var found: ?imports_mod.NamespaceTarget = null; var it = direct.keyIterator(); while (it.next()) |dep| { const dep_edges = edges.getPtr(dep.*) orelse continue; const t = dep_edges.get(alias) orelse continue; if (found) |f| { if (!std.mem.eql(u8, f.target_module_path, t.target_module_path)) return .ambiguous; } else found = t; } return if (found) |f| .{ .target = f } else .none; } /// `namespaceAliasVerdict` with the ambiguity diagnosed in place; callers /// that don't distinguish ambiguous-from-missing use this form. pub fn namespaceAliasTarget(self: *Lowering, alias: []const u8, span: ?ast.Span) ?imports_mod.NamespaceTarget { switch (self.namespaceAliasVerdict(alias)) { .target => |t| return t, .ambiguous => { if (self.diagnostics) |d| { d.addFmt(.err, span, "namespace '{s}' is ambiguous: aliases from multiple flat-imported modules point at different targets; declare the alias locally", .{alias}); } return null; }, .none => return null, } } /// True when ANY module in the program declares `alias` as a namespace /// edge — distinguishes a not-visible alias (gate error) from a name that /// was never an alias at all (fall through to other resolution). pub fn aliasDeclaredAnywhere(self: *Lowering, alias: []const u8) bool { const edges = self.program_index.namespace_edges orelse return false; var it = edges.valueIterator(); while (it.next()) |per_file| { if (per_file.contains(alias)) return true; } return false; } /// The target module's own fn member named `name` — a top-level fn decl /// or a const-wrapped fn (the same surface `registerNamespaceQualifiedFns` /// registers). Null when the member is absent or not a function. pub fn namespaceFnMember(target: *const imports_mod.NamespaceTarget, name: []const u8) ?*const ast.FnDecl { for (target.own_decls) |decl| { switch (decl.data) { .fn_decl => |*fd| if (std.mem.eql(u8, fd.name, name)) return fd, .const_decl => |*cd| if (std.mem.eql(u8, cd.name, name) and cd.value.data == .fn_decl) return &cd.value.data.fn_decl, else => {}, } } return null; } /// The inner member name when `node` is a namespace-rooted prefix /// (`alias.Member`) — the shape a qualified type/static head takes after /// stripping the alias. Null when `node` isn't that shape. pub fn namespaceRootedMember(self: *Lowering, node: *const Node) ?[]const u8 { if (node.data != .field_access) return null; const fa = node.data.field_access; const root = switch (fa.object.data) { .identifier => |id| id.name, else => return null, }; // A value binding shadows a same-named namespace alias. if (self.scope) |s| { if (s.lookup(root) != null) return null; } if (self.program_index.global_names.contains(root)) return null; if (self.namespaceAliasTarget(root, node.span) == null) return null; return fa.field; } pub fn isIntEx(self: *Lowering, ty: TypeId) bool { if (isInt(ty)) return true; if (!ty.isBuiltin()) { const info = self.module.types.get(ty); return switch (info) { .signed, .unsigned => true, else => false, }; } return false; } /// Value range of an integer type, for literal fits-checks. Null for /// 64-bit types — every i64 literal bit pattern is legal there (a 64-bit /// hex literal wraps negative through the lexer's i64 value, so a /// min/max check would false-positive) — and for non-integers. pub fn intLiteralRange(self: *Lowering, ty: TypeId) ?struct { min: i64, max: i64 } { var width: u8 = 0; var is_signed = false; switch (ty) { .s8 => { width = 8; is_signed = true; }, .s16 => { width = 16; is_signed = true; }, .s32 => { width = 32; is_signed = true; }, .u8 => width = 8, .u16 => width = 16, .u32 => width = 32, else => { if (ty.isBuiltin()) return null; // s64/u64/isize/usize/non-int switch (self.module.types.get(ty)) { .signed => |w| { width = w; is_signed = true; }, .unsigned => |w| width = w, else => return null, } if (width >= 64) return null; }, } if (is_signed) { const max = (@as(i64, 1) << @intCast(width - 1)) - 1; return .{ .min = -max - 1, .max = max }; } const max = (@as(i64, 1) << @intCast(width)) - 1; return .{ .min = 0, .max = max }; } /// Diagnose an integer literal that cannot be represented in `ty` /// (REJECTED PATTERNS: no silent wrap). The constant is still emitted by /// the caller so lowering continues and surfaces further errors. pub fn checkIntLiteralFits(self: *Lowering, value: i64, ty: TypeId, span: ast.Span) void { if (self.suppress_int_fit_check) return; const r = self.intLiteralRange(ty) orelse return; if (value < r.min or value > r.max) { if (self.diagnostics) |d| { // Custom-width ints are structural (unnamed in the type // table) — render them as s{N}/u{N}. var name_buf: [8]u8 = undefined; const tn = blk: { if (ty.isBuiltin()) break :blk self.module.types.typeName(ty); break :blk switch (self.module.types.get(ty)) { .signed => |w| std.fmt.bufPrint(&name_buf, "s{d}", .{w}) catch "integer", .unsigned => |w| std.fmt.bufPrint(&name_buf, "u{d}", .{w}) catch "integer", else => self.module.types.typeName(ty), }; }; d.addFmt(.err, span, "integer literal {} does not fit in {s} (range {}..{}) — use an explicit `xx` / `cast` to truncate", .{ value, tn, r.min, r.max }); } } } /// Operands valid for a scalar numeric op (`+ - * / %`): ints (incl. /// custom widths), floats, SIMD vectors, and pointers (pointer /// arithmetic). `.unresolved` returns true so a type we couldn't infer /// is never diagnosed — the check only fires on a concretely /// incompatible operand (e.g. `string`, a struct, an enum). pub fn isArithOperand(self: *Lowering, ty: TypeId) bool { if (ty == .unresolved) return true; if (isInt(ty) or isFloat(ty)) return true; if (ty.isBuiltin()) return false; return switch (self.module.types.get(ty)) { .signed, .unsigned, .vector, .pointer, .many_pointer => true, else => false, }; } /// Operands valid for ordering comparisons (`< <= > >=`): numbers /// (incl. custom int widths), enums (ordinal), pointers (address /// order), bool, and SIMD vectors. NOT strings (no lexicographic `<` /// lowering exists) or any other aggregate. `.unresolved` passes so an /// un-inferable operand is never falsely diagnosed. pub fn isOrderingOperand(self: *Lowering, ty: TypeId) bool { if (ty == .unresolved) return true; if (isInt(ty) or isFloat(ty) or ty == .bool) return true; if (ty.isBuiltin()) return false; return switch (self.module.types.get(ty)) { .signed, .unsigned, .@"enum", .pointer, .many_pointer, .vector => true, else => false, }; } /// Operands valid for bitwise/shift ops (`& | ^ << >>`): integers /// (incl. custom widths), enums (flags are int-backed), bool, and SIMD /// vectors. NOT floats, strings, pointers, or aggregates. `.unresolved` /// passes (see `isOrderingOperand`). pub fn isBitwiseOperand(self: *Lowering, ty: TypeId) bool { if (ty == .unresolved) return true; if (isInt(ty) or ty == .bool) return true; if (ty.isBuiltin()) return false; return switch (self.module.types.get(ty)) { .signed, .unsigned, .@"enum", .vector => true, else => false, }; } /// Human-readable description of a typed module-const initializer, used in /// the typed-const type-mismatch diagnostic. A literal names its kind; a /// const-expression is described by its inferred type category, so the /// message is accurate for `N : string : M + 2` ("an integer expression") /// as well as for `N : string : 4` ("an integer literal"). pub fn initializerDescription(self: *Lowering, node: *const Node) []const u8 { return switch (node.data) { .int_literal => "an integer literal", .float_literal => "a float literal", .bool_literal => "a boolean literal", .string_literal => "a string literal", .null_literal => "null", .undef_literal => "'---'", else => self.constExprDescription(self.inferExprType(node)), }; } fn constExprDescription(self: *Lowering, init_ty: TypeId) []const u8 { if (self.isIntEx(init_ty)) return "an integer expression"; if (isFloat(init_ty)) return "a floating-point expression"; if (init_ty == .bool) return "a boolean expression"; if (init_ty == .string) return "a string expression"; return "an expression of an incompatible type"; } pub fn binOpSymbol(op: ast.BinaryOp.Op) []const u8 { return switch (op) { .add => "+", .sub => "-", .mul => "*", .div => "/", .mod => "%", .eq => "==", .neq => "!=", .lt => "<", .lte => "<=", .gt => ">", .gte => ">=", .and_op => "and", .or_op => "or", .bit_and => "&", .bit_or => "|", .bit_xor => "^", .shl => "<<", .shr => ">>", .in_op => "in", }; } fn typeBits(ty: TypeId) u32 { return switch (ty) { .bool => 1, .s8, .u8 => 8, .s16, .u16 => 16, .s32, .u32 => 32, .s64, .u64 => 64, .usize, .isize => 0, // target-dependent — use typeBitsEx .f32 => 32, .f64 => 64, else => 0, }; } pub fn typeBitsEx(self: *Lowering, ty: TypeId) u32 { if (ty == .usize or ty == .isize) return @as(u32, self.module.types.pointer_size) * 8; const b = typeBits(ty); if (b > 0) return b; if (!ty.isBuiltin()) { const info = self.module.types.get(ty); return switch (info) { .signed => |w| @as(u32, w), .unsigned => |w| @as(u32, w), else => 0, }; } return 0; } // --- moved to lower/error.zig (lower_error) --- pub const getTraceFids = lower_error.getTraceFids; pub const tracesEnabled = lower_error.tracesEnabled; pub const emitTracePush = lower_error.emitTracePush; pub const emitTraceClear = lower_error.emitTraceClear; pub const placeholderTraceFrame = lower_error.placeholderTraceFrame; pub const errorSetTypeOf = lower_error.errorSetTypeOf; pub const isErrorTagLiteralNode = lower_error.isErrorTagLiteralNode; pub const tryLowerErrorSetEquality = lower_error.tryLowerErrorSetEquality; pub const effectiveReturnType = lower_error.effectiveReturnType; pub const errorChannelOf = lower_error.errorChannelOf; pub const isInferredErrorSet = lower_error.isInferredErrorSet; pub const checkErrorSetSubset = lower_error.checkErrorSetSubset; pub const diagTagsNotInSet = lower_error.diagTagsNotInSet; pub const lowerRaise = lower_error.lowerRaise; pub const lowerFailableSuccessReturn = lower_error.lowerFailableSuccessReturn; pub const buildFailableTuple = lower_error.buildFailableTuple; pub const failableSuccessType = lower_error.failableSuccessType; pub const failableReturnTarget = lower_error.failableReturnTarget; pub const extractSuccessValue = lower_error.extractSuccessValue; pub const extractErrorSlot = lower_error.extractErrorSlot; pub const emitTupleRet = lower_error.emitTupleRet; pub const diagRaiseNotFailable = lower_error.diagRaiseNotFailable; pub const exprIsFailable = lower_error.exprIsFailable; pub const lowerCallerLocation = lower_error.lowerCallerLocation; pub const sourceForFile = lower_error.sourceForFile; pub const currentFunctionName = lower_error.currentFunctionName; pub const lowerTry = lower_error.lowerTry; pub const emitErrorReturn = lower_error.emitErrorReturn; pub const diagTryNotFailable = lower_error.diagTryNotFailable; pub const lowerCatch = lower_error.lowerCatch; pub const lowerCatchOverChain = lower_error.lowerCatchOverChain; pub const finishCatchHandler = lower_error.finishCatchHandler; pub const runCatchBody = lower_error.runCatchBody; pub const checkEscapeWidening = lower_error.checkEscapeWidening; pub const orIsFailableChain = lower_error.orIsFailableChain; pub const operandIsFailableLike = lower_error.operandIsFailableLike; pub const orChainSuccessType = lower_error.orChainSuccessType; pub const unwrapTryNode = lower_error.unwrapTryNode; pub const flattenOrChain = lower_error.flattenOrChain; pub const lowerFailableOr = lower_error.lowerFailableOr; pub const callTargetName = lower_error.callTargetName; pub const astIsPureBareInferred = lower_error.astIsPureBareInferred; pub const astPureNamedSet = lower_error.astPureNamedSet; pub const namedSetTags = lower_error.namedSetTags; pub const convergeInferredErrorSets = lower_error.convergeInferredErrorSets; pub const containsTag = lower_error.containsTag; pub const convergeClosureShapeSets = lower_error.convergeClosureShapeSets; pub const recordClosureShape = lower_error.recordClosureShape; pub const calleeEscapeTags = lower_error.calleeEscapeTags; pub const unionShapeTags = lower_error.unionShapeTags; pub const closureShapeKey = lower_error.closureShapeKey; pub const returnValuePart = lower_error.returnValuePart; pub const shapeKeyOfCallee = lower_error.shapeKeyOfCallee; // --- moved to lower/comptime.zig (lower_comptime) --- pub const SelectedConst = lower_comptime.SelectedConst; pub const evalComptimeCondition = lower_comptime.evalComptimeCondition; pub const evalComptimeMatch = lower_comptime.evalComptimeMatch; pub const evalComptimeInt = lower_comptime.evalComptimeInt; pub const evalComptimeString = lower_comptime.evalComptimeString; pub const lowerComptimeGlobal = lower_comptime.lowerComptimeGlobal; pub const lowerComptimeSideEffect = lower_comptime.lowerComptimeSideEffect; pub const lowerComptimeCall = lower_comptime.lowerComptimeCall; pub const lowerInlineComptime = lower_comptime.lowerInlineComptime; pub const lowerInsertExpr = lower_comptime.lowerInsertExpr; pub const lowerInsertExprValue = lower_comptime.lowerInsertExprValue; pub const lowerComptimeDeps = lower_comptime.lowerComptimeDeps; pub const substituteComptimeNodes = lower_comptime.substituteComptimeNodes; pub const fnBodyHasReturn = lower_comptime.fnBodyHasReturn; pub const createComptimeFunction = lower_comptime.createComptimeFunction; pub const constExprValue = lower_comptime.constExprValue; pub const constArrayLiteral = lower_comptime.constArrayLiteral; pub const constStructLiteral = lower_comptime.constStructLiteral; pub const constEnumLiteral = lower_comptime.constEnumLiteral; pub const foldSourceConstInt = lower_comptime.foldSourceConstInt; pub const foldSourceConstFloat = lower_comptime.foldSourceConstFloat; pub const sourceConstIsFloatTyped = lower_comptime.sourceConstIsFloatTyped; pub const comptimeIntNamed = lower_comptime.comptimeIntNamed; pub const selectModuleConst = lower_comptime.selectModuleConst; pub const GlobalAuthor = lower_comptime.GlobalAuthor; pub const selectGlobalAuthor = lower_comptime.selectGlobalAuthor; pub const resolveGlobalRef = lower_comptime.resolveGlobalRef; pub const sourceModuleConst = lower_comptime.sourceModuleConst; pub const pinConstAuthorSource = lower_comptime.pinConstAuthorSource; pub const foldComptimeFloatInit = lower_comptime.foldComptimeFloatInit; // --- moved to lower/stmt.zig (lower_stmt) --- pub const lowerBlock = lower_stmt.lowerBlock; pub const lowerInlineBranch = lower_stmt.lowerInlineBranch; pub const lowerBlockValue = lower_stmt.lowerBlockValue; pub const lowerValueBody = lower_stmt.lowerValueBody; pub const tryLowerAsExpr = lower_stmt.tryLowerAsExpr; pub const lowerStmt = lower_stmt.lowerStmt; pub const lowerVarDecl = lower_stmt.lowerVarDecl; pub const lowerLocalFnDecl = lower_stmt.lowerLocalFnDecl; pub const lowerConstDecl = lower_stmt.lowerConstDecl; pub const lowerReturn = lower_stmt.lowerReturn; pub const lowerAssignment = lower_stmt.lowerAssignment; pub const fieldLvaluePtr = lower_stmt.fieldLvaluePtr; pub const lowerExprAsPtr = lower_stmt.lowerExprAsPtr; pub const storeOrCompound = lower_stmt.storeOrCompound; pub const emitCompoundOp = lower_stmt.emitCompoundOp; pub const lowerMultiAssign = lower_stmt.lowerMultiAssign; pub const lowerDestructureDecl = lower_stmt.lowerDestructureDecl; pub const lowerPush = lower_stmt.lowerPush; pub const lowerDefer = lower_stmt.lowerDefer; pub const lowerOnFail = lower_stmt.lowerOnFail; pub const diagOnFailNotFailable = lower_stmt.diagOnFailNotFailable; pub const emitBlockDefers = lower_stmt.emitBlockDefers; pub const emitLoopExitDefers = lower_stmt.emitLoopExitDefers; pub const lowerCleanupBody = lower_stmt.lowerCleanupBody; pub const emitErrorCleanup = lower_stmt.emitErrorCleanup; // --- moved to lower/control_flow.zig (lower_control_flow) --- pub const lowerIfExpr = lower_control_flow.lowerIfExpr; pub const tryConstBoolCondition = lower_control_flow.tryConstBoolCondition; pub const lowerWhile = lower_control_flow.lowerWhile; pub const listView = lower_control_flow.listView; pub const lowerFor = lower_control_flow.lowerFor; pub const lowerInlineRangeFor = lower_control_flow.lowerInlineRangeFor; pub const lowerMatch = lower_control_flow.lowerMatch; pub const lowerBreak = lower_control_flow.lowerBreak; pub const lowerContinue = lower_control_flow.lowerContinue; pub const freshBlock = lower_control_flow.freshBlock; pub const freshBlockWithParams = lower_control_flow.freshBlockWithParams; pub const currentBlockHasTerminator = lower_control_flow.currentBlockHasTerminator; pub const ensureTerminator = lower_control_flow.ensureTerminator; // --- moved to lower/decl.zig (lower_decl) --- pub const SelectedFunc = lower_decl.SelectedFunc; pub const BareCallee = lower_decl.BareCallee; pub const VisibleStructAuthor = lower_decl.VisibleStructAuthor; pub const lowerRoot = lower_decl.lowerRoot; pub const validateMainSignature = lower_decl.validateMainSignature; pub const checkRequiredEntryPoints = lower_decl.checkRequiredEntryPoints; pub const injectComptimeConstants = lower_decl.injectComptimeConstants; pub const findVariantIndex = lower_decl.findVariantIndex; pub const lowerDeferredTypeFns = lower_decl.lowerDeferredTypeFns; pub const lowerDecls = lower_decl.lowerDecls; pub const detectContextDecl = lower_decl.detectContextDecl; pub const funcWantsImplicitCtx = lower_decl.funcWantsImplicitCtx; pub const fnPtrTypeWantsCtx = lower_decl.fnPtrTypeWantsCtx; pub const scanDecls = lower_decl.scanDecls; pub const registerTypedModuleConst = lower_decl.registerTypedModuleConst; pub const typedConstInitFits = lower_decl.typedConstInitFits; pub const constExprInitFits = lower_decl.constExprInitFits; pub const registerTopLevelGlobal = lower_decl.registerTopLevelGlobal; pub const globalInitValue = lower_decl.globalInitValue; pub const diagnoseNonConstGlobal = lower_decl.diagnoseNonConstGlobal; pub const resolveForwardIdentifierAliases = lower_decl.resolveForwardIdentifierAliases; pub const aliasResolvedInSource = lower_decl.aliasResolvedInSource; pub const declareFunction = lower_decl.declareFunction; pub const registerNamespaceQualifiedFns = lower_decl.registerNamespaceQualifiedFns; pub const registerQualifiedFn = lower_decl.registerQualifiedFn; pub const isVisible = lower_decl.isVisible; pub const visibleOverEdges = lower_decl.visibleOverEdges; pub const isCImportVisible = lower_decl.isCImportVisible; pub const isNameVisible = lower_decl.isNameVisible; pub const lazyLowerFunction = lower_decl.lazyLowerFunction; pub const lowerFunctionBodyInto = lower_decl.lowerFunctionBodyInto; pub const lowerFunction = lower_decl.lowerFunction; pub const lowerMainAndComptime = lower_decl.lowerMainAndComptime; pub const lowerRetainedSameNameAuthors = lower_decl.lowerRetainedSameNameAuthors; pub const selectPlainCallableAuthor = lower_decl.selectPlainCallableAuthor; pub const selectNominalLeaf = lower_decl.selectNominalLeaf; pub const isNamedTypeKind = lower_decl.isNamedTypeKind; pub const namedRefTid = lower_decl.namedRefTid; pub const nameAuthoredAsTypeAnywhere = lower_decl.nameAuthoredAsTypeAnywhere; pub const recordLocalTypeName = lower_decl.recordLocalTypeName; pub const localTypeInSource = lower_decl.localTypeInSource; pub const localTypeInAnySource = lower_decl.localTypeInAnySource; pub const resolveNominalLeaf = lower_decl.resolveNominalLeaf; pub const fnDeclOfRaw = lower_decl.fnDeclOfRaw; pub const structDeclOfRaw = lower_decl.structDeclOfRaw; pub const structMethodFn = lower_decl.structMethodFn; pub const typeFnAuthor = lower_decl.typeFnAuthor; pub const selectedFuncId = lower_decl.selectedFuncId; pub const bareAuthorFuncId = lower_decl.bareAuthorFuncId; pub const putTypeAlias = lower_decl.putTypeAlias; pub const putModuleConst = lower_decl.putModuleConst; pub const putGlobal = lower_decl.putGlobal; pub const dropModuleConst = lower_decl.dropModuleConst; pub const emitModuleConst = lower_decl.emitModuleConst; pub const emitPlaceholder = lower_decl.emitPlaceholder; // --- moved to lower/nominal.zig (lower_nominal) --- pub const registerErrorSetDecl = lower_nominal.registerErrorSetDecl; pub const registerStructDecl = lower_nominal.registerStructDecl; pub const registerEnumDecl = lower_nominal.registerEnumDecl; pub const registerUnionDecl = lower_nominal.registerUnionDecl; pub const qualifyAnonType = lower_nominal.qualifyAnonType; pub const nominalIdOf = lower_nominal.nominalIdOf; pub const stampNominalId = lower_nominal.stampNominalId; pub const reserveShadowStructSlot = lower_nominal.reserveShadowStructSlot; pub const reserveShadowEnumSlot = lower_nominal.reserveShadowEnumSlot; pub const reserveShadowUnionSlot = lower_nominal.reserveShadowUnionSlot; pub const topLevelTypeDecl = lower_nominal.topLevelTypeDecl; pub const reserveShadowSlot = lower_nominal.reserveShadowSlot; pub const internNamedTypeDecl = lower_nominal.internNamedTypeDecl; pub const adoptsForwardStructStub = lower_nominal.adoptsForwardStructStub; pub const shadowNominalId = lower_nominal.shadowNominalId; pub const nameHasMultipleTypeAuthors = lower_nominal.nameHasMultipleTypeAuthors; pub const rawNamedTypePtr = lower_nominal.rawNamedTypePtr; pub const buildGenericStructTemplate = lower_nominal.buildGenericStructTemplate; pub const qualifiedStructTemplate = lower_nominal.qualifiedStructTemplate; pub const qualifiedMemberMissing = lower_nominal.qualifiedMemberMissing; pub const bareVisibleStructDecl = lower_nominal.bareVisibleStructDecl; pub const bareVisibleStructTemplate = lower_nominal.bareVisibleStructTemplate; pub const registerGenericStructAlias = lower_nominal.registerGenericStructAlias; // --- moved to lower/protocol.zig (lower_protocol) --- pub const ProjectionPosition = lower_pack.ProjectionPosition; pub const PackProjection = lower_pack.PackProjection; pub const registerProtocolDecl = lower_protocol.registerProtocolDecl; pub const instantiateParamProtocol = lower_protocol.instantiateParamProtocol; pub const lookupProtocolArg = lower_protocol.lookupProtocolArg; pub const lookupProtocolField = lower_protocol.lookupProtocolField; pub const isProtocolType = lower_protocol.isProtocolType; pub const getProtocolInfo = lower_protocol.getProtocolInfo; pub const getOrCreateThunks = lower_protocol.getOrCreateThunks; pub const emitDefaultContextGlobal = lower_protocol.emitDefaultContextGlobal; pub const createProtocolThunk = lower_protocol.createProtocolThunk; pub const buildProtocolValue = lower_protocol.buildProtocolValue; pub const emitProtocolDispatch = lower_protocol.emitProtocolDispatch; pub const resolveConcreteTypeName = lower_protocol.resolveConcreteTypeName; pub const computeHasImpl = lower_protocol.computeHasImpl; // --- moved to lower/coerce.zig (lower_coerce) --- pub const lowerXX = lower_coerce.lowerXX; pub const isClosureToBlockCast = lower_coerce.isClosureToBlockCast; pub const tryPackImplMatch = lower_coerce.tryPackImplMatch; pub const tryUserConversion = lower_coerce.tryUserConversion; pub const isLvalueExpr = lower_coerce.isLvalueExpr; pub const coerceOrErase = lower_coerce.coerceOrErase; pub const buildProtocolErasure = lower_coerce.buildProtocolErasure; pub const inferConcreteTypeName = lower_coerce.inferConcreteTypeName; pub const lowerAnyToF64Dispatch = lower_coerce.lowerAnyToF64Dispatch; pub const buildDefaultValue = lower_coerce.buildDefaultValue; pub const optionalOfFlattened = lower_coerce.optionalOfFlattened; pub const zeroValue = lower_coerce.zeroValue; pub const lowerCoercedDefault = lower_coerce.lowerCoercedDefault; pub const coerceToType = lower_coerce.coerceToType; pub const coerceExplicit = lower_coerce.coerceExplicit; pub const coerceMode = lower_coerce.coerceMode; pub const diagNonIntegralNarrow = lower_coerce.diagNonIntegralNarrow; pub const promoteCVariadicArgs = lower_coerce.promoteCVariadicArgs; pub const coerceCallArgs = lower_coerce.coerceCallArgs; // --- moved to lower/ffi.zig (lower_ffi) --- pub const internObjcSelector = lower_ffi.internObjcSelector; pub const internObjcClassObject = lower_ffi.internObjcClassObject; pub const getSelRegisterNameFid = lower_ffi.getSelRegisterNameFid; pub const lowerFfiIntrinsicCall = lower_ffi.lowerFfiIntrinsicCall; pub const lowerJniCall = lower_ffi.lowerJniCall; pub const lowerForeignMethodCall = lower_ffi.lowerForeignMethodCall; pub const resolveForeignClassMemberType = lower_ffi.resolveForeignClassMemberType; pub const resolveForeignMethodReturnType = lower_ffi.resolveForeignMethodReturnType; pub const foreignClassStructType = lower_ffi.foreignClassStructType; pub const lowerObjcMethodCall = lower_ffi.lowerObjcMethodCall; pub const lowerObjcStaticCall = lower_ffi.lowerObjcStaticCall; pub const lowerForeignStaticCall = lower_ffi.lowerForeignStaticCall; pub const lowerSuperCall = lower_ffi.lowerSuperCall; pub const registerForeignClassDecl = lower_ffi.registerForeignClassDecl; pub const resolveObjcParentName = lower_ffi.resolveObjcParentName; pub const declareObjcDefinedStateIvarGlobal = lower_ffi.declareObjcDefinedStateIvarGlobal; pub const declareObjcDefinedClassGlobal = lower_ffi.declareObjcDefinedClassGlobal; pub const registerObjcDefinedClassMethods = lower_ffi.registerObjcDefinedClassMethods; pub const synthesizeFnDeclFromObjcMethod = lower_ffi.synthesizeFnDeclFromObjcMethod; pub const lookupObjcDefinedClassForMethod = lower_ffi.lookupObjcDefinedClassForMethod; pub const getJniEnvTlFids = lower_ffi.getJniEnvTlFids; pub const registerNamespacedForeignClasses = lower_ffi.registerNamespacedForeignClasses; pub const synthesizeJniMainStubs = lower_ffi.synthesizeJniMainStubs; pub const synthesizeJniMainStub = lower_ffi.synthesizeJniMainStub; // --- moved to lower/objc_class.zig (lower_objc_class) --- pub const lowerObjcDefinedClassMethods = lower_objc_class.lowerObjcDefinedClassMethods; pub const lookupObjcPropertyOnPointer = lower_objc_class.lookupObjcPropertyOnPointer; pub const findForeignMethodInChain = lower_objc_class.findForeignMethodInChain; pub const findForeignPropertyInChain = lower_objc_class.findForeignPropertyInChain; pub const lookupObjcDefinedStateFieldOnPointer = lower_objc_class.lookupObjcDefinedStateFieldOnPointer; pub const lowerObjcDefinedStateFieldRead = lower_objc_class.lowerObjcDefinedStateFieldRead; pub const lowerObjcDefinedStateForObj = lower_objc_class.lowerObjcDefinedStateForObj; pub const lowerObjcPropertyGetter = lower_objc_class.lowerObjcPropertyGetter; pub const lowerObjcPropertySetter = lower_objc_class.lowerObjcPropertySetter; pub const ensureCRuntimeDecl = lower_objc_class.ensureCRuntimeDecl; pub const ensureArcRuntimeDecls = lower_objc_class.ensureArcRuntimeDecls; pub const emitObjcDefinedClassImps = lower_objc_class.emitObjcDefinedClassImps; pub const emitObjcDefinedClassPropertyImps = lower_objc_class.emitObjcDefinedClassPropertyImps; pub const emitObjcDefinedPropertyGetter = lower_objc_class.emitObjcDefinedPropertyGetter; pub const emitObjcDefinedPropertySetter = lower_objc_class.emitObjcDefinedPropertySetter; pub const registerObjcDefinedPropertyMethodEntries = lower_objc_class.registerObjcDefinedPropertyMethodEntries; pub const emitObjcDefinedClassImp = lower_objc_class.emitObjcDefinedClassImp; pub const emitObjcDefinedClassAllocImp = lower_objc_class.emitObjcDefinedClassAllocImp; pub const emitObjcDefinedAllocAndInit = lower_objc_class.emitObjcDefinedAllocAndInit; pub const emitObjcDefinedClassStaticImp = lower_objc_class.emitObjcDefinedClassStaticImp; pub const emitObjcDefinedClassDeallocImp = lower_objc_class.emitObjcDefinedClassDeallocImp; pub const internStringConstantGlobal = lower_objc_class.internStringConstantGlobal; pub const lookupGlobalIdByName = lower_objc_class.lookupGlobalIdByName; // --- moved to lower/call.zig (lower_call) --- pub const CaptureInfo = lower_closure.CaptureInfo; pub const lowerCall = lower_call.lowerCall; pub const diagnoseMissingContext = lower_call.diagnoseMissingContext; pub const allocViaContext = lower_call.allocViaContext; pub const callForeign = lower_call.callForeign; pub const prependCtxIfNeeded = lower_call.prependCtxIfNeeded; pub const resolveFuncByName = lower_call.resolveFuncByName; pub const resolveBuiltin = lower_call.resolveBuiltin; pub const lowerGenericCall = lower_call.lowerGenericCall; pub const hasCastWithRuntimeType = lower_call.hasCastWithRuntimeType; pub const lowerRuntimeDispatchCall = lower_call.lowerRuntimeDispatchCall; pub const tryLowerReflectionCall = lower_call.tryLowerReflectionCall; pub const reflectionArgIsType = lower_call.reflectionArgIsType; pub const reflectionTypeArgGuard = lower_call.reflectionTypeArgGuard; pub const reflectionErrorSentinel = lower_call.reflectionErrorSentinel; pub const appendDefaultArgs = lower_call.appendDefaultArgs; pub const expandCallDefaults = lower_call.expandCallDefaults; pub const userParamTypes = lower_call.userParamTypes; pub const resolveCallParamTypes = lower_call.resolveCallParamTypes; // --- moved to lower/pack.zig (lower_pack) --- pub const lowerPackElems = lower_pack.lowerPackElems; pub const lowerPackValueProjection = lower_pack.lowerPackValueProjection; pub const packSpreadRefs = lower_pack.packSpreadRefs; pub const diagPackIndexOOB = lower_pack.diagPackIndexOOB; pub const packArgNodeAt = lower_pack.packArgNodeAt; pub const comptimeIndexOf = lower_pack.comptimeIndexOf; pub const diagPackAsValue = lower_pack.diagPackAsValue; pub const isPackName = lower_pack.isPackName; pub const lowerPackToSlice = lower_pack.lowerPackToSlice; pub const lowerVariadicArgs = lower_pack.lowerVariadicArgs; pub const packVariadicCallArgs = lower_pack.packVariadicCallArgs; pub const buildPackSliceValue = lower_pack.buildPackSliceValue; pub const materialisePackSlice = lower_pack.materialisePackSlice; pub const inferPackBodyReturnType = lower_pack.inferPackBodyReturnType; pub const lowerPackFnCall = lower_pack.lowerPackFnCall; pub const monomorphizePackFn = lower_pack.monomorphizePackFn; pub const resolvePackProjection = lower_pack.resolvePackProjection; pub const isPackFn = lower_pack.isPackFn; pub const isPackParam = lower_pack.isPackParam; // --- moved to lower/generic.zig (lower_generic) --- pub const monomorphizeFunction = lower_generic.monomorphizeFunction; pub const instantiateGenericStruct = lower_generic.instantiateGenericStruct; pub const instantiateTypeFunction = lower_generic.instantiateTypeFunction; pub const instantiateTypeUnion = lower_generic.instantiateTypeUnion; pub const findStructInBody = lower_generic.findStructInBody; pub const findUnionInBody = lower_generic.findUnionInBody; pub const findReturnTypeExpr = lower_generic.findReturnTypeExpr; pub const genericInstanceMethod = lower_generic.genericInstanceMethod; pub const ensureGenericInstanceMethodLowered = lower_generic.ensureGenericInstanceMethodLowered; pub const assertInstanceMapsCoincide = lower_generic.assertInstanceMapsCoincide; pub const isStaticTypeArg = lower_generic.isStaticTypeArg; pub const isStaticTypeRef = lower_generic.isStaticTypeRef; pub const resolveTupleLiteralTypeArg = lower_generic.resolveTupleLiteralTypeArg; pub const resolveTypeArg = lower_generic.resolveTypeArg; pub const formatTypeName = lower_generic.formatTypeName; pub const formatFnTypeString = lower_generic.formatFnTypeString; pub const matchTypeParam = lower_generic.matchTypeParam; pub const matchTypeParamStatic = lower_generic.matchTypeParamStatic; pub const extractTypeParam = lower_generic.extractTypeParam; pub const mangleTypeName = lower_generic.mangleTypeName; pub const resolveTypeCategoryTags = lower_generic.resolveTypeCategoryTags; pub const inferMatchResultType = lower_generic.inferMatchResultType; pub const isTypeCategoryMatch = lower_generic.isTypeCategoryMatch; pub const isTypeParamDecl = lower_generic.isTypeParamDecl; pub const hasComptimeParams = lower_generic.hasComptimeParams; pub const isPlainFreeFn = lower_generic.isPlainFreeFn; pub const selectGenericStructHead = lower_generic.selectGenericStructHead; pub const headTypeLeak = lower_generic.headTypeLeak; pub const headNameOfCallee = lower_generic.headNameOfCallee; pub const headTypeGate = lower_generic.headTypeGate; pub const headFnLeak = lower_generic.headFnLeak; pub const flatFnAuthorAmbiguous = lower_generic.flatFnAuthorAmbiguous; pub const flatFnAuthorVisible = lower_generic.flatFnAuthorVisible; pub const resolveTypeCallWithBindings = lower_generic.resolveTypeCallWithBindings; pub const resolveParameterizedWithBindings = lower_generic.resolveParameterizedWithBindings; pub const resolveValueParamArg = lower_generic.resolveValueParamArg; pub const canonicalIntConstraintName = lower_generic.canonicalIntConstraintName; pub const diagValueParamNotConst = lower_generic.diagValueParamNotConst; pub const diagValueParamRange = lower_generic.diagValueParamRange; // --- moved to lower/expr.zig (lower_expr) --- pub const lowerStructLiteral = lower_expr.lowerStructLiteral; pub const lowerInitBlock = lower_expr.lowerInitBlock; pub const getStructFields = lower_expr.getStructFields; pub const fixupMethodReceiver = lower_expr.fixupMethodReceiver; pub const getStructTypeName = lower_expr.getStructTypeName; pub const builtinTypeName = lower_expr.builtinTypeName; pub const resolveFieldType = lower_expr.resolveFieldType; pub const lowerFieldAccess = lower_expr.lowerFieldAccess; pub const identifierBindsValue = lower_expr.identifierBindsValue; pub const lowerNumericLimit = lower_expr.lowerNumericLimit; pub const lowerStructConstant = lower_expr.lowerStructConstant; pub const lowerOptionalChain = lower_expr.lowerOptionalChain; pub const vectorLaneIndex = lower_expr.vectorLaneIndex; pub const lowerFieldAccessOnType = lower_expr.lowerFieldAccessOnType; pub const lowerEnumLiteral = lower_expr.lowerEnumLiteral; pub const lowerErrorTagLiteral = lower_expr.lowerErrorTagLiteral; pub const lowerTaggedEnumLiteral = lower_expr.lowerTaggedEnumLiteral; pub const findTaggedVariant = lower_expr.findTaggedVariant; pub const emitBadVariant = lower_expr.emitBadVariant; pub const resolveVariantValue = lower_expr.resolveVariantValue; pub const resolveVariantIndex = lower_expr.resolveVariantIndex; pub const lowerArrayLiteral = lower_expr.lowerArrayLiteral; pub const resolveArrayLiteralType = lower_expr.resolveArrayLiteralType; pub const lowerIndexExpr = lower_expr.lowerIndexExpr; pub const lowerSliceExpr = lower_expr.lowerSliceExpr; pub const lowerTupleLiteral = lower_expr.lowerTupleLiteral; pub const lowerDerefExpr = lower_expr.lowerDerefExpr; pub const lowerForceUnwrap = lower_expr.lowerForceUnwrap; pub const lowerNullCoalesce = lower_expr.lowerNullCoalesce; pub const resolveOptionalInner = lower_expr.resolveOptionalInner; pub const lowerExpr = lower_expr.lowerExpr; pub const refCapturePointee = lower_expr.refCapturePointee; pub const lowerBinaryOp = lower_expr.lowerBinaryOp; pub const lowerTupleOp = lower_expr.lowerTupleOp; pub const lowerTupleLexCompare = lower_expr.lowerTupleLexCompare; pub const lowerTupleMembership = lower_expr.lowerTupleMembership; pub const lowerChainedComparison = lower_expr.lowerChainedComparison; pub const emitCmp = lower_expr.emitCmp; // --- moved to lower/closure.zig (lower_closure) --- pub const lowerLambda = lower_closure.lowerLambda; pub const createBareFnTrampoline = lower_closure.createBareFnTrampoline; pub const createClosureToBareFnAdapter = lower_closure.createClosureToBareFnAdapter; pub const collectCaptures = lower_closure.collectCaptures; pub const computeEnvSize = lower_closure.computeEnvSize; };