I was hoping to define an interface hierarchy, where the base interface declares a function, and each extension's version of that function receives its own type (rather than the base type). Minimally, I tried:
interface IBase {
a: string,
f: (x: IBase) => any // Cause of the problem
}
interface IExtension extends IBase {
b: string,
}
const f1 = (x: IExtension) => //... typechecks when using x.b
const ext1: IExtension = {
a: "a1",
b: "b1",
f: f1 // This line doesn't typecheck because IExtension is not strictly IBase
}
The type error:
Type '(x: IExtension) => {}[]' is not assignable to type '(x: IBase) => any'
Digging around, I saw this answer regarding strictFunctionTypes. Making the following changes causes the program to typecheck, because methods aren't subject to strictFunctionTypes
, and therefore allow bivariance:
interface IBase {
a: string,
f(x: IBase): any
}
Edit: as explained in the comment by @jcalz, this approach is blatantly unsound. It also doesn't capture the constraint that f
is called with the type it's defined on.
Is there a way express this typing in TypeScript? Something like:
interface IBase {
a: string,
f: (x: IBase | * extends IBase) => any
}
I haven't been able to find anything like that that avoids generics. I understand that generics could be used here, but I won't be walking that route, especially given the method syntax works as expected. Really appreciate any additional insight on this topic!
CodePudding user response:
It looks like you want to use the polymorphic this
type, which is sort of an implicit generic type referring to the current type:
interface IBase {
a: string,
f: (x: this) => any
// ^^^^
}
Then when you extend IBase
, the f
method of the extensions will automatically refer to the extensions and not IBase
:
interface IExtension extends IBase {
b: string,
}
type IXF = IExtension['f'];
// type IXF = (x: IExtension) => any
const ext1: IExtension = {
a: "a1",
b: "b1",
f: x => x.b.toUpperCase()
}
and
interface ISomethingElse extends IBase {
z: number
}
type ISF = ISomethingElse['f']
// type ISF = (x: ISomethingElse) => any
const sth2: ISomethingElse = {
a: "a2",
f: s => s.z.toFixed(),
z: 123
}