Skip to content

Incorrect type inference with PEP 646 Unpack and self-type annotations #20651

@zzzeek

Description

@zzzeek

Bug Report

When a generic class uses TypeVarTuple (PEP 646) and a method has a self-type annotation that "reshapes" the type parameters, mypy incorrectly infers the return type when the class is instantiated with Unpack[Tuple[Any, ...]].

Specifically, mypy infers the return type as None instead of Any | None, leading to false "unreachable code" errors.

To Reproduce

from typing import Generic, TypeVar, Optional, Iterator, Any, Tuple
from typing_extensions import TypeVarTuple, Unpack

_T = TypeVar("_T", bound=Any)
_Ts = TypeVarTuple("_Ts")


class Result(Generic[Unpack[_Ts]]):
    """Generic result class that takes variadic type parameters.

    This mimics SQLAlchemy's Result class which is generic over Unpack[_Ts].
    """

    def scalar(self: "Result[_T, Unpack[Tuple[Any, ...]]]") -> Optional[_T]:
        return None




def test_function() -> Iterator[Any]:
    result: Result[Unpack[Tuple[Any, ...]]] = Result()
    reveal_type(result)
    data = result.scalar()
    reveal_type(data)
    if data is None:
        return
    yield from iter(data)

Expected Behavior

If we run pyright, the type of "data" is "Any | None" and the yield from is reachable:

$ pyright mypy_bug_repro.py 
/home/classic/dev/sqlalchemy/mypy_bug_repro.py
  /home/classic/dev/sqlalchemy/mypy_bug_repro.py:22:17 - information: Type of "result" is "Result[*tuple[Any, ...]]"
  /home/classic/dev/sqlalchemy/mypy_bug_repro.py:24:17 - information: Type of "data" is "Any | None"
0 errors, 0 warnings, 2 informations

Actual Behavior

Mypy sees "data" as None in all cases

$ mypy --warn-unreachable mypy_bug_repro.py 
mypy_bug_repro.py:22: note: Revealed type is "mypy_bug_repro.Result[Unpack[builtins.tuple[Any, ...]]]"
mypy_bug_repro.py:24: note: Revealed type is "None"
mypy_bug_repro.py:27: error: Statement is unreachable  [unreachable]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version: mypy 1.19.1 (compiled: yes)
  • Python version: 3.14
  • typing_extensions version: latest (for TypeVarTuple, Unpack)
  • Pyright version (for comparison): 1.1.408

notes

So I used Claude to refactor SQLAlchemy's structures into standalone features. Additional commentary from Claude which may or may not be helpful:

The bug appears to be in mypy's type parameter matching logic. When trying to match:

  • Instance type: Result[Unpack[Tuple[Any, ...]]]
  • Against self-type: Result[_T, Unpack[Tuple[Any, ...]]]

Mypy should extract _T = Any from the first element of the unpacked tuple, but instead appears to fail at this extraction, possibly binding _T to Never or treating it as non-existent, resulting in Optional[Never] = None.

downstream SQLAlchemy issues is at sqlalchemy/sqlalchemy#13089 which is local to the new 2.1.0beta1 release that uses pep-646

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions