When error
and extraData
are simply two variables belonging to the types ErrorCodes
and ExtraData[typeof error]
respectively, a straightforward solution is not available. This is due to the fact that ErrorCodes
is a union type, which results in ExtraData[typeof error]
also being treated as a union type. In TypeScript, two union types are considered independent and unrelated entities. The specific type of typeof error
remains strictly ErrorCodes
, without any correlation to the actual value of error
. Therefore, it is impossible to express a relationship between two separate variables of correlated union types.
If you wish to enable a mechanism where checking the value of error
affects the inferred type of
extraData</code, both variables should be defined within a unified discriminated union type. Here’s an example illustrating how such a type can be defined:</p>
<pre><code>type ErrorAndExtra = { [K in keyof ExtraData]:
{ error: K, extraData: ExtraData[K] }
}[keyof ExtraData]
/* type ErrorAndExtra = {
error: ErrorCodes.Unknown;
extraData: string;
} | {
error: ErrorCodes.BadRequest;
extraData: number;
} */
The ErrorAndExtra
type embodies a union structure where each member includes a distinct discriminant property error
of literal type, along with an associated extraData
property whose type depends on the value of the corresponding error
property.
If a variable is declared to have the type ErrorAndExtra
, the targeted narrowing behavior can be achieved as demonstrated below:
declare const e: ErrorAndExtra;
if (e.error === ErrorCodes.Unknown) {
console.log(e.extraData.toUpperCase()) // valid
} else {
console.log(e.extraData.toFixed()) // valid
}
This approach may meet your requirements effectively. Alternatively, the same outcome can be facilitated using separate variables by deconstructing them from a discriminated union type value:
declare const { error, extraData }: ErrorAndExtra;
if (error === ErrorCodes.Unknown) {
console.log(extraData.toUpperCase()) // valid
} else {
console.log(extraData.toFixed()) // valid
}
This method also works efficiently because TypeScript recognizes that the variables originate from the discriminated union type ErrorAndExtra
, allowing the check performed on error
to impact the apparent type of
extraData</code as intended.</p>
<p>It's important to note that this functionality is only feasible when the declarations of <code>error
and
extraData
conform to the supported model within TypeScript for this particular purpose. Deviations from this pattern could lead to issues, as demonstrated by the following scenario:
declare let { error, extraData }: ErrorAndExtra;
if (error === ErrorCodes.Unknown) {
console.log(extraData.toUpperCase()) // error
} else {
console.log(extraData.toFixed()) // error
}
In this case, changing the declaration from const
to let
results in a breakdown of the functionality. Since let
variables allow reassignment, the compiler would need to monitor these assignments in order to determine if a check on
error</code should influence the observable type of <code>extraData
. However, this additional complexity is not deemed worthwhile for the compiler to handle.
Playground link to code