diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 493c03a4e57c..5da925e56467 100644 --- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -5,6 +5,7 @@ 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.dataflow.scope.ScopeUtil import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.getFunctionTypeAnnotation @@ -80,8 +81,17 @@ class PyFinalInspection : PyInspection() { super.visitPyTargetExpression(node) if (!node.hasAssignedValue()) { - node.annotation?.value.takeIf(this::resolvesToFinal).let { - registerProblem(it, "If assigned value is omitted, there should be an explicit type argument to 'Final'") + node.annotation?.value?.let { + if (PyiUtil.isInsideStub(node) || ScopeUtil.getScopeOwner(node) is PyClass) { + if (resolvesToFinal(it)) { + registerProblem(it, "If assigned value is omitted, there should be an explicit type argument to 'Final'") + } + } + else { + if (resolvesToFinal(if (it is PySubscriptionExpression) it.operand else it)) { + registerProblem(node, "'Final' name should be initialized with a value") + } + } } } } diff --git a/python/testData/inspections/PyFinalInspection/omittedAssignedValueInStubOnClassLevel.pyi b/python/testData/inspections/PyFinalInspection/omittedAssignedValueInStubOnClassLevel.pyi new file mode 100644 index 000000000000..63a3990d182d --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/omittedAssignedValueInStubOnClassLevel.pyi @@ -0,0 +1,13 @@ +from typing_extensions import Final + +class A: + a: Final + b: Final[int] + c: int + +MY_FINAL = Final +MY_FINAL_INT = Final[int] + +class B: + с: MY_FINAL + d: MY_FINAL_INT \ No newline at end of file diff --git a/python/testData/inspections/PyFinalInspection/omittedAssignedValueInStubOnModuleLevel.pyi b/python/testData/inspections/PyFinalInspection/omittedAssignedValueInStubOnModuleLevel.pyi new file mode 100644 index 000000000000..9064a055c4eb --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/omittedAssignedValueInStubOnModuleLevel.pyi @@ -0,0 +1,7 @@ +from typing_extensions import Final + +a: Final[int] +b: Final +b = "10" +c: Final[str] = "10" +d: int \ 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 347e959f6cca..34f2c79d5194 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -69,22 +69,66 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { } // PY-34945 - public void testOmittedAssignedValue() { + public void testOmittedAssignedValueOnModuleLevel() { runWithLanguageLevel( LanguageLevel.PYTHON36, () -> doTestByText("from typing_extensions import Final\n" + "\n" + - "a: Final\n" + - "b: Final[int]\n" + + "a: Final[int]\n" + + "b: Final\n" + + "b = \"10\"\n" + + "c: Final[str] = \"10\"\n" + + "d: int\n") + ); + } + + // PY-34945 + public void testOmittedAssignedValueOnClassLevel() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> doTestByText("from typing_extensions import Final\n" + + "\n" + + "class A:\n" + + " a: Final\n" + + " b: Final[int]\n" + + " c: int\n" + "\n" + "MY_FINAL = Final\n" + "MY_FINAL_INT = Final[int]\n" + "\n" + - "с: MY_FINAL\n" + - "в: MY_FINAL_INT") + "class B:\n" + + " с: MY_FINAL\n" + + " d: MY_FINAL_INT") ); } + // PY-34945 + public void testOmittedAssignedValueOnFunctionLevel() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> doTestByText("from typing_extensions import Final\n" + + "\n" + + "def foo(self):\n" + + " a: Final[int]\n" + + " b: Final\n" + + " c: Final[str] = \"10\"\n") + ); + } + + // PY-34945 + public void testOmittedAssignedValueInStubOnModuleLevel() { + final PsiFile currentFile = myFixture.configureByFile(getTestFilePath() + "i"); + configureInspection(); + assertSdkRootsNotParsed(currentFile); + } + + // PY-34945 + public void testOmittedAssignedValueInStubOnClassLevel() { + final PsiFile currentFile = myFixture.configureByFile(getTestFilePath() + "i"); + configureInspection(); + assertSdkRootsNotParsed(currentFile); + } + // PY-34945 public void testOverloadedFinalMethod() { runWithLanguageLevel(