From 20deef94902846aa2a5262371390061585b673fa Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Sep 2023 21:38:04 +0100 Subject: [PATCH 1/3] Special-case type inference of empty collections --- mypy/solve.py | 14 ++++++++++++ mypy/subtypes.py | 7 ++++++ test-data/unit/check-inference-context.test | 11 ++-------- test-data/unit/check-inference.test | 24 +++++++++++++++++++++ test-data/unit/check-varargs.test | 22 ++++--------------- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/mypy/solve.py b/mypy/solve.py index 7cdf1c10c9b5..52e6549e98a6 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -239,6 +239,20 @@ def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None: top: Type | None = None candidate: Type | None = None + # Filter out previous results of failed inference, they will only spoil the current pass... + new_uppers = [] + for u in uppers: + pu = get_proper_type(u) + if not isinstance(pu, UninhabitedType) or not pu.ambiguous: + new_uppers.append(u) + uppers = new_uppers + + # ...unless this is the only information we have, then we just pass it on. + if not uppers and not lowers: + candidate = UninhabitedType() + candidate.ambiguous = True + return candidate + # Process each bound separately, and calculate the lower and upper # bounds based on constraints. Note that we assume that the constraint # targets do not have constraint references. diff --git a/mypy/subtypes.py b/mypy/subtypes.py index e8339a8c4d69..86049c3394bf 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -18,6 +18,7 @@ ARG_STAR2, CONTRAVARIANT, COVARIANT, + INVARIANT, Decorator, FuncBase, OverloadedFuncDef, @@ -342,6 +343,12 @@ def _is_subtype( def check_type_parameter( left: Type, right: Type, variance: int, proper_subtype: bool, subtype_context: SubtypeContext ) -> bool: + # It is safe to consider empty collection literals and similar as covariant, since + # such type can't be stored in a variable, see checker.is_valid_inferred_type(). + if variance == INVARIANT: + p_left = get_proper_type(left) + if isinstance(p_left, UninhabitedType) and p_left.ambiguous: + variance = COVARIANT if variance == COVARIANT: if proper_subtype: return is_proper_subtype(left, right, subtype_context=subtype_context) diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 169fee65f127..773a9ffd8274 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -1321,11 +1321,7 @@ from typing import List, TypeVar T = TypeVar('T', bound=int) def f(x: List[T]) -> List[T]: ... -# TODO: improve error message for such cases, see #3283 and #5706 -y: List[str] = f([]) \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[str]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant +y: List[str] = f([]) [builtins fixtures/list.pyi] [case testWideOuterContextNoArgs] @@ -1342,10 +1338,7 @@ from typing import TypeVar, Optional, List T = TypeVar('T', bound=int) def f(x: Optional[T] = None) -> List[T]: ... -y: List[str] = f() \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[str]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant +y: List[str] = f() [builtins fixtures/list.pyi] [case testUseCovariantGenericOuterContext] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index f9a4d58c74af..caa44cb40ad4 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3686,3 +3686,27 @@ def g(*args: str) -> None: pass reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \ # E: Argument 1 to "f" has incompatible type "Callable[[VarArg(str)], None]"; expected "Call[Never]" [builtins fixtures/list.pyi] + +[case testInferenceWorksWithEmptyCollectionsNested] +from typing import List, TypeVar, NoReturn +T = TypeVar('T') +def f(a: List[T], b: List[T]) -> T: pass +x = ["yes"] +reveal_type(f(x, [])) # N: Revealed type is "builtins.str" +reveal_type(f(["yes"], [])) # N: Revealed type is "builtins.str" + +empty: List[NoReturn] +f(x, empty) # E: Cannot infer type argument 1 of "f" +f(["no"], empty) # E: Cannot infer type argument 1 of "f" +[builtins fixtures/list.pyi] + +[case testInferenceWorksWithEmptyCollectionsUnion] +from typing import Any, Dict, NoReturn, NoReturn, Union + +def foo() -> Union[Dict[str, Any], Dict[int, Any]]: + return {} + +empty: Dict[NoReturn, NoReturn] +def bar() -> Union[Dict[str, Any], Dict[int, Any]]: + return empty +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 41668e991972..22f529b03478 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -603,30 +603,16 @@ class B: pass if int(): a, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant - + # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") if int(): aa, a = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "A") if int(): ab, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant \ - # E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B" - + # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") if int(): - ao, ao = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[object]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant + ao, ao = G().f(*[a]) if int(): - aa, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") \ - # N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \ - # N: Consider using "Sequence" instead, which is covariant + aa, aa = G().f(*[a]) [builtins fixtures/list.pyi] [case testCallerTupleVarArgsAndGenericCalleeVarArg] From d99724644b99eb34022eaab8ff34698248e0a862 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Sep 2023 21:41:43 +0100 Subject: [PATCH 2/3] Correct test --- test-data/unit/check-varargs.test | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 22f529b03478..2495a883aa71 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -602,13 +602,11 @@ class A: pass class B: pass if int(): - a, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") + a, aa = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") if int(): aa, a = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "A") if int(): - ab, aa = G().f(*[a]) \ - # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") + ab, aa = G().f(*[a]) # E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B" if int(): ao, ao = G().f(*[a]) if int(): From 21fe4bb9f78ee14ba092ab9b31efd55b44a34270 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Sep 2023 21:58:02 +0100 Subject: [PATCH 3/3] Remove unused ignore --- mypy/test/testpep561.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 48d0658cd1e9..9d2628c1fa5f 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -131,7 +131,7 @@ def test_pep561(testcase: DataDrivenTestCase) -> None: steps = testcase.find_steps() if steps != [[]]: - steps = [[]] + steps # type: ignore[assignment] + steps = [[]] + steps for i, operations in enumerate(steps): perform_file_operations(operations)