mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Support __init_subclass__
(#20190)
## Summary `__init_subclass__` is implicitly a classmethod. closes https://github.com/astral-sh/ty/issues/1106 ## Test Plan Regression test
This commit is contained in:
parent
c71ce006c4
commit
5518c84ab3
6 changed files with 35 additions and 14 deletions
|
@ -1411,7 +1411,7 @@ quux.<CURSOR>
|
||||||
__getstate__ :: bound method Quux.__getstate__() -> object
|
__getstate__ :: bound method Quux.__getstate__() -> object
|
||||||
__hash__ :: bound method Quux.__hash__() -> int
|
__hash__ :: bound method Quux.__hash__() -> int
|
||||||
__init__ :: bound method Quux.__init__() -> Unknown
|
__init__ :: bound method Quux.__init__() -> Unknown
|
||||||
__init_subclass__ :: bound method Quux.__init_subclass__() -> None
|
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
||||||
__module__ :: str
|
__module__ :: str
|
||||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||||
__new__ :: bound method Quux.__new__() -> Quux
|
__new__ :: bound method Quux.__new__() -> Quux
|
||||||
|
@ -1456,7 +1456,7 @@ quux.b<CURSOR>
|
||||||
__getstate__ :: bound method Quux.__getstate__() -> object
|
__getstate__ :: bound method Quux.__getstate__() -> object
|
||||||
__hash__ :: bound method Quux.__hash__() -> int
|
__hash__ :: bound method Quux.__hash__() -> int
|
||||||
__init__ :: bound method Quux.__init__() -> Unknown
|
__init__ :: bound method Quux.__init__() -> Unknown
|
||||||
__init_subclass__ :: bound method Quux.__init_subclass__() -> None
|
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
||||||
__module__ :: str
|
__module__ :: str
|
||||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||||
__new__ :: bound method Quux.__new__() -> Quux
|
__new__ :: bound method Quux.__new__() -> Quux
|
||||||
|
@ -1506,7 +1506,7 @@ C.<CURSOR>
|
||||||
__getstate__ :: def __getstate__(self) -> object
|
__getstate__ :: def __getstate__(self) -> object
|
||||||
__hash__ :: def __hash__(self) -> int
|
__hash__ :: def __hash__(self) -> int
|
||||||
__init__ :: def __init__(self) -> None
|
__init__ :: def __init__(self) -> None
|
||||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
__init_subclass__ :: bound method <class 'C'>.__init_subclass__() -> None
|
||||||
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
|
__instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
|
||||||
__itemsize__ :: int
|
__itemsize__ :: int
|
||||||
__module__ :: str
|
__module__ :: str
|
||||||
|
@ -1575,7 +1575,7 @@ Meta.<CURSOR>
|
||||||
__getstate__ :: def __getstate__(self) -> object
|
__getstate__ :: def __getstate__(self) -> object
|
||||||
__hash__ :: def __hash__(self) -> int
|
__hash__ :: def __hash__(self) -> int
|
||||||
__init__ :: Overload[(self, o: object, /) -> None, (self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None]
|
__init__ :: Overload[(self, o: object, /) -> None, (self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None]
|
||||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
__init_subclass__ :: bound method <class 'Meta'>.__init_subclass__() -> None
|
||||||
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
__instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
|
||||||
__itemsize__ :: int
|
__itemsize__ :: int
|
||||||
__module__ :: str
|
__module__ :: str
|
||||||
|
@ -1682,7 +1682,7 @@ Quux.<CURSOR>
|
||||||
__getstate__ :: def __getstate__(self) -> object
|
__getstate__ :: def __getstate__(self) -> object
|
||||||
__hash__ :: def __hash__(self) -> int
|
__hash__ :: def __hash__(self) -> int
|
||||||
__init__ :: def __init__(self) -> Unknown
|
__init__ :: def __init__(self) -> Unknown
|
||||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
__init_subclass__ :: bound method <class 'Quux'>.__init_subclass__() -> None
|
||||||
__instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
|
__instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
|
||||||
__itemsize__ :: int
|
__itemsize__ :: int
|
||||||
__module__ :: str
|
__module__ :: str
|
||||||
|
@ -1756,7 +1756,7 @@ Answer.<CURSOR>
|
||||||
__getstate__ :: def __getstate__(self) -> object
|
__getstate__ :: def __getstate__(self) -> object
|
||||||
__hash__ :: def __hash__(self) -> int
|
__hash__ :: def __hash__(self) -> int
|
||||||
__init__ :: def __init__(self) -> None
|
__init__ :: def __init__(self) -> None
|
||||||
__init_subclass__ :: def __init_subclass__(cls) -> None
|
__init_subclass__ :: bound method <class 'Answer'>.__init_subclass__() -> None
|
||||||
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
|
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
|
||||||
__itemsize__ :: int
|
__itemsize__ :: int
|
||||||
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
|
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
|
||||||
|
|
|
@ -462,6 +462,22 @@ reveal_type(C.f2(1)) # revealed: str
|
||||||
reveal_type(C().f2(1)) # revealed: str
|
reveal_type(C().f2(1)) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `__init_subclass__`
|
||||||
|
|
||||||
|
The [`__init_subclass__`] method is implicitly a classmethod:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Base:
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.custom_attribute: int = 0
|
||||||
|
|
||||||
|
class Derived(Base):
|
||||||
|
pass
|
||||||
|
|
||||||
|
reveal_type(Derived.custom_attribute) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
## `@staticmethod`
|
## `@staticmethod`
|
||||||
|
|
||||||
### Basic
|
### Basic
|
||||||
|
@ -571,3 +587,4 @@ reveal_type(C().f2(1)) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
[functions and methods]: https://docs.python.org/3/howto/descriptor.html#functions-and-methods
|
[functions and methods]: https://docs.python.org/3/howto/descriptor.html#functions-and-methods
|
||||||
|
[`__init_subclass__`]: https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__
|
||||||
|
|
|
@ -46,7 +46,7 @@ use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOO
|
||||||
pub use crate::types::display::DisplaySettings;
|
pub use crate::types::display::DisplaySettings;
|
||||||
use crate::types::enums::{enum_metadata, is_single_member_enum};
|
use crate::types::enums::{enum_metadata, is_single_member_enum};
|
||||||
use crate::types::function::{
|
use crate::types::function::{
|
||||||
DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
|
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
|
||||||
};
|
};
|
||||||
use crate::types::generics::{
|
use crate::types::generics::{
|
||||||
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context,
|
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context,
|
||||||
|
@ -8819,10 +8819,7 @@ impl<'db> BoundMethodType<'db> {
|
||||||
/// a `@classmethod`, then it should be an instance of that bound-instance type.
|
/// a `@classmethod`, then it should be an instance of that bound-instance type.
|
||||||
pub(crate) fn typing_self_type(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn typing_self_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
let mut self_instance = self.self_instance(db);
|
let mut self_instance = self.self_instance(db);
|
||||||
if self
|
if self.function(db).is_classmethod(db) {
|
||||||
.function(db)
|
|
||||||
.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
|
||||||
{
|
|
||||||
self_instance = self_instance.to_instance(db).unwrap_or_else(Type::unknown);
|
self_instance = self_instance.to_instance(db).unwrap_or_else(Type::unknown);
|
||||||
}
|
}
|
||||||
self_instance
|
self_instance
|
||||||
|
|
|
@ -272,7 +272,7 @@ impl<'db> Bindings<'db> {
|
||||||
for (overload_index, overload) in binding.matching_overloads_mut() {
|
for (overload_index, overload) in binding.matching_overloads_mut() {
|
||||||
match binding_type {
|
match binding_type {
|
||||||
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
|
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
|
||||||
if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) {
|
if function.is_classmethod(db) {
|
||||||
match overload.parameter_types() {
|
match overload.parameter_types() {
|
||||||
[_, Some(owner)] => {
|
[_, Some(owner)] => {
|
||||||
overload.set_return_type(Type::BoundMethod(
|
overload.set_return_type(Type::BoundMethod(
|
||||||
|
@ -308,7 +308,7 @@ impl<'db> Bindings<'db> {
|
||||||
if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] =
|
if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] =
|
||||||
overload.parameter_types()
|
overload.parameter_types()
|
||||||
{
|
{
|
||||||
if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) {
|
if function.is_classmethod(db) {
|
||||||
match overload.parameter_types() {
|
match overload.parameter_types() {
|
||||||
[_, _, Some(owner)] => {
|
[_, _, Some(owner)] => {
|
||||||
overload.set_return_type(Type::BoundMethod(
|
overload.set_return_type(Type::BoundMethod(
|
||||||
|
|
|
@ -1258,7 +1258,7 @@ pub(super) enum MethodDecorator {
|
||||||
impl MethodDecorator {
|
impl MethodDecorator {
|
||||||
fn try_from_fn_type(db: &dyn Db, fn_type: FunctionType) -> Result<Self, ()> {
|
fn try_from_fn_type(db: &dyn Db, fn_type: FunctionType) -> Result<Self, ()> {
|
||||||
match (
|
match (
|
||||||
fn_type.has_known_decorator(db, FunctionDecorators::CLASSMETHOD),
|
fn_type.is_classmethod(db),
|
||||||
fn_type.has_known_decorator(db, FunctionDecorators::STATICMETHOD),
|
fn_type.has_known_decorator(db, FunctionDecorators::STATICMETHOD),
|
||||||
) {
|
) {
|
||||||
(true, true) => Err(()), // A method can't be static and class method at the same time.
|
(true, true) => Err(()), // A method can't be static and class method at the same time.
|
||||||
|
|
|
@ -721,6 +721,13 @@ impl<'db> FunctionType<'db> {
|
||||||
self.literal(db).has_known_decorator(db, decorator)
|
self.literal(db).has_known_decorator(db, decorator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this method is decorated with `@classmethod`, or if it is implicitly a
|
||||||
|
/// classmethod.
|
||||||
|
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
|
||||||
|
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
||||||
|
|| self.name(db) == "__init_subclass__"
|
||||||
|
}
|
||||||
|
|
||||||
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
|
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
|
||||||
///
|
///
|
||||||
/// Checking if an overload is deprecated requires deeper call analysis.
|
/// Checking if an overload is deprecated requires deeper call analysis.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue