A value of type T
is defined to be empty, if
- it is an array and has no elements
- it is an object and has no properties
any
can be seen as the union of all types. A union distributes in conditional types, soany
may or may not be empty.true | false = boolean
never
is the empty union, it also ditributes in conditional types and leads to the empty union, which isnever
.unknown
has a very special meaning,unknown
is not a union. An unknown value could be empty or not, we don't know. I would sayIsEmpty<unknown> := unknown
.
Therefore we could define:
IsEmpty<any>
:=boolean
IsEmpty<unknown>
:=unknown
IsEmpty<never>
:=never
At the use-site this would lead to:
// false
type _is_any_definitely_empty = boolean extends true ? true : false;
// false
type _is_any_definitely_non_empty = boolean extends false ? true : false;
// false
type _is_unknown_definitely_empty = unknown extends true ? true : false;
// false
type _is_unknown_definitely_non_empty = unknown extends false ? true : false;
// true
type _is_never_definitely_empty = never extends true ? true : false;
// true
type _is_never_definitely_non_empty = never extends false ? true : false;
This will lead to problems. It implies that the universal types any
, unknown
and never
need a special treatment at the use-site.
// for example like this ...
Or<IsUniversal<T>, IsEmpty<T>> extends true ? DoThis<T> : DoThat<T>
// ... or like this, depending on the application semantics
Or<Not<IsUniversal<T>>, IsEmpty<T>> extends true ? DoThis<T> : DoThat<T>
where
IsUniversal<T> := Or<Is<T, any>, Or<Is<T, unknown>, Is<T, never>>>;