Soundness
Without a background in type theory, you're unlikely
to be familiar with the idea of a type system being "sound".
Soundness is the idea that the compiler can make guarantees
about the type a value has at runtime, and not just
during compilation. This is normal for most programming
languages that are built with types from day one.
Building a type system which models a language which has
existed for a few decades however becomes about making
decisions with trade-offs on three qualities: Simplicity,
Usability and Soundness.
With TypeScript's goal of being able to support all JavaScript
code, the language tends towards simplicity and usability
when presented with ways to add types to JavaScript.
Let's look at a few cases where TypeScript is provably
not sound, to understand what those trade-offs would look
like otherwise.
Type Assertions
// TypeScript will let you use type assertions to override
the inference to something which is quite wrong. Using
type assertions is a way of telling TypeScript you know
best, and TypeScript will try to let you get on with it.
Languages which are sound would occasionally use runtime checks
to ensure that the data matches what your types say - but
TypeScript aims to have no type-aware runtime impact on
your transpiled code.
Function Parameter Bi-variance
Params for a function support redefining the parameter
to be a subtype of the original declaration.
const usersAge = ("23" as any) as number;
// You can re-declare the parameter type to be a subtype of
the declaration. Above, handler expected a type InputEvent
but in the below usage examples - TypeScript accepts
a type which has additional properties.
interface InputEvent {
timestamp: number;
}
interface MouseInputEvent extends InputEvent {
x: number;
y: number;
}
interface KeyboardInputEvent extends InputEvent {
keyCode: number;
}
function listenForEvent(eventType: "keyboard" | "mouse", handler: (event: InputEvent) => void) { }
// This covers the real-world pattern of event listener
in JavaScript, at the expense of having being sound.
TypeScript can raise an error when this happens via
`strictFunctionTypes`. Or, you could work around this
particular case with function overloads,
see: example:typing-functions
Void special casing
Parameter Discarding
To learn about special cases with function parameters
see example:structural-typing
Rest Parameters
Rest parameters are assumed to all be optional, this means
TypeScript will not have a way to enforce the number of
parameters available to a callback.
listenForEvent("keyboard", (event: KeyboardInputEvent) => { });
listenForEvent("mouse", (event: MouseInputEvent) => { });
// This can go all the way back to the smallest common type:
listenForEvent("mouse", (event: {}) => { });
// But no further:
listenForEvent("mouse", (event: string) => { });
// A function which returns a void function, can accept a
function which takes any other type.
function getRandomNumbers(count: number, callback: (...args: number[]) => void) { }
getRandomNumbers(2, (first, second) => console.log([first, second]));
getRandomNumbers(400, (first) => console.log(first));
// Void Functions Can Match to a Function With a Return Value
// For more information on the places where soundness of the
type system is compromised, see:
https://github.com/Microsoft/TypeScript/wiki/FAQ#type-system-behavior
https://github.com/Microsoft/TypeScript/issues/9825
https://www.typescriptlang.org/docs/handbook/type-compatibility.html
const getPI = () => 3.14;
function runFunction(func: () => void) {
func();
}
runFunction(getPI);