Description
🔎 Search Terms
"type guard", "is object", "not function", "negated types", "union type", "supertype", "exclude"
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about Exclude Isn't Type Negation, Primitives are { }, and { } Doesn't Mean object, Negated types
⏯ Playground Link
💻 Code
type NotFunction<T> = T extends Function ? never : T;
function isObjectLike<T>(value?: T): value is NotFunction<T> /* & object */ { // Simplify the code, assume the `value` must be an object.
return value !== null && typeof value === "object";
}
declare const a: (() => void) | { foo: string };
type A = NotFunction<typeof a>; // { foo: string }
if (isObjectLike(a))
a; // { foo: string }
else
a; // () => void
declare const b: (() => void) | {};
type B = NotFunction<typeof b>; // {}
if (isObjectLike(b))
b; // Expect: {}; Actual: (() => void) | {};
else
b; // Expect: () => void; Actual: never;
🙁 Actual behavior
I'm trying to implement type guards for lodash's isObjectLike
function, and the current implementation works fine for most cases.
To make the code more concise, the step of checking if it is an object is commented here, leaving only the check of whether it is not a function.
If the type of the variable being checked (b
in the sample code) is a union type that contains a function and an empty object (or any supertype of the function, like { name: string }
), the result of type narrowing will incorrectly include the function as well.
In the sample code, the type of b
is (() => void) | {}
, and the type of NotFunction<typeof b>
is {}
, which is correct. However, the type of the function returned value is NotFunction<T>
is (() => void) | {}
, which is not as expected.
🙂 Expected behavior
The function isObjectLike
should narrow the type of b
to {}
, which consistent with the type of NotFunction<typeof b>
.
Additional information about the issue
In order to check if a variable is an object, I have to painfully write value !== null && typeof value === "object"
everywhere. The isObjectLike
function in lodash encapsulates this operation, but lacks type guards. There is also an isObject
function which returns value is object
, unfortunately it treats functions as objects.
I'm not sure if the current behavior is expected, and if so, it seems that the isObjectLike
function does not have a perfect type guard solution, and may have to wait until the negated type is available.