Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

narrow down a union type via a discriminant field that exists only on one of the used types #49095

Closed
5 tasks done
ReinisV opened this issue May 13, 2022 · 5 comments
Closed
5 tasks done

Comments

@ReinisV
Copy link

ReinisV commented May 13, 2022

Suggestion

πŸ” Search Terms

optional discriminant field

βœ… 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

it should be possible to narrow down a union type based on a field that only exists on one of the unionized types. I. e. the field should be considered undefined on the other type implicitly.

πŸ“ƒ Motivating Example

function executeIfSomethingIsControl(something: {someControlField: true, executeControl: () => void} | {executeFallback: () => void}) {
    if(something.someControlField) { // <-- Property 'someControlField' does not exist on type [....]
        something.executeControl();
    }else{
        something.executeFallback();
    }
}

playground example here

shouldnt throw an

Property 'someControlField' does not exist on type '{ someControlField: true; executeControl: () => void; } | { executeFallback: () => void; }'.
  Property 'someControlField' does not exist on type '{ executeFallback: () => void; }'.(2339)

error anymore.

πŸ’» Use Cases

there are often configuration objects passed to methods that contain a flag field to turn on some feature.
If this flag field is specified, then the configuration object can/should contain some other fields that contain configuration specific for the feature.

Otherwise, if the flag field is not specified, the other feature related fields should not be specified, as they will be ignored.

as a workaround, that is not super elegant, all missing keys from one type can be added as undefined in the other type:

export type EitherType<X, Y> = (X & {[YKey in Exclude<keyof Y, keyof X>]?: undefined})
| (Y & {[XKey in Exclude<keyof X, keyof Y>]?: undefined});

which would be used like this:

something: EitherType<{someControlField: true, executeControl: () => void}, {executeFallback: () => void}>

this solution is not elegant, because the union operator is nowhere to be seen here and so it is not immediately obvious that this a union.

@MartinJohns
Copy link
Contributor

Duplicate of #36194 / #1260 (and many others).

@ReinisV
Copy link
Author

ReinisV commented May 13, 2022

yeah, I guess I couldnt figure out the correct keywords to search by, so I didnt find any of the issues, but this is a duplicate.

Though the fact that there are so many duplicates for this request is quite telling.

So when can we wait for this to become a feature? Next release? the release after that? :)

@ReinisV ReinisV closed this as completed May 13, 2022
@MartinJohns
Copy link
Contributor

So when can we wait for this to become a feature? Next release? the release after that? :)

I honestly wouldn't expect this ever to become a feature. Too many pitfalls with too little benefit.

@ReinisV
Copy link
Author

ReinisV commented May 13, 2022

thats sad, I've always thought of TS as being that one tool that can do what no one else can, that is -- model the dynamic nature of archetypal* javascript code and verify it at compile time and I feel like the basic use case I described is archetypal javascript code.

* paradigmatic? classic? whats the word I'm looking here for? the equivalent of Pythonic, but for JS?

@MartinJohns
Copy link
Contributor

I would consider this poor JavaScript code. It's better to have a discriminator property to listen to. The suggested feature would ultimately lead to a decrease in type-safety, because types are not sealed in TypeScript. It's very valid to pass an object that has the property of type A, but is actually type B and otherwise incompatible with type A.

For the rare cases you'd need this you can use a type-assertion, or use the in operator (which results in type unsound behaviour as well). For example:

if ('someControlField' in something) { ... }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants