1

I'm trying to generate a class definition for mongoose where I would like to use the first generic to select the second generic. A simplified concept version would look something like this:

class StringClass { val: string; constructor(val: string) { this.val = val; } } class NumberClass { val: number; constructor(val: number) { this.val = val; } } class test<A extends string | number, B extends typeof StringClass | typeof NumberClass) { val: B; constructor(val: A, Class: B) { this.val = new Class(val); } } 

I would like to change the test to be defined as:

class test<A extends string | number, B extends (A instanceof string ? typeof StringClass : typeof NumberClass)) { val: B; constructor(val: A, Class: B) { this.val = new Class(val); } } 

This is not valid in Typescript 4.4 and thus I wonder, how do I make the second generic dependent on the first parameter?

1 Answer 1

1

Why this error appears ? Here you have an example:

class StringClass { val: string; constructor(val: string) { this.val = val; } } class NumberClass { val: number constructor(val: number) { this.val = val; } } type Check<A> = A extends string ? typeof StringClass : typeof NumberClass class test<A extends string | number, B extends Check<A>> { val: B; constructor(val: A, Class: B) { // Type 'string' is not assignable to type 'never' this.val = new Class(val); } } const result = new test('2', StringClass) 

In above case val infered to never, because

multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.

Hence string & number === never

COnsider this example:

 class StringClass { val: { string: string }; constructor(val: { string: string }) { this.val = val; } } class NumberClass { val: { number: number } constructor(val: { number: number }) { this.val = val; } } type Check<A> = A extends string ? typeof StringClass : typeof NumberClass class test<A extends string|number, B extends Check<A>> { val: B; constructor(val: A, Class: B) { // Type 'string' is not assignable to type '{ string: string; } & { number: number; }' this.val = new Class(val); } } const result = new test('2', StringClass) 

val is { string: string; } & { number: number; }.

So, how to fix it?

You can get rid of generics and overload your constructor with appropriate restrictions

 class StringClass { val: string; constructor(val: string) { this.val = val; } } class NumberClass { val: number constructor(val: number) { this.val = val; } } type Check<A> = A extends string ? typeof StringClass : typeof NumberClass interface Overloading { new(val: string): any new(val: number): any new(val: string | number): any } class test { val: StringClass | NumberClass constructor(val: number, Class: typeof NumberClass) constructor(val: string, Class: typeof StringClass) constructor(val: never, Class: typeof StringClass & typeof NumberClass) { this.val = new Class(val) } } const _ = new test('2', StringClass) // ok const __ = new test(2, StringClass) // expected error 

Playground

In general, runtime values can't rely on generic conditions (see Check). It is not safe.

This is why here:

 class StringClass { val: string; constructor(val: string) { this.val = val; } } class NumberClass { val: number constructor(val: number) { this.val = val; } } class test<A, B extends { 0: typeof StringClass, 1: typeof NumberClass, 2: never }[A extends string ? 0 : A extends number ? 1 : 2]> { val: B constructor(val: A, Class: B) { this.val = new Class(val) // error } } 

you have an error.

YOu can always use type assertion as never in this case:

 class StringClass { val: string; constructor(val: string) { this.val = val; } } class NumberClass { val: number constructor(val: number) { this.val = val; } } class test<A, B extends { 0: typeof StringClass, 1: typeof NumberClass, 2: never }[A extends string ? 0 : A extends number ? 1 : 2]> { val: StringClass | NumberClass constructor(val: A, Class: B) { this.val = new Class(val as never) // type asserion } } const _ = new test('2', StringClass) // ok const __ = new test(2, StringClass) // expected error 

Is it safe to use type assertion here? I'm not sure. I think it is up to you. If this code is just for testing - use as never or never just like I did.

If this a production code - please use conditional statements

Sign up to request clarification or add additional context in comments.

2 Comments

Awesome answer! As I mentioned in the beginning the real example involves mongoose where I have two different model-types depending on the base type - one is a regular model while the other is a plugin-extended model. I guess the last example solves the issue but it feels like voodoo and probably not really recommended...
@MaxGordon you are right, I can't personally recommend this way. Like I said, PROD code should be with conditional statements

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.