Initialize type hints inspection (PY-28243)

Implement checks about typevar name, placement and redefinition
This commit is contained in:
Semyon Proshev
2018-04-27 19:57:15 +03:00
parent c1abea5187
commit 80e7e95173
5 changed files with 133 additions and 1 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This inspection detects invalid usages of type hints.
</body>
</html>
@@ -424,6 +424,7 @@
<localInspection language="Python" shortName="PyNamedTupleInspection" suppressId="PyNamedTuple" displayName="Namedtuple definition" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyNamedTupleInspection"/>
<localInspection language="Python" shortName="PyDataclassInspection" suppressId="PyDataclass" displayName="Dataclass definition and usages" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyDataclassInspection"/>
<localInspection language="Python" shortName="PyProtocolInspection" suppressId="PyProtocol" displayName="Protocol definition and usages" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyProtocolInspection"/>
<localInspection language="Python" shortName="PyTypeHintsInspection" suppressId="PyTypeHints" displayName="Type hints definitions and usages" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyTypeHintsInspection"/>
<defaultLiveTemplatesProvider implementation="com.jetbrains.python.codeInsight.liveTemplates.PyDefaultLiveTemplatesProvider"/>
<liveTemplateContext implementation="com.jetbrains.python.codeInsight.liveTemplates.PythonTemplateContextType$General"/>
@@ -82,7 +82,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
private static final String DEQUE = "typing.Deque";
private static final String TUPLE = "typing.Tuple";
private static final String CLASS_VAR = "typing.ClassVar";
private static final String TYPE_VAR = "typing.TypeVar";
public static final String TYPE_VAR = "typing.TypeVar";
private static final String CHAIN_MAP = "typing.ChainMap";
private static final String UNION = "typing.Union";
private static final String OPTIONAL = "typing.Optional";
@@ -0,0 +1,82 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.inspections
import com.intellij.codeInsight.controlflow.ControlFlowUtil
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.QualifiedName
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.resolve.PyResolveUtil
class PyTypeHintsInspection : PyInspection() {
override fun buildVisitor(holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession): PsiElementVisitor = Visitor(holder, session)
private class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) {
override fun visitPyCallExpression(node: PyCallExpression?) {
super.visitPyCallExpression(node)
if (node != null) {
val callee = node.callee as? PyReferenceExpression
if (callee != null &&
QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_VAR) in PyResolveUtil.resolveImportedElementQNameLocally(callee)) {
val target = (node.parent as? PyAssignmentStatement)?.targetsToValuesMapping?.firstOrNull { it.second == node }?.first
checkTypeVarPlacement(node, target)
checkTypeVarRedefinition(target)
if (target != null) {
checkTypeVarName(node, target)
}
}
}
}
private fun checkTypeVarPlacement(call: PyCallExpression, target: PyExpression?) {
if (target == null) {
registerProblem(call, "A 'TypeVar()' expression must always directly be assigned to a variable")
}
}
private fun checkTypeVarName(call: PyCallExpression, target: PyExpression) {
val name = call.getArgument(0, "name", PyStringLiteralExpression::class.java)
if (name != null && name.stringValue != target.name) {
registerProblem(name, "The argument to 'TypeVar()' must be a string equal to the variable name to which it is assigned")
}
}
private fun checkTypeVarRedefinition(target: PyExpression?) {
val scopeOwner = ScopeUtil.getScopeOwner(target) ?: return
val name = target?.name ?: return
val instructions = ControlFlowCache.getControlFlow(scopeOwner).instructions
val startInstruction = ControlFlowUtil.findInstructionNumberByElement(instructions, target)
ControlFlowUtil.iteratePrev(
startInstruction,
instructions,
{ instruction ->
if (instruction is ReadWriteInstruction &&
instruction.num() != startInstruction &&
name == instruction.name &&
instruction.access.isWriteAccess) {
registerProblem(target, "Type variables must not be redefined")
ControlFlowUtil.Operation.BREAK
}
else {
ControlFlowUtil.Operation.NEXT
}
}
)
}
}
}
@@ -0,0 +1,44 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.inspections;
import com.jetbrains.python.fixtures.PyInspectionTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import org.jetbrains.annotations.NotNull;
public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
// PY-28243
public void testTypeVarAndTargetName() {
doTestByText("from typing import TypeVar\n" +
"\n" +
"T0 = TypeVar('T0')\n" +
"T1 = TypeVar(<warning descr=\"The argument to 'TypeVar()' must be a string equal to the variable name to which it is assigned\">'T2'</warning>)");
}
// PY-28243
public void testTypeVarPlacement() {
runWithLanguageLevel(
LanguageLevel.PYTHON36,
() -> doTestByText("from typing import List, TypeVar\n" +
"\n" +
"T0 = TypeVar('T0')\n" +
"a: List[T0]\n" +
"b: List[<warning descr=\"A 'TypeVar()' expression must always directly be assigned to a variable\">TypeVar('T1')</warning>]")
);
}
// PY-28243
public void testTypeVarRedefinition() {
doTestByText("from typing import TypeVar\n" +
"\n" +
"T0 = TypeVar('T0')\n" +
"print(T0)\n" +
"<warning descr=\"Type variables must not be redefined\">T0</warning> = TypeVar('T0')");
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {
return PyTypeHintsInspection.class;
}
}