TypeScript's type-independent output

April 10, 2016

Many questions about TypeScript's type system and implementation can be answered by understanding one of their central design goals: the output (generated JavaScript) is independent of the types found in the input. This seems simple enough but it has some surprising consequences. Here are some answers to questions you might have that are actually already answered by this principle.

Can a program with type errors still compile and produce JS? Yes, because the output is the same no matter the types. (There's a compiler flag, --noEmit to say "don't generate any output when there's an error"; you might want to turn it on in your tsconfig.json for serious apps.)

The compiler can even generate something if the input has syntax errors though that is likely not what you want. Type some gibberish in the TypeScript playground to see.

Can plain JavaScript files without any type annotations be transpiled by the compiler? Yes, because of the prior point. (You have to opt in to allow the .js extension.)

How do I make a run-time decision based on the underlying type of a variable? E.g. if a function accepts some BaseClass param, is there a way to determine which subclass was actually passed? The answer is no, unless you provide some method on BaseClass yourself or use typeof/instanceof, which only knows about the types that JavaScript knows about. (This is true in any language, of course — if the type is only known at run time, it must be a run-time type query!)

However, see also the docs on type guards for some assistance from the compiler.

Are TypeScript union types the same as sum types? A TypeScript union type is written like string|number, which looks a lot like a sum type as written in ML/Haskell. But a union type just means the variable (or return type of a function, etc.) can be either of the options, and determining which requires a typeof/instanceof check to determine the run-time JavaScript type. In contrast, an ML-style sum type has a type tag that e.g. requires you to pattern match to extract the contained value.

(Why provide these weak-looking union types? Because TypeScript is attempting to model existing JavaScript usage, and many APIs such as jQuery semantically are already using union types. Also, as you'll see in a future post, these union types end up being pretty powerful.)

Can I use for (... of ...) to iterate through ES6 maps? Because the generated code is independent of types, when targeting ES5 output, for-of loops always generate plain

for (var i = 0; i < x.length; i++) ...

loops. When targeting ES6 output, the output is the same as the input, a for-of loop. This means you can't use a for-of loop to iterate through an ES6 map when targeting ES5 output, which is pretty disappointing!

There are open bugs on this but the solution is not obvious. Perhaps the right thing would be to polyfill ES6 iterators and always use them.

How do overloaded functions work? TypeScript lets you write code with overloaded functions like this:

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any { return a + b; }

However, because the output is the same regardless of the types, you already know that the first two type-only definitions are dropped in the output. You can only proivde a single definition of a function to JavaScript, and that function, which provides the actual implementation, must be compatible with all the different type signatures declared. (In practice, overloaded functions are mostly used to describe the types of existing JS libraries.)