30 — Where types lie¶
One line: A program that tsc accepts under strict can still crash with a TypeError: five pages here show a way it happens, the clean type check beside the crash, and one shows the fix.
TypeScript is unsound by design: some checks are traded away for convenience and some claims are taken on trust. Each page shows a program that type-checks cleanly and then fails in Node. It starts with any, which switches checking off wherever it flows, then as assertions, then index access typed as always present. JSON.parse shows the door where most unchecked data comes in, and run-time validation is the fix at that door. It ends with array covariance, the unsoundness the language chose on purpose.
The pages¶
| Page | Level | In one line | Status |
|---|---|---|---|
any is contagious |
201 | any passes itself on: config.port read from an any is any, so const next: number = port + 1 type-checks while holding the string "80801", and next.toFixed(2) then throws TypeError with no word from tsc. |
stub |
| Type assertions | 201 | {} as User compiles and user.name.toUpperCase() then throws TypeError: as changes what tsc believes, not the value. tsc refuses only types with no overlap, like "42" as number, and as unknown as number gets past even that. |
stub |
| Index access | 201 | prices[99] on a number[] is typed number, so prices[99].toFixed(2) passes tsc under strict and throws TypeError in Node; noUncheckedIndexedAccess, which strict leaves off, adds undefined to every index read and catches it. |
stub |
| External data | 201 | JSON.parse is declared to return any, so const order: Order = JSON.parse(text) compiles whatever the text holds — with total sent as "99", order.total + 1 prints 991. Under Node's own types response.json() returns unknown instead, and tsc demands a check. |
stub |
| Run-time validation | 201 | Types are gone at run time, so the check has to be code: a parseOrder(data: unknown): Order that tests each field with typeof and in satisfies tsc and turns {"total": "99"} into a TypeError at the border instead of a wrong 991 later. |
stub |
| Array covariance | 301 | tsc lets a Dog[] be used as an Animal[], so pushing a plain Animal through that alias type-checks and the next d.bark() on the original array throws TypeError; typing the alias readonly Animal[] closes the hole by removing push. |
stub |
Boundaries¶
The compatibility rules themselves, variance included, are in Type compatibility; any, unknown and never are introduced in Everyday types.