Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
the-ress opened this issue Oct 15, 2018 · 12 comments
Labels
Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements Good First Issue Well scoped, documented and has the green light Help Wanted You can do this PursuitFellowship Help wanted from Pursuit fellowship; others please avoid until Dec 19 Suggestion An idea for TypeScript
Milestone

Comments

@the-ress
Copy link

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

@mattmccutchen
Copy link
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.

@the-ress
Copy link
Author

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

@weswigham weswigham added Domain: Error Messages The issue relates to error messaging Help Wanted You can do this Good First Issue Well scoped, documented and has the green light labels Oct 16, 2018
@Manish-Giri
Copy link
Contributor

Can I take this up?

@moccaplusplus
Copy link

moccaplusplus commented Jan 16, 2019

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 @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
Copy link
Member

@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.

@moccaplusplus
Copy link

moccaplusplus commented Jan 16, 2019

@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
}

@RyanCavanaugh
Copy link
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.

@moccaplusplus
Copy link

moccaplusplus commented Jan 16, 2019

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.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Experience Enhancement Noncontroversial enhancements labels Mar 7, 2019
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Mar 7, 2019
@eyelidlessness
Copy link

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.

@sandersn sandersn added the PursuitFellowship Help wanted from Pursuit fellowship; others please avoid until Dec 19 label Sep 18, 2020
@millsp
Copy link
Contributor

millsp commented Nov 3, 2020

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 @sandersn?

Maybe all unconstrained generics should extends unknown by default?

@ETARAZ
Copy link

ETARAZ commented Jan 17, 2021

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

its work

@vsDizzy

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements Good First Issue Well scoped, documented and has the green light Help Wanted You can do this PursuitFellowship Help wanted from Pursuit fellowship; others please avoid until Dec 19 Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.