What Checks Happen in Semantic Analysis and Type Checking?
Before a compiler generates machine code or intermediate representation, it must verify that a syntactically valid program actually makes sense. Semantic analysis and type checking form the bridge between parsing and code generation, catching logical errors, undefined identifiers, and mismatched data types. By enforcing the language rules early, the compiler guarantees that the program has a well-defined meaning and prevents invalid operations from reaching the runtime environment.
Name Resolution and Scope Verification
The compiler first ensures that every identifier in the program refers to a valid, accessible entity. Using a symbol table, it tracks variables, functions, types, and modules across nested scopes.
- Declaration Before Use: In many languages, variables and functions must be declared before they are referenced. The compiler flags any undeclared identifier as an error.
- Duplicate Declarations: Defining two variables or functions with the same name in the same scope is caught, unless the language explicitly permits overloading.
- Scope and Shadowing Rules: The analyzer resolves which declaration an identifier refers to when inner scopes shadow outer scopes, ensuring references bind to the intended symbol.
- Visibility and Access Control: For object-oriented or modular languages, the compiler verifies that private, protected, or internal members are not accessed from unauthorized scopes.
Type Checking and Inference
Type checking verifies that operations are performed only on compatible data types. Whether a language uses explicit type annotations or type inference, the compiler assigns a type to every expression and validates how those types interact.
- Operator Compatibility: The compiler checks that operators receive valid operand types, such as preventing addition between a string and a boolean unless an explicit rule allows it.
- Assignment Compatibility: When assigning a value to a variable, the type of the expression must match or be safely convertible to the variable's declared type.
- Function Signatures and Arity: Every function call is checked to ensure the number of arguments matches the parameter count and that each argument type is compatible with the corresponding parameter.
- Return Type Verification: The compiler confirms that every return statement in a function yields a value compatible with the function's declared return type.
- Generic and Polymorphic Constraints: In languages with generics or templates, the compiler checks that type arguments satisfy any required interfaces, traits, or bounds.
Control Flow and Definite Assignment
Beyond types and names, semantic analysis inspects how execution flows through the program to catch structural issues that syntax alone cannot detect.
- Definite Assignment: Many compilers verify that local variables are initialized along all possible execution paths before they are read.
- Exhaustive Returns: For functions that declare a non-void return type, the analyzer checks that every possible control flow path ends with a return statement or throws an exception.
- Loop and Switch Contexts: Statements like
breakandcontinueare only valid inside loops or switch blocks; using them outside these contexts triggers a semantic error. - Unreachable Code Detection: Statements placed after an unconditional return, break, or infinite loop may be flagged as warnings or errors.
Mutability and Memory Safety Checks
Modern compilers often perform additional semantic checks to enforce immutability and memory safety guarantees before lowering to machine code.
- Const and Immutability Violations: Attempting to reassign a constant or mutate an immutable data structure is rejected during semantic analysis.
- Borrowing and Lifetime Rules: In languages with ownership models, the compiler verifies that references do not outlive the data they point to and that mutable aliasing rules are respected.
- Array Bounds and Constant Folding: When array indices or sizes are compile-time constants, the compiler can check for negative sizes or out-of-bounds access immediately.
Once all of these semantic and type checks pass, the compiler can safely proceed to intermediate code generation and optimization, confident that the program adheres to the language specification.