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
{{ message }}
Repository navigation
TS2367: This condition will always return 'false' since the types 'Constructor<T>' and 'typeof Child' have no overlap. #27910
abstractclassBase<T>{getItem(): T{returnnull}}classChildextendsBase<Item>{constructor(publicdata: any){super()}}interfaceItem{}declaretypeConstructor<T>=new(data: any)=>Base<T>functionfunc<T>(constructor: Constructor<T>): void{// TS2367: This condition will always return 'false' since the types 'Constructor<T>' and 'typeof Child' have no overlap.if(constructor===Child){// do something}newconstructor(null)}func(Child)// No errors
Expected behavior:
Compiles without errors.
Actual behavior:
test.ts:14:9 - error TS2367: This condition will always return 'false' since the types 'Constructor<T>' and 'typeof Child' have no overlap.
14 if (constructor === Child) {
~~~~~~~~~~~~~~~~~~~~~
The presence of an error is working as intended, as per the issue you cited (#25642), because neither constructor nor Child is assignable (or more technically, "comparable") to the other. To see the reasons for that, you can write out test assignments and look at the errors: Child isn't assignable to constructor because we don't know that the T chosen by the caller of func is Item, while constructor isn't assignable to Child because the instance type of Child has a data property and that of constructor doesn't.
The one thing we could do here is improve the error message to avoid the false claim that the condition will always return false. Along the lines of #25541, shall we say something like the following?
Comparison of expressions of types '{0}' and '{1}' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert one of the expressions to 'unknown' first.
If Child satisfies function signature function func<T>(c: Constructor<T>): void without cast,
then it should also satisfy the === operator with Constructor<T> type on one side.
This is inconsistent behaviour.
It should either produce error in both cases or in none of them.
By the way, the old message was not ambiguous, the inconsistency was.
However there are even more inconsistencies:
If you only change Constructor type to:
declaretypeConstructor<T>=new(data: T)=>Base<any>
then the comparison operator does NOT produce error.
(the same effect, of course, if you strip generics from this declaration)
Alternatively if you only make your Child class to be:
Tomasz Gawel (@moccaplusplus)=== is only allowed between two operands if there is a "comparable" type relationship between them. This is similar to the check that determines if a type assertion is legal, with some extra special cases.
However, in this case, the compiler sees a comparison that looks like comparing a Horse to a Cat - both are Animals but it doesn't believe that any value inhabits both sides of the comparison (the existence of a hypothetical HorseCat notwithstanding).
The root cause of that, in turn, is that a type parameter behaves as if it were an arbitrary subtype of itself, because during an actual call the type parameter will be instantiated with some subtype but we can't say which -- this is why you can't return a Horse from <T extends Animal>(): T - a Horse is Animal, but T might be Cat.
Having different code behave differently is why some code produces errors and some doesn't. It'd be unfortunate if all inputs to the type system had to agree to all produce an error or all not produce an error.
Your example is similar to (horse as Animal) === Cat, which is legal by similar logic.
the call: constrEquals(constructor, Child)
resolved to: constructor as Constructor<Item> === Child as Constructor<Item>
which in turn, resolved to: constructor as Constructor<{}> === Child as Constructor<{}>;
when the Item was empty interface,
which was OK.
adding some properties to Item interface made: constructor as Constructor<Item>
to correctly produce an error.
This looks like the same problem, and I'm honestly baffled by it. The comparison can be true, so I don't understand why it should error at all regardless of the message. The comparison is asking "is T this specific value that is assignable to T?" and that is a valid condition to check.
I've run into this checking for a sentinel value that is intentionally an instance of a subclass of the type parameter. This behavior prevents checking for sentinel values in this way without reasserting a fact we already know: the sentinel value must be assignable to T or the call would error.
Activity
mattmccutchen commentedon Oct 16, 2018
The presence of an error is working as intended, as per the issue you cited (#25642), because neither
constructornorChildis assignable (or more technically, "comparable") to the other. To see the reasons for that, you can write out test assignments and look at the errors:Childisn't assignable toconstructorbecause we don't know that theTchosen by the caller offuncisItem, whileconstructorisn't assignable toChildbecause the instance type ofChildhas adataproperty and that ofconstructordoesn't.The one thing we could do here is improve the error message to avoid the false claim that the condition will always return false. Along the lines of #25541, shall we say something like the following?
Comparison of expressions of types '{0}' and '{1}' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert one of the expressions to 'unknown' first.the-ress commentedon Oct 16, 2018
Thanks for the explanation. Yes, that error message would be more helpful.
Manish-Giri commentedon Oct 16, 2018
Can I take this up?
moccaplusplus commentedon Jan 16, 2019
Wait,
If
Childsatisfies function signaturefunction func<T>(c: Constructor<T>): voidwithout cast,then it should also satisfy the
===operator withConstructor<T>type on one side.This is inconsistent behaviour.
It should either produce error in both cases or in none of them.
By the way, the old message was not ambiguous, the inconsistency was.
However there are even more inconsistencies:
Constructortype to:then the comparison operator does NOT produce error.
(the same effect, of course, if you strip generics from this declaration)
Childclass to be:then the comparison operator does NOT produce error.
So if the parametrized type is
any(orunknown), then the explanation given by Matt McCutchen (@mattmccutchen) does not apply? ;)Having said all above - I guess there is some deeper problem - so it should not marked as "good first issue").
RyanCavanaugh commentedon Jan 16, 2019
Tomasz Gawel (@moccaplusplus)
===is only allowed between two operands if there is a "comparable" type relationship between them. This is similar to the check that determines if a type assertion is legal, with some extra special cases.However, in this case, the compiler sees a comparison that looks like comparing a
Horseto aCat- both areAnimals but it doesn't believe that any value inhabits both sides of the comparison (the existence of a hypotheticalHorseCatnotwithstanding).The root cause of that, in turn, is that a type parameter behaves as if it were an arbitrary subtype of itself, because during an actual call the type parameter will be instantiated with some subtype but we can't say which -- this is why you can't return a
Horsefrom<T extends Animal>(): T- aHorseisAnimal, butTmight beCat.moccaplusplus commentedon Jan 16, 2019
Ryan Cavanaugh (@RyanCavanaugh) It is incosistent. ;)
Proof:
Let's introduce function:
then
RyanCavanaugh commentedon Jan 16, 2019
Having different code behave differently is why some code produces errors and some doesn't. It'd be unfortunate if all inputs to the type system had to agree to all produce an error or all not produce an error.
Your example is similar to
(horse as Animal) === Cat, which is legal by similar logic.moccaplusplus commentedon Jan 16, 2019
I just realized inconsistency was due to the fact that
Itemwas an empty interface.When it is not - then it's consistent:
the call:
constrEquals(constructor, Child)resolved to:
constructor as Constructor<Item> === Child as Constructor<Item>which in turn, resolved to:
constructor as Constructor<{}> === Child as Constructor<{}>;when the
Itemwas empty interface,which was OK.
adding some properties to
Iteminterface made:constructor as Constructor<Item>to correctly produce an error.
eyelidlessness commentedon Dec 20, 2019
This looks like the same problem, and I'm honestly baffled by it. The comparison can be true, so I don't understand why it should error at all regardless of the message. The comparison is asking "is
Tthis specific value that is assignable toT?" and that is a valid condition to check.I've run into this checking for a sentinel value that is intentionally an instance of a subclass of the type parameter. This behavior prevents checking for sentinel values in this way without reasserting a fact we already know: the sentinel value must be assignable to
Tor the call would error.millsp commentedon Nov 3, 2020
I feel like this should work given that
Tisunknownhttps://www.typescriptlang.org/play?jsx=0#code/GYVwdgxgLglg9mABMMAeAKgPgBRQFyLoCUiA3gFCJWIBOAplCDUlIgLweIDkX5AvkA
Not sure why, but this works
https://www.typescriptlang.org/play?jsx=0#code/GYVwdgxgLglg9mABMMAeAKogpgDylsAEwGdFwBrMOAdzAD4AKKALkXQEpEBvAKEX8QAnLFBCCkURAF4ZiAORyeAXyA
Should I create a different issue Nathan Shively-Sanders (@sandersn)?
Maybe all unconstrained generics should extends
unknownby default?ETARAZ commentedon Jan 17, 2021
var t: any;t=21==t ? result = (a & b): console.log(a | b);its work
vsDizzy commentedon May 10, 2021
ManishPrakkash commentedon Mar 16, 2026
Checking in to see if this is still open for a PR? I’d like to help out with the requested changes
paigefletcher1282-code commentedon Mar 17, 2026
LeonxLJX commentedon Sep 1, 2026
I'd like to take this one — I'll follow up with a PR. (claiming via Stella (@LeonxLJX))
LeonxLJX commentedon Sep 1, 2026
Hi! I'd like to fix TS2367 false positive for 'Con...' types. Plan: reproduce, adjust the comparison logic, add a test. May I be assigned?