Skip to content

Inconsistent behavior when narrowing bounded generics. #13426

Closed

Description

@denballakh

Consider this example:

from typing import Generic, TypeVar

T = TypeVar('T')
G = TypeVar('G', bound='Y')

class X(Generic[T]): pass
class Y: pass

# Consider this function:
def f(a: X[T] | G) -> X[T] | G:
    if isinstance(a, X):
        reveal_type(a) # X[T`-1]
        return a
    if isinstance(a, Y):
        reveal_type(a) # G`-2
        return a

    print() # error: unreachable. It is ok, because all possibilities checked.

# The same function, but if's swapped
def g(a: X[T] | G) -> X[T] | G: # error: Missing return statement
    if isinstance(a, Y):
        reveal_type(a) # Y
        return a # error: Incompatible return value type (got "Y", expected "Union[X[T], G]")
    if isinstance(a, X):
        reveal_type(a) # X[T`-1]
        return a

    print() # no "error: unreachable", because it is reachable and mypy complains about "missing return"
    # I expect to get no errors here, because it isnt reachable

Expected Behavior
First function is fine. I expect to get no errors in second function. Also first reveal_type in second function is IMO invalid, it should be G.

Your Environment

  • Mypy version used: mypy 0.971 (compiled: yes)
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files):
disallow_any_unimported = True
disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
disallow_untyped_decorators = True
warn_unreachable = True
warn_redundant_casts = True
warn_unused_ignores = True
# warn_return_any = True
strict_equality = True
show_column_numbers = True
  • Python version used: 3.10.6
  • Operating system and version: Win10

PS: pls give better title to this issue, idk how to properly describe this issue.

Activity

  1. denballakh commented on Aug 15, 2022

    @denballakh
    ContributorAuthor

    May be related: #9023 #7913 #7362

  2. brianschubert commented on Dec 7, 2024

    @brianschubert
    Collaborator

    This incorrect reachability analysis in the second function was fixed in #14923.

    The narrowing issue in the isinstance(a, Y) branch is still present in mypy v1.13.0. Here's a simplified reproducer:

    from typing import TypeVar, assert_never
    
    class A: pass
    
    T = TypeVar("T", bound=A)
    
    def foo(x: T | int) -> T | int:
        if isinstance(x, A):
            reveal_type(x)  # N: Revealed type is "__main__.A"
            return x  # E: Incompatible return value type (got "A", expected "T | int")
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

    bugmypy got something wrongtopic-type-narrowingConditional type narrowing / binder

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions