mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Ensure various special-cased bound methods are understood as assignable to Callable
(#20330)
This commit is contained in:
parent
2ac4147435
commit
d23cae870e
2 changed files with 105 additions and 117 deletions
|
@ -621,9 +621,6 @@ static_assert(is_assignable_to(TypeOf[f], Callable))
|
||||||
|
|
||||||
# revealed: <method-wrapper `__get__` of `f`>
|
# revealed: <method-wrapper `__get__` of `f`>
|
||||||
reveal_type(f.__get__)
|
reveal_type(f.__get__)
|
||||||
|
|
||||||
# TODO: should pass
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_assignable_to(TypeOf[f.__get__], Callable))
|
static_assert(is_assignable_to(TypeOf[f.__get__], Callable))
|
||||||
|
|
||||||
# revealed: def __call__(self, *args: Any, **kwargs: Any) -> Any
|
# revealed: def __call__(self, *args: Any, **kwargs: Any) -> Any
|
||||||
|
@ -632,9 +629,6 @@ static_assert(is_assignable_to(TypeOf[types.FunctionType.__call__], Callable))
|
||||||
|
|
||||||
# revealed: <method-wrapper `__call__` of `f`>
|
# revealed: <method-wrapper `__call__` of `f`>
|
||||||
reveal_type(f.__call__)
|
reveal_type(f.__call__)
|
||||||
|
|
||||||
# TODO: should pass
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_assignable_to(TypeOf[f.__call__], Callable))
|
static_assert(is_assignable_to(TypeOf[f.__call__], Callable))
|
||||||
|
|
||||||
# revealed: <wrapper-descriptor `__get__` of `property` objects>
|
# revealed: <wrapper-descriptor `__get__` of `property` objects>
|
||||||
|
@ -651,9 +645,6 @@ static_assert(not is_assignable_to(TypeOf[MyClass.my_property], Callable))
|
||||||
|
|
||||||
# revealed: <method-wrapper `__get__` of `property` object>
|
# revealed: <method-wrapper `__get__` of `property` object>
|
||||||
reveal_type(MyClass.my_property.__get__)
|
reveal_type(MyClass.my_property.__get__)
|
||||||
|
|
||||||
# TODO: should pass
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__get__], Callable))
|
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__get__], Callable))
|
||||||
|
|
||||||
# revealed: <wrapper-descriptor `__set__` of `property` objects>
|
# revealed: <wrapper-descriptor `__set__` of `property` objects>
|
||||||
|
@ -665,9 +656,6 @@ static_assert(is_assignable_to(TypeOf[property.__set__], Callable))
|
||||||
|
|
||||||
# revealed: <method-wrapper `__set__` of `property` object>
|
# revealed: <method-wrapper `__set__` of `property` object>
|
||||||
reveal_type(MyClass.my_property.__set__)
|
reveal_type(MyClass.my_property.__set__)
|
||||||
|
|
||||||
# TODO: should pass
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable))
|
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable))
|
||||||
|
|
||||||
# revealed: def startswith(self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
|
# revealed: def startswith(self, prefix: str | tuple[str, ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
|
||||||
|
@ -676,9 +664,6 @@ static_assert(is_assignable_to(TypeOf[str.startswith], Callable))
|
||||||
|
|
||||||
# revealed: <method-wrapper `startswith` of `str` object>
|
# revealed: <method-wrapper `startswith` of `str` object>
|
||||||
reveal_type("foo".startswith)
|
reveal_type("foo".startswith)
|
||||||
|
|
||||||
# TODO: should pass
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_assignable_to(TypeOf["foo".startswith], Callable))
|
static_assert(is_assignable_to(TypeOf["foo".startswith], Callable))
|
||||||
|
|
||||||
def _(
|
def _(
|
||||||
|
@ -686,8 +671,7 @@ def _(
|
||||||
b: CallableTypeOf[f],
|
b: CallableTypeOf[f],
|
||||||
c: CallableTypeOf[f.__get__],
|
c: CallableTypeOf[f.__get__],
|
||||||
d: CallableTypeOf[types.FunctionType.__call__],
|
d: CallableTypeOf[types.FunctionType.__call__],
|
||||||
# TODO: false-positive diagnostic
|
e: CallableTypeOf[f.__call__],
|
||||||
e: CallableTypeOf[f.__call__], # error: [invalid-type-form]
|
|
||||||
f: CallableTypeOf[property],
|
f: CallableTypeOf[property],
|
||||||
g: CallableTypeOf[property.__get__],
|
g: CallableTypeOf[property.__get__],
|
||||||
h: CallableTypeOf[MyClass.my_property.__get__],
|
h: CallableTypeOf[MyClass.my_property.__get__],
|
||||||
|
@ -709,8 +693,7 @@ def _(
|
||||||
# revealed: (self, *args: Any, **kwargs: Any) -> Any
|
# revealed: (self, *args: Any, **kwargs: Any) -> Any
|
||||||
reveal_type(d)
|
reveal_type(d)
|
||||||
|
|
||||||
# TODO: this should be `(obj: type) -> None`
|
# revealed: (obj: type) -> None
|
||||||
# revealed: Unknown
|
|
||||||
reveal_type(e)
|
reveal_type(e)
|
||||||
|
|
||||||
# revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown
|
# revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown
|
||||||
|
|
|
@ -1321,6 +1321,12 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
Type::TypeAlias(alias) => alias.value_type(db).into_callable(db),
|
Type::TypeAlias(alias) => alias.value_type(db).into_callable(db),
|
||||||
|
|
||||||
|
Type::KnownBoundMethod(method) => Some(Type::Callable(CallableType::new(
|
||||||
|
db,
|
||||||
|
CallableSignature::from_overloads(method.signatures(db)),
|
||||||
|
false,
|
||||||
|
))),
|
||||||
|
|
||||||
Type::Never
|
Type::Never
|
||||||
| Type::DataclassTransformer(_)
|
| Type::DataclassTransformer(_)
|
||||||
| Type::AlwaysTruthy
|
| Type::AlwaysTruthy
|
||||||
|
@ -1334,8 +1340,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::TypedDict(_) => None,
|
| Type::TypedDict(_) => None,
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
Type::KnownBoundMethod(_)
|
Type::WrapperDescriptor(_)
|
||||||
| Type::WrapperDescriptor(_)
|
|
||||||
| Type::DataclassDecorator(_)
|
| Type::DataclassDecorator(_)
|
||||||
| Type::ModuleLiteral(_)
|
| Type::ModuleLiteral(_)
|
||||||
| Type::SpecialForm(_)
|
| Type::SpecialForm(_)
|
||||||
|
@ -4009,54 +4014,8 @@ impl<'db> Type<'db> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::KnownBoundMethod(
|
Type::KnownBoundMethod(method) => {
|
||||||
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
CallableBinding::from_overloads(self, method.signatures(db)).into()
|
||||||
| KnownBoundMethodType::PropertyDunderGet(_),
|
|
||||||
) => {
|
|
||||||
// Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`.
|
|
||||||
// This is required because we need to return more precise types than what the signature in
|
|
||||||
// typeshed provides:
|
|
||||||
//
|
|
||||||
// ```py
|
|
||||||
// class FunctionType:
|
|
||||||
// # ...
|
|
||||||
// @overload
|
|
||||||
// def __get__(self, instance: None, owner: type, /) -> FunctionType: ...
|
|
||||||
// @overload
|
|
||||||
// def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ...
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// 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`.
|
|
||||||
|
|
||||||
CallableBinding::from_overloads(
|
|
||||||
self,
|
|
||||||
[
|
|
||||||
Signature::new(
|
|
||||||
Parameters::new([
|
|
||||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
|
||||||
.with_annotated_type(Type::none(db)),
|
|
||||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
|
||||||
.with_annotated_type(KnownClass::Type.to_instance(db)),
|
|
||||||
]),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
Signature::new(
|
|
||||||
Parameters::new([
|
|
||||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
|
||||||
.with_annotated_type(Type::object(db)),
|
|
||||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
|
||||||
.with_annotated_type(UnionType::from_elements(
|
|
||||||
db,
|
|
||||||
[KnownClass::Type.to_instance(db), Type::none(db)],
|
|
||||||
))
|
|
||||||
.with_default_type(Type::none(db)),
|
|
||||||
]),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::WrapperDescriptor(
|
Type::WrapperDescriptor(
|
||||||
|
@ -4115,20 +4074,6 @@ impl<'db> Type<'db> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::PropertyDunderSet(_)) => Binding::single(
|
|
||||||
self,
|
|
||||||
Signature::new(
|
|
||||||
Parameters::new([
|
|
||||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
|
||||||
.with_annotated_type(Type::object(db)),
|
|
||||||
Parameter::positional_only(Some(Name::new_static("value")))
|
|
||||||
.with_annotated_type(Type::object(db)),
|
|
||||||
]),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
|
|
||||||
Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => Binding::single(
|
Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => Binding::single(
|
||||||
self,
|
self,
|
||||||
Signature::new(
|
Signature::new(
|
||||||
|
@ -4145,36 +4090,6 @@ impl<'db> Type<'db> {
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => Binding::single(
|
|
||||||
self,
|
|
||||||
Signature::new(
|
|
||||||
Parameters::new([
|
|
||||||
Parameter::positional_only(Some(Name::new_static("prefix")))
|
|
||||||
.with_annotated_type(UnionType::from_elements(
|
|
||||||
db,
|
|
||||||
[
|
|
||||||
KnownClass::Str.to_instance(db),
|
|
||||||
Type::homogeneous_tuple(db, KnownClass::Str.to_instance(db)),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
Parameter::positional_only(Some(Name::new_static("start")))
|
|
||||||
.with_annotated_type(UnionType::from_elements(
|
|
||||||
db,
|
|
||||||
[KnownClass::SupportsIndex.to_instance(db), Type::none(db)],
|
|
||||||
))
|
|
||||||
.with_default_type(Type::none(db)),
|
|
||||||
Parameter::positional_only(Some(Name::new_static("end")))
|
|
||||||
.with_annotated_type(UnionType::from_elements(
|
|
||||||
db,
|
|
||||||
[KnownClass::SupportsIndex.to_instance(db), Type::none(db)],
|
|
||||||
))
|
|
||||||
.with_default_type(Type::none(db)),
|
|
||||||
]),
|
|
||||||
Some(KnownClass::Bool.to_instance(db)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
|
|
||||||
// TODO: We should probably also check the original return type of the function
|
// TODO: We should probably also check the original return type of the function
|
||||||
// that was decorated with `@dataclass_transform`, to see if it is consistent with
|
// that was decorated with `@dataclass_transform`, to see if it is consistent with
|
||||||
// with what we configure here.
|
// with what we configure here.
|
||||||
|
@ -4906,10 +4821,8 @@ impl<'db> Type<'db> {
|
||||||
Binding::single(self, Signature::todo("Type::Intersection.call()")).into()
|
Binding::single(self, Signature::todo("Type::Intersection.call()")).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: these are actually callable
|
// TODO: this is actually callable
|
||||||
Type::KnownBoundMethod(_) | Type::DataclassDecorator(_) => {
|
Type::DataclassDecorator(_) => CallableBinding::not_callable(self).into(),
|
||||||
CallableBinding::not_callable(self).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: some `SpecialForm`s are callable (e.g. TypedDicts)
|
// TODO: some `SpecialForm`s are callable (e.g. TypedDicts)
|
||||||
Type::SpecialForm(_) => CallableBinding::not_callable(self).into(),
|
Type::SpecialForm(_) => CallableBinding::not_callable(self).into(),
|
||||||
|
@ -9483,6 +9396,98 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType,
|
KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the signatures of this bound method type.
|
||||||
|
///
|
||||||
|
/// If the bound method type is overloaded, it may have multiple signatures.
|
||||||
|
fn signatures(self, db: &'db dyn Db) -> impl Iterator<Item = Signature<'db>> {
|
||||||
|
match self {
|
||||||
|
// Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`.
|
||||||
|
// This is required because we need to return more precise types than what the signature in
|
||||||
|
// typeshed provides:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// class FunctionType:
|
||||||
|
// # ...
|
||||||
|
// @overload
|
||||||
|
// def __get__(self, instance: None, owner: type, /) -> FunctionType: ...
|
||||||
|
// @overload
|
||||||
|
// def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ...
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// 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`.
|
||||||
|
KnownBoundMethodType::FunctionTypeDunderGet(_)
|
||||||
|
| KnownBoundMethodType::PropertyDunderGet(_) => Either::Left(Either::Left(
|
||||||
|
[
|
||||||
|
Signature::new(
|
||||||
|
Parameters::new([
|
||||||
|
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||||
|
.with_annotated_type(Type::none(db)),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||||
|
.with_annotated_type(KnownClass::Type.to_instance(db)),
|
||||||
|
]),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
Signature::new(
|
||||||
|
Parameters::new([
|
||||||
|
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||||
|
.with_annotated_type(Type::object(db)),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||||
|
.with_annotated_type(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
[KnownClass::Type.to_instance(db), Type::none(db)],
|
||||||
|
))
|
||||||
|
.with_default_type(Type::none(db)),
|
||||||
|
]),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)),
|
||||||
|
KnownBoundMethodType::FunctionTypeDunderCall(function) => Either::Left(Either::Right(
|
||||||
|
function.signature(db).overloads.iter().cloned(),
|
||||||
|
)),
|
||||||
|
KnownBoundMethodType::PropertyDunderSet(_) => {
|
||||||
|
Either::Right(std::iter::once(Signature::new(
|
||||||
|
Parameters::new([
|
||||||
|
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||||
|
.with_annotated_type(Type::object(db)),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("value")))
|
||||||
|
.with_annotated_type(Type::object(db)),
|
||||||
|
]),
|
||||||
|
None,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
KnownBoundMethodType::StrStartswith(_) => {
|
||||||
|
Either::Right(std::iter::once(Signature::new(
|
||||||
|
Parameters::new([
|
||||||
|
Parameter::positional_only(Some(Name::new_static("prefix")))
|
||||||
|
.with_annotated_type(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
[
|
||||||
|
KnownClass::Str.to_instance(db),
|
||||||
|
Type::homogeneous_tuple(db, KnownClass::Str.to_instance(db)),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("start")))
|
||||||
|
.with_annotated_type(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
[KnownClass::SupportsIndex.to_instance(db), Type::none(db)],
|
||||||
|
))
|
||||||
|
.with_default_type(Type::none(db)),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("end")))
|
||||||
|
.with_annotated_type(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
[KnownClass::SupportsIndex.to_instance(db), Type::none(db)],
|
||||||
|
))
|
||||||
|
.with_default_type(Type::none(db)),
|
||||||
|
]),
|
||||||
|
Some(KnownClass::Bool.to_instance(db)),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a specific instance of `types.WrapperDescriptorType`
|
/// Represents a specific instance of `types.WrapperDescriptorType`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue