python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
17.97k stars 2.76k forks source link

MyPy fails on Union[str, int] < Union[str, int] when underlying type is known #10380

Open muggenhor opened 3 years ago

muggenhor commented 3 years ago

Bug Report

Given the following script I'm receiving several unexpected errors:

test.py:22: error: Unsupported operand types for < ("str" and "int")
test.py:22: error: Unsupported operand types for < ("int" and "str")
test.py:22: note: Both left and right operands are unions
test.py:24: error: Unsupported operand types for < ("str" and "int")
test.py:24: error: Unsupported operand types for < ("int" and "str")
test.py:24: note: Both left and right operands are unions
Found 4 errors in 1 file (checked 1 source file)

Even though the if-block and assert statement just before the comparison operator usage on a and b clearly filters out the case of mismatching types. I've only added the assert statement in the hope of telling mypy that these variables have the same type, but no luck there.

from typing import Union

def semver_prerelease_compare(lhs: str, rhs: str) -> bool:
    a: Union[str, int]
    b: Union[str, int]
    for a, b in zip(lhs.split("."), rhs.split(".")):
        try:
            a = int(a)
        except ValueError:
            pass
        try:
            b = int(b)
        except ValueError:
            pass

        if isinstance(a, int) and not isinstance(b, int):
            # Numeric identifiers sort before non-numeric ones
            return True
        if type(a) != type(b):
            return False
        if a < b:
            return True
        elif b < a:
            return False

    return len(lhs) < len(rhs)
JelleZijlstra commented 3 years ago

Mypy doesn't support this sort of conditional narrowing, where it has to understand that if a is of type A, b is also of type B. This is unfortunately unlikely to change.

muggenhor commented 3 years ago

Any way that I could annotate this in some way? To tell it that whatever type a has b has as well?

JelleZijlstra commented 3 years ago

The only way I can think of is to split off part of the body of the for loop into a function that takes a TypeVar("T", int, str) as an argument.

muggenhor commented 3 years ago

Unfortunately that doesn't work either, I'm just getting this instead:

test.py:31: error: Value of type variable "_TIS" of "_int_or_str_compare" cannot be "Union[str, int]"
test.py:31: error: Argument 2 to "_int_or_str_compare" has incompatible type "Union[str, int]"; expected "str"

That's with this added function that gets called after the last type comparison (type(a) != type(b)):

_TIS = TypeVar("_TIS", int, str)
def _int_or_str_compare(lhs: _TIS, rhs: str) -> bool: ...