diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt
index ac12a7555503..493c03a4e57c 100644
--- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt
+++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt
@@ -5,8 +5,10 @@ import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.impl.source.resolve.FileContextUtil
+import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.getFunctionTypeAnnotation
+import com.jetbrains.python.documentation.doctest.PyDocstringFile
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL_EXT
@@ -93,6 +95,23 @@ class PyFinalInspection : PyInspection() {
}
}
+ override fun visitPyReferenceExpression(node: PyReferenceExpression) {
+ super.visitPyReferenceExpression(node)
+
+ checkFinalIsOuterMost(node)
+ }
+
+ private fun checkFinalIsOuterMost(node: PyReferenceExpression) {
+ if (isTopLevelInAnnotationOrTypeComment(node)) return
+ (node.parent as? PySubscriptionExpression)?.let {
+ if (it.operand == node && isTopLevelInAnnotationOrTypeComment(it)) return
+ }
+
+ if (PyTypingTypeProvider.isInAnnotationOrTypeComment(node) && resolvesToFinal(node)) {
+ registerProblem(node, "'Final' could only be used as the outermost type")
+ }
+ }
+
private fun isFinal(decoratable: PyDecoratable): Boolean {
return PyKnownDecoratorUtil.getKnownDecorators(decoratable, myTypeEvalContext).any { it == TYPING_FINAL || it == TYPING_FINAL_EXT }
}
@@ -120,5 +139,12 @@ class PyFinalInspection : PyInspection() {
val file = FileContextUtil.getContextFile(node) ?: return null
return PyUtil.createExpressionFromFragment(typeComment, file)
}
+
+ private fun isTopLevelInAnnotationOrTypeComment(node: PyExpression): Boolean {
+ if (node.parent is PyAnnotation) return true
+ if (node.parent is PyExpressionStatement && node.parent.parent is PyDocstringFile) return true
+ if (node.parent is PyParameterTypeList) return true
+ return false
+ }
}
}
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java
index 26e125249fc0..347e959f6cca 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java
@@ -142,6 +142,26 @@ public class PyFinalInspectionTest extends PyInspectionTestCase {
);
}
+ // PY-34945
+ public void testOuterMostFinal() {
+ runWithLanguageLevel(
+ LanguageLevel.PYTHON36,
+ () -> doTestByText("from typing_extensions import Final\n" +
+ "\n" +
+ "a1: Final[int] = 10\n" +
+ "b1: List[Final[int]] = []\n" +
+ "\n" +
+ "a2 = 10 # type: Final[int]\n" +
+ "b2 = [] # type: List[Final[int]]\n" +
+ "\n" +
+ "a3: Final = 10\n" +
+ "b3: List[Final] = []\n" +
+ "\n" +
+ "a4 = 10 # type: Final\n" +
+ "b4 = [] # type: List[Final]")
+ );
+ }
+
@NotNull
@Override
protected Class extends PyInspection> getInspectionClass() {