简体   繁体   中英

union type inference in typescript

type p1 = { a: number, b: string }
type p3 = { a: string }
type p4 = p1 | p3

let demo: p4 = { a: '123', b: '123' }

function isP3(obj: p4): obj is p3 { 
    return typeof (<p3>obj).a === 'string'
}

function func(obj: p4) {
    if ('b' in obj) {
        // Uncaught TypeError: obj.a.toFixed is not a function
        obj.a.toFixed() //<- Now, no error is given
    } else { 

    }
}
func(demo)

Why the demo did not report an error when initialize? User-Defined Type Guards

This is an open issue (microsoft/TypeScript#20863) in TypeScript. Your union type is not a discriminated union , so the compiler doesn't split the union up into members before performing excess property checking . Most people (myself included) would expect that excess property checks should occur for each member of the union whether or not the union is a discriminated one. For now, though, that's just the way it is: the compiler sees that "b" is an acceptable property in at least one of the union members and decides not to complain.

Note that excess property checking is a convenience, and not a matter of type safety. Object types in TypeScript are open , and you can always add more properties to them over what's in the definition without violating the type. A value {x: 1, y: 2} is a valid {x: number} despite having that y property. Another way of saying this is that object types in TypeScript are not exact . It is therefore technically true that { a: '123', b: '123' } is a valid p3 and therefore a valid p4 . And so, technically, you can't just check for the presence or absence of b to distinguish between p1 and p3 . Yes, if you just try to say const demo: p3 = {a: '123', b: '123'} you'll get an excess property warning on "b" , but this is, as I said, just a convenience. It is easily defeated:

const demo1 = { a: '123', b: '123' };
const demo2: p3 = demo1; // no error

At this point you might be wondering: "wait, if "b" does not properly distinguish p1 from p3 , why does the compiler think it does inside func() ?". Good question:

if ('b' in obj) { // why does the compiler think this narrows obj to p1?
    obj.a.toFixed() // no error, but blows up at runtime
}

Well, it turns out that the in type guard is intentionally unsound . It's technically not safe to use it, but people do and usually it's not a problem. But it doesn't help you here. Oh well.


So, what should you do here? If your intent is to make a test for b distinguish between p1 and p3 , then your p3 type should make that clear:

type p3 = { a: string, b?: undefined }; // p3 cannot have a defined "b" property    

Now the p4 type is, as of TypeScript 3.2+ , a true discriminated union. And so this is an error:

let demo: p4 = { a: '123', b: '123' } // error now

And makes the unsound 'b' test show up as an error. If you want to do a "good" b test, you can now test for obj.b !== undefined , which will definitely distinguish between a p1 and p3 with the new p3 definition:

function func(obj: p4) {
    if ('b' in obj) {
        obj.a.toFixed() // error now
    }

    if (obj.b !== undefined) {
        obj.a.toFixed(); // okay
    }
}

Okay, hope that helps; good luck!

Link to code

Use your custom type guard function to narrow types down instead of 'b' in obj

    if (!isP3(obj)) {
        obj.a.toFixed() // Error
    } else { 

    }
}

About the assignment let demo: p4 = { a: '123', b: '123' } it bothers me too that no error is given. As I found out it will work properly (which means giving an error) if we define a as a boolean instead of a number. Looks like the assignment fails only if the discriminator type contains union type itself. You can subscribe to this issue for details https://github.com/microsoft/TypeScript/issues/35861

Section of the spec on the matter doesn't explain fully the current behavior. Looks like for an explanation one have to peek into the compiler itself. https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#34-union-types

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM