Repository: TypeScript
Issue: Idea for allowing destructuring non-common property of discriminated unions 
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

type narrowing discriminated union destructing `does not exist on type`


## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-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.

```ts
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.

```ts
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:

```ts
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.