Eric Liprandi Eric Liprandi - 3 months ago 10
TypeScript Question

Why is type inference not working on union types when one of the type is any?

When I use a union type for which one of the type is

any
, the TypeScript editor does not seem to resolve the type properly. I have the following contrived example:



interface ITest {
name: string;
age: number;
}

interface ITest2 {
last: string;
dob: number;
}

function isGood(input: any) : input is ITest{
return 'name' in input && 'age' in input;
}

function doIt(){
var data : ITest2 | ITest = {
last: 'Test', dob: 123
}

var data2 : any | ITest = {
name: 'else', age: 45
}

if(isGood(data)){
alert(`name:${data.name}, age:${data.age}`); // data shows ITest
}

if(isGood(data2)){
alert(`name:${data2.name}, age:${data2.age}`); // data2 shows any
}
}

doIt();


The code does execute properly, but the design time experience is not what I would expect (both in VS Code - 1.4.0 and in the TypeScript playground - 1.8).

As expected, the following line infers that
data
is of type
ITest
based on the type guard.

alert(`name:${data.name}, age:${data.age}`); // data shows ITest


However, the second part (with
data2
) does not infer the right type and is always
any
:

alert(`name:${data2.name}, age:${data2.age}`); // data2 shows any


In the above line, I would expect intellisense to know that
data2
is of type
ITest
just like it did for
data
previously.

I have tried swapping the 2 types in the union declaration but it did not change the outcome.

Am I missing something?

Use case

The real use case I am facing is that I get a response from some HTTP call. Right off the wire, it has to be of type
any
since the HTTP clients know nothing of our data types.

However, I want to to write a user-defined type guard that checks for the properties we want off that json object, then extract only the data we need and returns it.

Answer

It widens any type to include the type guard. any is already wider than ITest, so it picks any. If you change it to a more narrow type, it correctly infers ITest:

var data2 : {} | ITest = {
    name: 'else', age: 45
}

The reason this happens, is because any is the widest type imaginable, it is every other type at once, so any | ITest doesn't really make sense, that's just the same as any.

Edit: I'd also like to point out that simply removing the type works fine, even if implicit any is turned off. Not sure if this is helpful. Example:

var data2 = {
    name: 'else', age: 45
}

Second edit: Slightly rewrote the answer to be more clear, as the comments indicate, there was some confusion.