Open beauxq opened 2 years ago
The error message could potentially be better, but mypy does not have a way of knowing that random.choice
will only ever return 1 or 2. The type signature only promises that the return value is an int.
@hauntsaninja, I would think that mypy could handle this through bidirectional type inference.
For comparison, pyright is able to get it correct. It accepts a = choice([1, 2])
and rejects a = choice([1, 3])
.
Okay, I'll have to think about it more, but how we infer literals is not fully obvious to me.
For example, using something very close to the definition of sum
from typeshed (differences are a) ignore the return value of Literal[0]
i.e. pretend that sum asserts its input is non-empty, b) use SupportsLenAndGetItem
instead of Iterable
to further the analogy, c) drop extra overloads):
from typing import TypeVar, Literal, Union, Protocol, Any
_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)
class SupportsLenAndGetItem(Protocol[_T_co]):
def __len__(self) -> int: ...
def __getitem__(self, __k: int) -> _T_co: ...
class _SupportsSum(Protocol):
def __add__(self, __x: Any) -> Any: ...
_SumT = TypeVar("_SumT", bound=_SupportsSum)
def choice(seq: SupportsLenAndGetItem[_T]) -> _T: ...
def sum(seq: SupportsLenAndGetItem[_SumT]) -> _SumT: ...
b: Union[Literal[1], Literal[2]]
b = sum((1, 2)) # pyright doesn't complain, but b is now 3
Yes, inferring literal types for typevars is tricky. But in the OP's example, we accept the choice([1, 2])
case and reject the choice((1, 2))
case, and I don't think that makes sense. Either we accept both (using inference rules similar to pyright's) or we reject both (to avoid binding typevars to literals).
Oh oops, I missed that part of OP's question. Yes, mypy should definitely at least be self-consistent (probably one of few times where list's invariance results in fewer errors :-) ).
The tuple should be safer than the list, since tuple is immutable.
In this example, the choice
for the list should give an error, but the choice
on the tuple should not.
from typing import List, Literal, Tuple, Union
from random import choice
lists: List[List[int]] = []
tuples: List[Tuple[int, ...]] = []
def sneak_in_3() -> None:
lists[0][0] = 3
# tuples[0][0] = 3 # can't sneak 3 into tuple
def main() -> None:
x = [1, 2]
y = (1, 2)
lists.append(x)
tuples.append(y)
sneak_in_3()
a: Union[Literal[1], Literal[2]]
a = choice(x)
a = choice(y)
print(a)
Bug Report
When a function accepts a sequence of union of literals, a tuple of those literals is seen as an error.
To Reproduce
Expected Behavior
no error
Actual Behavior
Argument 1 to "choice" has incompatible type "Tuple[int, int]"; expected "Sequence[Union[Literal[1], Literal[2]]]" [arg-type]
Your Environment
mypy.ini
(and other config files):