10
10
from mypy .maptype import map_instance_to_supertype
11
11
from mypy import nodes
12
12
import mypy .subtypes
13
+ from mypy .erasetype import erase_typevars
13
14
14
15
15
16
SUBTYPE_OF = 0 # type: int
@@ -119,9 +120,82 @@ def infer_constraints(template: Type, actual: Type,
119
120
The constraints are represented as Constraint objects.
120
121
"""
121
122
123
+ # If the template is simply a type variable, emit a Constraint directly.
124
+ # We need to handle this case before handling Unions for two reasons:
125
+ # 1. "T <: Union[U1, U2]" is not equivalent to "T <: U1 or T <: U2",
126
+ # because T can itself be a union (notably, Union[U1, U2] itself).
127
+ # 2. "T :> Union[U1, U2]" is logically equivalent to "T :> U1 and
128
+ # T :> U2", but they are not equivalent to the constraint solver,
129
+ # which never introduces new Union types (it uses join() instead).
130
+ if isinstance (template , TypeVarType ):
131
+ return [Constraint (template .id , direction , actual )]
132
+
133
+ # Now handle the case of either template or actual being a Union.
134
+ # For a Union to be a subtype of another type, every item of the Union
135
+ # must be a subtype of that type, so concatenate the constraints.
136
+ if direction == SUBTYPE_OF and isinstance (template , UnionType ):
137
+ res = []
138
+ for t_item in template .items :
139
+ res .extend (infer_constraints (t_item , actual , direction ))
140
+ return res
141
+ if direction == SUPERTYPE_OF and isinstance (actual , UnionType ):
142
+ res = []
143
+ for a_item in actual .items :
144
+ res .extend (infer_constraints (template , a_item , direction ))
145
+ return res
146
+
147
+ # Now the potential subtype is known not to be a Union or a type
148
+ # variable that we are solving for. In that case, for a Union to
149
+ # be a supertype of the potential subtype, some item of the Union
150
+ # must be a supertype of it.
151
+ if direction == SUBTYPE_OF and isinstance (actual , UnionType ):
152
+ return any_constraints (
153
+ [infer_constraints_if_possible (template , a_item , direction )
154
+ for a_item in actual .items ])
155
+ if direction == SUPERTYPE_OF and isinstance (template , UnionType ):
156
+ return any_constraints (
157
+ [infer_constraints_if_possible (t_item , actual , direction )
158
+ for t_item in template .items ])
159
+
160
+ # Remaining cases are handled by ConstraintBuilderVisitor.
122
161
return template .accept (ConstraintBuilderVisitor (actual , direction ))
123
162
124
163
164
+ def infer_constraints_if_possible (template : Type , actual : Type ,
165
+ direction : int ) -> Optional [List [Constraint ]]:
166
+ """Like infer_constraints, but return None if the input relation is
167
+ known to be unsatisfiable, for example if template=List[T] and actual=int.
168
+ (In this case infer_constraints would return [], just like it would for
169
+ an automatically satisfied relation like template=List[T] and actual=object.)
170
+ """
171
+ if (direction == SUBTYPE_OF and
172
+ not mypy .subtypes .is_subtype (erase_typevars (template ), actual )):
173
+ return None
174
+ if (direction == SUPERTYPE_OF and
175
+ not mypy .subtypes .is_subtype (actual , erase_typevars (template ))):
176
+ return None
177
+ return infer_constraints (template , actual , direction )
178
+
179
+
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.
184
+ """
185
+ valid_options = [option for option in options if option is not None ]
186
+ if len (valid_options ) == 1 :
187
+ return valid_options [0 ]
188
+ # Otherwise, there are either no valid options or multiple valid options.
189
+ # Give up and deduce nothing.
190
+ return []
191
+
192
+ # TODO: In the latter case, it could happen that every valid
193
+ # option requires the same constraint on the same variable. Then
194
+ # we could include that that constraint in the result. Or more
195
+ # generally, if a given (variable, direction) pair appears in
196
+ # every option, combine the bounds with meet/join.
197
+
198
+
125
199
class ConstraintBuilderVisitor (TypeVisitor [List [Constraint ]]):
126
200
"""Visitor class for inferring type constraints."""
127
201
@@ -163,7 +237,8 @@ def visit_partial_type(self, template: PartialType) -> List[Constraint]:
163
237
# Non-trivial leaf type
164
238
165
239
def visit_type_var (self , template : TypeVarType ) -> List [Constraint ]:
166
- return [Constraint (template .id , self .direction , self .actual )]
240
+ assert False , ("Unexpected TypeVarType in ConstraintBuilderVisitor"
241
+ " (should have been handled in infer_constraints)" )
167
242
168
243
# Non-leaf types
169
244
@@ -177,23 +252,23 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
177
252
mapped = map_instance_to_supertype (template , instance .type )
178
253
for i in range (len (instance .args )):
179
254
# The constraints for generic type parameters are
180
- # invariant. Include the default constraint and its
181
- # negation to achieve the effect.
182
- cb = infer_constraints (mapped . args [ i ], instance . args [ i ],
183
- self .direction )
184
- res .extend (cb )
185
- res . extend ( negate_constraints ( cb ))
255
+ # invariant. Include constraints from both directions
256
+ # to achieve the effect.
257
+ res . extend ( infer_constraints (
258
+ mapped . args [ i ], instance . args [ i ], self .direction ) )
259
+ res .extend (infer_constraints (
260
+ mapped . args [ i ], instance . args [ i ], neg_op ( self . direction ) ))
186
261
return res
187
262
elif (self .direction == SUPERTYPE_OF and
188
263
instance .type .has_base (template .type .fullname ())):
189
264
mapped = map_instance_to_supertype (instance , template .type )
190
265
for j in range (len (template .args )):
191
266
# The constraints for generic type parameters are
192
267
# invariant.
193
- cb = infer_constraints (template . args [ j ], mapped . args [ j ],
194
- self .direction )
195
- res .extend (cb )
196
- res . extend ( negate_constraints ( cb ))
268
+ res . extend ( infer_constraints (
269
+ template . args [ j ], mapped . args [ j ], self .direction ) )
270
+ res .extend (infer_constraints (
271
+ template . args [ j ], mapped . args [ j ], neg_op ( self . direction ) ))
197
272
return res
198
273
if isinstance (actual , AnyType ):
199
274
# IDEA: Include both ways, i.e. add negation as well?
@@ -222,8 +297,8 @@ def visit_callable_type(self, template: CallableType) -> List[Constraint]:
222
297
if not template .is_ellipsis_args :
223
298
# The lengths should match, but don't crash (it will error elsewhere).
224
299
for t , a in zip (template .arg_types , cactual .arg_types ):
225
- # Negate constraints due function argument type contravariance.
226
- res .extend (negate_constraints ( infer_constraints (t , a , self .direction )))
300
+ # Negate direction due to function argument type contravariance.
301
+ res .extend (infer_constraints (t , a , neg_op ( self .direction )))
227
302
res .extend (infer_constraints (template .ret_type , cactual .ret_type ,
228
303
self .direction ))
229
304
return res
@@ -264,10 +339,8 @@ def visit_tuple_type(self, template: TupleType) -> List[Constraint]:
264
339
return []
265
340
266
341
def visit_union_type (self , template : UnionType ) -> List [Constraint ]:
267
- res = [] # type: List[Constraint]
268
- for item in template .items :
269
- res .extend (infer_constraints (item , self .actual , self .direction ))
270
- return res
342
+ assert False , ("Unexpected UnionType in ConstraintBuilderVisitor"
343
+ " (should have been handled in infer_constraints)" )
271
344
272
345
def infer_against_any (self , types : List [Type ]) -> List [Constraint ]:
273
346
res = [] # type: List[Constraint]
@@ -282,13 +355,6 @@ def visit_overloaded(self, type: Overloaded) -> List[Constraint]:
282
355
return res
283
356
284
357
285
- def negate_constraints (constraints : List [Constraint ]) -> List [Constraint ]:
286
- res = [] # type: List[Constraint]
287
- for c in constraints :
288
- res .append (Constraint (c .type_var , neg_op (c .op ), c .target ))
289
- return res
290
-
291
-
292
358
def neg_op (op : int ) -> int :
293
359
"""Map SubtypeOf to SupertypeOf and vice versa."""
294
360
0 commit comments