The type checker is the largest and most complex component of the TypeScript compiler. Located in src/compiler/checker.ts (over 3.1 MB), it performs semantic analysis and type validation.
The checker first resolves all symbols created by the binder:
// Example from checker.tsfunction getTypeOfSymbol(symbol: Symbol): Type { // Resolve the type associated with a symbol if (symbol.flags & SymbolFlags.Variable) { return getTypeOfVariableOrParameterOrProperty(symbol); } if (symbol.flags & SymbolFlags.Function) { return getTypeOfFuncClassEnumModule(symbol); } // ... many more cases}
// Simplified example of type checking processfunction getTypeFromTypeNode(node: TypeNode): Type { switch (node.kind) { case SyntaxKind.StringKeyword: return stringType; case SyntaxKind.NumberKeyword: return numberType; case SyntaxKind.TypeReference: return getTypeFromTypeReference(node); case SyntaxKind.UnionType: return getUnionType(map(node.types, getTypeFromTypeNode)); // ... hundreds more cases }}
The checker validates assignments using structural type comparison:
// Type compatibility exampleinterface Point { x: number; y: number; }interface Named { name: string; }let point: Point = { x: 0, y: 0 };let named: Named = { name: "origin" };// The checker validates this assignmentpoint = { x: 1, y: 2 }; // ✓ Compatible// And rejects this onepoint = named; // ✗ Error: Types have no properties in common
The checker handles union types by ensuring operations are valid on all constituents:
function processValue(value: string | number) { // Checker allows operations valid on both types console.log(value.toString()); // ✓ // Checker rejects operations not valid on all types console.log(value.toUpperCase()); // ✗ Error: Property 'toUpperCase' does not exist on type 'number'}
Control flow analysis narrows types within conditional branches:
function example(x: string | number) { if (typeof x === "string") { // Type narrowed to string console.log(x.toUpperCase()); // ✓ } else { // Type narrowed to number console.log(x.toFixed(2)); // ✓ }}
function identity<T>(value: T): T { return value;}// Checker infers T = numberconst num = identity(42);// Checker infers T = stringconst str = identity("hello");
let num: number = "hello";// Error: Type 'string' is not assignable to type 'number'
interface Point { x: number; y: number; }const point: Point = { x: 0 };// Error: Property 'y' is missing in type '{ x: number; }'
function greet(name: string) { }greet(42);// Error: Argument of type 'number' is not assignable to parameter of type 'string'
function first<T extends { length: number }>(arr: T): T[0] { return arr[0];}first(42);// Error: Type 'number' does not satisfy the constraint '{ length: number }'
Resolved function signatures are cached to avoid recomputation:
function getResolvedSignature( node: CallLikeExpression, candidatesOutArray?: Signature[], checkMode?: CheckMode): Signature { // Check cache first // Compute and cache if not found}