Skip to content

Problems with narrowing generic T | Sequence[T] #19202

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

Closed
randolf-scholz opened this issue Jun 2, 2025 · 1 comment
Closed

Problems with narrowing generic T | Sequence[T] #19202

randolf-scholz opened this issue Jun 2, 2025 · 1 comment
Labels
bug mypy got something wrong

Comments

@randolf-scholz
Copy link
Contributor

randolf-scholz commented Jun 2, 2025

Trying to narrow T | Sequence[T] with isinstance clauses leads to type errors. I believe this has to do with the example below actually wanting T | (Sequence[T] & ¬T).

So in the first clause, I guess mypy thinks this could be either L & Foo = Foo or Sequence[L] & Foo, which is not necessarily an instance of L. (so maybe not a bug but nevertheless a serious divergence between type checkers).

To Reproduce

from collections.abc import Sequence

class Foo: ...

def concat[L: Foo, R: Foo](
    left: L | Sequence[L],
    right: R | Sequence[R], /
) -> list[L | R]:
    match left, right:
        case Foo(), Foo():
            return list( (left, right) )  # ❌
        case Foo(), [*rvalues]:
            return list( (left, *rvalues) )  # ❌
        case [*lvalues], Foo():
            return list( (*lvalues, right) )  # ❌
        case [*lvalues], [*rvalues]:
            return list( (*lvalues, *rvalues) )
        case _:
            raise TypeError
            
def concat2[L: Foo, R: Foo](
    left: L | Sequence[L],
    right: R | Sequence[R], /
) -> list[L | R]:
    if isinstance(left, Foo) and isinstance(right, Foo):
        return list( (left, right) )  # ❌
    elif isinstance(left, Foo) and isinstance(right, Sequence):
        return list( (left, *right) )  # ❌
    elif isinstance(left, Sequence) and isinstance(right, Foo):
        return list( (*left, right) )  # ❌
    elif isinstance(left, Sequence) and isinstance(right, Sequence):
        return list( (*left, *right) )
    else:
        raise TypeError

Expected Behavior

This code passes without issues in pyright-playground and ty-playground

Actual Behavior

mypy-playground emits 6 errors

main.py:14: error: Argument 1 to "SeqFoo" has incompatible type "tuple[Foo, Foo]"; expected "Iterable[L | R]"  [arg-type]
main.py:16: error: Argument 1 to <tuple> has incompatible type "Foo"; expected "L | R"  [arg-type]
main.py:18: error: Argument 2 to <tuple> has incompatible type "Foo"; expected "L | R"  [arg-type]
main.py:29: error: Argument 1 to "SeqFoo" has incompatible type "tuple[Foo, Foo]"; expected "Iterable[L | R]"  [arg-type]
main.py:31: error: Argument 1 to <tuple> has incompatible type "Foo"; expected "L | R"  [arg-type]
main.py:33: error: Argument 2 to <tuple> has incompatible type "Foo"; expected "L | R"  [arg-type]
Found 6 errors in 1 file (checked 1 source file)

At the very least, the error messages are misleading, since the problematic bit are the arguments to SeqFoo, not to tuple.

@randolf-scholz randolf-scholz added the bug mypy got something wrong label Jun 2, 2025
@sterliakov
Copy link
Collaborator

This has the same reason as #15151: narrowing a bounded TypeVar currently produces whatever isinstance narrows to, TypeVar is essentially forgotten in such cases. This is fixed by #19183 (I checked out the PR and confirmed that that revision returns green for your snippet)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants