diff --git a/crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md b/crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md index eb45a6697e0c69..14d03c52cc3bb6 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md +++ b/crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md @@ -237,4 +237,26 @@ class Matrix: Matrix() < Matrix() ``` +## `self`-binding behaviour of function-like `Callable`s + +Binding the `self` parameter of a function-like `Callable` creates a new `Callable` that is also +function-like: + +`main.py`: + +```py +from typing import Callable + +def my_lossy_decorator(fn: Callable[..., int]) -> Callable[..., int]: + return fn + +class MyClass: + @my_lossy_decorator + def method(self) -> int: + return 42 + +reveal_type(MyClass().method) # revealed: (...) -> int +reveal_type(MyClass().method.__name__) # revealed: str +``` + [`tensorbase`]: https://github.com/pytorch/pytorch/blob/f3913ea641d871f04fa2b6588a77f63efeeb9f10/torch/_tensor.py#L1084-L1092 diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 48e58924d174c3..f910ef641b9a2a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -11018,11 +11018,19 @@ impl<'db> CallableType<'db> { db: &'db dyn Db, self_type: Option>, ) -> CallableType<'db> { - CallableType::new(db, self.signatures(db).bind_self(db, self_type), false) + CallableType::new( + db, + self.signatures(db).bind_self(db, self_type), + self.is_function_like(db), + ) } pub(crate) fn apply_self(self, db: &'db dyn Db, self_type: Type<'db>) -> CallableType<'db> { - CallableType::new(db, self.signatures(db).apply_self(db, self_type), false) + CallableType::new( + db, + self.signatures(db).apply_self(db, self_type), + self.is_function_like(db), + ) } /// Create a callable type which represents a fully-static "bottom" callable. diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 185bb9d2d552ef..8bfc0525a6aae3 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -300,7 +300,7 @@ impl<'db> ProtocolInterface<'db> { .and(db, || { our_type.has_relation_to_impl( db, - Type::Callable(other_type.bind_self(db, None)), + Type::Callable(protocol_bind_self(db, other_type, None)), inferable, relation, relation_visitor, @@ -313,7 +313,7 @@ impl<'db> ProtocolInterface<'db> { ProtocolMemberKind::Method(other_method), ) => our_method.bind_self(db, None).has_relation_to_impl( db, - other_method.bind_self(db, None), + protocol_bind_self(db, other_method, None), inferable, relation, relation_visitor, @@ -712,7 +712,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { .map(|callable| callable.apply_self(db, fallback_other)) .has_relation_to_impl( db, - method.bind_self(db, Some(fallback_other)), + protocol_bind_self(db, *method, Some(fallback_other)), inferable, relation, relation_visitor, @@ -912,3 +912,16 @@ fn proto_interface_cycle_initial<'db>( ) -> ProtocolInterface<'db> { ProtocolInterface::empty(db) } + +/// Bind `self`, and *also* discard the functionlike-ness of the callable. +/// +/// This additional upcasting is required in order for protocols with `__call__` method +/// members to be considered assignable to `Callable` types, since the `Callable` supertype +/// of the `__call__` method will be function-like but a `Callable` type is not. +fn protocol_bind_self<'db>( + db: &'db dyn Db, + callable: CallableType<'db>, + self_type: Option>, +) -> CallableType<'db> { + CallableType::new(db, callable.signatures(db).bind_self(db, self_type), false) +}