The Best Line of TypeScript I Have Written

March 5, 2017

In TypeScript, I was trying to get the type of the return value of a function. I naively tried:

type fnReturn = typeof myFunction();

Unfortunately, it didn't work, because that functionality isn't built into TypeScript yet. Bummer.

On the other hand:

const temp = myFunction();
type fnReturn = typeof temp;

This will work, but it's suboptimal. It calls myFunction, and if myFunction has side effects, this is pretty annoying. (Tsk tsk, writing functions with side effects!) Surely we can do better.

So I searched around and found a StackOverflow answer which suggested the following workaround:

const temp = false && myFunction();
type fnReturn = typeof temp;

Unfortunately, this was written back in the days of TypeScript 1.0. TypeScript has since added much more clever type inference. For example, TypeScript 2.0 is smart enough to determine that the type of true && 6 is just number.

It goes deeper. Sure, if you say let x = true && 6, it infers x to be a number. But if you say const x = true && 6, TypeScript knows x will never change - that's what const means after all - so it infers the type of x to be not just any number, but the specific number 6! This is called a type literal. This is useful because then if you write something like const y = x === 6 ? true : false it can statically determine that the type of y is false. Keep this in mind for later.

This code used to work, but nowadays TypeScript is smart enough to statically determine that false && myFunction() will short-circuit, and so the type of temp is (correctly, but frustratingly) determined to be the type literal false.

Not to be dissuaded, I tried something where TypeScript couldn't statically determine the type of the boolean:

let myBoolean: boolean = getFalseSomeObscureWay(); // type inferred to be boolean, not false.
const temp = myBoolean && myFunction();

Now TypeScript infers the type of temp to be... the union of false and the return value of the function.

Darn it, TypeScript.

I mean, sigh, yes, you're right, as usual. If myBoolean was false, then the type will be false. If it's true, the type will be the return value. So it's the union of false and the return value of the function. But that's not what I wanted! Argh. If only there was some way to make the boolean be false, but trick TypeScript into thinking it was true!

Hey, wait a minute...

What if I just...

const temp = (false as true) && myFunction();

Yep.

It's beautiful, and it works. It casts the true value to have the type of the false literal. The type system trusts your cast and goes ahead to infer that the type of temp is just the return value of myFunction. (You'd never lie to the type checker... right??) When the code is actually executed, the variable gets the value of false and doesn't call the function.

The crazy thing about this hack is that it can be extended to get the type of ANY arbitrary expression in TypeScript! e.g. in TypeScript there's no way to get the type of some arbitrarily deep nested access like foo.bar.baz.blah, but of course now you can just do const myType = (false as true) && foo.bar.baz.blah.

It's beautiful.

Addendum: arbitrarily deep nested type inference is coming to TypeScript! Someday. There's an issue for it, anyways.

Back


As you may have noticed, I don't have comments on my blog. Instead, I do coffee-comments! Email me at johnfn@gmail.com and ask to meet up for coffee and discussion. Coffee is on me for the first 5 emails that convert into meetups. :)