|
19 | 19 | from mypy.checkmember import analyze_member_access, has_operator |
20 | 20 | from mypy.checkstrformat import StringFormatterChecker |
21 | 21 | from mypy.constant_fold import constant_fold_expr |
22 | | -from mypy.constraints import Constraint |
| 22 | +from mypy.constraints import Constraint, infer_constraints_for_callable |
23 | 23 | from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars |
24 | 24 | from mypy.errors import ErrorInfo, ErrorWatcher, report_internal_error |
25 | 25 | from mypy.expandtype import ( |
|
121 | 121 | Plugin, |
122 | 122 | ) |
123 | 123 | from mypy.semanal_enum import ENUM_BASES |
| 124 | +from mypy.solve import solve_constraints |
124 | 125 | from mypy.state import state |
125 | 126 | from mypy.subtypes import ( |
126 | 127 | common_type, |
@@ -1796,6 +1797,21 @@ def check_callable_call( |
1796 | 1797 | initial_callee = callee |
1797 | 1798 | initial_formal_to_actual = None |
1798 | 1799 | constraints: list[Constraint] = [] |
| 1800 | + # The general flow of type inference is the following: |
| 1801 | + # * Infer constraints from outer (return) context and solve them. |
| 1802 | + # * Apply the inferred type arguments to the callable. |
| 1803 | + # * Infer argument types in this partial context. |
| 1804 | + # * Infer constraints from inner (argument) context (pass 1, non-callable arguments). |
| 1805 | + # * Solve the combined constraint set (outer + partial inner). |
| 1806 | + # * Apply the new (more precise) inferred type arguments to initial callable. |
| 1807 | + # * Re-infer argument types in more precise type context |
| 1808 | + # (including pass 2, callable arguments). |
| 1809 | + # * Re-infer constraints from inner context (now from all arguments). |
| 1810 | + # * Solve the final constraint set combining outer context and precise inner context. |
| 1811 | + # Apply the inferred final type arguments to the initial generic callable. |
| 1812 | + # * If we cannot solve (likely an error), we repeat the process starting from outer |
| 1813 | + # context. Now progressively applying inferred type arguments after each step, |
| 1814 | + # to give consistent error messages. |
1799 | 1815 | if callee.is_generic(): |
1800 | 1816 | initial_formal_to_actual = map_actuals_to_formals( |
1801 | 1817 | arg_kinds, |
@@ -2218,6 +2234,84 @@ def infer_function_type_arguments_using_context( |
2218 | 2234 | constraints, |
2219 | 2235 | ) |
2220 | 2236 |
|
| 2237 | + def infer_function_type_arguments_both_sides( |
| 2238 | + self, |
| 2239 | + initial_callee_type: CallableType, |
| 2240 | + ret_constraints: list[Constraint], |
| 2241 | + args: list[Expression], |
| 2242 | + pass1_arg_types: list[Type | None], |
| 2243 | + arg_pass_nums: list[int], |
| 2244 | + arg_kinds: list[ArgKind], |
| 2245 | + arg_names: Sequence[str | None] | None, |
| 2246 | + formal_to_actual: list[list[int]], |
| 2247 | + initial_formal_to_actual: list[list[int]], |
| 2248 | + need_refresh: bool, |
| 2249 | + context: Context, |
| 2250 | + ) -> CallableType | None: |
| 2251 | + """Infer type arguments of a generic callable by combining outer and inner contexts.""" |
| 2252 | + # Inline infer_function_type_arguments() to get access to argument-side constraints. |
| 2253 | + constraints = infer_constraints_for_callable( |
| 2254 | + initial_callee_type, |
| 2255 | + pass1_arg_types, |
| 2256 | + arg_kinds, |
| 2257 | + arg_names, |
| 2258 | + initial_formal_to_actual, |
| 2259 | + self.argument_infer_context(), |
| 2260 | + ) |
| 2261 | + constraints += ret_constraints |
| 2262 | + |
| 2263 | + type_vars = initial_callee_type.variables |
| 2264 | + type_vars_set = set(type_vars) |
| 2265 | + inferred_args, _ = solve_constraints( |
| 2266 | + type_vars, constraints, strict=self.chk.in_checked_function() |
| 2267 | + ) |
| 2268 | + |
| 2269 | + pass1_inferred_args = inferred_args |
| 2270 | + if 2 in arg_pass_nums: |
| 2271 | + # This is an adjusted version of logic in infer_function_type_arguments_pass2(). |
| 2272 | + # Here we proceed even if first pass inferred all type arguments, since |
| 2273 | + # second pass may infer narrower types in presence of wide outer context. |
| 2274 | + solid_inferred_args = list(inferred_args) |
| 2275 | + for i, arg in enumerate(inferred_args): |
| 2276 | + if not solid_inferred_type(arg, type_vars_set): |
| 2277 | + solid_inferred_args[i] = None |
| 2278 | + pass2_callee_type = self.apply_generic_arguments( |
| 2279 | + initial_callee_type, solid_inferred_args, context |
| 2280 | + ) |
| 2281 | + if need_refresh: |
| 2282 | + formal_to_actual = map_actuals_to_formals( |
| 2283 | + arg_kinds, |
| 2284 | + arg_names, |
| 2285 | + pass2_callee_type.arg_kinds, |
| 2286 | + pass2_callee_type.arg_names, |
| 2287 | + lambda a: self.accept(args[a]), |
| 2288 | + ) |
| 2289 | + |
| 2290 | + # (Re-)infer argument types in the more precise type context (for lambdas). |
| 2291 | + with self.msg.filter_errors(): |
| 2292 | + arg_types = self.infer_arg_types_in_context( |
| 2293 | + pass2_callee_type, args, arg_kinds, formal_to_actual |
| 2294 | + ) |
| 2295 | + inferred_args, _ = infer_function_type_arguments( |
| 2296 | + initial_callee_type, |
| 2297 | + arg_types, |
| 2298 | + arg_kinds, |
| 2299 | + arg_names, |
| 2300 | + initial_formal_to_actual, |
| 2301 | + context=self.argument_infer_context(), |
| 2302 | + extra_constraints=constraints, |
| 2303 | + ) |
| 2304 | + |
| 2305 | + # Only use two-side inference if it is fully successful, |
| 2306 | + # otherwise, fallback to return type context as a tiebreaker. |
| 2307 | + if all(solid_inferred_type(a, type_vars_set) for a in inferred_args): |
| 2308 | + return self.apply_inferred_arguments(initial_callee_type, inferred_args, context) |
| 2309 | + # Before giving up, check first pass result. In some cases this may help in situations |
| 2310 | + # where both passes together create a system too tricky for our solver. |
| 2311 | + if all(solid_inferred_type(a, type_vars_set) for a in pass1_inferred_args): |
| 2312 | + return self.apply_inferred_arguments(initial_callee_type, pass1_inferred_args, context) |
| 2313 | + return None |
| 2314 | + |
2221 | 2315 | def infer_function_type_arguments( |
2222 | 2316 | self, |
2223 | 2317 | callee_type: CallableType, |
@@ -2270,28 +2364,21 @@ def infer_function_type_arguments( |
2270 | 2364 | # recursive types (because values can never be recursive). |
2271 | 2365 | and not type_state.infer_unions |
2272 | 2366 | ): |
2273 | | - inferred_args, _ = infer_function_type_arguments( |
| 2367 | + inferred_callee = self.infer_function_type_arguments_both_sides( |
2274 | 2368 | initial_callee_type, |
| 2369 | + ret_constraints, |
| 2370 | + args, |
2275 | 2371 | pass1_args, |
| 2372 | + arg_pass_nums, |
2276 | 2373 | arg_kinds, |
2277 | 2374 | arg_names, |
| 2375 | + formal_to_actual, |
2278 | 2376 | initial_formal_to_actual, |
2279 | | - context=self.argument_infer_context(), |
2280 | | - strict=self.chk.in_checked_function(), |
2281 | | - extra_constraints=ret_constraints, |
| 2377 | + need_refresh, |
| 2378 | + context, |
2282 | 2379 | ) |
2283 | | - # Only use two-side inference if it is fully successful, |
2284 | | - # otherwise, fallback to return type context as a tiebreaker. |
2285 | | - if not any( |
2286 | | - a is None |
2287 | | - or isinstance(get_proper_type(a), UninhabitedType) |
2288 | | - or has_erased_component(a) |
2289 | | - or set(get_type_vars(a)) & set(callee_type.variables) |
2290 | | - for a in inferred_args |
2291 | | - ): |
2292 | | - return self.apply_inferred_arguments( |
2293 | | - initial_callee_type, inferred_args, context |
2294 | | - ) |
| 2380 | + if inferred_callee is not None: |
| 2381 | + return inferred_callee |
2295 | 2382 |
|
2296 | 2383 | if not callee_type.is_generic(): |
2297 | 2384 | # Fast path: return context inferred all type arguments. |
@@ -6904,6 +6991,20 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> bool: |
6904 | 6991 | return t.ambiguous |
6905 | 6992 |
|
6906 | 6993 |
|
| 6994 | +def solid_inferred_type(typ: Type | None, type_vars: set[TypeVarLikeType]) -> bool: |
| 6995 | + """Are we sure this is a good inferred type argument for a generic callable?""" |
| 6996 | + if typ is None: |
| 6997 | + return False |
| 6998 | + if isinstance(get_proper_type(typ), UninhabitedType): |
| 6999 | + return False |
| 7000 | + if has_erased_component(typ): |
| 7001 | + return False |
| 7002 | + # Make sure there are no type variable leaks in the type. |
| 7003 | + if set(get_type_vars(typ)) & type_vars: |
| 7004 | + return False |
| 7005 | + return True |
| 7006 | + |
| 7007 | + |
6907 | 7008 | def arg_approximate_similarity(actual: Type, formal: Type) -> bool: |
6908 | 7009 | """Return if caller argument (actual) is roughly compatible with signature arg (formal). |
6909 | 7010 |
|
|
0 commit comments