Skip to content

Commit 5680f5c

Browse files
authored
Don't simplify out ErasedType from unions during type inference (python#8095)
Fixes python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy