question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Feature Request] Non-Union Generic Type Params

See original GitHub issue

Search Terms

  • non-union
  • generic
  • type param
  • one of

Suggestion

A way to annotate that a generic type param will not accept union types. I don’t have the syntax or keyword in mind, but I’ll just go ahead and use nonUnion as a kind of type param modifier,

type NonUnionX<nonUnion T> = (
  /*Implementation*/
);

Use Cases

What do you want to use this for?

I work with pretty complex types (in my opinion); and a lot of them.

When working on a complex type, I tend to break the problem down into smaller subproblems.

The base case is usually assuming that all type params are non-union. After that, I build on top of the base case and implement types that support union type params.


Here is an example type that is a base case, PrimaryKey_NonUnion<TableT>


And here is an example of a type building upon the base case, PrimaryKey_Output<TableT>

It distributes TableT and uses PrimaryKey_NonUnion. The result is a union if TableT is a union.


And here is an example of another type building upon the base case, PrimaryKey_Input<TableT>

It distributes TableT and uses PrimaryKey_NonUnion. Then, it uses UnionToIntersection<> to combine the results into one type.


That experimental repository of mine is filled with instances of generic types that support union types and those that do not.

There are times where you really do not want to pass a union type to a generic type param because it’ll result in bugs that may not be noticed till later.

What shortcomings exist with current approaches?

One approach is to just write a comment that says,

/**
 * + Assumes `T` is not a union
 * + Assumes `U` may be a union
 * + Assumes `V` is not a union
 */

This gets very error-prone when you start having hundreds of types.


Another approach is to give your types names that are descriptive,

type SomeOperation_NonUnionT_UnionU_NonUnionV<
  T,
  U,
  V
> = (
  //Implementation
);

This is still error-prone; you may still use it incorrectly. Even if the name of the type says NonUnionT, you may still pass a union type to T.

Examples

type NonUnionX<nonUnion T> = (
  /*Implementation*/
);
//OK
type nonUnionX1 = NonUnionX<string>;
//Error, Type `NonUnionX` expects non-union for type parameter `0`
//  `string|number` is a union type
type nonUnionX2 = NonUnionX<string|number>;
//                          ~~~~~~~~~~~~~

type UnionX<T> = (
  T extends any ?
  //OK! `T` has been distributed
  NonUnionX<T> : 
  never
);
//OK
type unionX1 = UnionX<string>;
//OK
type unionX2 = UnionX<string|number>;

type Blah<T> = (
  //Error, Type `NonUnionX` expects non-union for type parameter `0`
  //  `T` may be a union type
  NonUnionX<T>
  //        ~
);

A non-approach is to expose only types that handle union type params and to leave non-union implementations unexported.

This is not a useful approach because it means building new types in a different file using these non-union implementations becomes impossible. Since they’re unexported.

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, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Similar issues

https://github.com/microsoft/TypeScript/issues/24085 https://github.com/microsoft/TypeScript/issues/27808

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:1
  • Comments:14 (11 by maintainers)

github_iconTop GitHub Comments

4reactions
jack-williamscommented, Aug 15, 2019

Can it not be done using a helper type?

type Check<T,U> = T extends unknown ? ExtractN<U,T> : never;
type ExtractN<T,U> = [T] extends [U] ? U : never;
type NonU<T> = Check<T,T>;

type SomeAlias<T extends NonU<T>> = T;

type A = SomeAlias<never>;
type B = SomeAlias<string>;
type C = SomeAlias<string|number>;
type D = SomeAlias<1 | 2 | 3>;
0reactions
AnyhowStepcommented, Oct 10, 2019

I’m going to copy-paste my response from Gitter,

I’ve said this many times before but overloads are terrible

foo(arg:{x:string}):void;
foo(arg:{y:string}):void;

foo({x:"",y:""}); //OK, but expected error

The overload thing only really works for simple cases.

foo(arg:oneof {x:string}|{y:string}):void

You can emulate this at function/method call sites at the moment with those fancy StrictUnion/AssertNonUnion/etc. helper types but they don’t work when you’re working with type aliases only.


Like, >70% of my code is just TS type-level stuff and 30% or less is actual executable code. Emulating stuff at call-sites is only useful for downstream users but not for implementors


A lot of my code has complex conditional types at call-sites for extra type safety (for downstream users) but none of that stuff really helps me as an implementor when I’m composing dozens of type aliases that I know will only work when the input type argument is a non-union type

Read more comments on GitHub >

github_iconTop Results From Across the Web

Constraining a generic parameter to be a union type in ...
Typescript only allows you to specify an upper bound for a generic type parameter, not a lower bound or any other constraint.
Read more >
Microsoft/TypeScript - Gitter
Which is why I have a feature request for non-union. Bruce Pascoe. @fatcerberus. I always assumed oneof would mostly end up being used...
Read more >
typescript generic return type based on parameter - TKoK RPG
I saw another closed-wontfix issue requesting generic parameter type guards, ... of indexed access types, [Feature Request] Non-Union Generic Type Params, ...
Read more >
Kinds of types - mypy 0.991 documentation
The type of a function that accepts arguments A1 , …, An and returns Rt is ... an isinstance() check to first narrow...
Read more >
NetSuite Applications Suite - Setting Up Fulfillment Requests
Non-Union OSS VAT Return Form in Accounts without SuiteTax · Non-Union OSS VAT Return Form in Accounts with ... Book-Generic and Book-Specific Records....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found