Luce / engineering
Learn Luce LuciaOS

Semantics and typed HIR

The parser can tell that value * 2 is multiplication. Semantics proves which value it is, lands 2 as an i64, chooses checked signed multiplication, and records the overflow rule.

This is where Luce decides language meaning. LLVM is not asked to resolve a name, infer a type, choose a method, or decide whether failure is handled. By the time LLVM appears, those questions are closed.

Why resolution and typing stay together

Resolving xs.append(value) needs the type of xs; typing the call needs the member that resolution found. Flow narrowing changes the type visible at a later expression; call selection changes the effect a containing function must handle. These are mutually recursive questions, so the code presents one semantic stage with concern modules rather than fictional independent passes.

Three simultaneous views of one expressionThe checked result crosses as plain HIR data
Names & scopes

Which declaration does this spelling denote? Is it visible? Has it been shadowed or used before initialization?

Types & shapes

What exact representation flows here? Which member, conversion, return shape, or witness applies?

Flow & effects

Is an optional narrowed? Does every path return? Can failure propagate? What must be released on each edge?

The two semantic spines

Analyzer

Collects modules, declarations, aliases, layouts, constants, signatures, interfaces, defaults, and the entry point. It settles the world in which bodies will be checked.

FunctionBuilder

Checks one body with scopes, narrowing, local state, temporary lifetime, receiver effects, and an HIR recorder.

Concern files provide small operations over those spines: calls.zig chooses a callable, assign.zig plans a place and replacement, closures.zig plans an environment, initializers.zig proves class fields, and interfaces.zig constructs witnesses.

Checking and recording happen together

The check that chooses meaning records that choice immediately into typed HIR. A numeric literal gains its landed width. A method call gains its resolved target and receiver store behavior. A closure gains its capture kinds. An assignment gains its destination place and the releases that replacement requires.

Nothing in semantics emits a MIR instruction. The stage records structured, typed meaning through one recorder API. Register numbering, basic blocks, constants, and instruction assembly belong downstream.

The last word on source-level rules

This is the only stage that can still explain a source mistake. It enforces immutable bindings, explicit numeric conversions, host access, class construction, interface conformance, recursive layout limits, closure cycles, worker sendability, return coverage, optional narrowing, and visibility.

RuleWhy MIR cannot reconstruct it
let cannot be reboundMIR sees a local protocol, not the author's binding promise.
A class cannot cross a workerThe static graph may contain the class transitively inside values and containers.
A public signature cannot expose a private typeVisibility is a source-module relation erased before code generation.
A closure cannot store a direct strong self cycleThe capture plan and destination relationship are clearest at the source expression.

Failure produces no partial program

Diagnostics are bounded and carry stable codes plus byte spans. When any semantic error exists, analysis returns no Analyzed value. A later stage never receives a tree with known bad names or types and therefore never needs recovery branches for user mistakes.

The concern map