Skip to content

TypeGuard functions not properly recognizing generic types #11428

Closed
@wbolster

Description

@wbolster

Bug Report

It seems the typing.TypeGuard support is not able to properly discriminate a union of two (generic) types.

To Reproduce

  • The example below defines two completely distinct types, Left[L] and Right[R], both of which happen to be generic and have a .value attribute.
  • The LeftOrRight is a Union of those types.
  • x is a variable of type LeftOrRight[str, int]. In this example it has value Left("")
  • Using isinstance() works fine to figure out whether x is either Left or Right, and within the if/else block the types are correct, including the types L and R (both TypeVar)
  • The is_left() and is_right() helpers use typing.TypeGuard, but Mypy does not recognize the types like it did with the isinstance() check.
from typing import Generic, TypeGuard, TypeVar, Union

L = TypeVar("L")
R = TypeVar("R")


class Left(Generic[L]):
    value: L

    def __init__(self, value: L):
        self.value = value


class Right(Generic[R]):
    value: R

    def __init__(self, value: R):
        self.value = value


LeftOrRight = Union[Left[L], Right[R]]


def is_left(obj: LeftOrRight[L, R]) -> TypeGuard[Left[L]]:
    return isinstance(obj, Left)


def is_right(obj: LeftOrRight[L, R]) -> TypeGuard[Right[R]]:
    return isinstance(obj, Right)


x: LeftOrRight[str, int] = Left("")
reveal_type(x)  # Revealed type is "Union[Left[builtins.str], Right[builtins.int]]"

if isinstance(x, Left):
    reveal_type(x)  # Revealed type is "Left[builtins.str]"
    x.value + ""  # only valid for str
else:
    reveal_type(x)  # Revealed type is "Right[builtins.int]"
    x.value + 10  # only valid for int

if is_left(x):
    reveal_type(x)  # Revealed type is "Left[L`-1]"
    x.value + ""  #  Unsupported left operand type for + ("L")  [operator]
else:
    reveal_type(x)  # Revealed type is "Union[Left[builtins.str], Right[builtins.int]]"
    x.value + 10  # Unsupported operand types for + ("str" and "int")  [operator]

Expected Behavior

is_left() and is_right() act as type-safe ways to discriminate between the two members of the Union, including the actual type of the (generic) .value attribute.

Actual Behavior

  • reveal_type() returns wrong results
  • type errors reported in what seems to be perfectly fine and very strict code

Your Environment

This happens with both Mypy 0.910 (current release as time of writing) and today's git checkout of the main branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-typeguard-typeisTypeGuard / TypeIs / PEP 647 / PEP 742

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions