Symbol Table Attributes for Variables and Functions: Type Checking & Semantic Analysis
A compiler’s SymbolTable stores metadata (attributes) about each declared Identifier—most importantly for TypeChecking and SemanticAnalysis.
In general, symbol table entries differ for variables vs. functions because variables primarily need storage/type/lifetime information, while functions additionally need signature/parameter and calling information. The semantic analyzer consults these entries when it resolves identifiers, checks declarations, builds type information for expressions, and validates function calls. Type checking then uses the stored types (and sometimes qualifiers/constraints) to ensure rules like “assignment compatibility” and “argument/parameter match” hold.
Below is a comprehensive, language-agnostic set of common attributes used in practice, along with how they are consumed during semantic analysis and type checking.
Note: In a real implementation, the exact set of attributes depends on the language (e.g., static vs. dynamic typing, overloading, mutability rules, generics) and the compiler phase design.
Compiler Symbol Table, Semantic Analysis & Type Checking (Conceptual Overview)
Symbol table entry for a variable (common attributes)
A variable entry typically records attributes required to (1) resolve uses of the variable, (2) validate contexts where it is used (l-value vs r-value), and (3) check type rules. Common fields include:
Core identity & declaration metadata
- Name / identifier key: The string used for lookup.
- Kind: Variable vs function vs type vs label (often an enum).
- Scope / nesting level: Where it is valid (block/function/class scope).
- Source location: File/line/column for error messages.
- Declaration node reference: Pointer/index to the AST node for the declaration.
Type-related attributes
- Declared type: e.g.,
int,float, user-defined type, or a type-variable. - Type qualifiers (language-dependent):
const,volatile,mut, ownership/borrowing info. - Resolved/instantiated type: Important if the language has type inference or generics.
- Array/function-pointer/structural type metadata: e.g., element type for arrays.
Storage & runtime-relevant attributes
Even if semantic analysis is separated from codegen, compilers often store enough to support later phases:
- Storage class / linkage: local stack variable vs global vs static vs external.
- Memory layout information: offset within a frame, global address, or an abstract “location”.
- Lifetime / validity window: often implicit via scope, but may include definite assignment/initialization tracking.
- Initialization state: whether a local is definitely assigned before use (common in languages like C#).
Usage constraints that semantic analysis can enforce
- Mutability flag: whether assignments are permitted.
- Addressable-ness: whether the variable can be referenced as an l-value.
- Special categories: captured variables (closures), thread-local, register variables.
VariableEntry
Symbol table entry for a function (common attributes)
Function entries must support (1) name resolution for call sites, (2) signature-based checking of calls, (3) return-type checking of function bodies, and (4) overload-resolution or generic instantiation if present.
Signature and call semantics
- Name / identifier key
- Kind: function/method/constructor.
- Scope / nesting level: where calls are visible.
- Parameter list:
- Parameter names (for diagnostics)
- Parameter types
- Parameter qualifiers:
in/out,readonly,const, default values
- Return type:
- Declared return type or “to be inferred” placeholder
- For languages with type inference, the return type may be resolved after analyzing body.
- Function type / signature representation:
- A unified object capturing
(param_types) -> return_type.
- A unified object capturing
- Varargs / calling convention (language/ABI dependent).
Polymorphism and constraints (language dependent)
- Generic parameters and constraints.
- Overload set membership:
- If the language supports overloading, the symbol table may store either:
- multiple function entries under the same name, or
- an overload-resolver structure that returns the best match.
- If the language supports overloading, the symbol table may store either:
- Implicit conversions/constraints rules:
- e.g.,
requiresclauses or concept-like constraints.
- e.g.,
Definition metadata for semantic analysis
- Defined vs declared (prototype): used to detect “used before defined” or “mismatched declaration”.
- Body reference: AST node for the function definition.
- Control-flow / return coverage info:
- Many compilers compute this during semantic analysis and may store it per function.
- Exception/termination specifiers: if language has
throws/noreturn, etc.
Runtime/codegen attributes
- Linkage / mangled name: for ABI.
- Captured context info (closures): if nested functions capture variables.
- Frame layout / local slots: offsets for parameters/locals.
FunctionEntry
How semantic analysis uses symbol attributes for name resolution and correctness
- 1Step 1
When the analyzer sees a variable/function declaration, it creates a symbol table entry with kind, scope, type signature (variables: type; functions: params+return), and diagnostic metadata.
- 2Step 2
On entering a new block/function/class scope, the analyzer pushes a new scope; on exit, it pops it so lookups respect lexical visibility.
- 3Step 3
When an identifier appears in an expression/call site, the analyzer performs lookup to retrieve the symbol entry and ensures the kind matches the use (e.g., calling a function kind, indexing a variable/array kind).
- 4Step 4
Using stored fields like defined-vs-declared (functions), mutability (variables), initialization state, and correct arity/parameter count, it emits errors for invalid uses.
- 5Step 5
For variables: attach the variable’s resolved type to expression nodes. For function calls: type-check arguments against stored parameter types and compute the resulting expression type from the function’s return type.
- 6Step 6
If the language supports inference or flow-sensitive analysis, the analyzer may update stored attributes (e.g., resolved return type, definite assignment) for later phases.
How variable attributes drive type checking
During TypeChecking the expression type is often computed by consulting variable attributes:
-
Identifier expression:
When an identifier is used as an expression, the checker uses the variable entry’s resolved type and possibly qualifiers. For example:- Using a
const-qualified variable as an l-value target for assignment triggers an error (mutability attribute). - Using an uninitialized local triggers a “use before assignment” diagnostic (initialization state attribute).
- Using a
-
Binary/unary operations:
Operator type rules require operand types. Since operand types are derived from variable entries, the variable’s declared/resolved type directly impacts:- whether an operator is permitted,
- whether implicit conversion is allowed,
- the resulting expression type.
-
Assignments / initialization:
Assignment compatibility checks rely on:- the variable entry’s type,
- qualifiers (e.g., const),
- and potentially the variable’s “addressable/l-value” status.
ResolvedType
Qualifier
Implementation detail: many compilers store both declared and resolved types to support inference or specialization.
How function attributes drive type checking at call sites
Function calls require more signature-driven logic than variable references. Semantic analysis typically:
-
Arity and parameter checking
Uses the function entry’s parameter list to ensure the number of provided arguments matches expected parameters (including varargs handling if present). -
Argument type compatibility
For each argument expression:- compute the argument type (which itself may depend on variable attributes),
- compare it against the corresponding parameter type using the language’s compatibility/conversion rules,
- check qualifiers and mode (e.g., pass-by-reference requiring addressable arguments).
-
Return type propagation
The call expression’s type is taken from the function entry’s return type (or inferred return type once computed). -
Overloading / overload resolution (if supported)
If multiple functions share a name, the symbol table may represent an overload set; type checking then selects candidates based on:- argument types,
- conversion ranking,
- and constraints/polymorphic instantiation rules.
-
Definition consistency checks (when both declaration and definition exist)
The compiler compares stored function attributes (signature, parameter types, return type, qualifiers) between prototypes and definitions.
Design tip: separate 'lookup' from 'checking'
A clean implementation first resolves an identifier to a symbol-table entry (name resolution), then applies type/scope/usage rules. Storing the right attributes (kind, scope, type signature, qualifiers) makes those checks local and deterministic.
Beware of phases when types are not yet resolved
If your language supports forward declarations, inference, or generics, some attributes (e.g., resolved types/return types) may be placeholders. Type checking must either run after resolution or be written to handle “unknown yet” types safely.
Typical semantic+type-checking lifecycle using symbol attributes
Enter symbols
1. Declaration passCreate variable/function entries with kind, declared types/signatures, and scope."
Resolve identifiers
2. Resolution passLook up entries for each use; verify expected kind (variable vs function)."
Compute expression types
3. Type synthesisVariable uses yield variable resolved type; call expressions yield function return type."
Validate rules
4. Constraint/type checkingCheck assignments, operator operands, and argument/parameter compatibility."
Update flow-sensitive facts
5. Record resultsStore initialization/definite assignment or inferred types if applicable."
Cross-cutting attribute patterns (what you should store)
Across both variables and functions, compilers commonly store attribute families that correspond to major semantic needs:
| Attribute family | Variable example | Function example | Used for |
|---|---|---|---|
| Kind + scope | variable kind=Var, block scope | function kind=Func, class scope | Correct resolution & visibility |
| Type info | declared/resolved type | signature: param types + return | Expression typing & compatibility |
| Qualifiers/modes | const/mutable, l-value requirements | parameter mode (in/out/ref), constness | Legal usage checks |
| Definition status | defined/inferred? (local) | declared vs defined; prototype info | Consistency diagnostics |
| Storage/runtime | stack offset/global address | linkage/mangled name, ABI info | Later IR generation (and sometimes semantic checks) |
| Diagnostic metadata | source location | source location + signature info | High-quality error messages |
Common questions during implementation
Knowledge Check
Which symbol-table attribute is primarily used to type-check an expression that is just an identifier (e.g., using a variable name in an arithmetic operation)?
Explore Related Topics
Symbol Table Attributes: Why the Correct Answer Is “All of These”
Virtual Memory, Its Implementation, and the Role of the TLB
Virtual memory abstracts physical RAM by giving each process a large contiguous logical address space, implemented with paging, page tables, and a Translation Lookaside Buffer (TLB) that caches recent translations.
- Provides protection, simplifies programming, enables demand paging and sharing of code/pages.
- Virtual address = (VPN, offset); physical address = (PFN, offset) with VPN → PFN via TLB or page‑table walk.
- Effective access time: , so high TLB hit rate is critical.
- Multi‑level page tables reduce memory use for sparse address spaces.
- TLB reach = (entries) × (page size); exceeding it causes TLB thrashing and performance loss.
Asymptotic Notation in Algorithm Analysis