question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Intersection of enum union with literal is unexpectedly `never`

See original GitHub issue

TypeScript 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?

Playground Link:

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:open
  • Created 6 years ago
  • Comments:14 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
Ghostbirdcommented, Dec 12, 2019

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:

foo( bar: keyof typeof A & keyof typeof B ) {
    x = bar as A;
    y = bar as B;
}
1reaction
weswighamcommented, Mar 14, 2018

I can see the argument that theoretically Pet.Dog & 1 should be 1, but do you have a compelling practical scenario for it?

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).

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found