diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index fea7c85a57c73e..0526f3dacd1fcc 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -229,9 +229,8 @@ def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | Non b.__str__() c.__str__() d.__str__() - # TODO: these should not error - e.__str__() # error: [missing-argument] - f.__str__() # error: [missing-argument] + e.__str__() + f.__str__() ``` ## Error cases: Calling `__get__` for methods diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index db868895b1479d..902423d25394c0 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -267,7 +267,7 @@ impl<'db> Place<'db> { Place::Defined(defined) => { if let Some((dunder_get_return_ty, _)) = - defined.ty.try_call_dunder_get(db, Type::none(db), owner) + defined.ty.try_call_dunder_get(db, None, owner) { Place::Defined(DefinedPlace { ty: dunder_get_return_ty, diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 88bb63999bb497..55b4df3866683d 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2750,13 +2750,13 @@ impl<'db> Type<'db> { pub(crate) fn try_call_dunder_get( self, db: &'db dyn Db, - instance: Type<'db>, + instance: Option>, owner: Type<'db>, ) -> Option<(Type<'db>, AttributeKind)> { tracing::trace!( "try_call_dunder_get: {}, {}, {}", self.display(db), - instance.display(db), + instance.unwrap_or_else(|| Type::none(db)).display(db), owner.display(db) ); match self { @@ -2776,15 +2776,13 @@ impl<'db> Type<'db> { // method of these synthesized functions. The method-wrapper would then be returned from // `find_name_in_mro` when called on function-like `Callable`s. This would allow us to // correctly model the behavior of *explicit* `SomeDataclass.__init__.__get__` calls. - return if instance.is_none(db) && callable.is_function_like(db) { + return if instance.is_none() && callable.is_function_like(db) { Some((self, AttributeKind::NormalOrNonDataDescriptor)) } else { - // For classmethod-like callables, bind to the owner class. For function-like callables, bind to the instance. - let self_type = if callable.is_classmethod_like(db) && instance.is_none(db) { + let self_type = instance.unwrap_or_else(|| { + // For classmethod-like callables, bind to the owner class. owner.to_instance(db).unwrap_or(owner) - } else { - instance - }; + }); Some(( Type::Callable(callable.bind_self(db, Some(self_type))), @@ -2792,6 +2790,22 @@ impl<'db> Type<'db> { )) }; } + Type::FunctionLiteral(function) + if instance.is_some_and(|ty| ty.is_none(db)) + && !function.is_staticmethod(db) + && !function.is_classmethod(db) => + { + // When the instance is of type `None` (`NoneType`), we must handle + // `FunctionType.__get__` here rather than falling through to the generic + // `__get__` path. The stubs for `FunctionType.__get__` use an overload + // with `instance: None` to indicate class-level access (returning the + // unbound function). This incorrectly matches when the instance is actually + // an instance of `None` + return Some(( + Type::BoundMethod(BoundMethodType::new(db, function, instance.unwrap())), + AttributeKind::NormalOrNonDataDescriptor, + )); + } _ => {} } @@ -2803,8 +2817,9 @@ impl<'db> Type<'db> { .. }) = descr_get { + let instance_ty = instance.unwrap_or_else(|| Type::none(db)); let return_ty = descr_get - .try_call(db, &CallArguments::positional([self, instance, owner])) + .try_call(db, &CallArguments::positional([self, instance_ty, owner])) .map(|bindings| { if descr_get_boundness == Definedness::AlwaysDefined { bindings.return_type(db) @@ -2834,7 +2849,7 @@ impl<'db> Type<'db> { fn try_call_dunder_get_on_attribute( db: &'db dyn Db, attribute: PlaceAndQualifiers<'db>, - instance: Type<'db>, + instance: Option>, owner: Type<'db>, ) -> (PlaceAndQualifiers<'db>, AttributeKind) { match attribute { @@ -3023,7 +3038,7 @@ impl<'db> Type<'db> { ) = Self::try_call_dunder_get_on_attribute( db, self.class_member_with_policy(db, name.into(), member_policy), - self, + Some(self), self.to_meta_type(db), ); @@ -3498,13 +3513,8 @@ impl<'db> Type<'db> { let class_attr_plain = class_attr_plain.map_type(|ty| ty.bind_self_typevars(db, self_instance)); - let class_attr_fallback = Self::try_call_dunder_get_on_attribute( - db, - class_attr_plain, - Type::none(db), - self, - ) - .0; + let class_attr_fallback = + Self::try_call_dunder_get_on_attribute(db, class_attr_plain, None, self).0; let result = self.invoke_descriptor_protocol( db, diff --git a/crates/ty_python_semantic/src/types/bound_super.rs b/crates/ty_python_semantic/src/types/bound_super.rs index 6765bf01750646..5ac7dd855b55fe 100644 --- a/crates/ty_python_semantic/src/types/bound_super.rs +++ b/crates/ty_python_semantic/src/types/bound_super.rs @@ -691,13 +691,7 @@ impl<'db> BoundSuperType<'db> { // Also, invoking a descriptor on a dynamic attribute is meaningless, so we don't handle this. SuperOwnerKind::Dynamic(_) => None, SuperOwnerKind::Class(_) => Some( - Type::try_call_dunder_get_on_attribute( - db, - attribute, - Type::none(db), - owner.owner_type(db), - ) - .0, + Type::try_call_dunder_get_on_attribute(db, attribute, None, owner.owner_type(db)).0, ), SuperOwnerKind::Instance(_) | SuperOwnerKind::InstanceTypeVar(..) => { let owner_type = owner.owner_type(db); @@ -705,7 +699,7 @@ impl<'db> BoundSuperType<'db> { Type::try_call_dunder_get_on_attribute( db, attribute, - owner_type, + Some(owner_type), owner_type.to_meta_type(db), ) .0, @@ -713,15 +707,7 @@ impl<'db> BoundSuperType<'db> { } SuperOwnerKind::ClassTypeVar(..) => { let owner_type = owner.owner_type(db); - Some( - Type::try_call_dunder_get_on_attribute( - db, - attribute, - Type::none(db), - owner_type, - ) - .0, - ) + Some(Type::try_call_dunder_get_on_attribute(db, attribute, None, owner_type).0) } } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index b6e254cdf88ab3..3ea1de68ce6d97 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3266,7 +3266,7 @@ impl<'db> StaticClassLiteral<'db> { if let Some(ref mut default_ty) = default_ty { *default_ty = default_ty - .try_call_dunder_get(db, Type::none(db), Type::from(self)) + .try_call_dunder_get(db, None, Type::from(self)) .map(|(return_ty, _)| return_ty) .unwrap_or_else(Type::unknown); }