mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[ty] Fix descriptor lookups for most types that overlap with None
(#19120)
Some checks failed
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / cargo build (release) (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (windows) (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks-instrumented (push) Has been cancelled
CI / benchmarks-walltime (push) Has been cancelled
Some checks failed
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / cargo build (release) (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (windows) (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks-instrumented (push) Has been cancelled
CI / benchmarks-walltime (push) Has been cancelled
This commit is contained in:
parent
44f2f77748
commit
08d8819c8a
4 changed files with 44 additions and 15 deletions
|
@ -343,7 +343,7 @@ def _(c: Callable[[int, Unpack[Ts]], int]):
|
|||
from typing import Callable
|
||||
|
||||
def _(c: Callable[[int], int]):
|
||||
reveal_type(c.__init__) # revealed: def __init__(self) -> None
|
||||
reveal_type(c.__init__) # revealed: bound method object.__init__() -> None
|
||||
reveal_type(c.__class__) # revealed: type
|
||||
reveal_type(c.__call__) # revealed: (int, /) -> int
|
||||
```
|
||||
|
|
|
@ -201,6 +201,36 @@ type IntOrStr = int | str
|
|||
reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any) -> _SpecialForm
|
||||
```
|
||||
|
||||
## Method calls on types not disjoint from `None`
|
||||
|
||||
Very few methods are defined on `object`, `None`, and other types not disjoint from `None`. However,
|
||||
descriptor-binding behaviour works on these types in exactly the same way as descriptor binding on
|
||||
other types. This is despite the fact that `None` is used as a sentinel internally by the descriptor
|
||||
protocol to indicate that a method was accessed on the class itself rather than an instance of the
|
||||
class:
|
||||
|
||||
```py
|
||||
from typing import Protocol, Literal
|
||||
from ty_extensions import AlwaysFalsy
|
||||
|
||||
class Foo: ...
|
||||
|
||||
class SupportsStr(Protocol):
|
||||
def __str__(self) -> str: ...
|
||||
|
||||
class Falsy(Protocol):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | None):
|
||||
a.__str__()
|
||||
b.__str__()
|
||||
c.__str__()
|
||||
d.__str__()
|
||||
# TODO: these should not error
|
||||
e.__str__() # error: [missing-argument]
|
||||
f.__str__() # error: [missing-argument]
|
||||
```
|
||||
|
||||
## Error cases: Calling `__get__` for methods
|
||||
|
||||
The `__get__` method on `types.FunctionType` has the following overloaded signature in typeshed:
|
||||
|
@ -234,16 +264,18 @@ method_wrapper(C())
|
|||
method_wrapper(C(), None)
|
||||
method_wrapper(None, C)
|
||||
|
||||
# Passing `None` without an `owner` argument is an
|
||||
# error: [invalid-argument-type] "Argument to method wrapper `__get__` of function `f` is incorrect: Expected `~None`, found `None`"
|
||||
reveal_type(object.__str__.__get__(object(), None)()) # revealed: str
|
||||
|
||||
# TODO: passing `None` without an `owner` argument fails at runtime.
|
||||
# Ideally we would emit a diagnostic here:
|
||||
method_wrapper(None)
|
||||
|
||||
# Passing something that is not assignable to `type` as the `owner` argument is an
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(None, 1)
|
||||
|
||||
# Passing `None` as the `owner` argument when `instance` is `None` is an
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
# TODO: passing `None` as the `owner` argument when `instance` is `None` fails at runtime.
|
||||
# Ideally we would emit a diagnostic here.
|
||||
method_wrapper(None, None)
|
||||
|
||||
# Calling `__get__` without any arguments is an
|
||||
|
|
|
@ -619,8 +619,9 @@ wrapper_descriptor()
|
|||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(f)
|
||||
|
||||
# Calling it without the `owner` argument if `instance` is not `None` is an
|
||||
# error: [invalid-argument-type] "Argument to wrapper descriptor `FunctionType.__get__` is incorrect: Expected `~None`, found `None`"
|
||||
# TODO: Calling it without the `owner` argument if `instance` is not `None` fails at runtime.
|
||||
# Ideally we would emit a diagnostic here,
|
||||
# but this is hard to model without introducing false positives elsewhere
|
||||
wrapper_descriptor(f, None)
|
||||
|
||||
# But calling it with an instance is fine (in this case, the `owner` argument is optional):
|
||||
|
|
|
@ -3069,10 +3069,6 @@ impl<'db> Type<'db> {
|
|||
|
||||
Type::ModuleLiteral(module) => module.static_member(db, name_str).into(),
|
||||
|
||||
Type::AlwaysFalsy | Type::AlwaysTruthy => {
|
||||
self.class_member_with_policy(db, name, policy)
|
||||
}
|
||||
|
||||
_ if policy.no_instance_fallback() => self.invoke_descriptor_protocol(
|
||||
db,
|
||||
name_str,
|
||||
|
@ -3094,6 +3090,8 @@ impl<'db> Type<'db> {
|
|||
| Type::KnownInstance(..)
|
||||
| Type::PropertyInstance(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy
|
||||
| Type::TypeIs(..) => {
|
||||
let fallback = self.instance_member(db, name_str);
|
||||
|
||||
|
@ -3533,7 +3531,6 @@ impl<'db> Type<'db> {
|
|||
// For `builtins.property.__get__`, we use the same signature. The return types are not
|
||||
// specified yet, they will be dynamically added in `Bindings::evaluate_known_cases`.
|
||||
|
||||
let not_none = Type::none(db).negate(db);
|
||||
CallableBinding::from_overloads(
|
||||
self,
|
||||
[
|
||||
|
@ -3549,7 +3546,7 @@ impl<'db> Type<'db> {
|
|||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(not_none),
|
||||
.with_annotated_type(Type::object(db)),
|
||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
|
@ -3575,7 +3572,6 @@ impl<'db> Type<'db> {
|
|||
// TODO: Consider merging this signature with the one in the previous match clause,
|
||||
// since the previous one is just this signature with the `self` parameters
|
||||
// removed.
|
||||
let not_none = Type::none(db).negate(db);
|
||||
let descriptor = match kind {
|
||||
WrapperDescriptorKind::FunctionTypeDunderGet => {
|
||||
KnownClass::FunctionType.to_instance(db)
|
||||
|
@ -3606,7 +3602,7 @@ impl<'db> Type<'db> {
|
|||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(descriptor),
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(not_none),
|
||||
.with_annotated_type(Type::object(db)),
|
||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue