With the --strictFunctionTypes flag enabled, the compiler protects you against unsafe function types by checking their parameter types contravariantly, which means that a function type (a: X) => void extends (or "is assignable to" or "is a subtype of") a function type (a: Y) => void if and only if Y extends X. Note that the assignability direction changes for the function type compared to that of its parameter type. That is, function types vary counter to their parameter types. In other words, they contra-vary.
Why does type safety require this? It has to do with direction of data flow. When data flows from a source to a target, the type the source sends must extend the type the target accepts. The source can safely get narrower (e.g., "I claimed to give you a Fruit, and I'm actually giving you a Banana") and the target can safely get wider (e.g., "you gave me a Fruit, and I'd actually accept any Food whatsoever"). It is unsafe for the source to get wider (e.g., "I claimed to give you a Fruit, but I'm actually giving you some Food which may or may not be a Fruit, I don't know, sorry) or for the target to get narrower (e.g., "you gave me a Fruit, but I really only accept a Banana and maybe you gave me something else, so I'm not satisfied")
When you pass data into a function parameter, the function is receiving the data, and thus the parameter type can be safely widened, not narrowed.
Let's examine your case:
interface Foo {
func: (arg0: unknown) => number;
}
Here, Foo's func() method apparently, right off the bat, claims to accept an argument of type unknown. That means it is already as wide as it can possibly be. Callers of func() can pass in anything whatsoever that they like:
function takeFoo(foo: Foo) { foo.func("this is fine"); }
But your definition of Bar does this:
interface Bar {
func: (arg0: SolidType) => number;
}
Any value of type Bar is free to expect that its func() method will be called with a SolidType:
const x: Bar = {
func: (arg0) => {
return Number(arg0.someNumber.toFixed(2));
}
}
If Bar extends Foo were true, it would require that the following line should be just fine:
takeFoo(x) // <-- this should be allowed if Bar extends Foo
But of course at runtime, this would fail with arg0.someNumber is undefined.
By saying Bar extends Foo, you are unsafely narrowing the function parameter of func() from unknown ("I'll take anything!") to SolidType ("I lied when I said I'd take anything, sorry!").
So, how to fix it? Well, it really depends strongly on your use cases.
If you have a Foo that isn't known to be of some specific intended subtype like Bar, will you ever call its func() method? Do you really plan on supporting foo.func("this is fine") and foo.func(123) and foo.func(new Date())? I'm guessing not, and that you will only actually call func() if you have some specific subtype. In this case, it means that you might want to say that func's parameter should be the narrowest possible type, which is the never type:
interface Foo {
func: (arg0: never) => number;
}
Now your subtype works with no error:
interface Bar extends Foo {
func: (arg0: SolidType) => number; // okay, no error
}
And the takeFoo() from above is no longer valid, so you don't have to worry about someone mistaking a Bar for something that accepts any possible arg0:
function takeFoo(foo: Foo) { foo.func("this is fine"); } // error
// ---------------------------------> ~~~~~~~~~~~~~~
If your use case requires some particular behavior with func() on the base type Foo, then you might have to tweak never to something else. In order to be type safe though, subtype function parameters can get wider but not narrower. If you find that you really need the opposite, then you might choose to give up type safety by using something like the any type instead of never. That could lead to takeFoo()-style runtime errors, so you'd need to be careful, which is always true when you intentionally loosen type safety.
Playground link to code
BarextendsFoothen I should be able to use aBaranywhere aFoois required. Imaginefunction takeFoo(foo: Foo) { foo.func("this is fine"); }. IsxaFooor not? If so, then I should be able to calltakeFoo(x), but that would do bad things at runtime. That's whyclass Bar extends Foois an error. If you don't care about safety then useanyinstead ofunknownlike this. If you care about safety then useneverlike this. Can you elaborate on the use case?Foowhere you don't know it's of typeBar? What can you do with the base class?neverand I'll accept itFooandBarexample, and you can translate that to your own code as I showed in your more recent playground link. Good luck!