Skip to content

the types 'T' and 'typeof Class' have no overlapΒ #44645

Description

@karikera

Bug Report

πŸ”Ž Search Terms

template class no overlap label:Bug

πŸ•— Version & Regression Information

4.3.4 / 4.4.0-dev.20210617 / Nightly Playground

⏯ Playground Link

Playground Link

πŸ’» Code

class Class {
    dummy = 0;
}


function test<T>(a:T):void{
    if (a === Class) {} // This condition will always return 'false' since the types 'T' and 'typeof Class' have no overlap.ts(2367)
}

πŸ™ Actual behavior

ts2367 error. a can be Class

πŸ™‚ Expected behavior

no error. a can be Class

Activity

  1. RyanCavanaugh commented on Jun 18, 2021

    @RyanCavanaugh
    Member

    Not all comparisons are allowed; you can upcast either side to unknown if this was intentional.

  2. karikera commented on Jun 19, 2021

    @karikera
    Author

    Thanks for replying.
    I'm reducing it from my use cases. but it seems I reduced it too much without knowing it's intentional work

    class Class {
    }
    
    class Class2 extends Class {
        dummy = 0;
    }
    
    class Class3 {
    }
    
    function test<T extends typeof Class>(a:T):void{
        if (a === Class) {} // it's okay
        if (a === Class3) {} // it's okay
        if (a === Class2) {} // it has problem
    }
    
    const test2:typeof Class = Class2; // it's okay
    

    Playground Link

    on this case. it has the same problem.
    I thought this should be possible. but it also occurred in overall versions.

  3. added
    BugA bug in TypeScript
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on Jun 21, 2021
  4. RyanCavanaugh commented on Jun 21, 2021

    @RyanCavanaugh
    Member

    That's sort of weird

  5. ssalbdivad commented on Apr 20, 2023

    @ssalbdivad

    I've noticed another similar scenario where TS incorrectly asserts two types have no overlap and complains about their comparison:

    class Foo<T extends true> {
        declare t: T
    
        hasKind<kind extends true> (kind: kind) {
            // This comparison appears to be unintentional because the types 'T' and 'kind' have no overlap.
            return this.t === kind
        }
    }

    TS Playground

    Amusingly in this case, this.t and kind are guaranteed to be reference equal.

    Various manifestations of this error come up fairly often in my experience with generic parameters. This feels like the kind of message where TS should err on that side of caution, as it's not assignment- it's the language going out of its way to say "hey, this should never be possible, double check what you've done," which should really only be happening when there is certainty that the two types being compared are disjoint.

  6. RyanCavanaugh commented on Apr 20, 2023

    @RyanCavanaugh
    Member

    TS should err on that side of caution

    Isn't that what's happening, though?

  7. ssalbdivad commented on Apr 20, 2023

    @ssalbdivad

    TS should not prompt a developer to double check their logic based on a comparison between two values unless their types form a disjoint.

    In the example I've highlighted, the two values are guaranteed to be equal based on their types, yet TS complains they have no overlap.

    The more general case is if the parameters are unconstrained, i.e. TS has no information about their relationship to one another, you get the same message:

    class Foo<T> {
        declare t: T
    
        hasKind<kind>(kind: kind) {
            // This comparison appears to be unintentional because the types 'T' and 'kind' have no overlap.
            return this.t === kind
        }
    }

    Two unknown values should certainly be comparable.

  8. RyanCavanaugh commented on Apr 20, 2023

    @RyanCavanaugh
    Member

    I don't know what to say except that this is super, super intentional, and you really should get warned here. These are the kinds of checks where when we tighten them even slightly, we find huge numbers of actual bugs in real code.

    There are all kinds of mistakes you can make with === which are not provably always-false but very unlikely to be intentional. Issuing a small number of false positives in weird code to get true positives in bad code is the trade-off that all typecheckers make on purpose.

  9. ssalbdivad commented on Apr 20, 2023

    @ssalbdivad

    But in the example I mentioned, there is absolutely no reason to believe that comparison is unintentional.

    Comparing two values of type unknown is of course legal. This scenario is completely equivalent from TS's perspective based on this context. You can constrain the generics however you want, e.g. both to number. Same result- TS won't let you compare the two numbers for equality.

    I know often the === warnings are useful and I'm not suggesting that the majority of them are wrong or unhelpful- usually when TS claims the operands have no overlap, it's because they don't.

    That said, there is something that occurs when comparing two generics like this that results in TS making an overtly false claim that it has no evidence to make- i.e. that those types have no overlap.

    The only time this error should happen when comparing two generics like these are when both are constrained and the types of the two constraints form a disjoint. Other than that, it's both factually and practically wrong to assume the comparison is unintentional.

    I originally encountered this while writing a very uninteresting snippet of code to check if a Node type had a certain constraint kind, represented as a string. Despite both generics being constrained to the exact same type (in this case, the union "domain" | "class" | "value"), TS wouldn't allow me to compare them.

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

    BugA bug in TypeScriptDomain: check: Control FlowThe issue relates to control flow analysis

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions