-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve usage of outer context for inference #5699
Changes from 10 commits
decb5fd
a262692
fb841b7
d941c23
e18a521
517d7c2
fc603f4
826806c
17146f7
3441ed5
9a0af3c
31d1ca2
f3a28c2
3cd7d25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -823,20 +823,36 @@ def infer_function_type_arguments_using_context( | |
# valid results. | ||
erased_ctx = replace_meta_vars(ctx, ErasedType()) | ||
ret_type = callable.ret_type | ||
if isinstance(ret_type, TypeVarType): | ||
if ret_type.values or (not isinstance(ctx, Instance) or | ||
not ctx.args): | ||
# The return type is a type variable. If it has values, we can't easily restrict | ||
# type inference to conform to the valid values. If it's unrestricted, we could | ||
# infer a too general type for the type variable if we use context, and this could | ||
# result in confusing and spurious type errors elsewhere. | ||
# | ||
# Give up and just use function arguments for type inference. As an exception, | ||
# if the context is a generic instance type, actually use it as context, as | ||
# this *seems* to usually be the reasonable thing to do. | ||
# | ||
# See also github issues #462 and #360. | ||
ret_type = NoneTyp() | ||
if mypy.checker.is_optional(ret_type) and mypy.checker.is_optional(ctx): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that |
||
# If both the context and the return type are optional, unwrap the optional, | ||
# since in 99% cases this is what a user expects. In other words, we replace | ||
# Optional[T] <: Optional[int] | ||
# with | ||
# T <: int | ||
# while the former would infer T <: Optional[int]. | ||
ret_type = mypy.checker.remove_optional(ret_type) | ||
erased_ctx = mypy.checker.remove_optional(erased_ctx) | ||
# | ||
# TODO: Instead of this hack and the one below, we need to use outer and | ||
# inner contexts at the same time. This is however not easy because of two | ||
# reasons: | ||
# * We need to support constraints like [1 <: 2, 2 <: X], i.e. with variables | ||
# on both sides. (This is not too hard.) | ||
# * We need to update all the inference "infrastructure", so that all | ||
# variables in an expression are inferred at the same time. | ||
# (And this is hard, also we need to be careful with lambdas that require | ||
# two passes.) | ||
if isinstance(ret_type, TypeVarType) and not (isinstance(ctx, Instance) and ctx.args): | ||
# Another special case: the return type is a type variable. If it's unrestricted, | ||
# we could infer a too general type for the type variable if we use context, | ||
# and this could result in confusing and spurious type errors elsewhere. | ||
# | ||
# Give up and just use function arguments for type inference. As an exception, | ||
# if the context is a generic instance type, actually use it as context, as | ||
# this *seems* to usually be the reasonable thing to do. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this is only reasonable if the generic instance type is invariant in some type arg? I wonder if that would be a bit more "principled". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH I am not sure this is better. Currently, we ignore the return context for cases like def f(x: List[T]) -> T: ...
y: object = f([1, 2, 3]) # Incompatible argument type "List[int]", expected "List[object]" In other words the problem appears when the return type appears as a variable in invariant context in one of argument types. However, if you prefer to only use invariant ones, I can update this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I meant is that if the context is, say, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, this is actually exactly what I meant :-) |
||
# | ||
# See also github issues #462 and #360. | ||
return callable.copy_modified() | ||
args = infer_type_arguments(callable.type_var_ids(), ret_type, erased_ctx) | ||
# Only substitute non-Uninhabited and non-erased types. | ||
new_args = [] # type: List[Optional[Type]] | ||
|
@@ -845,7 +861,10 @@ def infer_function_type_arguments_using_context( | |
new_args.append(None) | ||
else: | ||
new_args.append(arg) | ||
return self.apply_generic_arguments(callable, new_args, error_context) | ||
# Don't show errors after we have only used the outer context for inference. | ||
# We will use argument context to infer more variables. | ||
return self.apply_generic_arguments(callable, new_args, error_context, | ||
skip_unsatisfied=True) | ||
|
||
def infer_function_type_arguments(self, callee_type: CallableType, | ||
args: List[Expression], | ||
|
@@ -1613,9 +1632,10 @@ def check_arg(caller_type: Type, original_caller_type: Type, caller_kind: int, | |
return False | ||
|
||
def apply_generic_arguments(self, callable: CallableType, types: Sequence[Optional[Type]], | ||
context: Context) -> CallableType: | ||
context: Context, skip_unsatisfied: bool = False) -> CallableType: | ||
"""Simple wrapper around mypy.applytype.apply_generic_arguments.""" | ||
return applytype.apply_generic_arguments(callable, types, self.msg, context) | ||
return applytype.apply_generic_arguments(callable, types, self.msg, context, | ||
skip_unsatisfied=skip_unsatisfied) | ||
|
||
def visit_member_expr(self, e: MemberExpr, is_lvalue: bool = False) -> Type: | ||
"""Visit member expression (of form e.id).""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this docstring correct? It seems to me that the type is not replaced at all if it's unsatisfied.