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.
This commit is contained in:
Semyon Proshev
2018-04-27 19:57:17 +03:00
parent 90c31acea4
commit 3d233e9f52
4 changed files with 30 additions and 15 deletions
@@ -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<PyType> 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));
}
@@ -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)
}
}
}
@@ -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<KnownDecorator> 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();
}
}
@@ -22,3 +22,7 @@ assert issubclass(ClosableImpl, Closable1)
assert isinstance(ClosableImpl(), <error descr="Only @runtime protocols can be used with instance and class checks">Closable2</error>)
assert issubclass(ClosableImpl, <error descr="Only @runtime protocols can be used with instance and class checks">Closable2</error>)
assert isinstance(ClosableImpl(), <error descr="Only @runtime protocols can be used with instance and class checks">Protocol</error>)
B = Protocol
assert issubclass(ClosableImpl, <error descr="Only @runtime protocols can be used with instance and class checks">B</error>)