Given interfaces A and B, which contain a x1
property in common
interface A {
a1: number;
a2: number;
x1: number; // <<<<
}
interface B{
b1: number;
x1: number; // <<<<
}
And given the implementations a
and b
let a: A = {a1: 1, a2: 1, x1: 1};
let b: B = {b1: 1, x1: 1};
This simply passes, even though b1
does not belong to Partial<A>
:
let partialA: Partial<A> = b;
but this fails because b1
does not belong to Partial<A>
:
let partialA: Partial<A> = {b1: 1, x1: 1};
Could someone please tell me why?
This is going to be a bit of a journey so hang in there with me:
Generally a sub-type should be assignable to a base-type. In Typescript a type with more properties should be assignable to a where a type with just a subset of the properties is expected. So this for example is legal:
let source = { a1: 0, a2: 0}
let target: { a1: number } = source
Now surprisingly, because of the way structural typing works Partial<A>
is a subtype of Partial<B>
and Partial<B>
is a subtype of Partial<A>
. Optional properties can be missing in a subtype, so optional properties missing from a type do not disqualify a type from being a subtype. An if we remove the optional properties we are just left with {}
which can be a the base type of any other type. The compiler agrees with me on this point if we ask it to answer this subtyping question using conditional types:
type q1 = Partial<A> extends Partial<B> ? "Y" : "N" // Y
type q2 = Partial<B> extends Partial<A> ? "Y" : "N" // Y
There is one exception to this (ok maybe two), assignment of object literals directly to a reference of a particular type. This is called excess property checks as is the reason that if we were to perform the above assignment directly we would get an error:
let target: { a1: number } = { a1: 0, a2: 0} // err
The reason this is an error is that it is a common error to mistakenly create an object literal with more properties or misspelled properties and this check (which is a violation of the principle a sub-type should be assignable to a base-type) is there to catch this mistake. This is also the reason you are getting an error on
let partialA: Partial<A> = {b1: 1, x1: 1};
But excess property checks only kick in on direct assignment of an object literal to variable of a specific type. So on the assignment let partialA: Partial<A> = b;
it will not trigger an error on excess property checks.
One further complication is that Partial<A>
is what is called a weak type. From the PR introducing checks for such type:
A weak type is one which has only optional properties and is not empty. Because these types are assignable from anything except types with matching non-assignable properties, they are very weakly typechecked. The simple fix here is to require that a type is only assignable to a weak type if they are not completely disjoint.
Now since a weak type has no mandatory properties, following the principle a sub-type should be assignable to a base-type, any other object would be assignable to such a type:
let k = { foo: 0 }
let partialA: Partial<A> = k;
The type of k
is a sub-type of Partial<A>
, sure k
has nothing in common with Partial<A>
but that is not an issue. After all Partial<A>
does not require any propertie, so k
does not need any, and it has one extra property, which is generally what a sub-type does, become more specific by adding members.
The reason the code code above is an error is because of the PR are reference above which postulates that assigning a completely unrelated type to a weak type is probably an error. So like excess property checks a rule was introduced (a rule which again breaks the idea a subtype is assignable to a base type) that disallows such assignments.
In your case however Partial<A>
and Partial<B>
do have something in common x1
so this rule does not catch the possibly mistaken assignment. Although surprising because both Partial<A>
and Partial<B>
have no mandatory properties, they are subtypes of one another and unless there is a specific reason (such as excess property checks or this extra weak type detection rule) the assignment is allowed.
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