The distinction is essentially this:
-
the
any
type is deliberately unsound, in that it is assignable both to and from any other type (with the possible exception ofnever
, depending on where you’re using it). Unsoundness means that some basic rules for types are broken, such as transitivity of subtyping. Generally, ifA
is assignable toB
, andB
is assignable toC
, thenA
is assignable toC
. Butany
breaks this. For example:string
is assignable toany
, andany
is assignable tonumber
… butstring
is not assignable tonumber
. This particular unsoundness is very useful, because it allows us to essentially “turn off” type checking in a part of code which is either hard or impossible to type correctly. But you need to be very careful thinking ofany
as a type; it’s more of an “un-type”. -
the empty type,
{}
, is a type that can be treated like an object at runtime (that is, something you can read properties or methods from without a runtime error), but it has no known properties at compile time. That doesn’t mean it has no properties; it just means that the compiler doesn’t know about any of them. This implies that onlynull
andundefined
are not assignable to{}
(null.foo
orundefined.foo
are runtime errors). Even primitive types likestring
can be treated as having properties and methods at runtime ("".length
and"".toUpperCase()
work, and even"".foo
just returnsundefined
). And of course any actual object type will also be assignale to{}
.On the other hand, the
{}
type is not assignable to very many types. If I have value of type{}
as try to assign it to a variable of type{foo: string}
, there will be a compiler error, as{}
is not known to contain afoo
property. You can assign{}
to itself, or to a wider type likeunknown
, or to the “un-type”any
.This makes
{}
very nearly a top type, which is a type to which all other types are assignable. It’s essentially a top type withnull
andundefined
removed from it. -
the
unknown
type was introduced in TypeScript 3.0 and is the true top type; every type in TypeScript is assignable tounknown
. Evennull
andundefined
are assignable tounknown
.Again, on the other hand,
unknown
is only assignable to itself and the “un-type”any
. Even the{}
type isn’t wide enough for you to assignunknown
to it. Conceptually you should be able to assignunknown
to the union type{} | null | undefined
, but this is intentionally not implemented to keepunknown
as the “true” top type.
Most of your CheckIfExtends<A, B>
results can be explained by the above. The exception is T11
:
type T11 = CheckIfExtends<any, {}>; //boolean
Your CheckIfExtends<A, B>
type definition is a distributive conditional type, which does some interesting things when A
is a union type, in that it allows both branches of the conditional to be taken if the pieces of the union satisfy both branches. It also does the same distribution when A
is any
, except when B
is any
or unknown
(so T8
behaves normaly). There’s some discussion of this in microsoft/TypeScript#27418. Anyway, T11
takes both branches and you get true | false
which is boolean
. (From microsoft/TypeScript#27418, unknown
in the A
position does not distribute, so T7
and T12
behave normally as well).
Okay, hope that helps; good luck!