diff --git a/python/resources/inspectionDescriptions/PyTypeHintsInspection.html b/python/resources/inspectionDescriptions/PyTypeHintsInspection.html
new file mode 100644
index 000000000000..d5f597548c75
--- /dev/null
+++ b/python/resources/inspectionDescriptions/PyTypeHintsInspection.html
@@ -0,0 +1,5 @@
+
+
+This inspection detects invalid usages of type hints.
+
+
\ No newline at end of file
diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml
index 02dbacbf2c83..7b1180d154a7 100644
--- a/python/src/META-INF/python-core-common.xml
+++ b/python/src/META-INF/python-core-common.xml
@@ -424,6 +424,7 @@
+
diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java
index 7d50c0ddae3d..c5f9864f1b98 100644
--- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java
+++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java
@@ -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";
diff --git a/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt b/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt
new file mode 100644
index 000000000000..09671c1f854d
--- /dev/null
+++ b/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt
@@ -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
+ }
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java
new file mode 100644
index 000000000000..255a14666bd1
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java
@@ -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('T2')");
+ }
+
+ // 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[TypeVar('T1')]")
+ );
+ }
+
+ // PY-28243
+ public void testTypeVarRedefinition() {
+ doTestByText("from typing import TypeVar\n" +
+ "\n" +
+ "T0 = TypeVar('T0')\n" +
+ "print(T0)\n" +
+ "T0 = TypeVar('T0')");
+ }
+
+ @NotNull
+ @Override
+ protected Class extends PyInspection> getInspectionClass() {
+ return PyTypeHintsInspection.class;
+ }
+}