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

New "superset" keyword to constrain an union type to be a superset of a sub type. #30099

Closed
3 of 5 tasks
lifaon74 opened this issue Feb 26, 2019 · 9 comments
Closed
3 of 5 tasks
Labels
Duplicate An existing issue was already created

Comments

@lifaon74
Copy link

lifaon74 commented Feb 26, 2019

Search Terms

extend union type

Suggestion

Currently, there is no way to constrain a type to be a super set of union. For example:

type ATypes = 'a1' | 'a2';

// try to define T as a type which should contains at least 'a1' and 'a2' (as union)
// of course "extends" won't work, in this case is does: "subset of", so 'a1' or 'a2' will be valid but not 'b1'
class A<T extends ATypes> {
  a: T;
}

type BTypes = 'b1' | ATypes;

// BTypes should be allowed only if it contains 'a1' | 'a2'
class B<T extends BTypes> extends A<BTypes> {
}

We may imagine a new word: superset , supersetof or superset of to constrain a type to include every sub-types.

Use Cases

This would be particularly useful to extends a class with a lot of properties typed with T (attributes or methods) and constrained by a mandatory subset.

Examples

type ATypes = 'a';
class A<T supersetof ATypes> {
  dispatch(name: T): void {
    console.log(name);
  }
}

type BTypes = 'b' | ATypes ;
class B<T supersetof BTypes> extends A<T>{
}

const b1 = new B<'wrong-type'>(); // type error because 'wrong-type' is not a superset of BTypes 

type CTypes = 'c' | BTypes ;
class C extends B<CTypes> {
}

const c1 = new C();
// "c1" may dispatch 'a',  'b', or 'c' and must support all of them

type DTypes = 'd' ;
class D extends B<DTypes> { // type error because DTypes is not a superset of BTypes 
}

Others

We may imagine some other inferences like:

  • string is a superset of an union of strings (ex: string is a superset of 'a' | 'b')
  • number is a superset of an union of numbers (ex: number is a superset of 1 | 2)
  • same for symbols, booleans, enum
  • any is a superset of an union of anys (ex: any is a superset of 'a' | 2)
  • number | string is a superset of 'a' | 2 | 'b'

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.

Related links:
https://stackoverflow.com/questions/45745441/extending-union-type-alias-in-typescript

@NN---
Copy link

NN--- commented Feb 26, 2019

Is it the same proposal ? #28586

You can also try this https://github.com/Kotarski/ts-strictargs

@lifaon74
Copy link
Author

Is it the same proposal ? #28586

No, #28586 is about properties (does an object contains exact properties, subset properties or superset properties).

Here, I want to constrain an union => T supersetof S => T must includes al least all types present in the type S (which must be an union)

@jack-williams
Copy link
Collaborator

Duplicate of #14520 I think.

@lifaon74
Copy link
Author

Duplicate of #14520 I think.

Not sure, even if it looks really similar. My "proposal" is about union where #14520 is more about classes/instances.

function foo<T extends ('a' | 'b')>() {} // only 'a', 'b' or ('a' | 'b') allowed as T
function bar<T supersetof ('a' | 'b')>() {} // 'a' not allowed, 'b' not allowed, only ('a' | 'b') or ('a' | 'b' | X) allowed as T

@jack-williams
Copy link
Collaborator

The examples use interfaces but the proposal is just to add constraints on assigning from, rather than assigning to.

This would cover your union case that asserts that the parameter must be assignable from a union type.

@RyanCavanaugh
Copy link
Member

Original is #9252

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Feb 26, 2019
@jack-williams
Copy link
Collaborator

jack-williams commented Feb 26, 2019

Ye it’s unfortunate that the original uses the terminology upper bounds rather than lower

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@lifaon74
Copy link
Author

As it may help someone, I found some workaround:

interface A<T> {
  a: T;
}

interface B<T> extends A<T> {
  b: T;
}

interface C<T> {
  c: T;
}

type SuperSet<T, U> = {
  [key in keyof T]: key extends keyof U
    ? (U[key] extends T[key] ? T[key] : never)
    : T[key]
};

function foo<T extends A<any>>(a: SuperSet<T, A<string>>) {
  console.log(a);
}


foo<A<string | number>>((1 as unknown) as A<string | number>); // ok
foo<A<string>>((1 as unknown) as A<string>); // ok
foo<A<number>>((1 as unknown) as A<number>); // invalid
foo<A<number | symbol>>((1 as unknown) as A<number | symbol>); // invalid

foo<B<string | number>>((1 as unknown) as B<string | number>); // ok
foo<B<string>>((1 as unknown) as B<string>); // ok
foo<B<number>>((1 as unknown) as B<number>); // invalid
foo<B<number | symbol>>((1 as unknown) as B<number | symbol>); // invalid

foo<C<string | number>>((1 as unknown) as C<string | number>); // invalid
foo<C<string>>((1 as unknown) as C<string>); // invalid
foo<C<number>>((1 as unknown) as C<number>); // invalid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants