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(