mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Fix subtyping/assignability of function- and class-literal types to callback protocols (#20363)
## Summary Fixes https://github.com/astral-sh/ty/issues/377. We were treating any function as being assignable to any callback protocol, because we were trying to figure out a type's `Callable` supertype by looking up the `__call__` attribute on the type's meta-type. But a function-literal's meta-type is `types.FunctionType`, and `types.FunctionType.__call__` is `(...) -> Any`, which is not very helpful! While working on this PR, I also realised that assignability between class-literals and callback protocols was somewhat broken too, so I fixed that at the same time. ## Test Plan Added mdtests
This commit is contained in:
parent
c7f6b85fb3
commit
98708976e4
4 changed files with 137 additions and 29 deletions
|
@ -3268,13 +3268,6 @@ impl<'db> Type<'db> {
|
|||
policy: InstanceFallbackShadowsNonDataDescriptor,
|
||||
member_policy: MemberLookupPolicy,
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
// TODO: this is a workaround for the fact that looking up the `__call__` attribute on the
|
||||
// meta-type of a `Callable` type currently returns `Unbound`. We should fix this by inferring
|
||||
// a more sophisticated meta-type for `Callable` types; that would allow us to remove this branch.
|
||||
if name == "__call__" && matches!(self, Type::Callable(_) | Type::DataclassTransformer(_)) {
|
||||
return Place::bound(self).into();
|
||||
}
|
||||
|
||||
let (
|
||||
PlaceAndQualifiers {
|
||||
place: meta_attr,
|
||||
|
|
|
@ -541,17 +541,34 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
) -> ConstraintSet<'db> {
|
||||
match &self.kind {
|
||||
ProtocolMemberKind::Method(method) => {
|
||||
let Place::Type(attribute_type, Boundness::Bound) = other
|
||||
.invoke_descriptor_protocol(
|
||||
db,
|
||||
self.name,
|
||||
Place::Unbound.into(),
|
||||
InstanceFallbackShadowsNonDataDescriptor::No,
|
||||
MemberLookupPolicy::default(),
|
||||
)
|
||||
.place
|
||||
else {
|
||||
return ConstraintSet::from(false);
|
||||
// `__call__` members must be special cased for several reasons:
|
||||
//
|
||||
// 1. Looking up `__call__` on the meta-type of a `Callable` type returns `Place::Unbound` currently
|
||||
// 2. Looking up `__call__` on the meta-type of a function-literal type currently returns a type that
|
||||
// has an extremely vague signature (`(*args, **kwargs) -> Any`), which is not useful for protocol
|
||||
// checking.
|
||||
// 3. Looking up `__call__` on the meta-type of a class-literal, generic-alias or subclass-of type is
|
||||
// unfortunately not sufficient to obtain the `Callable` supertypes of these types, due to the
|
||||
// complex interaction between `__new__`, `__init__` and metaclass `__call__`.
|
||||
let attribute_type = if self.name == "__call__" {
|
||||
let Some(attribute_type) = other.into_callable(db) else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
attribute_type
|
||||
} else {
|
||||
let Place::Type(attribute_type, Boundness::Bound) = other
|
||||
.invoke_descriptor_protocol(
|
||||
db,
|
||||
self.name,
|
||||
Place::Unbound.into(),
|
||||
InstanceFallbackShadowsNonDataDescriptor::No,
|
||||
MemberLookupPolicy::default(),
|
||||
)
|
||||
.place
|
||||
else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
attribute_type
|
||||
};
|
||||
|
||||
let proto_member_as_bound_method = method.bind_self(db);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue