Nocter v0.14.0 Semantic Architecture Migration
Status: Phases 0 through 3 complete. Phase 4 is next.
v0.14.0 replaces the compiler's span-, string-, and AST-shape-driven semantic spine with explicit compiler identities and typed intermediate representations. It is an incremental migration under the complete v0.13.0 regression suite, not a rewrite and not a language-feature milestone.
The published v0.13.0 language, tag, archive, release notes, and qualification record are immutable. A migration phase may preserve public behavior or improve diagnostics, but it must not add compatibility syntax or silently retain two semantic authorities.
Migration Invariants
- Source syntax remains authored AST. Resolver, type checking, analysis, and lowering do not add semantic identities by inventing source names or treating byte ranges as identifiers.
- Each compile-unit definition, body, expression, type, requirement, intrinsic, and monomorphized item receives a typed ID from one semantic database.
- A span is location metadata owned by the database. It may diagnose or navigate an ID, but map equality on spans must not decide whether two semantic entities are identical.
- The checker produces the semantic result consumed by ownership, provenance, LSP, specialization, and lowering. No post-check AST walker may recompute a successful semantic decision.
- Recovery produces explicit error or partial semantic nodes. LSP support for incomplete source is not permission to create a second successful-language model.
- Every temporary adapter has a named consumer, an explicit removal phase, and a regression that prevents new callers after the removal boundary.
- Old paths are deleted in the same phase that completes their replacement. The migration never ends with two permanent type systems, call identities, or lowering pipelines.
Phase 0: Semantic Identity and Declaration Core
Goal
Establish one compile-unit identity domain and make authored declarations structurally honest before typed HIR depends on them. Phase 0 removes the most edit-sensitive identities first: instance member ordering, operator/coercion synthetic names, callable summary keys, trusted declaration roles, and source-backed editor targets.
Semantic IDs
Phase 0 and Phase 1 introduce opaque IDs with separate domains:
DefId source declaration or member
BodyId executable authored body
ExprId expression in one body
TyId interned semantic type
RequirementId, IntrinsicId, and MonoItemId remain names for later migration boundaries, not empty Phase 0 scaffolding. They are introduced only when the requirement, intrinsic, and backend convergence phases can replace their old authorities in the same change.
SemanticDb owns definition, body, and expression source records plus their locations. The checker-owned typed HIR owns the TyId arena for each checked file in the same semantic generation. IDs are meaningful only inside that generation. Package-wide LSP generations retain their existing stable source identity for edit planning, but they map that identity to a fresh DefId instead of making a stable byte span the compiler's internal identity.
Anonymous opaque result declarations use a separate OpaqueTypeId. They are not named declarations and therefore do not borrow an enclosing DefId; their authored some span is only the diagnostic and navigation projection of the opaque type identity.
The first implementation may add typed IDs and definition records while existing span-bearing diagnostic fields remain. It may not call a span wrapper a semantic ID or make raw numeric IDs public API.
Declaration Model
InstanceDecl owns one source-ordered Vec<InstanceMember>:
InstanceMember::Method(MethodDecl)
InstanceMember::Operator(OperatorDecl)
InstanceMember::Coercion(CoercionDecl)
Filtering iterators serve method, operator, coercion, and callable consumers without creating parallel vectors. Formatting and AST JSON preserve authored member order.
Method, operator, coercion, literal, function, construct, conformance, and destructor bodies share a name-free semantic callable record after resolution. Source declarations retain their actual syntax; an operator or coercion is not represented as a MethodDecl with a name beginning __nocter$. Resolved lookup and lowering dispatch by DefId and callable kind.
Work Order
- 1. Replace parallel instance member vectors with one source-ordered enum and migrate every AST visitor, formatter, serializer, declaration-surface filter, resolver, checker, analysis query, and lowering entry point.
- Add opaque ID types and an immutable compile-unit
SemanticDbdefinition index with typed source-location records and deterministic construction tests. - Introduce a name-free callable signature/body abstraction. Give every authored callable a
DefId; remove operator and coercion synthetic names from parser AST. - Migrate callable provenance summaries, allocation summaries, trusted roles, coercion plans, operator plans, specialization targets, editor occurrences, and lowering call targets to IDs.
- Remove span-keyed callable identity and string-keyed target dispatch from migrated paths. Keep spans only as database metadata and diagnostic locations.
- Run the complete repository and distributed-home matrix, update architecture documentation, and record the exact removed paths and remaining Phase 1 boundary.
Completion Definition
Phase 0 is complete only when every source declaration and executable body in a compile unit has one typed semantic identity; instance members retain source order in one collection; operator and coercion declarations contain no synthetic method or name; callable summaries, trusted roles, selection plans, specialization indexes, lowering resolution, and editor targets use DefId or BodyId for semantic equality; diagnostic, navigation, and backend symbol locations project from those IDs; no span-to-span callable adapter remains; all public behavior is unchanged except deliberate diagnostic improvements; and the complete verification, documentation, formatting, Clippy, and diff gates pass. String backend labels may exist after semantic target selection, but they are presentation and linkage output rather than source-level identity.
Non-goals
Phase 0 does not add language syntax, standard-library APIs, dynamic dispatch, persistent IDs across compiler processes, incremental query execution, typed HIR, MIR, a new ABI, or another target. It does not rename old fields without changing their authority.
Completion Record
InstanceDeclnow stores one source-orderedVec<InstanceMember>; formatting, AST JSON, resolution, checking, analysis, and lowering no longer reconstruct mixed member order from parallel collections.- One compile-unit
SemanticDbassigns deterministicDefIdvalues to top-level, member, and block-scoped import declarations. Every per-file resolver and the post-opaque-elaboration pass share the same database generation. - Resolver symbols retain their table-local
SymbolIdonly as an indexing handle and separately carry the declaration's compile-unitDefId. - Borrow-coercion selection carries
DefIdthrough resolved type surfaces, conversion selection, type-check facts, generic requirement specialization, call-specialization collection, and IR callable indexing. Its former declaration-span and synthetic-target-name identity fields have been removed; backend symbol spelling is generated only at the IR boundary. - Trusted-library validation now produces pre-semantic
TrustedDeclarationInputs; analysis binds those registrations once intoHashMap<DefId, TrustedDeclarationRole>. Type checking no longer uses byte spans as trusted-role identity. - Protocol-method and ordinary method specialization records carry
DefId. Collection, interpolation, comparison, expansion, and indexing plans feed oneDefId-keyed specialization table shared by buildability and IR indexing. - Callable contract/body pairs and their receivers, parameters, and literal captures are keyed by
DefId. The index retains source locations only to project existing diagnostics and editor ranges, while all consumers share the sameArc<SemanticDb>owned by the compile unit. - Every authored callable block and nested closure has a source-ordered
BodyIdwith an owningDefIdand optional parentBodyId. Function, method, coercion, destructor, literal, and closure specialization tables use the appropriate definition or body domain. - Trusted interpolation and iteration discovery remains a pre-semantic shape-validation step, but
it binds String, constructor, Format, Iterator, method, and associated-type locations to
DefIdbefore type checking. Semantic consumers no longer use those spans as identity. - Editor declarations use
DefIdacross package indexes and convert IDs to source ranges only at navigation and edit boundaries. Generic parameters use explicit ordinal identity instead of stable byte ranges. CallableDeclis name-free.MethodDeclalone owns a source name, while operator and coercion kinds remain semantic enums through selection and specialization; no synthetic method AST node or callable identity remains.- Callable names used by buildability and IR are keyed by canonical
DefId. Contract/body lookup asksSemanticDbfor the declaration body owned by a definition, so functions, methods, operators, and coercions do not depend on matching focused spans or pretending every callable is a named method. - The complete repository test suite, including the distributed installed-home iteration, sequence-spread, ordering, and standard-library runtime matrix, passes with formatting and warnings-denied Clippy.
Phase 1: Partial Typed HIR
Goal
Make one checker invocation own the semantic product consumed by analysis, specialization, buildability, and lowering. Establish expression and type identity plus explicit error semantics before Phase 2 migrates the remaining editor-facing fact tables and Phase 3 introduces MIR.
Implemented Boundary
SemanticDbassigns every authored expression anExprIdand owningBodyId.- A checked-file type arena interns normalized
TypeExprvalues asTyId; aTyIdis never used as source spelling or across semantic generations. TypedExpressionrecordsExprId,BodyId, andPartialSemantic<TyId>. Unresolved expressions recordPartialSemantic::Errorrather than disappearing or receiving a guessed type.TypecheckOutputreturns diagnostics and the exactTypedHirfrom one checker invocation. Ordinary compile-unit analysis and single-file LSP recovery retain that result; downstream consumers cannot invoke the raw typed-HIR builder.- The only provisional entry point is the explicitly named opaque-result prepass. It exists before final result witnesses are known, is inaccessible outside type checking and analysis orchestration, and its result is replaced by the ordinary checker output after elaboration.
- Legacy fact collections inside
TypedHirremain a compatibility payload, not a second checker. Their keys are migrated to expression/definition records in Phase 2 and control-flow records in Phase 3; no new consumer may rebuild successful semantics from AST.
Completion Definition
Phase 1 is complete when authored expressions have body-scoped identity; known and erroneous expression types survive diagnostics in one partial semantic product; normal compilation and LSP recovery consume TypecheckOutput; no post-check collector or externally callable successful semantic recomputation path remains; existing downstream consumers receive the checker-owned instance; the provisional opaque prepass is explicit and isolated; and the complete repository, formatting, Clippy, and diff gates pass.
Completion Record
Phase 1 satisfies the definition above. Tests cover deterministic expression/body identity, type interning, explicit error expressions, preservation of checker output after diagnostics, and LSP single-file recovery. The full verification matrix passes after Phase 0's final callable-identity migration.
Later Phases
Phase 2: Editor Projection
Goal
Build hover, completion, navigation, semantic tokens, signature help, and edits from one semantic occurrence index plus analysis-time syntax projections. A successful editor query must not rescan the complete AST, rediscover a resolver member by span, or recompute compile-unit specialization.
Completed Work
SemanticOccurrenceIndexusesDefIdandLocalSymbolId; navigation, references, and rename project source locations only after semantic selection.EditorSyntaxIndexowns calls, literal sites and expressions, interpolation sites, module paths, import selectors, export surfaces, documentation, callable syntax, destructor syntax, and source edit targets built once per analyzed file.- Resolver declarations have one
DefId-keyed projection index covering top-level symbols, fields, variants, associated types and functions, methods, conformance methods, destructors, literals, and coercions. Hover no longer scans every resolved type surface by declaration span. - Semantic token classification and lexical-region facts are computed once per file. Literal, interpolation, region, signature-help, hover, and import-completion queries consume retained projections.
- Opaque results use
OpaqueTypeId; callable contract/body records carryDefIddirectly rather than resolving an identity span after matching. - Call specializations are lazily computed once per compile unit and shared by buildability, IR lowering, and literal editor facts.
EditorSyntaxIndexalso owns member gaps, enum patterns, struct fields, literal shapes, region allocators, result-provenance clauses,whererequirements, and instance operator slots.LexicalScopeIndexowns visible local bindings and scoped imports. Successful completion queries therefore perform indexed lookup; AST scope walkers exist only in explicitly named recovery paths for incomplete source.- Type occurrences identify their selected declarations by
DefId. Generic-parameter occurrences carry the parameter'sDefId, and binding type, scalar-view, mutability, and payload facts are keyed byLocalSymbolId. Declaration and reference spans are resolved to that local identity once rather than serving as fact identity. - Every instance callable syntax record covers methods, operators, and coercions together with its generic owner. Specialization no longer loses owner substitutions for an operator or coercion.
Completion Definition
Phase 2 is complete when successful-source editor requests perform indexed lookup rather than AST or resolver-surface discovery; every declaration target is selected by semantic identity; all retained syntax-only sites are built once per file; incomplete-source recovery is isolated and cannot override successful semantics; and the complete repository, documentation, formatting, Clippy, and diff gates pass.
Completion Record
Phase 2 satisfies the definition above. The complete repository matrix passes, including 2,557 library tests, 223 distributed installed-home tests, public examples, source-corpus tests, formatting, and warnings-denied Clippy. The matrix caught and permanently covered one missing instance-operator syntax identity that had removed generic owner substitutions from sequence expansion lowering.
Phase 3: Control-Flow MIR
Goal
Replace AST-shaped buildability and backend lowering with one checked control-flow representation. MIR owns places, operands, rvalues, basic blocks, terminators, loans, initialization state, and explicit drops. Source AST remains an input to typed-HIR-to-MIR construction and source spans remain diagnostic metadata; neither may determine backend semantics after MIR construction.
Migration Order
- 1. Introduce typed local and basic-block identities, the MIR body model, and a structural verifier. Cover deterministic construction, valid control flow, invalid targets, terminator completeness, and local/projection validity before routing production code through it.
- Migrate straight-line scalar evaluation and native control flow: local initialization,
assignment, return,
if, loops,break,continue, and short-circuit operations. - Represent calls and outcome edges explicitly, then migrate optional/fallible propagation,
catch, andotherwisewithout terminal/nonterminal lowering variants. - Add aggregate places and projections, moves, borrows, loans, initialization/drop flags, and explicit cleanup edges. Migrate records, variants, arrays, fields, indexing, and destructors.
- Migrate regions, closures, typed literals, interpolation, collection expansion, and iteration. Lower MIR to the existing machine IR and delete each AST-driven lowering family when its MIR route becomes authoritative.
Completion Record
mirowns body-localLocalId,BasicBlockId, andScopeIddomains, typed scalar locals, places, constants, copy operands, arithmetic and comparison rvalues, assignments,Goto,Switch, directCall, failure propagation, loops, andReturn.- MIR locals retain
LocalSymbolIdfor parameters and bindings,ExprIdfor temporaries,TyIdfor checked type equality, and independent checked representation, ownership, logical-storage, source-origin, and lexical-scope contracts. Basic blocks retain the same validatedScopeIdtree; MIR parameter storage is a source ordinal, while machine-local and parameter ABI indexes are projected only while lowering MIR to machine IR. - One backend parameter projection maps those ordinals to scalar ABI word positions or aggregate staging slots. A parameter after a multiword value no longer relies on ordinal/ABI-index coincidence.
- Copy aggregate parameters now remain aggregate MIR locals. Checked field selection becomes a
typed
ProjectionPath, and MIR-to-machine-IR maps that path onto the parameter's ABI staging slot. Scalar field reads therefore no longer recover an aggregate member from source AST. - MIR construction receives one immutable compile-unit semantic input bundle. Aggregate layout follows the shared source-resolution map, so fields following cross-source aggregate members use the same layout authority as function ABI classification.
- Copy aggregate parameters can flow through MIR calls as whole-place arguments. Backend projection preserves the validated direct/indirect ABI classification; indirect stack-backed arguments also disable tail-call frame reuse instead of leaking an address into a destroyed caller frame.
- MIR call edges retain a semantic callable instance made from canonical
DefId, optional concrete receiverTyId, and ordered concrete type-argumentTyIdvalues. Runtime symbol spelling stays in the backend index, and buildability uses the same canonical instance-key projection instead of a competingDefId -> namemap. Direct and nested generic calls therefore cannot collide across monomorphizations. - Method calls now enter that same edge. Typed HIR retains the method's concrete receiver parameter
TyId; MIR passes view receivers as views, models aggregate receiver borrows as ordinary loans, and moves owned receivers through the existing operand path. Generic instance methods and concrete receiver specializations consequently share one backend target projection. - Calls through closure and generic callable values retain callable type
TyIdplus capability as a closed semantic identity. Their receiver parameter type is fixed during type checking, and the shared instance registry alone projects the callable identity to its monomorphized runtime name. - Readonly and readwrite borrow parameters retain MIR borrow representation and may be forwarded as whole-place call arguments. ABI projection maps the source ordinal to the existing borrow parameter word; one IR-level frame-dependence predicate prevents both borrow and indirect aggregate arguments from entering an unsafe tail call in either lowering route.
- Borrow bindings over stored scalar identifiers create explicit
Loanrecords andBeginLoanstatements. Cleanup derivesEndLoanstatements from lexical scope exits on the CFG, before backend projection materializes the borrow pointer; loan lifetimes are not inferred from AST block endings or kept as backend-only local metadata. - Copy aggregate call results may initialize MIR locals and feed checked field projections. The
backend derives local aggregate slots after retained parameter/outcome slots, reserves them from
the aggregate's checked ABI layout, and uses the same location projection for field reads and
whole-place call arguments. Local forwarding preserves direct/indirect ABI classification from
the checked
TyIdinstead of treating only parameters as aggregate storage. - Nested checked member selections build parent-linked MIR projection paths from the root local to the selected field. Backend lowering folds relative layout offsets along that path, so nested and cross-source aggregate access does not flatten or rediscover source member syntax.
- MIR scalar identity now covers every built-in integer. Narrow and wide integer parameters, constants, arithmetic, shifts, comparisons, calls, aggregate-field loads, and plain or fallible returns project to the existing machine IR without returning to AST lowering.
u8retains a dedicated MIR scalar representation because the current machine IR gives it dedicated locations and calls. One parameter projection now maps it to theU8ABI slot rather than pretending it occupies the generic-integer slot; all ten integer types therefore reach the MIR route without a hidden storage exception.- The structural verifier rejects missing locals and blocks, type or scalar-representation drift, invalid local or scope contracts, non-boolean conditions, invalid control-flow targets, and malformed loop regions before machine IR is produced.
- A fixed-point definite-initialization verifier checks reachable operands and returns. Branch joins intersect initialized sets, while fallible calls initialize their destination only along the success edge.
- Initialization and drop-obligation state is place-sensitive rather than local-sensitive. Whole roots, explicitly initialized projections, and invalidated projections remain distinct, so a partial move preserves sibling fields, prevents a later whole-value move, and survives CFG joins without flattening the aggregate into one bit.
- Owned values and borrows now have independent path-sensitive MIR domains. Explicit drop edges
are checked with may-live and must-live obligations;
LoanIdplus begin/end statements detect alias conflicts, moves or mutations while borrowed, and loans that cross an exit. - Loan conflicts compare projected-place overlap rather than only root-local equality. Root/child and ancestor/descendant accesses conflict, while distinct fields may carry independent mutable loans; unresolved index aliasing remains conservatively overlapping.
- Aggregate places use typed, parent-linked
ProjectionPathIdrecords for fields and indexes. The verifier rejects scalar bases, forward parents, base drift, invalid index operands, and place references outside the body's projection arena. - Definite-initialization retains per-edge and per-exit states. A dedicated cleanup pass combines
them with the validated scope tree and materializes explicit
Dropchains without walking AST blocks or flattening outcome success and failure into one state. - Cleanup selects whole owned roots when available and maximal remaining owned projections after a partial move. This prevents both whole-value destruction of moved storage and loss of sibling field destructors.
- Primitive lowering now uses a closed
IntrinsicIdafter resolution. Pointer, view, process, allocation, I/O, and syscall operations no longer select backend behavior with string equality. - Every executable body is built and validated through MIR. A construction or validation failure is authoritative; no AST-driven buildability or machine-IR fallback remains.
- Value
ifand terminalifnormalize to the same control-flow diamond with one return join. The same conditional builder handles nested values inside arithmetic and call arguments, assigns both child scopes into one destination place, and rejoins in the parent scope. Nested scalar evaluation uses explicit MIR temporaries, so evaluation order is not reconstructed by backend lowering. - Scalar calls, fallible success/failure edges,
while, unconditionalloop, and i32/usize range loops now use the retained MIR body in both buildability and machine-IR lowering. Range setup uses an explicit loop-scope preheader, and branch joins plus loop exits derive innermost-first exited scopes from CFG block identities. - Scalar
otherwiserecovery is an ordinary MIR outcome failure branch that assigns the result place and rejoins the success block. Machine lowering derivesOutcomeFailureMode::Recoverfrom that edge shape for optional and fallible callees; it does not inspect the source expression. - Discarding scalar
catch _uses the same recovery edge and creates no synthetic error local. An unused named catch instead owns one logicalerrorlocal initialized only by its failure edge; late machine-storage projection expands that value into code and message views. Named catches use typedBuiltinErrorFieldprojections for code and message views, so those views may flow into ordinary call arguments without exposing their machine offsets. Returning the complete error payload uses the same logical MIR value and typed projections as failure returns. stris the first nonnumeric MIR view representation. String literals, source parameters, ordinary calls, aliases, and returns keep logical locals while late storage projection assigns their two ABI words; pointer borrows and string views are no longer conflated.- Boolean
&&and||normalize to a MIR switch, a selected right-hand or constant short-circuit path, and one join. Right-hand calls and failures therefore cannot be evaluated by eager backend expression lowering. - Numeric negation and boolean inversion remain explicit unary MIR rvalues through structural validation. Only machine-IR projection rewrites them to target-independent arithmetic or boolean values, preventing invalid operator/scalar combinations from reaching code generation.
- Exact and typechecked-lossless integer
asexpressions become MIR casts with explicit source and target types. MIR validation independently rejects narrowing or signed-to-unsigned drift; machine projection performs sign/zero extension without re-reading conversion syntax. - Scalar
+=,-=,*=,/=, and%=normalize to ordinary MIR binary rvalues whose left operand and destination are the same place. Backend lowering no longer maintains a separate compound-assignment semantic path for MIR-routed integers. - One construction-only
LoweringContextowns locals, semantic-symbol bindings, projections, scopes, loop regions, and the open CFG. Statement, expression, conditional, and outcome lowering mutate that authority directly instead of threading and potentially desynchronizing parallel state references through every recursive lowering call. - Scalar value blocks use one statement-plus-tail normalization path in conditional branches and outcome recovery. Leading bindings and nested conditionals no longer fall back to an AST route; backend structuring discovers the nearest shared MIR join recursively rather than requiring a one-block failure path.
- Outcome recovery distinguishes implicit values from explicit returns in MIR. An explicit return writes function return storage and exits instead of being assigned at the recovery join.
- Backend projection preserves one return after a shared terminal join. Redundant tests that fixed legacy AST-lowering temporary numbers or nested instruction shape were removed; MIR construction tests retain short-circuit and evaluation-order semantics, while representative backend tests retain the projection boundary.
Dropnow names a body-local semantic destruction plan. Plans contain destructorDefIds and recursive struct or fixed-array structure, never linker names or machine-IR call targets. Backend projection alone selects runtime symbols and ABI offsets.- Owned aggregate parameters enter MIR, receive path-sensitive cleanup edges, and lower direct, recursively contained, and reverse-order array element destruction from those plans. A move-only type with no runtime destructor retains a no-op plan so ownership consumption is not confused with emitted cleanup code. Payload enums and outcome storage use the same plan domain.
- MIR validation rejects missing or mismatched local/projection plans, invalid terminator plan references, and forward or cyclic plan references.
- Scalar-field struct literals now use an aggregate MIR rvalue. Construction reserves its checked
ABI slot, stores fields in resolved declaration order, activates the ownership obligation, and
reaches the same semantic cleanup plan as owned parameters. Branch and loop structuring follow
cleanup
Goto/Dropchains instead of assuming syntax-shaped direct edges. - Aggregate construction now records scalar leaves under semantic field/index paths rather than embedding ABI offsets in MIR. Nested struct and fixed-array literals share this representation; validation rejects empty or duplicate leaf paths, and only backend projection computes offsets.
- Struct and fixed-array construction is now staged explicitly:
BeginAggregatereserves logical storage, projected assignments initialize fields in evaluation order, andFinishAggregateconsolidates completed children into their parent. If a later field propagates failure, ordinary place-state cleanup drops only completed owned children; AST-era runtime live flags and the atomic aggregate rvalue have been removed from this route. - Borrow bindings over aggregate fields now create loans from projected MIR places; backend storage projection alone converts those places to slot offsets. Owned aggregate call arguments use explicit move operands, transferring initialization and drop obligations at the call edge.
- Direct scalar and aggregate-field borrow arguments now materialize the same temporary MIR loan as stored borrow bindings. A one-definition/one-use backend projection elides only the machine borrow slot, preserving the checked loan without adding runtime work.
- Fixed-array element borrows now use
ProjectionElement::Indexin production MIR. The checked projection retains itsusizeoperand, array length, and element stride; backend projection emits the existing bounds-checked aggregate address for constant and dynamic indexes. Array fields and root arrays therefore share the same place and loan analyses. - Fixed-array scalar reads, assignments, and compound assignments now use that same index place. MIR validation checks the projected destination contract rather than the aggregate root type; one backend scalar projection emits field or bounds-checked index loads and stores for every built-in scalar representation.
- Lexical allocation regions now have body-local
RegionIdrecords containing their scope, allocator capability, parent place, and saved allocation-context locals. MIR cleanup insertsExitRegionon every scope-exiting CFG edge after owned-value cleanup; backend lowering no longer walksRegionStmtblocks or maintains a parallel region-cleanup stack for routed bodies. - Generic destructor plans keep semantic
DefIdidentity plus the concreteTyId; a dedicated backend index selects the corresponding monomorphized symbol without putting that symbol in MIR. - Payload-variant construction retains the selected variant definition and semantic payload paths in MIR. Enum cleanup tests the stored tag and recursively destroys only the active payload. Optional and fallible cleanup retains ordered outcome layers plus a typed payload plan; backend projection derives offsets from the shared outcome storage layout.
- Named-field moves retain projected place identity through calls. MIR expands the owned sibling projection tree before the partial move, and cleanup destroys only the remaining live fields. Machine IR addresses the selected aggregate range directly rather than staging a copy.
- Value-block tails distinguish implicit results from explicit returns. Outcome recovery can now either assign its call destination and rejoin success or write function return storage and terminate; machine-IR failure modes derive that distinction from reachability.
- Effect-only calls use destination-free MIR continuations. Plain
voidcalls have one successor; falliblevoid!calls retain distinct success and failure edges for trapping or propagation. Initialization, ownership, loans, cleanup, and backend projection consume those same edges without inventing a scalar result local. - Plain aggregate literal and call returns now use aggregate MIR return locals. MIR retains value
ownership without encoding direct or indirect return passing; machine-storage projection alone
selects
DirectReturnor the indirect return pointer. Direct literals write return registers without a staging slot, and eligible aggregate calls remain tail calls. - Conditional value blocks use one representation-independent MIR builder. Scalar, string-view, and aggregate branches assign the same logical destination and retain child scopes, calls, ownership cleanup, and nested conditionals without selecting separate AST-shaped return implementations. Shared post-branch cleanup executes once after the join.
- Stored optional, fallible, and composed-outcome call results now enter aggregate MIR locals. The
checked body retains the outcome's semantic
TyId; only machine-IR projection derives recursive tag, error, and payload storage from the shared outcome layout authority. MIR does not encode ABI offsets or introduce a second outcome-layout model. - One semantic
InspectOutcometerminator now owns stored single-layer optional/fallible recovery, propagation, trapping, and catch edges. Definite initialization, cleanup, drop obligations, and loans consume those edges directly. A dedicated backend projection derives tag, error, and payload offsets and emits the existing machine-IR inspection operations without consulting AST. - Stored optional and composed-outcome forwarding now uses an ownership-aware
ReturnOutcomeMIR edge. Validation checks the source operand's aggregate representation and copy/move contract; backend projection independently verifies the callable result shape before emitting recursive outcome ABI unpacking. Whole-place aggregate copies use the same checked local/location boundary. - Static and evaluated
error.new(code, message)failure returns now terminate MIR with logical string-view operands. Evaluation order, initialization, loans, and cleanup are checked before backend projection emitsReturnFallibleFailure; the backend no longer parses constructor AST for this route. - Buildability now walks reachable MIR call instances and validates the retained body used by machine-IR projection. Static error helpers are classified from checked MIR rather than by a special AST body parser.
- Closures, typed literals, interpolation, expansion, iteration, composed outcomes, aggregate indexing, and specialized calls all use the same production MIR route.
- The temporary feature routers and AST-driven buildability and lowering families were deleted. Machine-IR projection reads checked MIR and uses source blocks only at the separate cache/build boundary; it never inspects source expressions or statements for execution semantics.
- The complete verification matrix passes after the migration, including 2,028 library tests and 223 distributed installed-home tests, formatting, warnings-denied Clippy, documentation generation, and diff checks.
Completion Definition
Phase 3 is complete when buildability is MIR construction and validation, machine-IR lowering does not inspect source Expr, Stmt, or Block nodes, control-flow joins and cleanup are represented once, drops and loans are explicit and path-sensitive, spans are metadata rather than equality, all temporary routers and migrated AST lowerers are deleted, and the complete verification, documentation, formatting, Clippy, and diff gates pass.
Later Phases
- Phase 4 — Backend Convergence: replace string-based primitive dispatch with
IntrinsicId, centralize checked-type-to-ABI projection, unify duplicated integer and aggregate projection paths, and remove backend adapters that restate MIR contracts. - Phase 5 — Stabilization: remove migration adapters, narrow lint exceptions, consolidate tests around the new boundaries, qualify, and publish v0.14.0.