diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.java index b0f77c672d16..8ec4a54d6a5b 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -97,13 +97,6 @@ public class PyTypeChecker { } } - if (expected instanceof PyInstantiableType && actual instanceof PyInstantiableType) { - Optional match = match((PyInstantiableType)expected, (PyInstantiableType)actual, context); - if (match.isPresent()) { - return match; - } - } - if (expected instanceof PyGenericType) { return Optional.of(match((PyGenericType)expected, actual, context)); } @@ -181,25 +174,6 @@ public class PyTypeChecker { return Optional.empty(); } - @NotNull - private static Optional match(@NotNull PyInstantiableType expected, @NotNull PyInstantiableType actual, - @NotNull MatchContext context) { - if (expected instanceof PyGenericType && typeVarAcceptsBothClassAndInstanceTypes((PyGenericType)expected)) { - return Optional.empty(); - } - - if (expected.isDefinition() ^ actual.isDefinition()) { - if (actual.isDefinition() && - actual instanceof PyClassLikeType && - matchClassObjectAndMetaclass(expected, (PyClassLikeType)actual, context)) { - return Optional.of(true); - } - return Optional.of(false); - } - - return Optional.empty(); - } - /** * Match {@code actual} versus {@code PyGenericType expected}. * @@ -234,6 +208,9 @@ public class PyTypeChecker { } if (actual != null) { + if (expected.isDefinition() && !(actual instanceof PyInstantiableType && ((PyInstantiableType)actual).isDefinition())) { + return false; + } context.substitutions.put(expected, actual); } else if (bound != null) { @@ -275,6 +252,14 @@ public class PyTypeChecker { return Optional.of(true); } + if (expected.isDefinition() ^ actual.isDefinition()) { + if (!expected.isDefinition() && actual.isDefinition()) { + final PyClassLikeType metaClass = actual.getMetaClassType(context.context, true); + return Optional.of(metaClass != null && match((PyType)expected, metaClass.toInstance(), context).orElse(true)); + } + return Optional.of(false); + } + if (expected instanceof PyTupleType && actual instanceof PyTupleType) { return match((PyTupleType)expected, (PyTupleType)actual, context); } @@ -488,21 +473,6 @@ public class PyTypeChecker { return Optional.empty(); } - private static boolean matchClassObjectAndMetaclass(@NotNull PyType expected, - @NotNull PyClassLikeType actual, - @NotNull MatchContext context) { - - if (!actual.isDefinition()) { - return false; - } - final PyClassLikeType metaClass = actual.getMetaClassType(context.context, true); - return metaClass != null && match(expected, metaClass.toInstance(), context).orElse(true); - } - - private static boolean typeVarAcceptsBothClassAndInstanceTypes(@NotNull PyGenericType typeVar) { - return !typeVar.isDefinition() && typeVar.getBound() == null; - } - private static boolean consistsOfSameElementNumberTuples(@NotNull PyUnionType unionType, int elementCount) { for (PyType type : unionType.getMembers()) { if (type instanceof PyTupleType) { diff --git a/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughCall.py b/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughCall.py index a75cd3cff6fe..8885cf7cabae 100644 --- a/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughCall.py +++ b/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughCall.py @@ -1,8 +1,8 @@ -import attr +import abcd class A(object): - a = attr.ib() + a = abcd.ib() -A = attr.s(A) +A = abcd.s(A) A(a="test") \ No newline at end of file diff --git a/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughDecorator.py b/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughDecorator.py index c959ed4fbd2f..1a5409a3c4bc 100644 --- a/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughDecorator.py +++ b/python/testData/inspections/PyArgumentListInspection/classDecoratedThroughDecorator.py @@ -1,7 +1,7 @@ -import attr +import abcd -@attr.s +@abcd.s class A(object): - a = attr.ib() + a = abcd.ib() A(a="test") \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index efe3d1a1e8cb..cce9e19fb248 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -3877,6 +3877,33 @@ public class PyTypeTest extends PyTestCase { "expr = example.__doc__"); } + // PY-38786 + public void testParticularTypeAgainstTypeVarBoundedWithBuiltinType() { + runWithLanguageLevel( + LanguageLevel.getLatest(), + () -> doTest("Type[MyClass]", + "from typing import TypeVar, Type\n" + + "\n" + + "T = TypeVar(\"T\", bound=type)\n" + + "\n" + + "def foo(t: T) -> T:\n" + + " pass\n" + + "\n" + + "class MyClass:\n" + + " pass\n" + + "\n" + + "expr = foo(MyClass)") + ); + } + + // PY-38786 + public void testDunderSubclasses() { + doTest("List[Type[Base]]", + "class Base(object):\n" + + " pass\n" + + "expr = Base.__subclasses__()"); + } + private static List getTypeEvalContexts(@NotNull PyExpression element) { return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(), TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());