Intersection of enum union with literal is unexpectedly `never`
See original GitHub issueTypeScript Version: 2.8.0-dev.20180211
Search Terms: enum literal intersection never
Code
type VerifyExtends<A, B extends A> = true
type VerifyMutuallyAssignable<A extends B, B extends C, C=A> = true
// string enum
enum Bug {
ant = "a",
bee = "b"
}
declare var witness: VerifyExtends<'a', Bug.ant> // okay, as expected
declare var witness: VerifyExtends<'b', Bug.ant> // error, as expected
declare var witness: VerifyMutuallyAssignable<Bug, Bug.ant | Bug.bee> // okay, as expected
declare var witness: VerifyMutuallyAssignable<Bug.ant, Bug.ant & 'a'> // okay, as expected
declare var witness: VerifyExtends<Bug, Bug.ant> // okay as expected
declare var witness: VerifyExtends<Bug & 'a', Bug.ant & 'a'> // error, not expected!!
declare var witness: VerifyMutuallyAssignable<Bug & 'a', never> // okay, not expected!!
// numeric enum
enum Pet {
cat = 0,
dog = 1
}
declare var witness: VerifyExtends<0, Pet.cat> // okay, as expected
declare var witness: VerifyExtends<1, Pet.cat> // error, as expected
declare var witness: VerifyMutuallyAssignable<Pet, Pet.cat | Pet.dog> // okay, as expected
declare var witness: VerifyMutuallyAssignable<Pet.cat, Pet.cat & 0> // okay, as expected
declare var witness: VerifyExtends<Pet, Pet.cat> // okay, as expected
declare var witness: VerifyExtends<Pet & 0, Pet.cat & 0> // error, not expected!!
declare var witness: VerifyMutuallyAssignable<Pet & 'a', never> // okay, not expected!!
Expected behavior:
I expect that Bug & 'a'
should reduce to Bug.ant
, or at least to Bug.ant | (Bug.bee & 'a')
.
Similarly, Pet & 0
should reduce to Pet.cat
, or at least to Pet.cat | (Pet.dog & 0)
.
Actual behavior:
Both Bug & 'a'
and Pet & 0
reduce to never
, which is bizarre to me. I was trying to solve a StackOverflow question and realized that my solution was narrowing literals to never
after a type guard. Something like:
declare function isBug(val: string): val is Bug
declare const a: "a"
if (isBug(a)) {
a // never?!
}
Thoughts?
Related Issues: I’m really not finding any, after searching for an hour. A few near misses but nothing that seems particularly relevant.
Issue Analytics
- State:
- Created 6 years ago
- Comments:14 (9 by maintainers)
Top Results From Across the Web
Handbook - Unions and Intersection Types - TypeScript
Discriminating Unions A common technique for working with unions is to have a single field which uses literal types which you can use...
Read more >returning T inside a type resolves to never when a string enum ...
In the case of a string enum, the easiest thing here would be to transform it via template literal as well before checking...
Read more >TypeScript | Union Types vs Enums
You will see a difference in the code size once TypeScript code is compiled into JavaScript code. Using string literal unions will “reduce”...
Read more >Advanced Types
Union types are closely related to intersection types, but they are used very ... to both enum member types as well as numeric/string...
Read more >Untitled
The only exception to this is `Union` and `Intersection` schemas, ... Zod enums An enum is just a union of string literals, so...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
This only works because by convention we kept our string enum keys and values identical
I ran into a similar issue, where I had to restrict a function argument string literal to the intersection of two string literal enums. I solved it by unpacking the values from the enums and intersecting the two sets, then casting the argument as appropriate inside the function:We mentioned this during last friday’s design meeting and said we needed to do it to use conditionals for more correct control flow things (otherwise switch case exhaustiveness breaks).