[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:
David Peter 2025-09-01 10:16:28 +02:00 committed by GitHub
parent c71ce006c4
commit 5518c84ab3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 35 additions and 14 deletions

View file

@ -46,7 +46,7 @@ use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOO
pub use crate::types::display::DisplaySettings;
use crate::types::enums::{enum_metadata, is_single_member_enum};
use crate::types::function::{
DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
};
use crate::types::generics::{
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.
pub(crate) fn typing_self_type(self, db: &'db dyn Db) -> Type<'db> {
let mut self_instance = self.self_instance(db);
if self
.function(db)
.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
{
if self.function(db).is_classmethod(db) {
self_instance = self_instance.to_instance(db).unwrap_or_else(Type::unknown);
}
self_instance

View file

@ -272,7 +272,7 @@ impl<'db> Bindings<'db> {
for (overload_index, overload) in binding.matching_overloads_mut() {
match binding_type {
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) {
if function.is_classmethod(db) {
match overload.parameter_types() {
[_, Some(owner)] => {
overload.set_return_type(Type::BoundMethod(
@ -308,7 +308,7 @@ impl<'db> Bindings<'db> {
if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] =
overload.parameter_types()
{
if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) {
if function.is_classmethod(db) {
match overload.parameter_types() {
[_, _, Some(owner)] => {
overload.set_return_type(Type::BoundMethod(

View file

@ -1258,7 +1258,7 @@ pub(super) enum MethodDecorator {
impl MethodDecorator {
fn try_from_fn_type(db: &dyn Db, fn_type: FunctionType) -> Result<Self, ()> {
match (
fn_type.has_known_decorator(db, FunctionDecorators::CLASSMETHOD),
fn_type.is_classmethod(db),
fn_type.has_known_decorator(db, FunctionDecorators::STATICMETHOD),
) {
(true, true) => Err(()), // A method can't be static and class method at the same time.

View file

@ -721,6 +721,13 @@ impl<'db> FunctionType<'db> {
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`.
///
/// Checking if an overload is deprecated requires deeper call analysis.