Skip to content

Analysis

Project resolution, type and effect checking, layouts, and typed HIR.

Updated View as Markdown

Semantic analysis turns parsed files into one typed HirProject. It is the compiler’s authority for source meaning: declarations, symbol identity, types, effects, visibility, layouts, host capabilities, and integrated data plans.

Project phases

Project analysis has an intentional declaration/body split:

parsed files
    ↓ install prelude and project configuration
declaration hoist

project exports and declaration surface
    ↓ resolve imports and cross-file names
frozen declaration base
    ↓ check and lower function bodies
typed HIR contributions
    ↓ canonical project assembly
HirProject

Hoisting lets files refer to declarations that appear later or live in another module. Body checking runs only after the project can answer those cross-declaration lookups.

Resolution

Resolution replaces textual names with symbol identities. The symbol table records declaration kind, visibility, module ownership, type information, and source provenance. Scopes layer local bindings over project declarations.

Important invariants include:

  • each use either resolves to one permitted symbol or receives a diagnostic;
  • private declarations cannot leak across their visibility boundary;
  • imports use module-qualified identity rather than matching an unqualified display name by accident;
  • value, type, method, and trait lookups obey their own namespaces and rules;
  • synthetic compiler symbols remain distinguishable from source declarations.

Later stages work with SymbolId, not source spelling. They may print names for diagnostics, but must not re-resolve a string.

Type checking

Atoll uses contextual inference. Some expressions synthesize a type upward; others are checked against an expected type flowing downward. Calls combine both directions: the callee establishes parameter expectations, arguments are checked against those expectations, and the result is synthesized to the surrounding expression.

The checker also maintains constraints for polymorphism, numeric choices, branch agreement, trait requirements, and overload relationships. Unification solves inference variables while blame information preserves which source relationship caused a mismatch.

Expected types are especially important for:

  • empty and aggregate literals;
  • closures whose parameters omit annotations;
  • None and error-channel coercions;
  • numeric literals with a non-default width;
  • branches that must produce one common result;
  • generic calls whose quantified variables are fixed by context.

The HIR must not expose unresolved inference variables at a backend boundary that requires concrete layout. A diagnostic is preferable to selecting an arbitrary representation.

Effects

Effects are checked with types because they constrain which calls and control transfers are valid. A function’s effect row can include suspension, fallibility, and host capabilities. Effects may be declared, inferred from callees, or propagated through generic instantiation.

The analyzer records why an effect is present so diagnostics can identify the callee or operation that introduced it. MIR later decides how suspension is represented, but it relies on sema’s classification; it does not infer source effects from low-level operations.

HIR

HIR is typed, resolved, and still close to source structure. HirProject contains arenas and side tables for:

  • functions, expressions, statements, patterns, and declarations;
  • symbol and type registries;
  • function schemes and effect rows;
  • struct, enum, tuple, collection, and instantiated layouts;
  • source origins and cross-layer mappings;
  • host-operation and datasource metadata;
  • query plans and schema information;
  • diagnostics and lints.

Side tables are part of the representation contract. Moving a fact out of a node does not make it optional; every clone, merge, relocation, or specialization path must preserve or deliberately rebuild the corresponding entry.

Project assembly

Per-function checking can use local arenas and then merge into the global project. The merge is not arbitrary concatenation:

  1. type contributions are reconciled with the global registry;
  2. local IDs are relocated into global ranges;
  3. symbols and functions are appended in canonical declaration order;
  4. source and effect side tables are remapped;
  5. diagnostics are stabilized independently of worker completion order.

This map/reduce shape permits parallel checking without allowing thread scheduling to choose public IDs or output bytes.

Prelude and configuration

The compiler installs core declarations before checking user code. Prelude stubs define the language-visible signatures for collections, tasks, I/O, networking, and intrinsics. A declaration in an old document is not built in unless the current prelude installs it.

Project configuration contributes additional semantic inputs:

  • root module and file-to-module mapping;
  • local dependency roots;
  • host capability allowlists;
  • datasource routing;
  • whether internal prelude frames remain visible in diagnostics.

Because these inputs can change resolution and effects, they belong in the incremental database key, not in ambient process state.

Failure model

Analysis accumulates diagnostics instead of failing at the first invalid body. This improves editor feedback and lets independent declarations continue checking. Error nodes and sentinel types contain damage, but they are not permission for code generation to proceed through an error-severity result.

Typical owning diagnostics include unknown or ambiguous names, type mismatch, invalid mutation, missing trait support, visibility violations, effect-row violations, invalid decorators, and unavailable host capabilities.

After a clean analysis, Specialization consumes the typed HIR. For the representation itself, see HIR, Nodes, and Invariants.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close