Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
[red-knot] Allow classes with __call__ method to be a subtype of Callable #17005
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
[red-knot] Allow classes with __call__ method to be a subtype of Callable #17005
Changes from all commits
34f6824
a343bb8
f2c748b
2309715
dbc5867
dc2a5b0
ba1e58f
eed5e1a
fa18160
8cd517c
04cb547
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
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.
We should not check the name of the parameter, it doesn't matter.
I think this method should return the removed parameter, because in the caller we should check if its annotated type (if any) is assignable from the instance type. That does matter. (We should add a test for the case where it isn't, e.g.
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.
How should that example be handled? It seems like this should be rejected before getting to this stage but i could be wrong.
pylance says "Type of parameter "self" must be a supertype of its class "A""
I guess since type of self is not yet dealt with, we cant do this.
I may need some more guidance on this anyway, what do you think of https://github.com/python/mypy/blob/b1be379f88e1e3735e04931f8253bbde4b387602/mypy/typeops.py#L366 and implementing a bind method (instead of into_callable_type_without_self_parameter which i think currently is not a very good function) to FunctionType that can take in the
Class
instance and returnSelf
orType
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.
Good call, thanks for pushing on whether this should be done in a better way. After looking at it, I think it should be done differently. We shouldn't need this function. Instead of using
class_member
to look up__call__
, we should just use.member()
on the instance type itself, and then call.signature()
on the resulting type. In the typical case the.member()
call will give us back aType::Callable(BoundMethod(_))
, andType::signature
already does the right thing for a bound method, including issuing a diagnostic in the case whereself
has a wrong type annotation.I do think we'll probably want to also issue a diagnostic at the definition site of a method with a bad
self
annotation, but that's really separate and should be handled separately. Notice that pyright issues diagnostics in both places (both the definition site of__call__
, and the site where it is "used"). We are just worrying about the latter in this PR.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.
into_callable_type here still give a GeneralCallableType with the self parameter, it seems like maybe BoundMethodType should handle removing self from the parameters of its function?
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.
No we should not be matching on the specific type of the symbol result like that and pulling out internal details of the type. Instead we should just call
.signature()
on the type (no matter what it is) and construct a general callable type from that signature, and check if that's a subtype oftarget
.Type::signature
already takes care of correctly handling theself
parameter. The resulting code should be quite a bit simpler and more general than what you pasted above. (And as a bonus it will handle all kinds of edge cases better where__call__
is something other than a normal method, too).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.
(We also don't need the separate if test for
is_unbound()
, that can just fall into the default case of the match, since it's notSymbol::Type
.)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.
I just realized this should go one step further:
Type::signatures
already has handling for looking up__call__
on aType::Instance
, so we don't even have to do that here, we can just call.signatures(db)
directly on the Instance type.There is some complexity about the fact that
GeneralCallableType
only holds a singleSignature
, but if you get a more complexSignatures
back from the instance type, you can just punt on that with a TODO for now.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.
I may be on the wrong track here, but callable_type here still has a self parameter
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.
No, you're on the right track. It looks like the issue here is that the way we handle "binding self" for a bound method is not by removing the parameter, but by adding a
bound_type
to theCallableSignature
, and then when we call it we know to bind thatbound_type
to the first parameter, and bind the provided arguments to the rest of the parameters. I think this is the right approach, as it lets us check type assignability forself
.The problem you're hitting here is that we set
bound_type
on theCallableSignature
(which represents potentially a set of overloads), butGeneralCallableType
only wraps a singleSignature
, which doesn't carry thatbound_type
.I think really we need to resolve this issue in the context of broader support for overloads; we need to add support for callable subtyping with overloads, and with
bound_type
, and we don't have that yet. Probably ultimatelyGeneralCallableType
should wrap aCallableSignature
, not just a singleSignature
. So I guess proper resolution of this issue is a bigger task than we'd realized. It may be best to just hold off on it for now.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.
Okay sounds good. Thank you for the help anyway, i really appreciate it.