Per the Liskov Subsitution Principle, if "Y extends X" or equivalently "Y is a subtype of X", then a value conforming to type Y can be used wherever a value conforming to type X is requested. An subtype type is more specific, not more permissive. This leads to a counter-intuitive conclusion: if Y extends X, Y is more constrained than X. All Ys are Xs, but not all Xs are Ys.
In your example, because 'a' | 'b'
could be either 'a'
or 'b'
, that union type does not extend type 'a'
: 'b'
wouldn't substitute for 'a'
. Instead, 'a' extends ('a' | 'b')
, because all values that match 'a'
would work where 'a' | 'b'
is requested.
As such, A extends B
iff A ⊆ B
.
One reason this is less intuitive in your TypeScript example is that it deals in unions of literal values. It might make more sense for us to think of this in terms of objects, where {foo: number, bar: number} extends {foo: number}
. The latter supertype, {foo: number}
, could have a bar
property of any type or no bar
at all. The former subtype {foo: number, bar: number}
is more specific and more constrained: not only is foo
a number, but bar
is also a number. This also matches the use of extends
in class or interface definitions: The subclass or subinterface adds properties and methods, which further constrains instances compared to the superclass or superinterface.
This explanation is consistent with the fact that never
is assignable to everything: never
is the most constrained type because no actual values match it, so never
extends everything. The empty set is a subset of every set; the empty type never
is the subtype of every type and can be assigned to every other type.
let unionExtendsMember: 'a' | 'b' extends 'a' ? true : false;
// ^? let unionExtendsMember: false
let memberExtendsUnion: 'a' extends 'a' | 'b' ? true : false;
// ^? let memberExtendsUnion: true
let objectExtendsObject: {foo: number, bar: number} extends {foo: number} ? true : false;
// ^? let objectExtendsObject: true
let neverExtendsObject: never extends { foo: number } ? true : false;
// ^? let neverExtendsObject: true
Playground Link