c218d875 · core f63c1e69 · built 2026-10-26 22:39 UTCType Construction
The pass that builds the typed IR from Type Inference's results: typechecker::construction (~8,050 lines across typechecker/construction.rs and typechecker/construction/{declarations,expressions,calls,patterns,narrowing}.rs), typed_ast/ (748 lines, the typed IR itself), and the typechecker's shared support consumed by construction — registry.rs, projections.rs, overload.rs, conversions.rs, object_safety.rs, handoff.rs (~3,500 lines). ~12,300 lines total. Owning crate: metel-frontend. This section consumes Type Inference's output; it does not re-derive it.
Model
Construction turns solved facts into the typed program consumed by move checking, elaboration, and evaluation. It processes modules in dependency order and carries their exported schemes forward (module handoff). Where earlier stages resolved an identity, construction preserves it on typed nodes instead of replacing it with another name lookup (identity stamping). Locals, nested generic functions and move state are keyed by the binding's identity (local identity); that identity is required of every run (required identity); and a written type name becomes a declaration id in one place (written names). Module-level values and every scheme are keyed the same way, by the declaration they belong to.
It is also the last place to enforce rules whose operands must already be concrete: ascriptions disappear after guiding construction (ascriptions), operator and import diagnostics retain their distinct meanings (operators, visibility), and defaults, overload selections, and generic shape recovery become explicit typed facts (defaults, overloads, generic reconstruction).
Verifiable architecture claims
Owner metel-frontend unless a requirement names another.
Requirement №1
check_graph typechecks a normalized module graph in topological order, running inference then construction per module, and accumulates each module's exported schemes in GlobalExports so later modules import them without re-inferring.
Requirement №2
Construction stamps the typed IR with the identities the frontend already produced (FieldId/VariantId member ids, the span-to-BindingId bridge) through a FrozenIdentity bundle in check_graph_with_report, which every run supplies.
Requirement №3
Opaque return variables are validated at constraint-composition time, not only against the final substitution: when an opaque-return marker resolves directly to a concrete type, inference reports T0018, and construction must not reintroduce the concrete identity.
Requirement №4
The typechecker treats [T] as Copy unconditionally, as a deliberate InferType::Array special case that does not inspect the element type; other array-aspect eligibility stays conditional on the element through ordinary stdlib impl and bound machinery.
Requirement №5
Until destructors run, a std::core::Drop impl may declare only an empty drop body: construction identifies the standard aspect by declaring module (a same-named user aspect is unaffected) and rejects a non-empty body with T0001.
Requirement №6
Type ascriptions constrain inference and construction but are erased from typed IR: construction builds the inner expression using the resolved annotation as context instead of emitting a runtime no-op TypedExpr::Ascribe node.
Requirement №7
Operand legality is checked once operand types are resolved: arithmetic and unary negation accept numeric or Never operands, ordering comparisons also accept Str, and construction reports T0005 rather than leaving invalid operator shapes for evaluation.
Requirement №8
Import visibility is diagnosed while building import schemes, where the full module graph is known: name resolution records import bindings without collapsing a private-item error into a missing-item one, so construction reports T0009 (visibility) apart from T0003 (absence).
Requirement №9
Aspect default methods are materialized as typed methods before evaluation: inference collects an impl's inherited defaults and construction emits a typed body for each, except that negative impls acquire none, so runtime dispatch never evaluates untyped default-method syntax.
Requirement №10
Free-function overload selection is exact-match, and construction stamps the selected callable's SymbolId on the typed call, an id derived from the program and so identical on every compilation; the runtime dispatches it through its symbol-value registry, and overload sets stay module-local.
Requirement №11
At a generic struct literal or field access, construction instantiates the declared raw field template with the receiver's resolved type arguments; it does not cache one concrete field type under the generic declaration's surface name.
Requirement №12
Generic runtime reconstruction recovers a struct or enum's type arguments from its typed field values against the declared field templates when the value carries no argument list; this is best-effort for values whose fields reveal no parameter, rather than storing generic metadata on every value.
Requirement №13
Construction resolves a value by identity: locals and move state are keyed by the binding's LocalId, module-level values and schemes by the BindingId they were declared under, and a reference reaches them through the span → BindingId bridge, never by spelling.
Requirement №14
The frozen identity tables (the member table and the span → BindingId bridge) are required by every checking entry point and by runtime reconstruction of a generic body; there is no run without them. A caller builds them from the parsed graph before normalization.
Requirement №15
A written type name becomes a declaration id in one place, ConstructCtx::written_type_id (block-local declarations first). Everything else reads the id off the type; a struct's concrete fields are keyed by that SymbolId, and a top-level declaration's own SymbolId is read off its span.
Known limitations
LIMIT-TYPE-CONSTRUCTION-001 — Call::callee_id falls back to name dispatch for first-class functions. callee_id is empty for methods, nested functions and first-class function calls, which fall back to name dispatch. Open record ↗knownLIMIT-TYPE-CONSTRUCTION-002 — Overload sets are not exportable across modules. Overload sets are seeded by two different mechanisms; exporting them across modules (METEL-188) is future work. Open record ↗knownLIMIT-TYPE-CONSTRUCTION-003 — == and != reject non-primitive operands instead of dispatching through Eq. == and != reject operands that are not numbers, booleans, strings or chars; call .eq(..) instead. Open record ↗Resolved (1)
LIMIT-TYPE-CONSTRUCTION-004 — Construction finds module-level values and their schemes by name. Module-level values and the inference SchemeEnv are keyed by spelling in construction; everything a binding names is keyed by identity. Open record ↗