mirror of https://github.com/astral-sh/ruff
[ty] Support implicit type of `cls` in signatures (#21771)
## Summary Extends https://github.com/astral-sh/ruff/pull/20517 to support the implicit type of `cls` in `@classmethod` signatures. Part of https://github.com/astral-sh/ty/issues/159.
This commit is contained in:
parent
1b44d7e2a7
commit
29bf2cd201
|
|
@ -607,7 +607,7 @@ class X:
|
||||||
def __init__(self, val: int): ...
|
def __init__(self, val: int): ...
|
||||||
def make_another(self) -> Self:
|
def make_another(self) -> Self:
|
||||||
reveal_type(self.__new__) # revealed: def __new__(cls) -> Self@__new__
|
reveal_type(self.__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||||
return self.__new__(X)
|
return self.__new__(type(self))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Builtin functions and methods
|
## Builtin functions and methods
|
||||||
|
|
|
||||||
|
|
@ -271,8 +271,7 @@ reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(itera
|
||||||
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
|
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
|
||||||
reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace
|
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: Person
|
||||||
reveal_type(Person._make(("Alice", 42))) # revealed: Unknown
|
|
||||||
|
|
||||||
person = Person("Alice", 42)
|
person = Person("Alice", 42)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use ruff_python_ast::ParameterWithDefault;
|
||||||
use smallvec::{SmallVec, smallvec_inline};
|
use smallvec::{SmallVec, smallvec_inline};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DynamicType, Type, TypeVarVariance, definition_expression_type, infer_definition_types,
|
ClassType, DynamicType, Type, TypeVarVariance, definition_expression_type,
|
||||||
semantic_index,
|
infer_definition_types, semantic_index,
|
||||||
};
|
};
|
||||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||||
|
|
@ -31,8 +31,8 @@ use crate::types::infer::nearest_enclosing_class;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind, ClassLiteral,
|
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind, ClassLiteral,
|
||||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
||||||
KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, TypeContext,
|
KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, SubclassOfInner,
|
||||||
TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
SubclassOfType, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
use ruff_python_ast::{self as ast, name::Name};
|
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 method_info = infer_method_information(db, definition);
|
||||||
let is_static_or_classmethod =
|
let is_staticmethod = method_info.is_some_and(|f| f.is_staticmethod);
|
||||||
method_info.is_some_and(|f| f.is_staticmethod || f.is_classmethod);
|
let is_classmethod = method_info.is_some_and(|f| f.is_classmethod);
|
||||||
|
|
||||||
let inferred_annotation = |arg: &ParameterWithDefault| {
|
let inferred_annotation = |arg: &ParameterWithDefault| {
|
||||||
if let Some(MethodInformation {
|
if let Some(MethodInformation {
|
||||||
|
|
@ -1685,7 +1685,7 @@ impl<'db> Parameters<'db> {
|
||||||
class_is_generic,
|
class_is_generic,
|
||||||
..
|
..
|
||||||
}) = method_info
|
}) = method_info
|
||||||
&& !is_static_or_classmethod
|
&& !is_staticmethod
|
||||||
&& arg.parameter.annotation().is_none()
|
&& arg.parameter.annotation().is_none()
|
||||||
&& parameters.index(arg.name().id()) == Some(0)
|
&& parameters.index(arg.name().id()) == Some(0)
|
||||||
{
|
{
|
||||||
|
|
@ -1700,17 +1700,31 @@ impl<'db> Parameters<'db> {
|
||||||
let index = semantic_index(db, scope_id.file(db));
|
let index = semantic_index(db, scope_id.file(db));
|
||||||
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
|
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
|
||||||
|
|
||||||
Some(
|
let typing_self = typing_self(db, scope_id, typevar_binding_context, class)
|
||||||
typing_self(db, scope_id, typevar_binding_context, class)
|
.expect("We should always find the surrounding class for an implicit self: Self annotation");
|
||||||
.map(Type::TypeVar)
|
|
||||||
.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 {
|
} else {
|
||||||
// For methods of non-generic classes that are not otherwise generic (e.g. return `Self` or
|
// 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
|
// have additional type parameters), the implicit `Self` type of the `self`, or the implicit
|
||||||
// be the only type variable, so we can just use the class directly.
|
// `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))
|
Some(class_literal.to_non_generic_instance(db))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue