diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 3d2b29853876..26121eabdbce 100644 --- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -3,8 +3,10 @@ package com.jetbrains.python.inspections import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.jetbrains.python.PyNames +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.* @@ -126,6 +128,16 @@ class PyFinalInspection : PyInspection() { checkFinalIsOuterMost(node) } + override fun visitPyForStatement(node: PyForStatement) { + super.visitPyForStatement(node) + checkFinalInsideLoop(node) + } + + override fun visitPyWhileStatement(node: PyWhileStatement) { + super.visitPyWhileStatement(node) + checkFinalInsideLoop(node) + } + private fun getClassLevelFinalsAndInitAttributes(cls: PyClass): Pair, Map> { val classLevelFinals = mutableMapOf() cls.classAttributes.forEach { if (isFinal(it)) classLevelFinals[it.name] = it } @@ -313,6 +325,24 @@ class PyFinalInspection : PyInspection() { } } + private fun checkFinalInsideLoop(loop: PyLoopStatement) { + loop.acceptChildren( + object : PyRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + if (element !is ScopeOwner) super.visitElement(element) + } + + override fun visitPyForStatement(node: PyForStatement) {} + + override fun visitPyWhileStatement(node: PyWhileStatement) {} + + override fun visitPyTargetExpression(node: PyTargetExpression) { + if (isFinal(node)) registerProblem(node, "'Final' could not be used inside a loop") + } + } + ) + } + private fun isFinal(decoratable: PyDecoratable) = isFinal(decoratable, myTypeEvalContext) private fun isFinal(node: T): Boolean where T : PyAnnotationOwner, T : PyTypeCommentOwner { diff --git a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java index 7e02a875187a..02c7c8235232 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -500,6 +500,27 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { runWithLanguageLevel(LanguageLevel.PYTHON36, this::doMultiFileTest); } + // PY-34945 + public void testFinalInsideLoop() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> doTestByText("from typing_extensions import Final\n" + + "\n" + + "for i in undefined:\n" + + " if undefined:\n" + + " x: Final[int] = 1\n" + + "while undefined:\n" + + " y: Final[str] = '1'\n" + + " \n" + + "def foo():\n" + + " for i in undefined:\n" + + " if undefined:\n" + + " x: Final[int] = 1\n" + + " while undefined:\n" + + " y: Final[str] = '1'") + ); + } + @NotNull @Override protected Class getInspectionClass() {