You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Narrowing discriminated unions with user-defined type guards #11787
Tim Ruffles (@timruffles) It doesn't seem like what you're proposing generalizes well. Your narrows invocation is oblivious to the type of cheeseSample, you're just passing in an arbitrary expression of type string that you establish for the remainder of the function to be a particular string literal.
What you want to do can be accomplished with the existing type system, it is just somewhat awkward:
typeCheese={type: 'brie';brieOnly: true,}|{type: 'cheddar';cheddarOnly: true,};functionnarrows<Wide,NarrowextendsWide>(w: Wide,f: (w: Wide)=>Narrow): w is Narrow{returnf(w)!==undefined;}functiondoSomethingWithCheese(cheeseSample: Cheese){if(cheeseSample.type==='cheddar'){cheeseSample.cheddarOnly;}if(narrows(cheeseSample,c=>c.type==='cheddar' ? c : undefined)){constt: 'cheddar'=cheeseSample.type;cheeseSample.cheddarOnly;}}
Right now I'm relying on type inference to make this work, but the fact that you have to use undefined and return the actual instance instead of a boolean is awkward. Possibly with some more indirection for type inference and narrowing in the language, this could be expressed less hackishly.
This is still an issue in 3.x. I had a slightly simpler variant that didn't have a type-parameterized type guard, but I think it's the same basic issue:
interfaceFoo{type: "foo";foo: string;}interfaceBar{type: "bar";bar: string;}typeFooBar=Foo|Bar;functionisFooType(t: "foo"|"bar"): t is "foo"{returntrue;}constf: FooBar=nullasany;if(f.type==="foo"){f.foo;// works}if(isFooType(f.type)){f.foo;// breaks}
This is a bummer, cause it means that a user-defined type guard deep in the type hierarchy may get bubbled up with "wrapper" type guards for containing types in order to discriminate them:
functionisFoo(foobar: Foo|Bar): foobar is Foo{returnisFooType(foobar.type);}
Asad Saeeduddin (@masaeedu) the narrows call is not attempting to narrow cheeseSample, it's narrowing its type field. I'm suggesting that the information we gain from that narrowing is used to infer the type of cheeseSample.
If you re-read my example you'll see it's the same narrowing & inference logic as is used in the primitive example .type === 'cheese I gave above it, just with user-defined type guards rather than ===.
Activity
aluanhaddad commentedon Oct 22, 2016
What is
narrowsInUnionin your example? I cannot find it.masaeedu commentedon Oct 24, 2016
Aluan Haddad (@aluanhaddad) I think that's just a typo for
narrows.masaeedu commentedon Oct 25, 2016
Tim Ruffles (@timruffles) It doesn't seem like what you're proposing generalizes well. Your
narrowsinvocation is oblivious to the type ofcheeseSample, you're just passing in an arbitrary expression of type string that you establish for the remainder of the function to be a particular string literal.What you want to do can be accomplished with the existing type system, it is just somewhat awkward:
Right now I'm relying on type inference to make this work, but the fact that you have to use
undefinedand return the actual instance instead of a boolean is awkward. Possibly with some more indirection for type inference and narrowing in the language, this could be expressed less hackishly.seansfkelley commentedon Aug 31, 2018
This is still an issue in 3.x. I had a slightly simpler variant that didn't have a type-parameterized type guard, but I think it's the same basic issue:
playground link
This is a bummer, cause it means that a user-defined type guard deep in the type hierarchy may get bubbled up with "wrapper" type guards for containing types in order to discriminate them:
timruffles commentedon Nov 29, 2018
Asad Saeeduddin (@masaeedu) the
narrowscall is not attempting to narrowcheeseSample, it's narrowing its type field. I'm suggesting that the information we gain from that narrowing is used to infer the type ofcheeseSample.If you re-read my example you'll see it's the same narrowing & inference logic as is used in the primitive example
.type === 'cheeseI gave above it, just with user-defined type guards rather than===.cnradich commentedon Jun 3, 2019
Also running into this issue as of
3.6.0-dev.20190603. Another simple example:Using a basic
Shapediscriminated union example.Demonstrates expected discriminated union type inference:
Demonstrates incorrect discriminated union type inference using type guard:
playground
RyanCavanaugh commentedon Aug 22, 2019
The
narrowsdefinition is not something that can narrow the outer type, as discussed aboveAlternately, duplicate #30557