I encountered the following problem:
type NonUndefined<T> = T extends undefined ? never : T;
enum TestEnum {
T1 = "T1",
T2 = "T2",
}
interface Data {
Val: TestEnum;
}
type AltFormData<T> = T extends object
? {
readonly [P in keyof T]-?: AltFormData<NonUndefined<T[P]>>;
} & AltFormValue<NonUndefined<T>>
: AltFormValue<NonUndefined<T>>;
type Alt1FormData<T> = AltFormValue<NonUndefined<T>>;
type Alt2FormData<T> = T extends object ? AltFormValue<NonUndefined<T>> : AltFormValue<NonUndefined<T>>;
type AltFormValue<T = any> = {
readonly $setValue: (value: T | undefined) => void;
};
const x2: AltFormData<Data> = {};
x2.Val.$setValue(TestEnum.T1); // ERROR - x2.Val is of type AltFormValue<TestEnum.T1> | AltFormValue<TestEnum.T2> and should be of type AltFormValue<TestEnum>
const x3: AltFormValue<TestEnum> = {};
x3.$setValue(TestEnum.T1); // GOOD
const x4: AltFormData<TestEnum> = {};
x4.$setValue(TestEnum.T1); // ERROR - x4 is of type AltFormValue<TestEnum.T1> | AltFormValue<TestEnum.T2> and should be of type AltFormValue<TestEnum>
const x5: AltFormValue<NonUndefined<TestEnum>> = {};
x5.$setValue(TestEnum.T1); // GOOD
const x6: Alt1FormData<TestEnum> = {};
x6.$setValue(TestEnum.T1); // GOOD
const x7: Alt2FormData<TestEnum> = {};
x7.$setValue(TestEnum.T1); // ERROR - x7 is of type AltFormValue<TestEnum.T1> | AltFormValue<TestEnum.T2> and should be of type AltFormValue<TestEnum>
It seems that type check T extends object
is somehow changing the result. Same thing happends when using type "T1" | "T2"
instead of enum. You can copy and paste this code into the TS Playground or just click on the link.
CodePudding user response:
It looks like typescript is treating the enum
type as union of it's values really. This has consequences for conditional types.
So, when you have this type:
type Alt2FormData<T> = T extends object ? AltFormValue<NonUndefined<T>> : AltFormValue<NonUndefined<T>>;
And you will feed it the enum, which is really TestEnum.T1 | TestEnum.T2
, typescript will actually compute the type.
AltFormValue<NonUndefined<TestEnum.T1>> | AltFormValue<NonUndefined<TestEnum.T2>>
This is call Distributive conditional types, the default how typescript is treating union types in conditionals.
If you don't want that, you need to turn off this by wrapping T
and object
into brackets.
type Alt2FormData<T> = [T] extends [object] ? AltFormValue<NonUndefined<T>> : AltFormValue<NonUndefined<T>>;
This will result into this type to be computed instead:
AltFormValue<NonUndefined<TestEnum.T1 | TestEnum.T2>>
If you simplify, then this is the desired type
AltFormValue<NonUndefined<TestEnum>>
which is alias for:
{
readonly $setValue: (value: TestEnum | undefined) => void;
}
Then you shouldn't see the compilation errors.