[ty] Treat __new__ as a static method (#20212)

## Summary

Pull this out of https://github.com/astral-sh/ruff/pull/18473 as an
isolated change to make sure it has no adverse effects.

The wrong behavior is observable on `main` for something like
```py
class C:
    def __new__(cls) -> "C":
        cls.x = 1

C.x  # previously: Attribute `x` can only be accessed on instances
     # now:        Type `<class 'C'>` has no attribute `x`
```
where we currently treat `x` as an *instance* attribute (because we
consider `__new__` to be a normal function and `cls` to be the "self"
attribute). With this PR, we do not consider `x` to be an attribute,
neither on the class nor on instances of `C`. If this turns out to be an
important feature, we should add it intentionally, instead of
accidentally.

## Test Plan

Ecosystem checks.
This commit is contained in:
David Peter 2025-09-03 19:55:20 +02:00 committed by GitHub
parent cb1ba0d4c2
commit 0d4f7dde99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 10 additions and 10 deletions

View file

@ -290,8 +290,7 @@ impl<'db> Bindings<'db> {
}
_ => {}
}
} else if function.has_known_decorator(db, FunctionDecorators::STATICMETHOD)
{
} else if function.is_staticmethod(db) {
overload.set_return_type(Type::FunctionLiteral(function));
} else if let [Some(first), _] = overload.parameter_types() {
if first.is_none(db) {

View file

@ -3,10 +3,8 @@ use std::sync::{LazyLock, Mutex};
use super::TypeVarVariance;
use super::{
BoundTypeVarInstance, IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator,
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers,
class_base::ClassBase,
function::{FunctionDecorators, FunctionType},
infer_expression_type, infer_unpack_types,
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase,
function::FunctionType, infer_expression_type, infer_unpack_types,
};
use crate::FxOrderMap;
use crate::module_resolver::KnownModule;
@ -1257,10 +1255,7 @@ pub(super) enum MethodDecorator {
impl MethodDecorator {
fn try_from_fn_type(db: &dyn Db, fn_type: FunctionType) -> Result<Self, ()> {
match (
fn_type.is_classmethod(db),
fn_type.has_known_decorator(db, FunctionDecorators::STATICMETHOD),
) {
match (fn_type.is_classmethod(db), fn_type.is_staticmethod(db)) {
(true, true) => Err(()), // A method can't be static and class method at the same time.
(true, false) => Ok(Self::ClassMethod),
(false, true) => Ok(Self::StaticMethod),

View file

@ -731,6 +731,12 @@ impl<'db> FunctionType<'db> {
)
}
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
/// static method.
pub(crate) fn is_staticmethod(self, db: &'db dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::STATICMETHOD) || self.name(db) == "__new__"
}
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
///
/// Checking if an overload is deprecated requires deeper call analysis.