18. Semantic Analysis: The Orchestrator
In monolithic compilers, the semantic analysis phase often becomes a tangled mess of scope tracking, type inference, and control-flow checks. To maintain a production-grade architecture, we decouple these responsibilities.
The Semantic Analyzer (Sema) is not a monolith; it is an AST Orchestrator.
Sema’s sole responsibility is to walk the Abstract Syntax Tree (AST) generated by the parser, manage contextual state, and delegate specialized checks to independent, stateless engines.
The Multi-Pass Pipeline
Sema executes a strict multi-pass pipeline over the AST. Attempting to do all semantic checks in a single pass leads to brittle code and makes forward declarations impossible.
Pass 1: Name Resolution (Symbol Gathering)
Sema walks the AST to find all declarations (let x, def foo(), class Point). It populates the Symbol Table and resolves lexical scoping. Every identifier usage is mapped to its specific declaration. If a variable is used but never declared, Sema halts and reports an undefined reference error.
Pass 2: Type Checking
Once all symbols are gathered and resolved, Sema walks the AST again, this time delegating node evaluation to the Bidirectional Type Checker. Because the symbol table is fully populated, the Type Checker can easily verify signatures, synthesize expressions, and check assignments without worrying about scoping rules.
Pass 3: Borrow Checking & Liveness
After the types are proven sound, Sema extracts the Control Flow Graph (CFG) and base facts (like loan_issued_at) from the AST. It runs a backward liveness pass and feeds the data to the Z3 Borrow Checker to statically verify memory safety.
By isolating these passes, the compiler maintains clean boundaries. A bug in type checking will never pollute the borrow checker’s logic, and the symbol table remains completely agnostic to how types are inferred.