Suppose I have a class with an elaborate generic type argument like
class SomeStuff<T extends ElaborateStuff> {...}
Elsewhere I have a function with a SomeStuff
parameter like
function doSimpleThings(stuff: SomeStuff<X>): void {...}
which is independent of the specific incarnation of X
as an extension of ElaborateStuff
. The above is complained about by the compiler, because X
must extend ElaborateStuff
. I could write
function doSimpleThings(stuff: SomeStuff<any>): void {...}
but then eslint complains about the explicit use of any
. Is there another way than switching off the any
warning? In Java I would use <?>
, is there a similar syntax in Typescript?
CodePudding user response:
TL;DR: There is no ?
syntax in typescript. It really depends on the variance of your generic type if you can use anything except any
, but you probably will use any
classes usually end up being invariant.
At it's core this is a question of variance. If you want to know more on that you can check out my talk here
If the class is co-varinat you can use ElaborateStuff
and any instantiation of SomeStuff
will be compatible with it as SomeStuff<ElaborateStuff>
will be the base class of all instantiations:
type ElaborateStuff = {
a: string
}
class SomeStuff<T extends ElaborateStuff> {
getVStuff(): T {
return null!;
}
}
function doSimpleThingsElaborateStuff(stuff: SomeStuff<ElaborateStuff>): void { }
doSimpleThingsElaborateStuff(new SomeStuff<{ a: "", }>()); //OK
doSimpleThingsElaborateStuff(new SomeStuff<{ a: "", b: "",}>()); // OK
function doSimpleThingsNever(stuff: SomeStuff<never>): void { }
doSimpleThingsNever(new SomeStuff<{ a: "", }>()); // Error
doSimpleThingsNever(new SomeStuff<{ a: "", b: "",}>()); // Error
If your class is contra-variant you can use never
and any instantiation of SomeStuff
will be compatible with it as SomeStuff<never>
will be the base class of all instantiations:
type ElaborateStuff = {
a: string
}
class SomeStuff<T extends ElaborateStuff> {
getStuff = (v: T): void => null!
}
function doSimpleThingsElaborateStuff(stuff: SomeStuff<ElaborateStuff>): void { }
doSimpleThingsElaborateStuff(new SomeStuff<{ a: "", }>()); // error
doSimpleThingsElaborateStuff(new SomeStuff<{ a: "", b: "",}>()); //error
function doSimpleThingsNever(stuff: SomeStuff<never>): void { }
doSimpleThingsNever(new SomeStuff<{ a: "", }>()); // ok
doSimpleThingsNever(new SomeStuff<{ a: "", b: "",}>());// ok
If your type is invariant however, you can only use any
. Lint rules are useful but can be ignored and there are good use cases for any
, such as in this case. However you should be aware this is type not safe, so you should not rely on the compiler to check any operation in which the type parameter is involed:
type ElaborateStuff = {
a: string
}
class SomeStuff<T extends ElaborateStuff> {
getStuff = (v: T): T => {
v.a.big(); // error if a is not there
return v;
}
}
function doSimpleThingsElaborateStuff(stuff: SomeStuff<ElaborateStuff>): void { }
doSimpleThingsElaborateStuff(new SomeStuff<{ a: "", }>()); // error
doSimpleThingsElaborateStuff(new SomeStuff<{ a: "", b: "",}>()); //error
function doSimpleThingsNever(stuff: SomeStuff<never>): void { }
doSimpleThingsNever(new SomeStuff<{ a: "", }>()); // error
doSimpleThingsNever(new SomeStuff<{ a: "", b: "",}>());// error
function doSimpleThingsAny(stuff: SomeStuff<any>): void {
stuff.getStuff({}) // will trigger an error
stuff.getStuff({ a: ""}).foo // error on foo access
}
doSimpleThingsAny(new SomeStuff<{ a: "", }>()); // ok
doSimpleThingsAny(new SomeStuff<{ a: "", b: "",}>()); // ok