Semantic Analysis
Names, scopes and everything the grammar could not express. Symbol tables, shadowing, resolution, and the annotated tree the type checker needs.
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`.
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.
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.
`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.
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.
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.
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.
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.