Skip to main content
v0.14.0

Type 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).

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.

Implemented by (1)
  • check_graph metel-frontend/src/pipeline/type_checking/mod.rs
Tested by (1)
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; callers that pass None get every id as None, so construction degrades gracefully.

Implemented by (1)
Tested by (1)
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.

Implemented by (1)
Tested by (1)
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.

Implemented by (1)
Tested by (1)
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.

Implemented by (1)
Tested by (2)
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.

Implemented by (1)
  • construct_expr metel-frontend/src/pipeline/type_checking/construction/expressions.rs
Tested by (2)
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.

Implemented by (2)
Tested by (1)
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).

Implemented by (1)
Tested by (2)
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.

Implemented by (1)
  • infer_decl metel-frontend/src/pipeline/type_checking/inference/declarations.rs
Tested by (1)
Requirement №10​

Free-function overload selection is exact-match, and construction stamps the selected callable's stable SymbolId on the typed call; the runtime dispatches that identity through its symbol-value registry, and overload sets stay module-local.

Implemented by (1)
Tested by (1)
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.

Implemented by (1)
Tested by (1)
  • 53_generic_struct metel-interpreter/tests/integration/sources/evaluator/generics/53_generic_struct.mtl
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.

owner metel-frontend, metel-interpreter

Implemented by (1)
Tested by (1)

Known limitations​

knownLIMIT-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 ↗plannedLIMIT-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 ↗ Language gap: GAP-FUNCTIONS-001 ↗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)
resolvedLIMIT-TYPE-CONSTRUCTION-004 — Generic closure reconstruction omitted captured types. Runtime construction lacked the captured bindings' creation-time types when rebuilding a polymorphic closure body. Open record ↗