Skip to content

Specifying self-type for first __init__ overload affects isinstance type narrowing #19221

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
bkeryan opened this issue Jun 3, 2025 · 0 comments · Fixed by #19223
Closed

Specifying self-type for first __init__ overload affects isinstance type narrowing #19221

bkeryan opened this issue Jun 3, 2025 · 0 comments · Fixed by #19223
Labels
bug mypy got something wrong topic-self-types Types for self topic-type-narrowing Conditional type narrowing / binder

Comments

@bkeryan
Copy link

bkeryan commented Jun 3, 2025

Bug Report

I have a generic class with multiple __init__ overloads. One of the __init__ overloads specifies the type of self in order to control what the type variable evaluates to for this overload.

When I upgraded this code to Mypy 1.16, I started getting errors in a method that uses isinstance to check whether another object is an instance of the same class. I expected if isinstance(other, A): to narrow the type of other to A[Any], but it actually narrows to the self-type specified in the first __init__ overload, such as A[int].

If I reorder the __init__ overloads so the first overload does not specify a self-type, then if isinstance(other, A): seems to narrow to A[Any], or A[T] in methods that take a union of A[T] and other types. This is the behavior I expected.

With Mypy 1.15 and older, the behavior is different. Specifying a self-type for the first __init__ overload causes if isinstance(other, A): to narrow the type of other to Never. I think this is why I didn't get any errors before upgrading to Mypy 1.16.

Pyright doesn't complain about this code.

To Reproduce

Classes A and B are the same except for the order of the __init__ overloads.

https://mypy-play.net/?mypy=latest&python=3.12&gist=dae546cdc2f11d2f5bd582efa826e188

from typing import Generic, TypeVar, overload

T = TypeVar("T")

class A(Generic[T]):
    @overload
    def __init__(self, x: T) -> None: ...

    @overload
    def __init__(self: A[int]) -> None: ...

    def __init__(self, x: T | None = None) -> None:
        pass
    
    def f(self, other: A[T] | str) -> None:
        reveal_type(other) # Union[__main__.A[T`1], builtins.str]
        if isinstance(other, A):
            reveal_type(other) # __main__.A[T`1]
        else:
            raise TypeError

class B(Generic[T]):
    @overload
    def __init__(self: B[int]) -> None: ...

    @overload
    def __init__(self, x: T) -> None: ...

    def __init__(self, x: T | None = None) -> None:
        pass
    
    def f(self, other: B[T] | str) -> None:
        reveal_type(other) # Union[__main__.B[T`1], builtins.str]
        if isinstance(other, B):
            reveal_type(other) # __main__.B[builtins.int]
        else:
            raise TypeError

Expected Behavior

main.py:16: note: Revealed type is "Union[__main__.A[T`1], builtins.str]"
main.py:18: note: Revealed type is "__main__.A[T`1]"
main.py:33: note: Revealed type is "Union[__main__.B[T`1], builtins.str]"
main.py:35: note: Revealed type is "__main__.B[T`1]"

Actual Behavior

Mypy 1.16:

main.py:16: note: Revealed type is "Union[__main__.A[T`1], builtins.str]"
main.py:18: note: Revealed type is "__main__.A[T`1]"
main.py:33: note: Revealed type is "Union[__main__.B[T`1], builtins.str]"
main.py:35: note: Revealed type is "__main__.B[builtins.int]"

Mypy 1.15:

main.py:16: note: Revealed type is "Union[__main__.A[T`1], builtins.str]"
main.py:18: note: Revealed type is "__main__.A[T`1]"
main.py:33: note: Revealed type is "Union[__main__.B[T`1], builtins.str]"
main.py:35: note: Revealed type is "Never"

Your Environment

  • Mypy version used: 1.16
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): strict=true, but it's also reproducible with none
  • Python version used: 3.12
@bkeryan bkeryan added the bug mypy got something wrong label Jun 3, 2025
@sterliakov sterliakov added topic-type-narrowing Conditional type narrowing / binder topic-self-types Types for self labels Jun 3, 2025
JukkaL pushed a commit that referenced this issue Jun 5, 2025
Fixes #19221. Instead of trying to use the first (maybe) overload item
and erase it, just use the underlying type with Any-filled typevars
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-self-types Types for self topic-type-narrowing Conditional type narrowing / binder
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants