Idea for allowing destructuring non-common property of discriminated unions
See original GitHub issueSuggestion
🔍 Search Terms
type narrowing discriminated union destructing does not exist on type
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
⭐ Suggestion
Recent & future versions of TypeScript are strengthening support for discriminated unions. Looking at the most recent effort #46266 , it seems that we want to facilitate combination of destructuring assignment and discriminated unions. The ongoing work is wonderful, but it is one step behind being fully usable everywhere; in order to utilize that brand new feature, a common property (payload
in that PR) must exist on every union constituents. Generally, this is not the case.
So this suggestion shows off idea on how we can extend the feature to discriminated unions of other shapes.
Suggestion: destructuring a property that might exist (that exists on only some of union constituents) is allowed, but access to such variable is not allowed until proper narrowing is done.
type Option<T> = {
kind: 'some';
value: T;
} | {
kind: 'none';
};
// destructuring 'value' is allowed ↓
function unwrap<T>({ kind, value }: Option<T>): T {
// use of 'value' is not allowed here because it may not exist.
value;
if (kind === 'some') {
// use of 'value' is now allowed.
return value;
}
throw new Error('unwrap: expected some');
}
Such a variable that causes an error on access is not something novel; let
variables that aren’t assigned yet already behave similarly.
📃 Motivating Example
As shown in above code sample, discriminated union constituents may not always have properties of the same name. Rather, given that discriminated unions tend to be used to express “or” logics, such a situation feels rare.
In addition, even if all constituents could have a property of the same name, giving them different names is often better.
type OkResult<T> = {
type: "ok";
value: T;
};
type ErrorResult = {
type: "error";
error: Error;
};
type Result<T> = OkResult<T> | ErrorResult;
In this example, we could rename both value
and error
to payload
so that it can benefit from the ongoing PR, but the new name feels less descriptive.
Placeholder property definitions could be added so that all property names become common, but I don’t really feel it is the right way to go:
type OkResult<T> = {
type: "ok";
value: T;
error?: undefined;
};
type ErrorResult = {
type: "error";
value?: undefined;
error: Error;
};
This works but it allows users to easily skip inspecting type
, particularly thanks to the optional chaining operator. I have seen several bugs due to not properly handling the type
, so in my most recent project I decided to remove these placeholder ?: undefined
definitions from discriminated unions and force users to inspect type
before accessing each constituent’s contents.
Therefore, a solution that allows us to use non-common property names and also utilize destructuring assignments would be ideal.
💻 Use Cases
Above two types (Option<T>
and Result<T>
) are the most basic use cases; there should be a lot more in the wild.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:45
- Comments:9 (2 by maintainers)
Top GitHub Comments
This feature would be very very appreciated
I would love this idea to be implemented, thx for your suggestion!