From db3eb6b92e30a96183b8366b5c8bf3cd87501a34 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 28 Nov 2025 18:05:27 -0500 Subject: [PATCH] infer implicit type of `cls` in signatures --- .../resources/mdtest/call/methods.md | 2 +- .../resources/mdtest/named_tuple.md | 3 +- .../src/types/signatures.rs | 44 ++++++++++++------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index 054f6d6a6a..0536ded1e6 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -607,7 +607,7 @@ 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) + return self.__new__(type(self)) ``` ## Builtin functions and methods diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index ab1dafa187..5fd71b988b 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -271,8 +271,7 @@ reveal_type(Person._make) # revealed: bound method ._make(itera reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace -# TODO: should be `Person` once we support implicit type of `self` -reveal_type(Person._make(("Alice", 42))) # revealed: Unknown +reveal_type(Person._make(("Alice", 42))) # revealed: Person person = Person("Alice", 42) diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 028087ceff..9f8d7ccacd 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -18,8 +18,8 @@ use ruff_python_ast::ParameterWithDefault; use smallvec::{SmallVec, smallvec_inline}; use super::{ - DynamicType, Type, TypeVarVariance, definition_expression_type, infer_definition_types, - semantic_index, + ClassType, DynamicType, Type, TypeVarVariance, definition_expression_type, + infer_definition_types, semantic_index, }; use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; @@ -31,8 +31,8 @@ use crate::types::infer::nearest_enclosing_class; use crate::types::{ ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind, ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, - KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, TypeContext, - TypeMapping, TypeRelation, VarianceInferable, todo_type, + KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, SubclassOfInner, + SubclassOfType, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type, }; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -1675,8 +1675,8 @@ impl<'db> Parameters<'db> { }; let method_info = infer_method_information(db, definition); - let is_static_or_classmethod = - method_info.is_some_and(|f| f.is_staticmethod || f.is_classmethod); + let is_staticmethod = method_info.is_some_and(|f| f.is_staticmethod); + let is_classmethod = method_info.is_some_and(|f| f.is_classmethod); let inferred_annotation = |arg: &ParameterWithDefault| { if let Some(MethodInformation { @@ -1685,7 +1685,7 @@ impl<'db> Parameters<'db> { class_is_generic, .. }) = method_info - && !is_static_or_classmethod + && !is_staticmethod && arg.parameter.annotation().is_none() && parameters.index(arg.name().id()) == Some(0) { @@ -1700,16 +1700,30 @@ impl<'db> Parameters<'db> { let index = semantic_index(db, scope_id.file(db)); let class = nearest_enclosing_class(db, index, scope_id).unwrap(); - Some( - typing_self(db, scope_id, typevar_binding_context, class) - .map(Type::TypeVar) - .expect("We should always find the surrounding class for an implicit self: Self annotation"), - ) + let typing_self = typing_self(db, scope_id, typevar_binding_context, class) + .expect("We should always find the surrounding class for an implicit self: Self annotation"); + + if is_classmethod { + Some(SubclassOfType::from( + db, + SubclassOfInner::TypeVar(typing_self), + )) + } else { + Some(Type::TypeVar(typing_self)) + } } else { // For methods of non-generic classes that are not otherwise generic (e.g. return `Self` or - // have additional type parameters), the implicit `Self` type of the `self` parameter would - // be the only type variable, so we can just use the class directly. - Some(class_literal.to_non_generic_instance(db)) + // have additional type parameters), the implicit `Self` type of the `self`, or the implicit + // `type[Self]` type of the `cls` parameter, would be the only type variable, so we can just + // use the class directly. + if is_classmethod { + Some(SubclassOfType::from( + db, + SubclassOfInner::Class(ClassType::NonGeneric(class_literal)), + )) + } else { + Some(class_literal.to_non_generic_instance(db)) + } } } else { None