Repository navigation
Discriminated Union Return TypeΒ #60467
Copy link
Copy link
Closed as not planned
Labels
Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
Description
π Search Terms
discriminated union return type
π Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about discrimintated union return types
β― Playground Link
π» Code
type Classifier = 'fooString' | 'barNumber';
type ClassifierType<T extends Classifier> = T extends 'fooString'
? string
: number;
type GenericType<T extends Classifier> = {
classifier: T;
value: ClassifierType<T>;
};
type GenericTypeMap = {
fooString: GenericType<'fooString'>;
barNumber: GenericType<'barNumber'>;
};
// use this to create a type a discriminated union: GenericType<'fooString'> | GenericType<'barNumber'>
// as opposed to a non-discimintated union GenericType<'fooString' | 'barNumber'>
type GenericTypeUnion<T extends Classifier> = {
[K in T]: GenericTypeMap[K];
}[T];
type ClassifierTypeMap = {
[K in Classifier]: GenericType<K>['value'];
};
// use this to create a type a discriminated union: ClassifierType<'fooString'> | ClassifierType<'barNumber'>
// as opposed to a non-discimintated union ClassifierType<'fooString' | 'barNumber'>
type ClassifierTypeUnion<T extends Classifier> = {
[K in T]: ClassifierTypeMap[K];
}[T];
function genericFunction<T extends Classifier>(
_classifier: T, // need to pass a dummy classifier var to help the typescript compiler, weird but not what this report is about
input: GenericTypeUnion<T>
): ClassifierTypeUnion<T> {
if (input.classifier === 'fooString') {
return input.value[0];
//. ^^^^^^ ERROR: string is not assignable to type never
} else {
return input.value + 1;
//. ^^^^^^ ERROR: number is not assignable to type never
}
}
const val = genericFunction('fooString', {
//. ^^^ but is correctly inferred as a string here
classifier: 'fooString',
value: 'foo',
});π Actual behavior
When calling genericFunction the return type is correctly infered as a string (or number depending on the input).
However inside the function itself: the type checking is not working for return types
π Expected behavior
The type checking should be consistent between the place where the function is called and where the actual function return is defined.
Additional information about the issue
All type checking works as expected where the function is called.
e.g.
// TS knows that the input type (second arg) is not matching the classifier type (first argument)
// TS has inferred that the return type should be a number based on the classifer type (first argument)
const val = genericFunction('barNumber', {
classifier: 'fooString',
value: 'foo',
});and
// TS knows that the input type value is not matching the classifier type
const val = genericFunction3('fooString', {
classifier: 'fooString',
value: 9,
});Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
Activity
jcalz commentedon Nov 10, 2024
ClassifierType<'fooString'> | ClassifierType<'barNumber'>is juststring | number. That's not a discriminated union, it's just a union. You are expecting TS to perform a sort of narrowing of generic type parameters that it doesn't do... well, not the way you want. Until recently I'd have said you want #33912 or #33014, but those have been closed as fixed by #56941. Unfortunately what you're doing is one of the "unsupported scenarios" from that (e.g.,// Property type).For now, you're sort of hitting #30581 and the supported approach to that is described in #47109. Your example would be refactored to
which compiles and behaves as desired.
Playground link to code
Maybe we can open a new version of #33014/#33912 for the unsupported cases not handled by #56941, and then this would be lumped in there?
alanbacon commentedon Nov 10, 2024
Joe Calzaretta (@jcalz) Thank you so much for the links, explanation and reworked example.
typescript-bot commentedon Nov 15, 2024
This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.