Frontend

Semantic Analysis

Names, scopes and everything the grammar could not express. Symbol tables, shadowing, resolution, and the annotated tree the type checker needs.

Semantic Analysis
▶ lab

The phase between "this parses" and "this means something". It resolves names, enforces scopes, checks types, and asks the control-flow questions the grammar could not express — every variable assigned before use, every path returning a value, no statement after a `return`.

Q · What does a compiler check after parsing succeeds, and why could the parser not check it?
The Symbol Table
▶ lab

Name to declaration to type to scope. `x` is a local variable of type `int` in the block starting at line 12; `foo` is a function of type `(int) -> bool` at file scope. It is a hash map with a scope discipline, and every name-related question a compiler or an editor answers is a query against it.

Q · Where does a compiler keep what it knows about every name, and how does it find the right one?
Lexical Scope
▶ lab

A name means whatever the enclosing text says it means. Global contains the function, the function contains the block, the block contains another block, and a lookup walks outward until it finds a binding — which is why you can read a program's meaning off the page without running it.

Q · How does a compiler decide which declaration an identifier refers to?
Shadowing
▶ lab

`let x = 1; { let x = 2; }` — the inner `x` hides the outer one for the length of its scope. It is not a special rule; it is what "walk outward and take the first hit" does. Whether it is a feature or a warning depends entirely on which language you are in.

Q · What happens when an inner scope declares a name that already exists outside it?
Name Resolution
▶ lab

Identifier, find the declaration, attach the symbol. Straightforward for a local variable, and genuinely hard for an overload set, a re-exported import, two glob imports that both provide the name, or a method on a receiver whose type has not been inferred yet.

Q · How does an identifier get connected to the thing it names, and where does that get hard?
Static and Dynamic Scoping
▶ lab

Under lexical scope a name means what the enclosing text says; under dynamic scope it means whatever the most recent caller bound it to. Nearly every modern general-purpose language chose lexical — and then reintroduced dynamic scoping deliberately, as `this`, thread-locals and context variables.

Q · What is dynamic scoping, and why does my lexically-scoped language still have some?
Declaration Order
▶ lab

Can a function call one defined later in the file? C says no without a forward declaration; Java, Rust and Go say yes anywhere; JavaScript says yes for functions and throws for `let`. The compiler achieves order-independence with one extra pass, and the language decides whether to make you do it by hand.

Q · Can a function call another one that is defined further down the file, and what does the compiler have to do to allow it?
The Annotated AST
▶ lab

Same tree, new fields. Every identifier now points at a declaration and every expression carries a type: `BinaryExpression{type: int, lhs: int, rhs: int}`. This is the artifact semantic analysis produces and the thing lowering consumes, and its defining property is that the shape did not change.

Q · What exactly does the type checker hand to the next phase, and how is it different from what the parser produced?