Added additional special-case handling for __init_subclass__ and __class_getitem__ to treat them as implicit class methods even if they are not declared using a def statement. This addresses #5500.

This commit is contained in:
Eric Traut 2023-07-14 01:25:54 -07:00
parent 6236155cb1
commit 844256cfb6
3 changed files with 32 additions and 11 deletions

View File

@ -5405,6 +5405,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
) {
setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(errorNode), memberInfo.symbol, errorNode);
}
// Special-case `__init_subclass` and `__class_getitem__` because
// these are always treated as class methods even if they're not
// decorated as such.
if (memberName === '__init_subclass__' || memberName === '__class_getitem__') {
if (isFunction(type) && !FunctionType.isClassMethod(type)) {
type = FunctionType.cloneWithNewFlags(type, type.details.flags | FunctionTypeFlags.ClassMethod);
}
}
}
const descriptorResult = applyDescriptorAccessMethod(

View File

@ -1411,7 +1411,7 @@ export function* getClassIterator(classType: Type, flags = ClassIteratorFlags.De
let foundSkipMroClass = skipMroClass === undefined;
for (const mroClass of classType.details.mro) {
// Are we still searching fro teh skipMroClass?
// Are we still searching for the skipMroClass?
if (!foundSkipMroClass && skipMroClass) {
if (!isClass(mroClass)) {
foundSkipMroClass = true;

View File

@ -6,7 +6,7 @@ from datetime import datetime
from typing import Any, Optional, Type
class Foo:
class ClassA:
def __init_subclass__(
cls, *, param1: str, param2: float, param3: Optional[Any] = None
) -> None:
@ -15,32 +15,44 @@ class Foo:
# This should generate an error because param1 is
# the wrong type.
class Bar1(Foo, param1=0, param2=4):
class ClassB(ClassA, param1=0, param2=4):
pass
# This should generate an error because param2 is missing.
class Bar2(Foo, param1="0", param3=datetime.now()):
class ClassC(ClassA, param1="0", param3=datetime.now()):
pass
class Bar3(Foo, param1="0", param2=5.0):
class ClassD(ClassA, param1="0", param2=5.0):
pass
class Bar4:
class ClassE:
def __init_subclass__(cls, *, arg: int) -> None:
func(cls, arg)
func1(cls, arg)
def __new__(cls) -> "Bar4":
func(cls, 9)
def __new__(cls) -> "ClassE":
func1(cls, 9)
return super().__new__(cls)
def func(klass: Type[Bar4], arg: int):
def func1(klass: Type[ClassE], arg: int):
pass
class Bar5(Foo, param1="hi", param2=3.4):
class ClassF(ClassA, param1="hi", param2=3.4):
def __init_subclass__(cls, param_alt1: int):
super().__init_subclass__(param1="yo", param2=param_alt1)
def func2(cls):
pass
class ClassG:
__init_subclass__ = func2
class ClassH(ClassG):
pass