Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ty_ide/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1791,7 +1791,7 @@ quux.<CURSOR>
__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
Expand Down
22 changes: 22 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/call/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 16 additions & 11 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 3 additions & 5 deletions crates/ty_python_semantic/src/types/call/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down
10 changes: 2 additions & 8 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
Loading