-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[red-knot] Check subtype relation between callable types #16804
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
Conversation
e6e65d7
to
6192776
Compare
8e60a84
to
eb8f3ca
Compare
|
eb8f3ca
to
f9ee155
Compare
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.
This is looking really good! I don't have any significant advice based on what's here already; happy to consider any specific problems you're having with completing the PR.
I think that when we add assignability for callable types, it will make most sense to just expand this function to handle gradual types also and rename it is_assignable_to
. Then the function can work both for is_subtype_of
and is_assignable_to
, in the former case it will just never receive a non-fully-static callable type.
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Outdated
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Outdated
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Outdated
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Show resolved
Hide resolved
|
Yeah, I think that makes sense. For now, I did make the assumption that we're only getting fully static types so I avoided the fallback to |
12dc9eb
to
98da221
Compare
98da221
to
79a4afc
Compare
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.
This looks really good!! Quite a complex feature, thanks for working through it so thoroughly.
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Outdated
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Show resolved
Hide resolved
crates/red_knot_python_semantic/resources/mdtest/type_properties/is_subtype_of.md
Outdated
Show resolved
Hide resolved
let (self_parameters, other_parameters) = parameters.into_remaining(); | ||
|
||
// Collect all the keyword-only parameters and the unmatched standard parameters. | ||
let mut self_keywords = HashMap::new(); |
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.
It's possible that argument lists will tend to be short enough that it will be cheaper to use a Vec and just iterate through it than to pay the overhead of hashing. But I'm fine leaving this for a future performance investigation if we see this code as a hot spot. A mapping is semantically the appropriate data structure here.
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.
Yeah, another option is to use smallmap
crate.
dfe5fcc
to
6a1f150
Compare
12ff6b9
to
a519c2e
Compare
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.
This is really excellent!!
a519c2e
to
3b1e030
Compare
* main: (26 commits) Use the common `OperatorPrecedence` for the parser (#16747) [red-knot] Check subtype relation between callable types (#16804) [red-knot] Check whether two callable types are equivalent (#16698) [red-knot] Ban most `Type::Instance` types in type expressions (#16872) Special-case value-expression inference of special form subscriptions (#16877) [syntax-errors] Fix star annotation before Python 3.11 (#16878) Recognize `SyntaxError:` as an error code for ecosystem checks (#16879) [red-knot] add test cases result in false positive errors (#16856) Bump 0.11.1 (#16871) Allow discovery of venv in VIRTUAL_ENV env variable (#16853) Split git pathspecs in change determination onto separate lines (#16869) Use the correct base commit for change determination (#16857) Separate `BitXorOr` into `BitXor` and `BitOr` precedence (#16844) Server: Allow `FixAll` action in presence of version-specific syntax errors (#16848) [`refurb`] Fix starred expressions fix (`FURB161`) (#16550) [`flake8-executable`] Add pytest and uv run to help message for `shebang-missing-python` (`EXE003`) (#16855) Show more precise messages in invalid type expressions (#16850) [`flake8-executables`] Allow `uv run` in shebang line for `shebang-missing-python` (`EXE003`) (#16849) Add `--exit-non-zero-on-format` (#16009) [red-knot] Ban list literals in most contexts in type expressions (#16847) ...
## Summary Part of #15382 This PR adds support for checking the assignability of two general callable types. This is built on top of #16804 by including the gradual parameters check and accepting a function that performs the check between the two types. ## Test Plan Update `is_assignable_to.md` with callable types section.
Very nice work! One minor question: should from knot_extensions import static_assert, is_subtype_of
from typing import Callable
static_assert(is_subtype_of(Callable[[int], str], object)) https://playknot.ruff.rs/2fd03ad6-c4d9-45e5-bde5-8f2d1ec55abc |
Hmm, good question. I think not because |
That's opposite to the way you should think about it -- all fully static types should be a subtype of Consider the way that all instance types are a subtype of |
Callables (fully static ones) should be a subtype of It is true that all objects are not callable, but this means that object is not a subtype of Callable. In my understanding this PR wasn't aiming to be comprehensive yet, it was just handling the core calculation of signature subtyping. We have other relations missing as well, eg a FunctionLiteral can be a subtype of a Callable type. Also an Instance type with a |
Yeah, I think I got it backwards, thanks.
Yes, that is correct. I'll open an issue to keep track of the remaining relationship, it might be something that a contributor could pick up. Edit: #16953 |
Summary
Part of #15382
This PR adds support for checking the subtype relationship between the two callable types.
The main source of reference used for implementation is https://typing.python.org/en/latest/spec/callables.html#assignability-rules-for-callables.
The implementation is split into two phases:
HashMap
to do the keyword parameters check via name lookupFor (1), there's a helper struct which is similar to
.zip_longest
(fromitertools
) except that it allows control over one of the iterator as that's required when processing a variadic parameter. This is required because positional parameters needs to be checked as per their position between the two callable types. The struct also keeps track of the current iteration element because when the loop is exited (to move on to the phase 2) the current iteration element would be carried over to the phase 2 check.This struct is internal to the
is_subtype_of
method as I don't think it makes sense to expose it outside. It also allows me to use "self" and "other" suffixed field names as that's only relevant in that context.Test Plan
Add extensive tests in markdown.
Converted all of the code snippets from https://typing.python.org/en/latest/spec/callables.html#assignability-rules-for-callables to use
knot_extensions.is_subtype_of
and verified the result.