I get a problem in non strict mode, Here is my code I can assign a function with bad type to a typed variable.
Do I make a mistake ?
Thanks.
interface A {
f1( ) : void;
}
interface B extends A {
f2( ) : void;
}
interface C {
f3( ) : void;
}
type expectA = ( a: A ) => void;
type expectB = ( b: B ) => void;
function testA( a: A ) {
console.log( "a" );
}
function testB( b: B ) {
console.log( "b" );
}
function testC( c: C ) {
console.log( "c" );
}
let v1: expectA = testA; // ok: assign type A to a type A
let v2: expectA = testB; // ok: assign type B (extending A) to a type A
let v3: expectA = testC; // fail -> normal: error TS2322: Type '(c: C) => void' is not assignable to type 'expectA'.
let v4: expectB = testA; // ok -> **abnormal**: there is no error in !strict mode
TypeScript is actually protecting you against doing something silly here, but it's doing so in a confusing way.
Let's reduce your example to just the interfaces A
and B
.
interface A {
f1( ) : void;
}
interface B extends A {
f2( ) : void;
}
type expectA = ( a: A ) => void;
type expectB = ( b: B ) => void;
function testA( a: A ) {
a.f1();
}
function testB( b: B ) {
b.f1();
b.f2();
}
const v1: expectA = testA;
const v2: expectA = testB; // error: Property 'f2' is missing in type 'A' but required in type 'B'
const v3: expectB = testB;
const v4: expectB = testA;
On first glance, the result at the bottom where only v2
has an error might seem counterintuitive. If B
extends A
, then why can't you use B
everywhere you can use A
?
The answer is because we're dealing with functions here. Look closely at the implementation of testB()
. It calls the property b.f2()
because it expects that its parameter b
will have that property. But the left hand side of const v2: expectB
is equivalent to the type (a: A) => void
. The parameter of type A
does not have f2()
. So we're telling TypeScript conflicting things about what the proper type of v2
is; either it's a function that has a: A
in which case a.f2()
is not safe to call, or it's a function that has b: B
in which case it is safe. It's a paradox!
(Keep in mind that this has nothing to do with whether testB
actually tries to call b.f2()
; the point is that it could, based on how its argument types are set, which would cause a runtime error in the v2
scenario.)
Now for const v4
you say you think it's "abnormal" that this would be OK, but if we look carefully at the functions again we can see that it makes sense that it is OK. If you passed either a type A
or B
variable to testA()
, there are no possible errors because it will never try to access the f2()
property.
Note also that extends
does not work in TypeScript quite the way you might expect it to. Writing interface B extends A
simply says that B
will inherit all the properties of A
. It does not establish any kind of relationship allowing B
to stand in for any instance of A
. Such behavior is called "polymorphism" and to do that you would need to use classes instead, e.g. class B extends A implements A
.
class A {
foo = '';
}
class B extends A implements A {
bar = '';
}
let isA = new A();
isA = new B(); // this is fine because B implements A
let isB = new B();
isB = new A(); // error: Property 'bar' is missing in type 'A' but required in type 'B'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With