Reputation: 5457
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?
Upvotes: 1
Views: 423
Reputation: 33041
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
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
Upvotes: 1