-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Associativity fails for intersections of unions #19055
Comments
Here is a practical example where this causes problems. This is analogous to the issue I ran into at my company. If I have a union type (Animal) and a mixin (Domestic) and I define their composite type (Domestic Animal), I am unable to restrict this type to a subset of the union (Domestic Dog) by using an intersection. Instead I need to expose the mixin - interface Laborador {
breed: 'laborador';
bark: () => void;
}
interface Chihuahua {
breed: 'chihuahua';
yip: () => void;
}
interface Cat {
breed: 'cat';
}
type Animal = Laborador | Chihuahua | Cat;
interface Domestic {
wearsLeash: boolean;
}
type DomesticAnimal = Animal & Domestic;
function makeDogNoise(domesticDog: DomesticAnimal & (Laborador | Chihuahua)): void {
switch (domesticDog.breed) {
case 'laborador':
domesticDog.bark(); // Error: Property 'bark' does not exist on `Chihuahua & Domestic`
case 'chihuahua':
domesticDog.yip(); // Error: Property 'bark' does not exist on `Laborador & Domestic`
}
} |
You could think of this as a failure of associativity (it looks like the type system is not collapsing nested intersections to a flat intersection before processing further). Or you could think of this as a failure of absorption (see #16386): since Anyway if I inspect what happens to For the problem you noted, there's the obvious workaround of assigning to the right type before doing case analysis: function f(value: ((A | B) & C) & (A | B)): number {
const v: (A & C) | (B & C) = value; // works, no problem
switch (v.identity) {
case 'a':
return v.a; // okay
case 'b':
return v.b; // okay
default:
const exhaustivenessCheck: never = v;
return exhaustivenessCheck;
}
} For the animal example, I agree there might not be a workaround that doesn't involve using the function makeDogNoise(domesticDog: DomesticAnimal & (Labrador | Chihuahua)): void {
const dog: Labrador | Chihuahua = domesticDog; // forget domesticity
switch (dog.breed) {
case 'labrador':
dog.bark();
break;
case 'chihuahua':
if (domesticDog.wearsLeash) {
dog.yip();
}
}
} |
Thanks @jcalz, agreed on every point. The reason this one matters to my team is that in our real code there are 31 "dogs" and 51 total "animals", and these combine with either of two modifying types like "domestic". We're using this fine-grained typing because we need to write N x N interleaving rules for every animal pairing and very much want discriminated unions with type Dog = Laborador | Chihuahua | GreatDane | Poodle /* | ... x 31 */;
type Cat = Siamese | Tawny | Calico /* | ... x 12 */;
type OtherAnimal = Elephant | Giraffe /* | ... x 8 */;
type Animal = Dog | Cat | OtherAnimal;
interface Domestic { wearsLeash: boolean; }
type DomesticAnimal = Animal & Domestic;
function getsAlong(animal1: DomesticAnimal, animal2: UndomesticAnimal) {
switch (animal1.breed) {
case 'poodle':
case 'chihuahua':
return getsAlongWithSmallDomesticAnimal(animal2);
// ...
default:
return assertNever(animal1);
}
} For now we've decided to expose |
The examples in this thread should be addressed by #18438. most of the cases with unit types (the discriminant) that were in the past an intersection (e.g. |
@mhegazy great, this seems correct. Thanks. |
TypeScript Version: 2.5.1-insiders.20170825
Code
Expected behavior:
No error. This is a discriminated union and the switch statement should infer property
a
.Furthermore, I expect that the following types are equivalent:
((A | B) & C) & (A | B)
(A | B) & C & (A | B)
C & (A | B)
However, 1 behaves differently from 2 and 3.
Actual behavior:
TS errors because it fails to infer that the type cannot be
B & C
in the first case.The text was updated successfully, but these errors were encountered: