mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
[red-knot] Method calls and the descriptor protocol (#16121)
## Summary This PR achieves the following: * Add support for checking method calls, and inferring return types from method calls. For example: ```py reveal_type("abcde".find("abc")) # revealed: int reveal_type("foo".encode(encoding="utf-8")) # revealed: bytes "abcde".find(123) # error: [invalid-argument-type] class C: def f(self) -> int: pass reveal_type(C.f) # revealed: <function `f`> reveal_type(C().f) # revealed: <bound method: `f` of `C`> C.f() # error: [missing-argument] reveal_type(C().f()) # revealed: int ``` * Implement the descriptor protocol, i.e. properly call the `__get__` method when a descriptor object is accessed through a class object or an instance of a class. For example: ```py from typing import Literal class Ten: def __get__(self, instance: object, owner: type | None = None) -> Literal[10]: return 10 class C: ten: Ten = Ten() reveal_type(C.ten) # revealed: Literal[10] reveal_type(C().ten) # revealed: Literal[10] ``` * Add support for member lookup on intersection types. * Support type inference for `inspect.getattr_static(obj, attr)` calls. This was mostly used as a debugging tool during development, but seems more generally useful. It can be used to bypass the descriptor protocol. For the example above: ```py from inspect import getattr_static reveal_type(getattr_static(C, "ten")) # revealed: Ten ``` * Add a new `Type::Callable(…)` variant with the following sub-variants: * `Type::Callable(CallableType::BoundMethod(…))` — represents bound method objects, e.g. `C().f` above * `Type::Callable(CallableType::MethodWrapperDunderGet(…))` — represents `f.__get__` where `f` is a function * `Type::Callable(WrapperDescriptorDunderGet)` — represents `FunctionType.__get__` * Add new known classes: * `types.MethodType` * `types.MethodWrapperType` * `types.WrapperDescriptorType` * `builtins.range` ## Performance analysis On this branch, we do more work. We need to do more call checking, since we now check all method calls. We also need to do ~twice as many member lookups, because we need to check if a `__get__` attribute exists on accessed members. A brief analysis on `tomllib` shows that we now call `Type::call` 1780 times, compared to 612 calls before. ## Limitations * Data descriptors are not yet supported, i.e. we do not infer correct types for descriptor attribute accesses in `Store` context and do not check writes to descriptor attributes. I felt like this was something that could be split out as a follow-up without risking a major architectural change. * We currently distinguish between `Type::member` (with descriptor protocol) and `Type::static_member` (without descriptor protocol). The former corresponds to `obj.attr`, the latter corresponds to `getattr_static(obj, "attr")`. However, to model some details correctly, we would also need to distinguish between a static member lookup *with* and *without* instance variables. The lookup without instance variables corresponds to `find_name_in_mro` [here](https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance). We currently approximate both using `member_static`, which leads to two open TODOs. Changing this would be a larger refactoring of `Type::own_instance_member`, so I chose to leave it out of this PR. ## Test Plan * New `call/methods.md` test suite for method calls * New tests in `descriptor_protocol.md` * New `call/getattr_static.md` test suite for `inspect.getattr_static` * Various updated tests
This commit is contained in:
parent
f62e5406f2
commit
d2e034adcd
21 changed files with 1577 additions and 174 deletions
|
@ -109,6 +109,7 @@ pub enum KnownModule {
|
|||
#[allow(dead_code)]
|
||||
Abc, // currently only used in tests
|
||||
Collections,
|
||||
Inspect,
|
||||
KnotExtensions,
|
||||
}
|
||||
|
||||
|
@ -123,6 +124,7 @@ impl KnownModule {
|
|||
Self::Sys => "sys",
|
||||
Self::Abc => "abc",
|
||||
Self::Collections => "collections",
|
||||
Self::Inspect => "inspect",
|
||||
Self::KnotExtensions => "knot_extensions",
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +151,7 @@ impl KnownModule {
|
|||
"sys" => Some(Self::Sys),
|
||||
"abc" => Some(Self::Abc),
|
||||
"collections" => Some(Self::Collections),
|
||||
"inspect" => Some(Self::Inspect),
|
||||
"knot_extensions" => Some(Self::KnotExtensions),
|
||||
_ => None,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue