[ty] Fix lookup of __new__ on instances (#21147)

## Summary

We weren't correctly modeling it as a `staticmethod` in all cases,
leading us to incorrectly infer that the `cls` argument would be bound
if it was accessed on an instance (rather than the class object).

## Test Plan

Added mdtests that fail on `main`. The primer output also looks good!
This commit is contained in:
Alex Waygood 2025-10-30 13:42:46 -04:00 committed by GitHub
parent f0fe6d62fb
commit 9bacd19c5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 44 additions and 25 deletions

View file

@ -3584,16 +3584,21 @@ impl<'db> Type<'db> {
}
}
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
#[allow(unused_variables)]
// If we choose name `_unit`, the macro will generate code that uses `_unit`, causing clippy to fail.
fn lookup_dunder_new(self, db: &'db dyn Db, unit: ()) -> Option<PlaceAndQualifiers<'db>> {
self.find_name_in_mro_with_policy(
db,
"__new__",
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
)
fn lookup_dunder_new(self, db: &'db dyn Db) -> Option<PlaceAndQualifiers<'db>> {
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn lookup_dunder_new_inner<'db>(
db: &'db dyn Db,
ty: Type<'db>,
_: (),
) -> Option<PlaceAndQualifiers<'db>> {
let mut flags = MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK;
if !ty.is_subtype_of(db, KnownClass::Type.to_instance(db)) {
flags |= MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK;
}
ty.find_name_in_mro_with_policy(db, "__new__", flags)
}
lookup_dunder_new_inner(db, self, ())
}
/// Look up an attribute in the MRO of the meta-type of `self`. This returns class-level attributes
@ -6089,7 +6094,7 @@ impl<'db> Type<'db> {
// An alternative might be to not skip `object.__new__` but instead mark it such that it's
// easy to check if that's the one we found?
// Note that `__new__` is a static method, so we must inject the `cls` argument.
let new_method = self_type.lookup_dunder_new(db, ());
let new_method = self_type.lookup_dunder_new(db);
// Construct an instance type that we can use to look up the `__init__` instance method.
// This performs the same logic as `Type::to_instance`, except for generic class literals.

View file

@ -25,8 +25,8 @@ use crate::types::diagnostic::{
};
use crate::types::enums::is_enum_class;
use crate::types::function::{
DataclassTransformerFlags, DataclassTransformerParams, FunctionDecorators, FunctionType,
KnownFunction, OverloadLiteral,
DataclassTransformerFlags, DataclassTransformerParams, FunctionType, KnownFunction,
OverloadLiteral,
};
use crate::types::generics::{
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
@ -357,9 +357,7 @@ impl<'db> Bindings<'db> {
_ => {}
}
} else if function
.has_known_decorator(db, FunctionDecorators::STATICMETHOD)
{
} else if function.is_staticmethod(db) {
overload.set_return_type(*function_ty);
} else {
match overload.parameter_types() {

View file

@ -1051,16 +1051,10 @@ impl<'db> ClassType<'db> {
return Type::Callable(metaclass_dunder_call_function.into_callable_type(db));
}
let dunder_new_function_symbol = self_ty
.member_lookup_with_policy(
db,
"__new__".into(),
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
)
.place;
let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);
let dunder_new_signature = dunder_new_function_symbol
.ignore_possibly_undefined()
.and_then(|place_and_quals| place_and_quals.ignore_possibly_undefined())
.and_then(|ty| match ty {
Type::FunctionLiteral(function) => Some(function.signature(db)),
Type::Callable(callable) => Some(callable.signatures(db)),