Skip to content

Incorrect error on legitimate type overlap #41402

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

Open
millsp opened this issue Nov 4, 2020 · 8 comments
Open

Incorrect error on legitimate type overlap #41402

millsp opened this issue Nov 4, 2020 · 8 comments
Labels
Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript

Comments

@millsp
Copy link
Contributor

millsp commented Nov 4, 2020

TypeScript Version: 4.1.0-dev-20201102

Search Terms:

This condition will always return 'false' since the types 'X' and 'Y' have no overlap.

Code

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

fn('') // but here we pass it

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

Expected behavior:

It works when we extend unknown. While this works on simple generics, it becomes inconvenient when the type parameter is nested deeper in a type structure.

function fn<T extends unknown>(t: T) {
    return t === '' // works
}

fn('') // works

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

Actual behavior:

This condition will always return 'false' since the types 'T' and 'string' have no overlap.

Related issues
#27910

@RyanCavanaugh RyanCavanaugh added Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript labels Nov 4, 2020
@RyanCavanaugh
Copy link
Member

This is intentionally an error, but the wording needs work

@millsp
Copy link
Contributor Author

millsp commented Nov 4, 2020

Hey @RyanCavanaugh

Why would this be an error? It's simple, valid JS:

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

fn('') // but here we pass it

@millsp
Copy link
Contributor Author

millsp commented Nov 4, 2020

I actually expected an explanation (why is this OK?)

function fn<T extends unknown>(t: T) {
    return t === '' // works
}

fn('') // works

and works perfectly, but not the equivalent

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

fn('') // works

@RyanCavanaugh would you be so kind to explain, please :)

@RyanCavanaugh
Copy link
Member

A generic type parameter is comparable to the type of its constraint if a constraint was explicitly declared

@millsp
Copy link
Contributor Author

millsp commented Nov 5, 2020

Should we build a library to replace our comparison operators, to make this simple use-case work?

const equals = <A>(thing: unknown, other: A): thing is A => {
    return thing === other;
};

function fn<T>(t: T) {
    if (equals(t, '')) {
        // t: T & string
    } // works
}

fn(''); // works

Why is the equals type guard working but not the basic ===?

@MartinJohns
Copy link
Contributor

Why is the equals type guard working but not the basic ===?

unknown and any overlap with the generic type, so you're allowed to compare them. T is always assignable to unknown and any.

The same would apply without the additional type-guard:

function fn<T>(t: T) {
    const val: unknown = '';
    if (t === val) {
        t // t is T
    }
}

Just of course the type information string is lost and t is T instead of T & string.

@millsp
Copy link
Contributor Author

millsp commented Nov 5, 2020

Just of course the type information string is lost and t is T instead of T & string

That's exactly what I'm complaining about.

The same would apply without the additional type-guard

I get that a generic T intersected with any or unknown yields T.

I'm pointing out the fact that we could safely narrow T with the type that sits on the rhs of the comparison operator.

But @RyanCavanaugh seems to say that it's not possible, and I would like to know why should we have such a limitation.

Another example:

function fn<A, B>(a: A, b: B) {
    if (a === b) { // fails here
		// ...
    } else {	
		// ...
    }
}
const equals = <A>(thing: unknown, other: A): thing is A => {
    return thing === other;
};

function fn<A, B>(a: A, b: B) {
    if (equals(a, b)) { // works
        // a: A & B
    } else {
        // a: A
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants