microsoft/TypeScript
Type checker
The largest and most consequential subsystem in the compiler. The checker answers all type questions: it builds Type objects, computes assignability, runs control-flow narrowing, infers generic type arguments, resolves overloads, and produces the long tail of TypeScript-specific diagnostics.
Source
src/compiler/checker.ts — 54,434 lines in a single file. Roughly 12% of all TS source in the repository. Internally organised by section comments rather than by export structure.
Purpose
The checker has three rough jobs that are interleaved across its surface:
- Lookup — given an AST node, find its symbol, then the symbol's type.
- Inference — run type inference for generics, contextual typing, and
--noImplicitAnydefaulting. - Validation — emit diagnostics when inferred types fail assignability, when
--strictNullChecksrules are violated, when overloads are ambiguous, etc.
It also acts as the resolution oracle for downstream tools — the language service, the emitter, and the declaration-emit pipeline all call into it.
Key abstractions
| Symbol | Role |
|---|---|
createTypeChecker(host, produceDiagnostics) |
Factory; returns a fully-configured TypeChecker |
TypeChecker (interface in types.ts) |
The public surface used by callers |
Type, TypeFlags, ObjectType, UnionType, IntersectionType, IndexedAccessType, MappedType, ConditionalType, … |
Type representations; see primitives/type |
Signature |
Function/constructor signature; see primitives/signature |
getSymbolAtLocation(node) |
Resolve a node to its Symbol |
getTypeOfSymbol(symbol) |
Compute (and cache) the type of a symbol |
getTypeAtLocation(node) |
Compute the contextual type at a location |
getApparentType(type) |
Apply intersection-with-base for primitives, etc. |
isTypeAssignableTo(source, target) |
Public assignability |
checkSourceFile(file) |
Drives full-file checking |
Architecture
graph LR
Host["TypeCheckerHost (Program)"] --> Checker["createTypeChecker"]
Checker --> Resolve["resolveName / resolveSymbol"]
Checker --> SymTypes["getTypeOfSymbol (cache: symbol.type)"]
Checker --> Assign["checkTypeAssignableTo"]
Checker --> CFA["getFlowTypeOfReference"]
Checker --> Infer["inferTypes / inferFromTypes"]
Checker --> Resolution["resolveCall / chooseOverload"]
Checker --> Emit["getEmitResolver (for emitter)"]
Checker --> Diag["error + addDiagnostic"]
Diag --> ProgramDiag["Program.getSemanticDiagnostics"]The checker holds a TypeCheckerHost (the program) so it can ask for source files and bind them on demand. Internally it caches:
symbol.id → TypeoncegetTypeOfSymbolresolves it.Typeinstances by structural shape so identical types are shared (flyweight pattern).- Per-call inference results.
Lazy evaluation
The checker does not pre-compute types for every symbol. Instead it answers questions on demand and memoises the results. This is what makes the language service responsive — opening one file doesn't force checking unrelated parts of the program.
Control-flow analysis
CFA is the part of the checker that turns
function f(x: string | number) {
if (typeof x === 'string') {
x.toUpperCase(); // x: string here
}
}into something the rest of the checker can reason about. It walks FlowNodes built by the binder backwards from the reference site. Each flow node represents a control-flow joint (assignment, branch, call), and the walker accumulates narrowing predicates along the way. CFA also drives definite-assignment analysis (--strictPropertyInitialization), unreachable-code detection, and never propagation.
Inference
inferTypes(context, source, target, priority) is the workhorse. Given a target type like Array<T> and a source type like string[], it walks both in parallel, recording inferences for T. Inference handles:
- Conditional-type distribution (
infer Tclauses) - Variance (covariant, contravariant, bivariant)
- Higher-order inference for callbacks
- Contextual typing — propagating expected types into expression trees so e.g.
x.map(item => item.foo)knowsitem's type
Overload resolution
resolveCall chooses among overloaded signatures. It tries each candidate, accumulates errors, and picks the most specific match — or, if none match, reports a diagnostic against the closest candidate. This is one of the heaviest paths in the checker because each candidate's inference has its own type arguments and assignability checks.
Diagnostics
User-visible errors, warnings, and messages are queued via error / errorOrSuggestion / addDiagnostic. They reference entries in src/compiler/diagnosticMessages.json so they can be localised. The checker also produces suggestion diagnostics (e.g., "Did you mean to mark this method async?") that only the language service surfaces.
Integration points
- Program (
program.ts) creates exactly one checker per program viacreateTypeChecker(host, /*produceDiagnostics*/ true). - Language service (
src/services/) uses the checker for completions, quick info, signature help, find-references, refactorings — basically every IDE feature. - Emitter asks the checker for an
EmitResolver(a stripped-down API for emit-time questions like "is this name exported?", "what's the resolved external module?"). - Declaration emit (
transformers/declarations.ts) calls the checker to resolve nominal references to their declarations and produce minimal.d.tsshapes.
Performance
Because of the file's size, performance changes are noisy. The team treats certain patterns as load-bearing:
- Plain
forloops over arrays (faster thanfor…ofon V8 fast paths). Object.create(null)-style maps (MapLike) instead of nativeMapfor short-lived lookups.- Flyweighting types so equality is reference equality.
- A
Debug.assertDefineddiscipline so error paths don't deopt intoundefined-handling.
The tracing.ts module ships hooks that emit Chrome-trace events when --generateTrace is set; the team uses them to diagnose checker hotspots.
Entry points for modification
There is rarely a "small" change to checker.ts. Even a single-line fix usually requires:
- Identifying the right entry point (
getTypeOfX,checkX,isTypeAssignableTo, etc.). - Writing a compiler test under
tests/cases/compiler/ortests/cases/conformance/that fails before the change. - Producing baselines via
hereby runtests --tests=…and inspecting the diffs. - Sweeping the rest of the suite with
hereby runtests-parallelbecause the checker is exercised by many tests.
The team's coding-guideline document (linked from CONTRIBUTING.md) covers more checker-specific conventions.
For the data structures the checker manipulates, see primitives/type, primitives/symbol, primitives/signature. For who calls into the checker, see systems/program and systems/language-service.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.