I want my code editor to infer the type of extraData based on the value of error which is being narrowed by the if statement:
export enum ErrorCodes {
Unknown = 'UNKWN',
BadRequest = 'BDREQ',
}
interface ExtraData {
[ErrorCodes.Unknown]: string,
[ErrorCodes.BadRequest]: number,
}
let error: ErrorCodes;
let extraData: ExtraData[typeof error];
if(error === ErrorCodes.Unknown){
console.log(extraData) // on hover VS code says it's a string | number
}
But if I put the extraData type assigment inside the if block then it works:
export enum ErrorCodes {
Unknown = 'UNKWN',
BadRequest = 'BDREQ',
}
interface ExtraData {
[ErrorCodes.Unknown]: string,
[ErrorCodes.BadRequest]: number,
}
let error: ErrorCodes;
if(error === ErrorCodes.Unknown){
let extraData: ExtraData[typeof error];
extraData // on hover VS code says it's a string
}
I don't want to do that in every conditional block since there will be dozens of error codes and I can't validate using typeof since we will use objects as the actual extra data
If
errorandextraDataare just two variables of the typeErrorCodesandExtraData[typeof error]respectively, there's nothing you can do.ErrorCodesis a union, and soExtraData[typeof error]is also a union type. Two union types are always treated as independent or uncorrelated with each other. The typetypeof erroris justErrorCodesand doesn't "remember" anything abouterror. There is no way to say in types that two separately-declared variables are of correlated union types.So if you'd like
errorto be something you can check and have the check have an effect on the apparent type ofextraData, you can't declare them separately. Instead they should be declared together as fields of a single discriminated union type. Here's one way to express such a type:The
ErrorAndExtratype is a union where each member has a discriminanterrorproperty of literal type, and anextraDataproperty whose type depends on the type of theerrorproperty.If you had a variable of type
ErrorAndExtra, you could get the narrowing behavior you're looking for:That might be sufficient for you; still, you can get the same behavior with two separate variables, as long as you destructure them from a value of the discriminated union type:
This also works; TypeScript understands that
errorandextraDatacame from theErrorAndExtradiscriminated union, and so the check onerrorhas the effect onextraDatayou're expecting.Again, this only works because the declaration of
errorandextraDatais specifically following a pattern that TypeScript supports for this purpose. If you change the pattern, it could break. For example:Here we changed the declaration from a
constto alet. And that breaks the spell. Sinceletvariables can be reassigned, the compiler would have to track such assignments before it could verify that a check onerrorshould have any effect on the apparent type ofextraData, and it's not considered worth the extra work for the compiler to do this.Playground link to code