Repository navigation
Type narrowing generics/unknown doesn't seem to work with typeof and null checkΒ #43997
Description
Activity
MartinJohns commented
on May 7, 2021 ContributorMore actionsDuplicate of #28131.
The second part of this is. There are two issues i would say, the generic function can be re-ordered to have the same order and will still error.
e.g.
function func<T>(obj: T): boolean { // Errors because `hasOwnProperty` doesn't exist on T, doesn't know it is an object if ((typeof obj === 'object') && obj !== null && obj.hasOwnProperty('xyz')) { return true; } return false; }
MartinJohns commented
on May 7, 2021 ContributorMore actionsT extends ObjectOtherwise you can pass in this object:
{ hasOwnProperty: () => "example" }.Tdoesn't extendobject...function func<T>(obj: T): boolean { if ((typeof obj === 'object') && obj !== null && obj.hasOwnProperty('xyz')) { return true; } return false; } func('foo'); // false func(123); // false func({xyz: 123}); // true // etc etc
MartinJohns commented
on May 7, 2021 ContributorMore actionsSee my update. If you don't make
TextendsObjectyou can pass in an object like{ hasOwnProperty: () => "example" }.And
'foo' and123both extendObject.function func<T extends Object>(_val: T) {} // Works fine func('example') func(123) func(true)out of interest, why isn't that behaviour the same without generics?
unknowncould also be{ hasOwnProperty: () => "example" }could it not?const foo: unknown = {hasOwnProperty: () => "blah"}; if (typeof foo === 'object' && foo !== null && foo.hasOwnProperty('blah')) { // WORKS }
so why is it any different just because we have a
Trather thanunknown? which is just as lacking in typei suppose because
Tmeans the compiler could have inferred the type from higher up the chain and would knowhasOwnPropertyis something different here, whereas withunknownit really has no clue so is a bit dumber about it. so only because the compiler knows more in the case of generics it disallows thisMartinJohns commented
on May 7, 2021 ContributorMore actionsI don't know about the specifics, you'd need to wait for the TypeScript team or someone else more knowledgable.
But your guess sounds like a good one to me. It's similar to this example:
function func<T extends Object>(value: T) { if (isTest(value)) { value; } } type Test = { test: string } function isTest(val: unknown): val is Test { return true }valueis not narrowed toTest, it's narrowed toT & Test. The generic type argument is kept.Reacted by James GarbuttRyanCavanaugh commented
on May 11, 2021 MemberMore actionsThere are some weird corner cases here I don't recall that lead to this. It's certainly not desirable.
- addedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on May 21, 2022 - locked as resolved and limited conversation to collaborators
on Oct 21, 2025
Bug Report
π Search Terms
typeof object, null checks, narrowing object, narrow generics
π Version & Regression Information
All
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
The generic function doesn't infer that
objmust be anobjectof some sort.The first if doesn't infer that
testVariablecan't be null.π Expected behavior
The generic function should infer that
Tis anobjectbecause we did a null check and checkedtypeof.Both ifs should infer that
testVariablecan't be null.Notes
Is this just me missing some 'known behaviour' typescript has? the two conditions seem to depend on ordering, maybe thats just a known thing i was unaware of (i.e. by design)?