I have a function, and it can operate on any type T. The only constraint is "if T is an object, it can't be an object which can potentially be empty".
I tried this:
declare function func<T>(o: T extends Record<string, never> ? never : T): void;
func("string") // passes, as expected
func({}) // fails, as expected
func<{x?: number}>({}) // passes, noooo
Also this:
type EmptyObj = Omit<{x: number}, 'x'>
declare function func2<T>(o: T extends EmptyObj ? never : T): void;
func2("HI") // fails
func2({}). // fails
func2<{x?: number}>({x: 1}) // fails, as expected
func2<{x: number}>({x: 1}) // fails
func2 is probably running into the the fact {} is the top type, so everything extends it. Is typescript capable of describing what I need it to?
Here's one approach:
This is a recursive constraint on the type parameter
T(such self-referential constraints are known as F-bounded quantification and allow more expressive constraints where they are allowed) which amounts to "allowTif and only if the empty object type{}is not assignable toT". If you can assign an empty object toT, thenTshould be rejected; if you cannot, thenTshould be accepted.This is implemented by constraining
Tto the conditional type{} extends T ? never : unknown. If{} extends Tis true, then{}is assignable toT, and so the conditional type resolves tonever, and so the constraint isT extends neverwhich will not be met, and you'll get an error. On the other hand, if{} extends Tis false, then{}is not assignable toT, and so the conditional type resolves tounknown, and so the constraint isT extends unknown, which will essentially always be met, and you won't get an error.Let's test it:
Looks good. Since
{} extends stringis false, you can callfunc("string"). But since{} extends {}is true and since{} extends { x?: number}is true, then you cannot callfunc({})orfunc<{x?: number}>({}).Playground link to code