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
anycan be seen as the union of all types. A union distributes in conditional types, soanymay or may not be empty.true | false = booleanneveris the empty union, it also ditributes in conditional types and leads to the empty union, which isnever.unknownhas a very special meaning,unknownis 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>:=booleanIsEmpty<unknown>:=unknownIsEmpty<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>>>;