diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 18af1da727..99debdf4d9 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1791,7 +1791,7 @@ quux. __init_subclass__ :: bound method type[Quux].__init_subclass__() -> None __module__ :: str __ne__ :: bound method Quux.__ne__(value: object, /) -> bool - __new__ :: bound method Quux.__new__() -> Quux + __new__ :: def __new__(cls) -> Self@__new__ __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...] __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method Quux.__repr__() -> str diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index 8179e3272d..f101aa6e64 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -588,6 +588,28 @@ reveal_type(C.f2(1)) # revealed: str reveal_type(C().f2(1)) # revealed: str ``` +### `__new__` + +`__new__` is an implicit `@staticmethod`; accessing it on an instance does not bind the `cls` +argument: + +```py +from typing_extensions import Self + +reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__ +reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__ +# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__] +reveal_type(int.__new__) +# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__] +reveal_type((42).__new__) + +class X: + def __init__(self, val: int): ... + def make_another(self) -> Self: + reveal_type(self.__new__) # revealed: def __new__(cls) -> Self@__new__ + return self.__new__(X) +``` + ## Builtin functions and methods Some builtin functions and methods are heavily special-cased by ty. This mdtest checks that various diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 78844ef0c3..6b48499e9b 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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> { - 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> { + #[salsa::tracked(heap_size=ruff_memory_usage::heap_size)] + fn lookup_dunder_new_inner<'db>( + db: &'db dyn Db, + ty: Type<'db>, + _: (), + ) -> Option> { + 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. diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 81ed3fed50..1b4629b301 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -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() { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index d7f50ce716..75190a3c3a 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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)),