Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Partial accepts extra properties from another type?

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?

like image 533
Douglas C Vasconcelos Avatar asked Jun 12 '19 10:06

Douglas C Vasconcelos


1 Answers

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.

like image 66
Titian Cernicova-Dragomir Avatar answered Sep 20 '22 23:09

Titian Cernicova-Dragomir