Skip to content

TS2367: This condition will always return 'false' since the types 'Constructor<T>' and 'typeof Child' have no overlap. #27910

Open

Description

TypeScript Version: 3.2.0-dev.20181011

Search Terms: TS2367

Code

abstract class Base<T> {
    get Item(): T { return null }
}

class Child extends Base<Item> {
    constructor(public data: any) { super() }
}

interface Item { }
declare type Constructor<T> = new (data: any) => Base<T>

function func<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
    }
    new constructor(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) {
           ~~~~~~~~~~~~~~~~~~~~~

Playground Link

Related Issues: #25642

Activity

  1. mattmccutchen commented on Oct 16, 2018

    @mattmccutchen
    Contributor

    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.

  2. the-ress commented on Oct 16, 2018

    @the-ress
    Author

    Thanks for the explanation. Yes, that error message would be more helpful.

  3. Manish-Giri commented on Oct 16, 2018

    @Manish-Giri
    Contributor

    Can I take this up?

  4. moccaplusplus commented on Jan 16, 2019

    @moccaplusplus

    Wait,

    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:

    1. If you only change Constructor type to:
    declare type Constructor<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)

    1. Alternatively if you only make your Child class to be:
    class Child extends Base<any> {
      constructor(public data: any) { super() }
    }

    then the comparison operator does NOT produce error.

    So if the parametrized type is any (or unknown), 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").

  5. RyanCavanaugh commented on Jan 16, 2019

    @RyanCavanaugh
    Member

    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.

  6. moccaplusplus commented on Jan 16, 2019

    @moccaplusplus

    Ryan Cavanaugh (@RyanCavanaugh) It is incosistent. ;)

    Proof:

    Let's introduce function:

    function constrEquals<T>(c1: Constructor<T>, c2: Constructor<T>): boolean {
      return c1 === c2;
    }

    then

    function func<T>(constructor: Constructor<T>): void {
      constrEquals(constructor, Child); // No Error
      constructor === Child; // ERROR
    }
  7. RyanCavanaugh commented on Jan 16, 2019

    @RyanCavanaugh
    Member

    It is incosistent.

    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.

  8. moccaplusplus commented on Jan 16, 2019

    @moccaplusplus

    I just realized inconsistency was due to the fact that Item was an empty interface.

    When it is not - then it's consistent:

    function func<T>(constructor: Constructor<T>): void {
      constrEquals(constructor, Child); // ERROR
      constructor === Child; // ERROR
    }

    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.

  9. eyelidlessness commented on Dec 20, 2019

    @eyelidlessness

    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.

  10. millsp commented on Nov 3, 2020

    @millsp
    Contributor

    I feel like this should work given that T is unknown

    function fn<T>(t: T) {
        return t === '' // error
    }

    https://www.typescriptlang.org/play?jsx=0#code/GYVwdgxgLglg9mABMMAeAKgPgBRQFyLoCUiA3gFCJWIBOAplCDUlIgLweIDkX5AvkA

    Not sure why, but this works

    function fn<T extends unknown>(t: T) {
        return t === '' // 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 unknown by default?

  11. ETARAZ commented on Jan 17, 2021

    @ETARAZ

    var t: any;
    t=2
    1==t ? result = (a & b): console.log(a | b);

    its work

  12. vsDizzy commented on May 10, 2021

    @vsDizzy
  13. ManishPrakkash commented on Mar 16, 2026

    @ManishPrakkash

    Checking in to see if this is still open for a PR? I’d like to help out with the requested changes

  14. paigefletcher1282-code commented on Mar 17, 2026

    @paigefletcher1282-code
  15. LeonxLJX commented on Sep 1, 2026

    @LeonxLJX

    I'd like to take this one — I'll follow up with a PR. (claiming via Stella (@LeonxLJX))

  16. LeonxLJX commented on Sep 1, 2026

    @LeonxLJX

    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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions