Skip to content

Commit 5680f5c

Browse files
authored
Don't simplify out ErasedType from unions during type inference (#8095)
Fixes #8093 The fix is kind of simple, we really need to keep all `ErasedType`s intact to preserve the existing type inference logic, but it is also long because I need to thread the flag down like dozen function calls. The issue itself btw highlight how fragile is current type inference logic (note that `expand_type()` implicitly calls `make_simplified_union()` too, but I think that one doesn't need to be updated because it shouldn't be called with any erased components).
1 parent 130663b commit 5680f5c

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

mypy/constraints.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ def _infer_constraints(template: Type, actual: Type,
109109
actual = get_proper_type(actual)
110110

111111
# Type inference shouldn't be affected by whether union types have been simplified.
112+
# We however keep any ErasedType items, so that the caller will see it when using
113+
# checkexpr.has_erased_component().
112114
if isinstance(template, UnionType):
113-
template = mypy.typeops.make_simplified_union(template.items)
115+
template = mypy.typeops.make_simplified_union(template.items, keep_erased=True)
114116
if isinstance(actual, UnionType):
115-
actual = mypy.typeops.make_simplified_union(actual.items)
117+
actual = mypy.typeops.make_simplified_union(actual.items, keep_erased=True)
116118

117119
# Ignore Any types from the type suggestion engine to avoid them
118120
# causing us to infer Any in situations where a better job could

mypy/subtypes.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,15 +1116,18 @@ def covers_at_runtime(item: Type, supertype: Type, ignore_promotions: bool) -> b
11161116
return False
11171117

11181118

1119-
def is_proper_subtype(left: Type, right: Type, *, ignore_promotions: bool = False,
1120-
erase_instances: bool = False) -> bool:
1119+
def is_proper_subtype(left: Type, right: Type, *,
1120+
ignore_promotions: bool = False,
1121+
erase_instances: bool = False,
1122+
keep_erased_types: bool = False) -> bool:
11211123
"""Is left a proper subtype of right?
11221124
11231125
For proper subtypes, there's no need to rely on compatibility due to
11241126
Any types. Every usable type is a proper subtype of itself.
11251127
11261128
If erase_instances is True, erase left instance *after* mapping it to supertype
1127-
(this is useful for runtime isinstance() checks).
1129+
(this is useful for runtime isinstance() checks). If keep_erased_types is True,
1130+
do not consider ErasedType a subtype of all types (used by type inference against unions).
11281131
"""
11291132
if TypeState.is_assumed_proper_subtype(left, right):
11301133
return True
@@ -1135,50 +1138,63 @@ def is_proper_subtype(left: Type, right: Type, *, ignore_promotions: bool = Fals
11351138
with pop_on_exit(TypeState._assuming_proper, left, right):
11361139
return _is_proper_subtype(left, right,
11371140
ignore_promotions=ignore_promotions,
1138-
erase_instances=erase_instances)
1141+
erase_instances=erase_instances,
1142+
keep_erased_types=keep_erased_types)
11391143
return _is_proper_subtype(left, right,
11401144
ignore_promotions=ignore_promotions,
1141-
erase_instances=erase_instances)
1145+
erase_instances=erase_instances,
1146+
keep_erased_types=keep_erased_types)
11421147

11431148

1144-
def _is_proper_subtype(left: Type, right: Type, *, ignore_promotions: bool = False,
1145-
erase_instances: bool = False) -> bool:
1149+
def _is_proper_subtype(left: Type, right: Type, *,
1150+
ignore_promotions: bool = False,
1151+
erase_instances: bool = False,
1152+
keep_erased_types: bool = False) -> bool:
11461153
orig_left = left
11471154
orig_right = right
11481155
left = get_proper_type(left)
11491156
right = get_proper_type(right)
11501157

11511158
if isinstance(right, UnionType) and not isinstance(left, UnionType):
1152-
return any([is_proper_subtype(orig_left, item, ignore_promotions=ignore_promotions,
1153-
erase_instances=erase_instances)
1159+
return any([is_proper_subtype(orig_left, item,
1160+
ignore_promotions=ignore_promotions,
1161+
erase_instances=erase_instances,
1162+
keep_erased_types=keep_erased_types)
11541163
for item in right.items])
11551164
return left.accept(ProperSubtypeVisitor(orig_right,
11561165
ignore_promotions=ignore_promotions,
1157-
erase_instances=erase_instances))
1166+
erase_instances=erase_instances,
1167+
keep_erased_types=keep_erased_types))
11581168

11591169

11601170
class ProperSubtypeVisitor(TypeVisitor[bool]):
11611171
def __init__(self, right: Type, *,
11621172
ignore_promotions: bool = False,
1163-
erase_instances: bool = False) -> None:
1173+
erase_instances: bool = False,
1174+
keep_erased_types: bool = False) -> None:
11641175
self.right = get_proper_type(right)
11651176
self.orig_right = right
11661177
self.ignore_promotions = ignore_promotions
11671178
self.erase_instances = erase_instances
1179+
self.keep_erased_types = keep_erased_types
11681180
self._subtype_kind = ProperSubtypeVisitor.build_subtype_kind(
11691181
ignore_promotions=ignore_promotions,
11701182
erase_instances=erase_instances,
1183+
keep_erased_types=keep_erased_types
11711184
)
11721185

11731186
@staticmethod
1174-
def build_subtype_kind(*, ignore_promotions: bool = False,
1175-
erase_instances: bool = False) -> SubtypeKind:
1176-
return True, ignore_promotions, erase_instances
1187+
def build_subtype_kind(*,
1188+
ignore_promotions: bool = False,
1189+
erase_instances: bool = False,
1190+
keep_erased_types: bool = False) -> SubtypeKind:
1191+
return True, ignore_promotions, erase_instances, keep_erased_types
11771192

11781193
def _is_proper_subtype(self, left: Type, right: Type) -> bool:
11791194
return is_proper_subtype(left, right,
11801195
ignore_promotions=self.ignore_promotions,
1181-
erase_instances=self.erase_instances)
1196+
erase_instances=self.erase_instances,
1197+
keep_erased_types=self.keep_erased_types)
11821198

11831199
def visit_unbound_type(self, left: UnboundType) -> bool:
11841200
# This can be called if there is a bad type annotation. The result probably
@@ -1201,6 +1217,9 @@ def visit_uninhabited_type(self, left: UninhabitedType) -> bool:
12011217
def visit_erased_type(self, left: ErasedType) -> bool:
12021218
# This may be encountered during type inference. The result probably doesn't
12031219
# matter much.
1220+
# TODO: it actually does matter, figure out more principled logic about this.
1221+
if self.keep_erased_types:
1222+
return False
12041223
return True
12051224

12061225
def visit_deleted_type(self, left: DeletedType) -> bool:

mypy/typeops.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ def callable_corresponding_argument(typ: CallableType,
312312

313313

314314
def make_simplified_union(items: Sequence[Type],
315-
line: int = -1, column: int = -1) -> ProperType:
315+
line: int = -1, column: int = -1,
316+
*, keep_erased: bool = False) -> ProperType:
316317
"""Build union type with redundant union items removed.
317318
318319
If only a single item remains, this may return a non-union type.
@@ -327,6 +328,8 @@ def make_simplified_union(items: Sequence[Type],
327328
328329
Note: This must NOT be used during semantic analysis, since TypeInfos may not
329330
be fully initialized.
331+
The keep_erased flag is used for type inference against union types
332+
containing type variables. If set to True, keep all ErasedType items.
330333
"""
331334
items = get_proper_types(items)
332335
while any(isinstance(typ, UnionType) for typ in items):
@@ -346,7 +349,7 @@ def make_simplified_union(items: Sequence[Type],
346349
# Keep track of the truishness info for deleted subtypes which can be relevant
347350
cbt = cbf = False
348351
for j, tj in enumerate(items):
349-
if i != j and is_proper_subtype(tj, ti):
352+
if i != j and is_proper_subtype(tj, ti, keep_erased_types=keep_erased):
350353
# We found a redundant item in the union.
351354
removed.add(j)
352355
cbt = cbt or tj.can_be_true

test-data/unit/check-inference.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,3 +2939,17 @@ X = Union[Cov[T], Inv[T]]
29392939
def f(x: X[T]) -> T: ...
29402940
x: Inv[int]
29412941
reveal_type(f(x)) # N: Revealed type is 'builtins.int*'
2942+
2943+
[case testOptionalTypeVarAgainstOptional]
2944+
# flags: --strict-optional
2945+
from typing import Optional, TypeVar, Iterable, Iterator, List
2946+
2947+
_T = TypeVar('_T')
2948+
2949+
def filter(__function: None, __iterable: Iterable[Optional[_T]]) -> List[_T]: ...
2950+
2951+
x: Optional[str]
2952+
2953+
y = filter(None, [x])
2954+
reveal_type(y) # N: Revealed type is 'builtins.list[builtins.str*]'
2955+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)