Skip to content

Commit 4b66fa9

Browse files
authored
Special-case type inference of empty collections (#16122)
Fixes #230 Fixes #6463 I bet it fixes some other duplicates, I closed couple yesterday, but likely there are more. This may look a bit ad-hoc, but after some thinking this now starts to make sense to me for two reasons: * Unless I am missing something, this should be completely safe. Special-casing only applies to inferred types (i.e. empty collection literals etc). * Empty collections _are_ actually special. Even if we solve some classes of issues with more principled solutions (e.g. I want to re-work type inference against unions in near future), there will always be some corner cases involving empty collections. Similar issues keep coming, so I think it is a good idea to add this special-casing (especially taking into account how simple it is, and that it closer some "popular" issues).
1 parent 0c8b761 commit 4b66fa9

File tree

6 files changed

+52
-30
lines changed

6 files changed

+52
-30
lines changed

mypy/solve.py

+14
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,20 @@ def solve_one(lowers: Iterable[Type], uppers: Iterable[Type]) -> Type | None:
239239
top: Type | None = None
240240
candidate: Type | None = None
241241

242+
# Filter out previous results of failed inference, they will only spoil the current pass...
243+
new_uppers = []
244+
for u in uppers:
245+
pu = get_proper_type(u)
246+
if not isinstance(pu, UninhabitedType) or not pu.ambiguous:
247+
new_uppers.append(u)
248+
uppers = new_uppers
249+
250+
# ...unless this is the only information we have, then we just pass it on.
251+
if not uppers and not lowers:
252+
candidate = UninhabitedType()
253+
candidate.ambiguous = True
254+
return candidate
255+
242256
# Process each bound separately, and calculate the lower and upper
243257
# bounds based on constraints. Note that we assume that the constraint
244258
# targets do not have constraint references.

mypy/subtypes.py

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ARG_STAR2,
1919
CONTRAVARIANT,
2020
COVARIANT,
21+
INVARIANT,
2122
Decorator,
2223
FuncBase,
2324
OverloadedFuncDef,
@@ -342,6 +343,12 @@ def _is_subtype(
342343
def check_type_parameter(
343344
left: Type, right: Type, variance: int, proper_subtype: bool, subtype_context: SubtypeContext
344345
) -> bool:
346+
# It is safe to consider empty collection literals and similar as covariant, since
347+
# such type can't be stored in a variable, see checker.is_valid_inferred_type().
348+
if variance == INVARIANT:
349+
p_left = get_proper_type(left)
350+
if isinstance(p_left, UninhabitedType) and p_left.ambiguous:
351+
variance = COVARIANT
345352
if variance == COVARIANT:
346353
if proper_subtype:
347354
return is_proper_subtype(left, right, subtype_context=subtype_context)

mypy/test/testpep561.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def test_pep561(testcase: DataDrivenTestCase) -> None:
131131

132132
steps = testcase.find_steps()
133133
if steps != [[]]:
134-
steps = [[]] + steps # type: ignore[assignment]
134+
steps = [[]] + steps
135135

136136
for i, operations in enumerate(steps):
137137
perform_file_operations(operations)

test-data/unit/check-inference-context.test

+2-9
Original file line numberDiff line numberDiff line change
@@ -1321,11 +1321,7 @@ from typing import List, TypeVar
13211321
T = TypeVar('T', bound=int)
13221322
def f(x: List[T]) -> List[T]: ...
13231323

1324-
# TODO: improve error message for such cases, see #3283 and #5706
1325-
y: List[str] = f([]) \
1326-
# E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[str]") \
1327-
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
1328-
# N: Consider using "Sequence" instead, which is covariant
1324+
y: List[str] = f([])
13291325
[builtins fixtures/list.pyi]
13301326

13311327
[case testWideOuterContextNoArgs]
@@ -1342,10 +1338,7 @@ from typing import TypeVar, Optional, List
13421338
T = TypeVar('T', bound=int)
13431339
def f(x: Optional[T] = None) -> List[T]: ...
13441340

1345-
y: List[str] = f() \
1346-
# E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[str]") \
1347-
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
1348-
# N: Consider using "Sequence" instead, which is covariant
1341+
y: List[str] = f()
13491342
[builtins fixtures/list.pyi]
13501343

13511344
[case testUseCovariantGenericOuterContext]

test-data/unit/check-inference.test

+24
Original file line numberDiff line numberDiff line change
@@ -3686,3 +3686,27 @@ def g(*args: str) -> None: pass
36863686
reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \
36873687
# E: Argument 1 to "f" has incompatible type "Callable[[VarArg(str)], None]"; expected "Call[Never]"
36883688
[builtins fixtures/list.pyi]
3689+
3690+
[case testInferenceWorksWithEmptyCollectionsNested]
3691+
from typing import List, TypeVar, NoReturn
3692+
T = TypeVar('T')
3693+
def f(a: List[T], b: List[T]) -> T: pass
3694+
x = ["yes"]
3695+
reveal_type(f(x, [])) # N: Revealed type is "builtins.str"
3696+
reveal_type(f(["yes"], [])) # N: Revealed type is "builtins.str"
3697+
3698+
empty: List[NoReturn]
3699+
f(x, empty) # E: Cannot infer type argument 1 of "f"
3700+
f(["no"], empty) # E: Cannot infer type argument 1 of "f"
3701+
[builtins fixtures/list.pyi]
3702+
3703+
[case testInferenceWorksWithEmptyCollectionsUnion]
3704+
from typing import Any, Dict, NoReturn, NoReturn, Union
3705+
3706+
def foo() -> Union[Dict[str, Any], Dict[int, Any]]:
3707+
return {}
3708+
3709+
empty: Dict[NoReturn, NoReturn]
3710+
def bar() -> Union[Dict[str, Any], Dict[int, Any]]:
3711+
return empty
3712+
[builtins fixtures/dict.pyi]

test-data/unit/check-varargs.test

+4-20
Original file line numberDiff line numberDiff line change
@@ -602,31 +602,15 @@ class A: pass
602602
class B: pass
603603

604604
if int():
605-
a, aa = G().f(*[a]) \
606-
# E: Incompatible types in assignment (expression has type "List[A]", variable has type "A") \
607-
# E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") \
608-
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
609-
# N: Consider using "Sequence" instead, which is covariant
610-
605+
a, aa = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[A]", variable has type "A")
611606
if int():
612607
aa, a = G().f(*[a]) # E: Incompatible types in assignment (expression has type "List[Never]", variable has type "A")
613608
if int():
614-
ab, aa = G().f(*[a]) \
615-
# E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") \
616-
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
617-
# N: Consider using "Sequence" instead, which is covariant \
618-
# E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B"
619-
609+
ab, aa = G().f(*[a]) # E: Argument 1 to "f" of "G" has incompatible type "*List[A]"; expected "B"
620610
if int():
621-
ao, ao = G().f(*[a]) \
622-
# E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[object]") \
623-
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
624-
# N: Consider using "Sequence" instead, which is covariant
611+
ao, ao = G().f(*[a])
625612
if int():
626-
aa, aa = G().f(*[a]) \
627-
# E: Incompatible types in assignment (expression has type "List[Never]", variable has type "List[A]") \
628-
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
629-
# N: Consider using "Sequence" instead, which is covariant
613+
aa, aa = G().f(*[a])
630614
[builtins fixtures/list.pyi]
631615

632616
[case testCallerTupleVarArgsAndGenericCalleeVarArg]

0 commit comments

Comments
 (0)