Type a branch, a subject or a topic — “round robin”, “paging”, “civil”.

Type Checking

Types flow up from the leaves, a coercion gets inserted, and a grammatically perfect program is rejected.

Skip to the animation

Type checking walks the annotated syntax tree bottom-up, computing each node's type from its children, inserting the conversions the language implies, and rejecting programs the grammar was perfectly happy to accept.

What the grammar cannot say

pos = initial + rate * "hello" is grammatically flawless. A context-free grammar cannot express "the operands of * must be numeric", because that rule depends on what the identifiers were declared as — context the grammar by definition ignores.

So a separate phase checks it, using the symbol table for declared types and the tree for structure. This is the phase that catches most of the errors a working programmer actually sees.

Types flow upward

  1. 1Leaves get their types from the symbol table (variables) or their form (literals).
  2. 2Each interior node computes its type from its children's, checking the operator permits them.
  3. 3If the operands mismatch but a safe conversion exists, insert a coercion node.
  4. 4If no conversion exists, report a type error.

A value computed from a node's children and passed upward is a synthesised attribute. An inherited attribute flows the other way, down from parent to child — needed for things like the expected type of a function argument.

Checking and rewriting

The phase does not only verify — it modifies the tree. Wrapping 60 in inttofloat makes an implicit language rule explicit, so that intermediate code generation can treat it as an ordinary operation and never think about types again.

This is a recurring pattern in compilers: each phase's real job is to remove a category of implicitness, so that later phases can be simpler.

Static, dynamic, and the rest of the phase

Static checking happens at compile time, so errors surface once and the generated code carries no type tags. Dynamic checking happens at run time, so values carry their types and errors appear only when that line executes. Most languages do both — static where they can, dynamic for array bounds and downcasts.

Type checking is the largest part of semantic analysis, not all of it. The same walk verifies that names are declared before use, that calls have the right number of arguments, that break is inside a loop, and that every path through a non-void function returns. All of them are things the grammar cannot express, and all of them need the symbol table.

Advantages and disadvantages

Advantages

  • Catches a large class of real errors before the program is ever run.
  • Makes implicit conversions explicit, simplifying every later phase.
  • Enables optimisation — knowing a value is an int permits code a dynamic language could not emit.

Disadvantages

  • Rejects some programs that would in fact have worked; that is the price of deciding in advance.
  • Implicit coercions can hide genuine bugs behind a silent conversion.
  • Rich type systems make checking slow, and their error messages notoriously hard to read.

Watch it work

loading visualisation…

Check yourself

question 1 / 4

One question at a time. Pick an answer to see why it is right or wrong, then move on — there is no score to keep and nothing is saved.

Types flow through the syntax tree in which direction, and why?
What is a coercion, and why is it inserted rather than just allowed?
What is the difference between static and dynamic type checking?
Why can a grammar not express type rules?

0 / 4

4 still unanswered — the dots above jump straight to them.