Changed behavior of conversion from class constructor to callable to conform with the typing spec in the case where the __new__ method of the class returns a value that indicates the __init__ method should be ignored. This addresses #7686. (#7698)

This commit is contained in:
Eric Traut 2024-04-13 21:41:56 -07:00 committed by GitHub
parent 74880b87d3
commit 43fe9a7e2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 10 deletions

View File

@ -787,11 +787,33 @@ export function createFunctionFromConstructor(
return fromMetaclassCall;
}
const fromInit = createFunctionFromInitMethod(evaluator, classType, selfType, recursionCount);
const fromNew = createFunctionFromNewMethod(evaluator, classType, selfType, recursionCount);
if (fromInit) {
return fromNew ? combineTypes([fromInit, fromNew]) : fromInit;
if (fromNew) {
let skipInitMethod = false;
doForEachSignature(fromNew, (signature) => {
const newMethodReturnType = FunctionType.getSpecializedReturnType(signature);
if (newMethodReturnType && shouldSkipInitEvaluation(evaluator, classType, newMethodReturnType)) {
skipInitMethod = true;
}
});
if (skipInitMethod) {
return fromNew;
}
}
const fromInit = createFunctionFromInitMethod(evaluator, classType, selfType, recursionCount);
// If there is both a __new__ and __init__ method, return a union
// comprised of both resulting function types.
if (fromNew && fromInit) {
return combineTypes([fromInit, fromNew]);
}
if (fromNew || fromInit) {
return fromNew ?? fromInit;
}
return fromNew ?? createFunctionFromObjectNewMethod(classType);
@ -1061,7 +1083,7 @@ function shouldSkipInitEvaluation(evaluator: TypeEvaluator, classType: ClassType
let skipInitCheck = false;
doForEachSubtype(returnType, (subtype) => {
if (isAnyOrUnknown(subtype)) {
if (isUnknown(subtype)) {
return;
}

View File

@ -2,7 +2,16 @@
# a callable.
from typing import Any, Callable, NoReturn, ParamSpec, Self, TypeVar, reveal_type
from typing import (
Any,
Callable,
Generic,
NoReturn,
ParamSpec,
Self,
TypeVar,
reveal_type,
)
P = ParamSpec("P")
R = TypeVar("R")
@ -43,8 +52,6 @@ reveal_type(r3(3), expected_text="Class3")
class Class4:
"""__new__ but no __init__"""
def __new__(cls, x: int) -> int: ...
@ -59,12 +66,38 @@ class Meta1(type):
class Class5(metaclass=Meta1):
"""Custom metaclass that overrides type.__call__"""
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
"""This __new__ is ignored for purposes of conversion"""
return super().__new__(cls)
r5 = accepts_callable(Class5)
reveal_type(r5, expected_text="(*args: Any, **kwargs: Any) -> NoReturn")
class Class6Proxy: ...
class Class6:
def __new__(cls) -> Class6Proxy:
return Class6Proxy.__new__(cls)
def __init__(self, x: int) -> None:
pass
r6 = accepts_callable(Class6)
reveal_type(r6, expected_text="() -> Class6Proxy")
reveal_type(r6(), expected_text="Class6Proxy")
class Class6_2:
def __new__(cls) -> Any:
return super().__new__(cls)
def __init__(self, x: int) -> None:
pass
r6_2 = accepts_callable(Class6_2)
reveal_type(r6_2, expected_text="() -> Any")
reveal_type(r6_2(), expected_text="Any")