microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.12k stars 12.5k forks source link

Nullish coalescing operator but for conditional types #58629

Open RebeccaStevens opened 5 months ago

RebeccaStevens commented 5 months ago

🔍 Search Terms

nullish coalescing operator conditional types

✅ Viability Checklist

⭐ Suggestion

Add syntactic sugar to allow this:

type T = A extends B ?? C

to be shorthand of:

type T = A extends B ? A : C

📃 Motivating Example

When working with large complicated types, it is often that you need to check that a type extends another type before passing it as a type argument to another type.

For example, ensuring it is an array before passing it to something that requires it to be an array:

type Foo<T> = Bar<
    Some<Really<Deep<Complex<Type<Utility<T>>>>>> extends readonly unknown[]
        ? Some<Really<Deep<Complex<Type<Utility<T>>>>>>
        : never
>;

type Bar<T extends readonly unknown[]> = ...;

The more complex the type you need to check, the more you need to repeat yourself to pass the same type in again.

This proposal aims to address that.

type Foo<T> = Bar<
    Some<Really<Deep<Complex<Type<Utility<T>>>>>> extends readonly unknown[] ?? never
>;

💻 Use Cases

  1. What do you want to use this for?

Making my type code more readable and easier to maintain.

  1. What shortcomings exist with current approaches?

The need to repeat yourself. Updates to the type need to be make in two places.

  1. What workarounds are you using in the meantime?

The long-form syntax.

whzx5byb commented 5 months ago

To not repeat yourself, you can use infer R extends as a workaround. Not sure what your real-world code looks like but this may work.

type Foo<T> = Bar<
    [Some<Really<Deep<Complex<Type<Utility<T>>>>>>] extends [infer R extends readonly unknown[]]
        ? R
        : never
>;
jcalz commented 5 months ago

You can just define your own utility type like type OrElse<A, B, C> = [A] extends [B] ? A : C and use that. TS is unlikely to add new syntax if utility types can serve the same purpose, even if you'd prefer to see A extends B ?? C and not OrElse<A, B, C>.

Also, bikeshedding syntax: ?? doesn't seem to have the same meaning in your suggestion as it does in JS. Where is the "nullishness" here? Maybe if B is {}? I'd expect a type like A??C to evaluate to A unless it is nullish, and then C otherwise, so that string ?? number is string, but null ?? number is number. Since that's not what you're suggesting, the particular syntax would be misleading.