Open Azureblade3808 opened 2 years ago
Is there any update on this? I just stumbled on this bug while generating a CST with my lexer/parser generator. In one case, PyStmt | list[PyStmt]
is not assignable to PyStmt | ImmutableList[PyStmt]
, while this definitely should be possible. (note that ImmutableList
is kind of like Sequence
in the standard library).
Bug Report
Starting from microsoft/pylance-release#2172.
Currently,
A
orB
seems not to be considered covariant with the union typeA | B
whenA
orB
comes as a type variable. Because of this, Mypy tends to give false positive error about variance settings in protocols.To Reproduce
Here are two sample files.
https://mypy-play.net/?mypy=latest&python=3.8&gist=2c061e7b00dbf6024dc4bf13cea7d896
```python from __future__ import annotations from typing import Protocol, TypeVar _T0 = TypeVar("_T0") _T0_co = TypeVar("_T0_co", covariant=True) _T1 = TypeVar("_T1") _T1_co = TypeVar("_T1_co", covariant=True) #%% class FP0(Protocol[_T0, _T1]): def __call__(self, arg0: _T0 | _T1, /) -> tuple[_T0, _T1]: ... def return_fp0_float_float() -> FP0[float, float]: def f(x: float) -> tuple[float, float]: x += 1 return (0, 0) return f def return_fp0_object_object() -> FP0[object, object]: # Dangerous cast, reported. return return_fp0_float_float() #%% class FP0A(Protocol[_T0_co, _T1_co]): def __call__(self, arg0: _T0_co | _T1_co, /) -> tuple[_T0_co, _T1_co]: ... def return_fp0a_float_float() -> FP0A[float, float]: def f(x: float) -> tuple[float, float]: x += 1 return (0, 0) return f def return_fp0a_object_object() -> FP0A[object, object]: # Dangerous cast, but not reported. return return_fp0a_float_float() fp0a_object_object: FP0A[object, object] = return_fp0a_object_object() _ = fp0a_object_object(object()) ```https://mypy-play.net/?mypy=latest&python=3.8&gist=686538007d253a9241dfeaf4c7a3598d
```python from __future__ import annotations from typing import Protocol, TypeVar _T0 = TypeVar("_T0") _T0_contra = TypeVar("_T0_contra", contravariant=True) _T1 = TypeVar("_T1") _T1_contra = TypeVar("_T1_contra", contravariant=True) #%% class FP1(Protocol[_T0, _T1]): def __call__(self, arg0: _T0, arg1: _T1, /) -> _T0 | _T1: ... def return_fp1_object_object() -> FP1[object, object]: def f(a: object, b: object) -> object: return object() return f def return_fp1_float_float() -> FP1[float, float]: # Dangerous cast, reported. return return_fp1_object_object() #%% class FP1A(Protocol[_T0_contra, _T1_contra]): def __call__(self, arg0: _T0_contra, arg1: _T1_contra, /) -> _T0_contra | _T1_contra: ... def return_fp1a_object_object() -> FP1A[object, object]: def f(a: object, b: object) -> object: return object() return f def return_fp1a_float_float() -> FP1A[float, float]: # Dangerous cast, but not reported. return return_fp1a_object_object() fp1a_float_float: FP1A[float, float] = return_fp1a_float_float() result: float = fp1a_float_float(1.0, 2.0) result += 1 ```FP0
andFP1
are in my expected forms, but get reported with errors because of variance settings.FP0A
andFP1A
are the revised versions based on the errors Mypy gives forFP0
andFP1
.Running either file would lead to a crash on the last line respectively, because of the unreported dangerous casts which are marked with comments.
Expected Behavior
Variance settings in
FP0
andFP1
are right, and those inFP0A
andFP1A
are wrong._T0
and_T1
are both covariant with the argument type_T0 | _T1
inFP0
, so they should be invariant when they also appear in the return type._T0
and_T1
are both covariant with the return type_T0 | _T1
inFP1
, so they should be invariant when they also appear in the argument types.Actual Behavior
Variance settings in
FP0
andFP1
are considered wrong, and those inFP0A
andFP1A
are considered right.Your Environment
mypy.ini
(and other config files): N/A