(using TypeScript 4.6.3)
I have the following SSCCE code that represents the outcome of a login operation. For some reason unrelated to this question, I want to handle the case of bad password under a resolve
branch:
Typescript Playground is here.
enum LoginOutcome {SUCCESS, FAIL}
type LoginResults = {
outcome: LoginOutcome.SUCCESS
person_name: string
} | {
outcome: LoginOutcome.FAIL
}
function login(): Promise<LoginResults> {
const promise: Promise<LoginResults> = new Promise( (resolve, reject) => {
setTimeout(()=>{
const r1 = {outcome: LoginOutcome.SUCCESS, person_name: "John"};
const r2 = {outcome: LoginOutcome.FAIL};
if (Math.random() <= 0.5)
return resolve(r1);
else
return resolve(r2); // TS complains: Type 'LoginOutcome' is not assignable to type 'LoginOutcome.FAIL'.(2345)
}, 3000);
});
return promise;
}
The above code does not typecheck as indicated in the comment. I don't understand why. Also, I have discovered that if I change the line:
const r2 = {outcome: LoginOutcome.FAIL};
to:
const r2: LoginResults = {outcome: LoginOutcome.FAIL};
… then the code typechecks. This makes no sense at all as TS should have been able to infer that {outcome: LoginOutcome.FAIL}
is a valid instance of type LoginResults
.
CodePudding user response:
The problem is with the way that TypeScript infers data in non-as const
expressions. When you have literal values such as enum members or strings or numbers, it will simply resolve to the generic enum type / string
/ number
. Therefore, the following assignment:
const r2 = {outcome: LoginOutcome.FAIL};
Actually resolves to this type:
declare const r2: { outcome: LoginOutcome }; // not specifically `LoginOutcome.FAIL`
When you type r2
to LoginResults
, it is forced to see if it matches the literals because the discriminated union uses literals for the values. Therefore, it can correctly type check as you expect it to.