You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
typeNotFunction<T>=TextendsFunction ? never : T;functionisObjectLike<T>(value?: T): value is NotFunction<T>/* & object */{// Simplify the code, assume the `value` must be an object.returnvalue!==null&&typeofvalue==="object";}declareconsta: (()=>void)|{foo: string};typeA=NotFunction<typeofa>;// { foo: string }if(isObjectLike(a))a;// { foo: string }elsea;// () => voiddeclareconstb: (()=>void)|{};typeB=NotFunction<typeofb>;// {}if(isObjectLike(b))b;// Expect: {}; Actual: (() => void) | {};elseb;// 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.
The text was updated successfully, but these errors were encountered:
That's how supertypes work; any non-nullish value, including a function, is assignable to {}. If you have X extends Y, that means Y is a supertype of X and every X is also a Y. A union like X | Y is morally the same as just Y, because X is already included in Y. If you have a type guard that only checks the supertype val is Y, then the true case will not eliminate X (it'll still be X | Y) and the false case will eliminate everything (it'll be never). So this behavior is not a bug, it's behaving as intended.
🔎 Search Terms
"type guard", "is object", "not function", "negated types", "union type", "supertype", "exclude"
🕗 Version & Regression Information
⏯ Playground Link
https://www.typescriptlang.org/play/?ts=5.9.0-dev.20250420#code/C4TwDgpgBAcg9sAYgVwHYGNgEs6oDwAqAfFALxQFQQAewEqAJgM5Qoba5QD8UqEAbhABOUAFwUA3ACgpAMzSYcqKFiYB5AEYArCJgAyWANYRCRABT8AhgBtkELuIIBKcVdvRVsBG0W5TUAHoAKigAMig4bV1gKCCAqABvKSgUqCEIYGQhZTc7KABCUnJUZGtrMPDQSDhZKFzoIvIAIkidTCbpAF8ZBl1rS3SodFwmGMtxMzMnMhJ+OCwGaYAfRKhZODhxUaEsVABzKE7pKugAQTIvJAUOfBOaqEsiCUD4hLWNreAd-cOpLFqzKpNG1gAZjGZLE4nMlUpZngFXu9NlBtrsDt0INYmBAYSk4S8oFMZnV5gwen0BtBhqhRlANBMiaRZqTlokjlITlAAEIXeBXdhKPB3WoaJ4EhLdf6EoFRfRGCBmDRQ3F0+HxACi1EgmHEEuep0wyBsDOmTJJC1ZeqkmOxKo0aqgmu1wAmpuZC31huNvAEwmkQA
💻 Code
🙁 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 ofNotFunction<typeof b>
is{}
, which is correct. However, the type of the function returnedvalue is NotFunction<T>
is(() => void) | {}
, which is not as expected.🙂 Expected behavior
The function
isObjectLike
should narrow the type ofb
to{}
, which consistent with the type ofNotFunction<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. TheisObjectLike
function in lodash encapsulates this operation, but lacks type guards. There is also anisObject
function which returnsvalue 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.The text was updated successfully, but these errors were encountered: