Skip to content

Commit b6ef694

Browse files
committed
Fix special cases where type context is a union
This helps with the new definition of `dict.get` in typeshed. The fix feels a little ad-hoc, but it seems to fix a real-world issue and doesn't seem to break anything. Fixes #2703.
1 parent 234297c commit b6ef694

2 files changed

Lines changed: 93 additions & 8 deletions

File tree

‎mypy/constraints.py‎

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
from typing import Iterable, List, Optional
44

5+
from mypy import experiments
56
from mypy.types import (
67
CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType,
78
Instance, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType,
8-
DeletedType, UninhabitedType, TypeType, TypeVarId, is_named_instance
9+
DeletedType, UninhabitedType, TypeType, TypeVarId, TypeQuery, ALL_TYPES_STRATEGY,
10+
is_named_instance
911
)
1012
from mypy.maptype import map_instance_to_supertype
1113
from mypy import nodes
@@ -149,13 +151,23 @@ def infer_constraints(template: Type, actual: Type,
149151
# be a supertype of the potential subtype, some item of the Union
150152
# must be a supertype of it.
151153
if direction == SUBTYPE_OF and isinstance(actual, UnionType):
154+
# If some of items is not a complete type, disregard that.
155+
items = simplify_away_incomplete_types(actual.items)
156+
# We infer constraints eagerly -- try to find constraints for a type
157+
# variable if possible. This seems to help with some real-world
158+
# use cases.
152159
return any_constraints(
153160
[infer_constraints_if_possible(template, a_item, direction)
154-
for a_item in actual.items])
161+
for a_item in items],
162+
eager=True)
155163
if direction == SUPERTYPE_OF and isinstance(template, UnionType):
164+
# When the template is a union, we are okay with leaving some
165+
# type variables indeterminate. This helps with some special
166+
# cases, though this isn't very principled.
156167
return any_constraints(
157168
[infer_constraints_if_possible(t_item, actual, direction)
158-
for t_item in template.items])
169+
for t_item in template.items],
170+
eager=False)
159171

160172
# Remaining cases are handled by ConstraintBuilderVisitor.
161173
return template.accept(ConstraintBuilderVisitor(actual, direction))
@@ -177,12 +189,18 @@ def infer_constraints_if_possible(template: Type, actual: Type,
177189
return infer_constraints(template, actual, direction)
178190

179191

180-
def any_constraints(options: List[Optional[List[Constraint]]]) -> List[Constraint]:
181-
"""Deduce what we can from a collection of constraint lists given that
182-
at least one of the lists must be satisfied. A None element in the
183-
list of options represents an unsatisfiable constraint and is ignored.
192+
def any_constraints(options: List[Optional[List[Constraint]]], eager: bool) -> List[Constraint]:
193+
"""Deduce what we can from a collection of constraint lists.
194+
195+
It's a given that at least one of the lists must be satisfied. A
196+
None element in the list of options represents an unsatisfiable
197+
constraint and is ignored. Ignore empty constraint lists if eager
198+
is true -- they are always trivially satisfiable.
184199
"""
185-
valid_options = [option for option in options if option is not None]
200+
if eager:
201+
valid_options = [option for option in options if option]
202+
else:
203+
valid_options = [option for option in options if option is not None]
186204
if len(valid_options) == 1:
187205
return valid_options[0]
188206
# Otherwise, there are either no valid options or multiple valid options.
@@ -196,6 +214,34 @@ def any_constraints(options: List[Optional[List[Constraint]]]) -> List[Constrain
196214
# every option, combine the bounds with meet/join.
197215

198216

217+
def simplify_away_incomplete_types(types: List[Type]) -> List[Type]:
218+
complete = [typ for typ in types if is_complete_type(typ)]
219+
if complete:
220+
return complete
221+
else:
222+
return types
223+
224+
225+
def is_complete_type(typ: Type) -> bool:
226+
"""Is a type complete?
227+
228+
A complete doesn't have uninhabited type components or (when not in strict
229+
optional mode) None components.
230+
"""
231+
return typ.accept(CompleteTypeVisitor())
232+
233+
234+
class CompleteTypeVisitor(TypeQuery):
235+
def __init__(self) -> None:
236+
super().__init__(default=True, strategy=ALL_TYPES_STRATEGY)
237+
238+
def visit_none_type(self, t: NoneTyp) -> bool:
239+
return experiments.STRICT_OPTIONAL
240+
241+
def visit_uninhabited_type(self, t: UninhabitedType) -> bool:
242+
return False
243+
244+
199245
class ConstraintBuilderVisitor(TypeVisitor[List[Constraint]]):
200246
"""Visitor class for inferring type constraints."""
201247

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,3 +826,42 @@ class A(Generic[T]):
826826
pass
827827
reveal_type(A()) # E: Revealed type is '__main__.A[builtins.None]'
828828
b = reveal_type(A()) # type: A[int] # E: Revealed type is '__main__.A[builtins.int]'
829+
830+
[case testUnionWithGenericTypeItemContext]
831+
from typing import TypeVar, Union, List
832+
833+
T = TypeVar('T')
834+
835+
def f(x: Union[T, List[int]]) -> Union[T, List[int]]: pass
836+
reveal_type(f(1)) # E: Revealed type is 'Union[builtins.int*, builtins.list[builtins.int]]'
837+
reveal_type(f([])) # E: Revealed type is 'builtins.list[builtins.int]'
838+
reveal_type(f(None)) # E: Revealed type is 'builtins.list[builtins.int]'
839+
[builtins fixtures/list.pyi]
840+
841+
[case testUnionWithGenericTypeItemContextAndStrictOptional]
842+
# flags: --strict-optional
843+
from typing import TypeVar, Union, List
844+
845+
T = TypeVar('T')
846+
847+
def f(x: Union[T, List[int]]) -> Union[T, List[int]]: pass
848+
reveal_type(f(1)) # E: Revealed type is 'Union[builtins.int*, builtins.list[builtins.int]]'
849+
reveal_type(f([])) # E: Revealed type is 'builtins.list[builtins.int]'
850+
reveal_type(f(None)) # E: Revealed type is 'Union[builtins.None, builtins.list[builtins.int]]'
851+
[builtins fixtures/list.pyi]
852+
853+
[case testUnionWithGenericTypeItemContextInMethod]
854+
from typing import TypeVar, Union, List, Generic
855+
856+
T = TypeVar('T')
857+
S = TypeVar('S')
858+
859+
class C(Generic[T]):
860+
def f(self, x: Union[T, S]) -> Union[T, S]: pass
861+
862+
c = C[List[int]]()
863+
reveal_type(c.f('')) # E: Revealed type is 'Union[builtins.list[builtins.int], builtins.str*]'
864+
reveal_type(c.f([1])) # E: Revealed type is 'builtins.list[builtins.int]'
865+
reveal_type(c.f([])) # E: Revealed type is 'builtins.list[builtins.int]'
866+
reveal_type(c.f(None)) # E: Revealed type is 'builtins.list[builtins.int]'
867+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)