mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
PY-76885 Conformance test failure: constructors_call_metaclass.py
(cherry picked from commit d4ae8eb0303ec141c9766599ede8be1d3bb538fb) GitOrigin-RevId: 90a67c7cc6266752ab8cdfa3379e95123c3959f9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c52f4bbadf
commit
9b70a7cf4a
@@ -923,25 +923,55 @@ public final class PyCallExpressionHelper {
|
||||
}
|
||||
|
||||
private static @NotNull List<? extends RatedResolveResult> resolveConstructors(@NotNull PyClassType type,
|
||||
@Nullable PyExpression location,
|
||||
@Nullable PyCallSiteExpression callSite,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
final var metaclassDunderCall = resolveMetaclassDunderCall(type, location, resolveContext);
|
||||
if (!metaclassDunderCall.isEmpty()) {
|
||||
// When evaluating a constructor call, a type checker should first check if the class has a custom metaclass (a
|
||||
// subclass of type) that defines a __call__ method. If so, it should evaluate the call of this method using the
|
||||
// supplied arguments. If the metaclass is type, this step can be skipped.
|
||||
//
|
||||
// If the evaluated return type of the __call__ method indicates something other than an instance of the class
|
||||
// being constructed, a type checker should assume that the metaclass __call__ method is overriding
|
||||
// type.__call__ in some special manner, and it should not attempt to evaluate the __new__ or __init__
|
||||
// methods on the class.
|
||||
final var metaclassDunderCall = resolveMetaclassDunderCall(type, callSite, resolveContext);
|
||||
final var context = resolveContext.getTypeEvalContext();
|
||||
boolean skipNewAndInitEvaluation = StreamEx.of(metaclassDunderCall)
|
||||
.map(RatedResolveResult::getElement)
|
||||
.select(PyTypedElement.class)
|
||||
.map(context::getType)
|
||||
.select(PyCallableType.class)
|
||||
.anyMatch(callableType -> {
|
||||
if (isReturnTypeAnnotated(callableType, context)) {
|
||||
PyType callType = callSite != null ? callableType.getCallType(context, callSite) : callableType.getReturnType(context);
|
||||
return !(callType instanceof PyClassType classType && classType.getPyClass() == type.getPyClass());
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (skipNewAndInitEvaluation) {
|
||||
return metaclassDunderCall;
|
||||
}
|
||||
|
||||
final var context = resolveContext.getTypeEvalContext();
|
||||
final var initAndNew = type.getPyClass().multiFindInitOrNew(true, context);
|
||||
return ContainerUtil.map(preferInitOverNew(initAndNew), e -> new RatedResolveResult(PyReferenceImpl.getRate(e, context), e));
|
||||
}
|
||||
|
||||
private static boolean isReturnTypeAnnotated(@NotNull PyCallableType callableType, @NotNull TypeEvalContext context) {
|
||||
PyCallable callable = callableType.getCallable();
|
||||
if (callable instanceof PyFunction function) {
|
||||
PyExpression returnTypeAnnotation = PyTypingTypeProvider.getReturnTypeAnnotation(function, context);
|
||||
return returnTypeAnnotation != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @NotNull Collection<? extends PyFunction> preferInitOverNew(@NotNull List<PyFunction> initAndNew) {
|
||||
final MultiMap<String, PyFunction> functions = ContainerUtil.groupBy(initAndNew, PyFunction::getName);
|
||||
return functions.containsKey(PyNames.INIT) ? functions.get(PyNames.INIT) : functions.values();
|
||||
}
|
||||
|
||||
private static @NotNull List<? extends RatedResolveResult> resolveMetaclassDunderCall(@NotNull PyClassType type,
|
||||
@Nullable PyExpression location,
|
||||
@Nullable PyCallSiteExpression callSite,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
final var context = resolveContext.getTypeEvalContext();
|
||||
|
||||
@@ -951,7 +981,7 @@ public final class PyCallExpressionHelper {
|
||||
final PyClassType typeType = PyBuiltinCache.getInstance(type.getPyClass()).getTypeType();
|
||||
if (metaClassType == typeType) return Collections.emptyList();
|
||||
|
||||
final var results = resolveDunderCall(metaClassType, location, resolveContext);
|
||||
final var results = resolveDunderCall(metaClassType, callSite, resolveContext);
|
||||
if (results.isEmpty()) return Collections.emptyList();
|
||||
|
||||
final Set<PsiElement> typeDunderCall =
|
||||
@@ -959,13 +989,7 @@ public final class PyCallExpressionHelper {
|
||||
? Collections.emptySet()
|
||||
: ContainerUtil.map2SetNotNull(resolveDunderCall(typeType, null, resolveContext), RatedResolveResult::getElement);
|
||||
|
||||
return ContainerUtil.filter(
|
||||
results,
|
||||
it -> {
|
||||
final var element = it.getElement();
|
||||
return !typeDunderCall.contains(element) && !ParamHelper.isSelfArgsKwargsCallable(element, context);
|
||||
}
|
||||
);
|
||||
return ContainerUtil.filter(results, it -> !typeDunderCall.contains(it.getElement()));
|
||||
}
|
||||
|
||||
private static @NotNull List<? extends RatedResolveResult> resolveDunderCall(@NotNull PyClassLikeType type,
|
||||
|
||||
@@ -15,7 +15,6 @@ callables_subtyping.py
|
||||
classes_classvar.py
|
||||
classes_override.py
|
||||
constructors_call_init.py
|
||||
constructors_call_metaclass.py
|
||||
constructors_call_new.py
|
||||
constructors_call_type.py
|
||||
constructors_callable.py
|
||||
|
||||
@@ -3492,6 +3492,67 @@ public class Py3TypeTest extends PyTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
public void testMetaclassHavingDunderCall() {
|
||||
doTest("object", """
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def call(cls, *args, **kwargs) -> object: ...
|
||||
|
||||
__call__ = call
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p) -> Self: ...
|
||||
|
||||
|
||||
expr = MyClass()
|
||||
""");
|
||||
doTest("MyClass", """
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __call__(cls): ...
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p: int) -> Self: ...
|
||||
|
||||
|
||||
expr = MyClass(1)
|
||||
""");
|
||||
doTest("MyClass", """
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __call__[T](cls: type[T], *args, **kwargs) -> T: ...
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p) -> Self: ...
|
||||
|
||||
|
||||
expr = MyClass(1)
|
||||
""");
|
||||
doTest("int", """
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __call__[T](cls, x: T) -> T: ...
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p1, p2) -> Self: ...
|
||||
|
||||
|
||||
expr = MyClass(1)
|
||||
""");
|
||||
}
|
||||
|
||||
private void doTest(final String expectedType, final String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
|
||||
|
||||
@@ -135,7 +135,7 @@ class PyNavigationTest : PyTestCase() {
|
||||
myFixture.configureByText(
|
||||
"a.py",
|
||||
"class MyMeta(type):\n" +
|
||||
" def __call__(self, p1, p2):\n" +
|
||||
" def __call__(self, p1, p2) -> object:\n" +
|
||||
" pass\n" +
|
||||
"class MyClass(metaclass=MyMeta):\n" +
|
||||
" def __init__(self, p3, p4):\n" +
|
||||
|
||||
@@ -401,4 +401,65 @@ public class Py3ArgumentListInspectionTest extends PyInspectionTestCase {
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaclassHavingDunderCall() {
|
||||
doTestByText("""
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def call(cls, *args, **kwargs) -> object: ...
|
||||
|
||||
__call__ = call
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p) -> Self: ...
|
||||
|
||||
|
||||
expr = MyClass()
|
||||
""");
|
||||
doTestByText("""
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __call__(cls): ...
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p) -> Self: ...
|
||||
|
||||
|
||||
c = MyClass(<warning descr="Parameter 'p' unfilled">)</warning>
|
||||
""");
|
||||
doTestByText("""
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __call__[T](cls: type[T], *args, **kwargs) -> T: ...
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p) -> Self: ...
|
||||
|
||||
|
||||
c = MyClass(<warning descr="Parameter 'p' unfilled">)</warning>
|
||||
""");
|
||||
doTestByText("""
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __call__[T](cls, x: T) -> T: ...
|
||||
|
||||
|
||||
class MyClass(metaclass=Meta):
|
||||
def __new__(cls, p1, p2) -> Self: ...
|
||||
|
||||
|
||||
c = MyClass(1)
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,11 +416,31 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
|
||||
|
||||
// PY-17877
|
||||
public void testMetaclassHavingDunderCall() {
|
||||
// TODO Metaclass's `__call__` has to be validated as well as `__init__` / `__new__`
|
||||
//runWithLanguageLevel(
|
||||
// LanguageLevel.getLatest(),
|
||||
// () -> doTestByText("""
|
||||
// class MetaFoo(type):
|
||||
// def __call__(cls, p3, p4):
|
||||
// print(f'MetaFoo.__call__: {cls}, {p3}, {p4}')
|
||||
//
|
||||
// class Foo(metaclass=MetaFoo):
|
||||
// pass
|
||||
//
|
||||
// class SubFoo(Foo):
|
||||
// def __new__(self, p1, p2):
|
||||
// # This never gets called
|
||||
// print(f'SubFoo.__new__: {p1}, {p2}')
|
||||
//
|
||||
// sub = SubFoo(1<warning descr="Parameter 'p4' unfilled">)</warning>
|
||||
// foo = Foo(3<warning descr="Parameter 'p4' unfilled">)</warning>""")
|
||||
//);
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.getLatest(),
|
||||
() -> doTestByText("""
|
||||
class MetaFoo(type):
|
||||
def __call__(cls, p3, p4):
|
||||
# type: (...) -> object
|
||||
print(f'MetaFoo.__call__: {cls}, {p3}, {p4}')
|
||||
|
||||
class Foo(metaclass=MetaFoo):
|
||||
|
||||
Reference in New Issue
Block a user