From 0fa827fa32e64a473c8f88d7ba67e42f34c91b64 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 20 Feb 2025 15:22:05 +0100 Subject: [PATCH] Model fallback MethodType => FunctionType --- .../resources/mdtest/call/methods.md | 8 ++++++++ crates/red_knot_python_semantic/src/types.rs | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/methods.md b/crates/red_knot_python_semantic/resources/mdtest/call/methods.md index cab27ceb7e..d6deb2cf7a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/methods.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/methods.md @@ -89,6 +89,14 @@ If we access an attribute on a bound method object itself, it will defer to `typ reveal_type(bound_method.__hash__) # revealed: ``` +If an attribute is not available on the bound method object, it will be looked up on the underlying +function object. We model this explicitly, which means that we can access `__module__` on bound +methods, even though it is not available on `types.MethodType`: + +```py +reveal_type(bound_method.__module__) # revealed: str +``` + ## Basic method calls on class objects and instances ```py diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a52447a669..6ca046970e 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1607,7 +1607,17 @@ impl<'db> Type<'db> { Type::Callable(CallableType::BoundMethod(bound_method)) => match name { "__self__" => Symbol::bound(bound_method.self_instance(db)), "__func__" => Symbol::bound(Type::FunctionLiteral(bound_method.function(db))), - _ => KnownClass::MethodType.to_instance(db).member(db, name), + _ => { + let member = KnownClass::MethodType.to_instance(db).member(db, name); + + // If an attribute is not available on the bound method object, it will be looked + // up on the underlying function object: + if member.is_unbound() { + Type::FunctionLiteral(bound_method.function(db)).member(db, name) + } else { + member + } + } }, Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { KnownClass::MethodWrapperType @@ -3747,7 +3757,7 @@ impl KnownFunction { } } -/// This type represents bound method objects that are created when a method is called +/// This type represents bound method objects that are created when a method is accessed /// on an instance of a class. For example, the expression `Path("a.txt").touch` creates /// a bound method object that represents the `Path.touch` method which is bound to the /// instance `Path("a.txt")`.