Discriminated Unions - Multiple Fields, SubClassing/interfacings, kind field detection failure
See original GitHub issueTypeScript Version: typescript@3.2.0-dev.20181004 Search Terms: Discriminated Unions - Multiple Fields
Issues:
When there are multiple common fields between interfaces the Discriminated Unions is not able to determine which field is to be the discriminator and all the logic that typescript will constrain the shape of the function interfaces bases on a field, check will not work. It would be great if we could in cases like these define a common interface on which there is a single field present from which all other instance inherited from that shape. Then when one defines a union of types, if it can’t find a single common field, then it inspects the base interfaces for an interface with a single field, if found, it will use that field from that interface. Seem to be present with many cases, were I need to communicate to typescript which field should be used.
There would still be a problem, if there is common class from which all inherited that only has a single field, in which case another form of syntax would be required when defined the discriminant, like including the common interface in the discriminate. This method, might be the preferable option, because it would be more performance than inspective all the interfaces form which a class extends from. I would choose this form, because simpler, as know exactly what to inspect at a top level. See examples below of all the cases. type Kinds = KindA | KindB | Kind
An improvement, would be to allow instanceof and similar variants to be used, versus string literals.
// just the common shape.
interface Kind {
field : any
}
// or more specific
interface Kind {
field : 'KindA' | 'KindB' // or any, should be good enough.
}
interface KindA extends Kind {
field : 'KindA'
fieldCommon : string;
fieldA : 'KindAFieldA'
}
interface SubKindA extends KindA
{
subFieldA : 'SubFieldKindA'
}
interface KindB extends Kind {
field : 'KindB'
fieldCommon : string;
fieldB : 'KindBFieldB'
}
interface SubKindB extends KindB {
subFieldB : 'SubFieldKindB'
}
type Kinds = KindA | KindB | Kind // Is the other way to specify which interface the single field discriminator is defined in.
type Kinds = KindA | KindB
const kind = {} as Kinds;
switch(kind.field) // On field should be available, fieldCommon must not be.
{
case 'KindA':
kind.fieldCommon
kind.field
kind.fieldA
case 'KindB':
kind.fieldCommon
kind.field
kind.fieldB // Field should be available, but is missing
}
type SubKinds = SubKindA | SubKindB | Kind // Is the other way to specify which interface the single field discriminator is defined in.
type SubKinds = SubKindA | SubKindB
const subKind = {} as SubKinds;
// expect only fields with literals, should be available here, fieldCommon must not be
switch(subKind.field)
{
case 'KindA':
{
subKind.field
subKind.fieldCommon
subKind.fieldA
subKind.subFieldA;
}
case 'KindB':
{
subKind.field
subKind.fieldCommon;
subKind.fieldB; // should be available here, but is missing
subKind.subFieldB; // should be available here, but is missing
}
}
Issue Analytics
- State:
- Created 5 years ago
- Reactions:1
- Comments:5
This issue is a bit hard to get into, maybe it could be written to gently get into the more deep issues? Am I correct in assuming that the root of many of these failures is simply described as the following?
In the above
x
should not be a valid value forU
since it’s not a valid value forT1
norT2
.And for a more concrete use case: Imagine specifying an options object for a function (so it generally cannot have any literal values to use for discriminating the types) that must include one of two options, but never both. I haven’t figured out a way to do this.
There’s a few different ways to think about these operations:
Operators as bitwise ops and types as Rows:
A | B == Row(A) | Row(B) == Row(A) ∪ Row(B)
ex:{ a: 1 } | { b: 2 }
is satisfied by{ a: 1, b: 2 }
A & B == Row(A) & Row(B) == Row(A) ∩ Row(B)
ex:{ a: 1, b: 2 } & { b: 2, c: 3}
is satisfied by{ b: 2 }
Operators as logical and types as validators:
A | B == is(A) || is(B)
ex:{ a: 1 } | { b: 2 }
is satisfied by{ a: 1}
or{ b: 2 }
but not{ a: 1, b: 2}
A & B == is(A) && is(B)
ex:{ a: 1, b: 2 } & { b: 2, c: 3}
is satisfied by{ a: 1, b: 2}
or{ b: 2, a: 3 }
but not{ a: 1, b: 2, c: 3}
Honestly I’d like both of these AND have types closed by default (otherwise unions are pretty silly). I’d say
||
should be the latter and|
should be the former. Also, XOR is super nice (and use^
or^^
for that).In the end (probably including things outside the scope of this bug), I’d love to be able to describe types like these: