From 3d233e9f528636b62442dc45595ee9917e38ff6f Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 20 Apr 2018 20:25:35 +0300 Subject: [PATCH] Highlight plain `Protocol` in instance and class checks (PY-28249) Improve processing `runtime` and `Protocol` from typing-extensions. Improve processing decorators when one of them could be resolved to some of known decorators. --- .../typing/PyTypingTypeProvider.java | 10 ++++--- .../inspections/PyProtocolInspection.kt | 26 ++++++++++++------- .../python/psi/PyKnownDecoratorUtil.java | 5 +++- .../instanceAndClassChecksOnProtocol.py | 4 +++ 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index c52d3539c10e..55beb7b33cc4 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -164,7 +164,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } // Check for the exact name in advance for performance reasons if ("Protocol".equals(referenceExpression.getName())) { - if (resolveToQualifiedNames(referenceExpression, context).contains(PROTOCOL)) { + if (ContainerUtil.exists(resolveToQualifiedNames(referenceExpression, context), n -> PROTOCOL.equals(n) || PROTOCOL_EXT.equals(n))) { return createTypingProtocolType(referenceExpression); } } @@ -465,16 +465,18 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { public Ref getReferenceType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context, @Nullable PsiElement anchor) { if (referenceTarget instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)referenceTarget; + final String targetQName = target.getQualifiedName(); + // Depends on typing.Generic defined as a target expression - if (GENERIC.equals(target.getQualifiedName())) { + if (GENERIC.equals(targetQName)) { return Ref.create(createTypingGenericType(target)); } // Depends on typing.Protocol defined as a target expression - if (PROTOCOL.equals(target.getQualifiedName())) { + if (PROTOCOL.equals(targetQName) || PROTOCOL_EXT.equals(targetQName)) { return Ref.create(createTypingProtocolType(target)); } // Depends on typing.Callable defined as a target expression - if (CALLABLE.equals(target.getQualifiedName())) { + if (CALLABLE.equals(targetQName)) { return Ref.create(createTypingCallableType(referenceTarget)); } diff --git a/python/src/com/jetbrains/python/inspections/PyProtocolInspection.kt b/python/src/com/jetbrains/python/inspections/PyProtocolInspection.kt index 1fb1a3944b88..28aa066e12bb 100644 --- a/python/src/com/jetbrains/python/inspections/PyProtocolInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyProtocolInspection.kt @@ -11,12 +11,11 @@ import com.jetbrains.python.PyNames import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider import com.jetbrains.python.codeInsight.typing.inspectProtocolSubclass import com.jetbrains.python.codeInsight.typing.isProtocol -import com.jetbrains.python.psi.PyCallExpression -import com.jetbrains.python.psi.PyClass -import com.jetbrains.python.psi.PyKnownDecoratorUtil +import com.jetbrains.python.psi.* import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_RUNTIME -import com.jetbrains.python.psi.PyTypedElement +import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_RUNTIME_EXT import com.jetbrains.python.psi.resolve.PyResolveContext +import com.jetbrains.python.psi.resolve.PyResolveUtil import com.jetbrains.python.psi.resolve.RatedResolveResult import com.jetbrains.python.psi.types.PyClassLikeType import com.jetbrains.python.psi.types.PyClassType @@ -85,15 +84,22 @@ class PyProtocolInspection : PyInspection() { private fun checkRuntimeProtocolInIsInstance(node: PyCallExpression) { if (node.isCalleeText(PyNames.ISINSTANCE, PyNames.ISSUBCLASS)) { - val base = node.arguments.getOrNull(1) - if (base != null) { - val type = myTypeEvalContext.getType(base) - if (type is PyClassType && - isProtocol(type, myTypeEvalContext) && - !PyKnownDecoratorUtil.getKnownDecorators(type.pyClass, myTypeEvalContext).contains(TYPING_RUNTIME)) { + val base = node.arguments.getOrNull(1) ?: return + + if (base is PyReferenceExpression) { + val qNames = PyResolveUtil.resolveImportedElementQNameLocally(base).asSequence().map { it.toString() } + if (qNames.any { it == PyTypingTypeProvider.PROTOCOL || it == PyTypingTypeProvider.PROTOCOL_EXT }) { registerProblem(base, "Only @runtime protocols can be used with instance and class checks", GENERIC_ERROR) + return } } + + val type = myTypeEvalContext.getType(base) + if (type is PyClassType && + isProtocol(type, myTypeEvalContext) && + !PyKnownDecoratorUtil.getKnownDecorators(type.pyClass, myTypeEvalContext).any { it == TYPING_RUNTIME || it == TYPING_RUNTIME_EXT}) { + registerProblem(base, "Only @runtime protocols can be used with instance and class checks", GENERIC_ERROR) + } } } diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index e5f2286104d9..93d0da19087d 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -62,6 +62,7 @@ public class PyKnownDecoratorUtil { TYPING_OVERLOAD("typing." + PyNames.OVERLOAD), TYPING_RUNTIME("typing.runtime"), + TYPING_RUNTIME_EXT("typing_extensions.runtime"), REPRLIB_RECURSIVE_REPR("reprlib.recursive_repr"), @@ -241,6 +242,8 @@ public class PyKnownDecoratorUtil { private static boolean allDecoratorsAreKnown(@NotNull PyDecoratable element, @NotNull List decorators) { final PyDecoratorList decoratorList = element.getDecoratorList(); - return decoratorList == null ? decorators.isEmpty() : decoratorList.getDecorators().length == decorators.size(); + return decoratorList == null + ? decorators.isEmpty() + : decoratorList.getDecorators().length == StreamEx.of(decorators).groupingBy(KnownDecorator::getShortName).size(); } } diff --git a/python/testData/inspections/PyProtocolInspection/instanceAndClassChecksOnProtocol.py b/python/testData/inspections/PyProtocolInspection/instanceAndClassChecksOnProtocol.py index a1332fa6c0fc..c8fc857a96b2 100644 --- a/python/testData/inspections/PyProtocolInspection/instanceAndClassChecksOnProtocol.py +++ b/python/testData/inspections/PyProtocolInspection/instanceAndClassChecksOnProtocol.py @@ -22,3 +22,7 @@ assert issubclass(ClosableImpl, Closable1) assert isinstance(ClosableImpl(), Closable2) assert issubclass(ClosableImpl, Closable2) + +assert isinstance(ClosableImpl(), Protocol) +B = Protocol +assert issubclass(ClosableImpl, B)