Skip to content

And of isinstances / typeguards containing unions #14434

Open
@Anonymous-Stranger

Description

@Anonymous-Stranger

Bug Report

When taking the and of multiple isinstances/typeguards, it seems like only the most recent is used to constrain the variable whose type is being checked.

To Reproduce

See Gist: https://gist.github.com/a4cc96c4bb9207a3ae6a344016d2e46d

from typing import Any, TypeGuard, reveal_type

def int_or_str(x: Any) -> TypeGuard[int | str]:
    return isinstance(x, int | str)

def int_or_list(x: Any) -> TypeGuard[int | list]:
    return isinstance(x, int | list)

def working_in_pyright(x: Any) -> TypeGuard[int]:
    if isinstance(x, int | str) and isinstance(x, int | list):
        reveal_type(x)  # pyright: int, mypy: int | list
        return True
    return False

def failing_in_both(x: Any) -> TypeGuard[int]:
    if int_or_str(x) and int_or_list(x):
        reveal_type(x)  # int | list
        return True
    return False

# incidentally, changing `and` to `or` results in the correct output `int | str | list`

Expected Behavior

I think the code reveal_type(x) should return int in each of these examples.

I'm not sure about the failing_in_both example: I vaguely understand that user-defined type guards do not constrain the negative case of an if (but I haven't yet understood the rationale) -- does similar reasoning explain why the type guards on the LHS and RHS of the and do not combine?

I mean, technically, I could write a TypeGuard that modifies the argument, and so I guess the LHS of the and could cease to hold when the RHS is called; is this why? Would @erictraut's StrictTypeGuard python/typing#1013 be able to resolve this?

Actual Behavior

main.py:14: note: Revealed type is "Union[builtins.int, builtins.str, builtins.list[Any]]"
main.py:21: note: Revealed type is "Union[builtins.int, builtins.str, builtins.list[Any]]"
Success: no issues found in 1 source file

Your Environment

  • Mypy version used: 0.991
  • Python version used: 3.11

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions