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!
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
}