-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Support self-types containing ParamSpec #15903
Changes from all commits
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 |
---|---|---|
|
@@ -303,7 +303,7 @@ class B(A): pass | |
return cast(F, func) | ||
self_param_type = get_proper_type(func.arg_types[0]) | ||
|
||
variables: Sequence[TypeVarLikeType] = [] | ||
variables: Sequence[TypeVarLikeType] | ||
if func.variables and supported_self_type(self_param_type): | ||
from mypy.infer import infer_type_arguments | ||
|
||
|
@@ -312,46 +312,40 @@ class B(A): pass | |
original_type = erase_to_bound(self_param_type) | ||
original_type = get_proper_type(original_type) | ||
|
||
all_ids = func.type_var_ids() | ||
# Find which of method type variables appear in the type of "self". | ||
self_ids = {tv.id for tv in get_all_type_vars(self_param_type)} | ||
self_vars = [tv for tv in func.variables if tv.id in self_ids] | ||
|
||
# Solve for these type arguments using the actual class or instance type. | ||
typeargs = infer_type_arguments( | ||
func.variables, self_param_type, original_type, is_supertype=True | ||
self_vars, self_param_type, original_type, is_supertype=True | ||
) | ||
if ( | ||
is_classmethod | ||
# TODO: why do we need the extra guards here? | ||
and any(isinstance(get_proper_type(t), UninhabitedType) for t in typeargs) | ||
and isinstance(original_type, (Instance, TypeVarType, TupleType)) | ||
): | ||
# In case we call a classmethod through an instance x, fallback to type(x) | ||
# In case we call a classmethod through an instance x, fallback to type(x). | ||
typeargs = infer_type_arguments( | ||
func.variables, self_param_type, TypeType(original_type), is_supertype=True | ||
self_vars, self_param_type, TypeType(original_type), is_supertype=True | ||
) | ||
|
||
ids = [tid for tid in all_ids if any(tid == t.id for t in get_type_vars(self_param_type))] | ||
|
||
# Technically, some constrains might be unsolvable, make them <nothing>. | ||
# Update the method signature with the solutions found. | ||
# Technically, some constraints might be unsolvable, make them <nothing>. | ||
to_apply = [t if t is not None else UninhabitedType() for t in typeargs] | ||
|
||
def expand(target: Type) -> Type: | ||
return expand_type(target, {id: to_apply[all_ids.index(id)] for id in ids}) | ||
|
||
arg_types = [expand(x) for x in func.arg_types[1:]] | ||
ret_type = expand(func.ret_type) | ||
variables = [v for v in func.variables if v.id not in ids] | ||
func = expand_type(func, {tv.id: arg for tv, arg in zip(self_vars, to_apply)}) | ||
variables = [v for v in func.variables if v not in self_vars] | ||
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. Seeing this whole "expand a type with certain type vars then remove those certain type vars" dance feels weird. Is there a reason 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, it is time for type theory lesson. There are two kinds of things that people usually call "generic types": type constructors, and polymorphic types. This terminology is still vague (and I am not sure it is standard), so it is better to explain on examples. For example, def foo(x: T) -> T: ... assuming (An implementation note: In an ideal world If we are dealing with type constructor (in particular with generic type aliases), we never need to "remove certain type vars" (what would it even mean). The only thing we should do is to formally substitute type parameters expected by constructor with type arguments we got. For example, with Now when we talk about type inference this usually involves several steps:
So what you are seeing here is some ad-hoc version of this logic. Or course it all can be refactored to reduce code duplication but FWIW I don't think it is a big deal in this case, since the amount of code duplication is really small here. 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. And one more clarifying comments on why we don't have the validation step (3a) in the case of type constructor: this is because validation can be complete during semantic analysis, see |
||
else: | ||
arg_types = func.arg_types[1:] | ||
ret_type = func.ret_type | ||
variables = func.variables | ||
|
||
original_type = get_proper_type(original_type) | ||
if isinstance(original_type, CallableType) and original_type.is_type_obj(): | ||
original_type = TypeType.make_normalized(original_type.ret_type) | ||
res = func.copy_modified( | ||
arg_types=arg_types, | ||
arg_types=func.arg_types[1:], | ||
arg_kinds=func.arg_kinds[1:], | ||
arg_names=func.arg_names[1:], | ||
variables=variables, | ||
ret_type=ret_type, | ||
bound_args=[original_type], | ||
) | ||
return cast(F, res) | ||
|
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.
Just to check my intuition here: I presume the speedup is decreasing the amount of typevars inferred here? (And a couple lines above)
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.
Yes, decreasing number of type vars to solve for will speed things up slightly, but the main speed-up is from not using a nested function in relatively hot code. (Which especially affects mypyc, that anecdotally emits inefficient code for nested functions).