Names, Scopes & Type Checking
The parser accepts 'undefined_thing + 3' happily — it's grammatically fine. Semantic analysis is where the compiler asks: does this name exist? Do these types fit? It's the stage that catches your actual bugs.
Name resolution walks the tree carrying a symbol table — a stack of scopes mapping names to their declarations. Enter a function or block: push a scope. Leave: pop it. A name lookup searches innermost outward, which is exactly why an inner 'x' shadows an outer one, and why 'undefined variable' errors can name the function but not variables from elsewhere.
Type checking then asks, for every operation, whether the operand types allow it — using declared types (C, Rust) or inferred ones (compilers can deduce that x = 3 makes x an int, and flow that through everything x touches; that's how TypeScript knows so much with so few annotations). Static checking happens here, at compile time; dynamic languages defer the same checks to runtime, one operation at a time.
This stage powers your editor: 'go to definition' reads the symbol table, autocomplete lists what's in scope with fitting types, and red squiggles are the semantic checker running continuously. A language server (LSP) is a compiler front end kept alive, re-checking as you type.