From 78d1343583c2369cec646e844f00d8e915491aaa Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 15 Jan 2026 15:42:16 -0500 Subject: [PATCH] [ty] Infer implicit type of `cls` in `__new__` methods (#22584) ## Summary Resolves https://github.com/astral-sh/ty/issues/2489. --- .../resources/mdtest/annotations/self.md | 4 ++-- .../ty_python_semantic/resources/mdtest/bidirectional.md | 2 +- .../resources/mdtest/call/constructor.md | 2 +- crates/ty_python_semantic/src/types/infer/builder.rs | 8 ++------ 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 32fd3930cf..32c210a130 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -585,8 +585,8 @@ class Baz(Bar[Self]): ... class MyMetaclass(type): # TODO: reject the Self usage. because self cannot be used within a metaclass. - def __new__(cls) -> Self: - return super().__new__(cls) + def __new__(cls, name, bases, dct) -> Self: + return cls(name, bases, dct) ``` ## Explicit annotations override implicit `Self` diff --git a/crates/ty_python_semantic/resources/mdtest/bidirectional.md b/crates/ty_python_semantic/resources/mdtest/bidirectional.md index c9c6a5c065..6e773ffbe7 100644 --- a/crates/ty_python_semantic/resources/mdtest/bidirectional.md +++ b/crates/ty_python_semantic/resources/mdtest/bidirectional.md @@ -271,7 +271,7 @@ def f[T](x: T) -> list[T]: class A: def __new__(cls, value: list[int | str]): - return super().__new__(cls, value) + return super().__new__(cls) def __init__(self, value: list[int | None]): ... diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index b6e65fcdf2..9416881e4c 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -76,7 +76,7 @@ from typing_extensions import Self class Base: def __new__(cls, x: int) -> Self: - return cls() + return cls(x) class Foo(Base): ... diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 34e8133b42..1aa6555b3e 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -101,7 +101,7 @@ use crate::types::diagnostic::{ use crate::types::enums::is_enum_class_by_inheritance; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, - is_implicit_classmethod, is_implicit_staticmethod, + is_implicit_classmethod, }; use crate::types::generics::{ GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar, @@ -2915,10 +2915,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let function_node = function_definition.node(self.module()); let function_name = &function_node.name; - if is_implicit_staticmethod(function_name) { - return None; - } - let mut is_classmethod = is_implicit_classmethod(function_name); let inference = infer_definition_types(db, method_definition); for decorator in &function_node.decorator_list { @@ -2942,7 +2938,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .as_class_literal()?; let typing_self = typing_self(db, self.scope(), Some(method_definition), class_literal); - if is_classmethod { + if is_classmethod || function_name == "__new__" { typing_self .map(|typing_self| SubclassOfType::from(db, SubclassOfInner::TypeVar(typing_self))) } else {