Lesson 4 of 7

Names, Scopes & Type Checking

Semantic analysis checks whether names exist and whether types fit, which the parser does not. It's the stage that catches many real before the program runs.

Name resolution walks the tree carrying a symbol table — a of mapping names to their declarations. Enter a 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 ' ' can name the function but not variables from elsewhere.

Text
let x = 10
fn f(y):           scope stack while checking f's body:
    let z = y + x     [ globals: x, f ]
    return z          [ f's params: y ]
                      [ f's body: z ]
z = 5              <- ERROR: 'z' not in any live scope —
                      f's scopes were popped at its end.

Type checking then asks, for every operation, whether the operand types allow it — using declared types (C, Rust) or inferred ones ( 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 ). Static checking happens here, at compile time; dynamic languages defer the same checks to runtime, one operation at a time.

Text
"hello" + 3

C:           compile error — char* + int is pointer math
             you didn't mean
Rust:        compile error — no impl of Add<i32> for &str
TypeScript:  allowed -> "hello3" (+ is overloaded, checked)
Python:      RUNTIME TypeError — same check, later
JavaScript:  "hello3" — coerces silently, no check ever

Same question — 'do these types fit this operation?' —
five different policies on when/whether to ask it.
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.