From 1dd5ee71036bb40e0b679f53935aa41995e1971c Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Tue, 28 May 2019 16:57:19 +0300 Subject: [PATCH] Update inspection for `Final` variables without assigned values (PEP 591) (PY-34945) Highlight class level finals that have no assignments in `__init__` GitOrigin-RevId: 28f5f655889df475d6a7b1084cd951a80e31e2cf --- .../python/inspections/PyFinalInspection.kt | 27 +++++++++++++++++++ .../inspections/PyFinalInspectionTest.java | 11 +++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 160dcb68d776..230356970816 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.PyNames import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache import com.jetbrains.python.codeInsight.controlflow.ScopeOwner import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil @@ -15,6 +16,7 @@ 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 +import com.jetbrains.python.psi.impl.PyClassImpl import com.jetbrains.python.psi.search.PySuperMethodsSearch import com.jetbrains.python.psi.types.PyClassType import com.jetbrains.python.pyi.PyiUtil @@ -51,6 +53,9 @@ class PyFinalInspection : PyInspection() { myTypeEvalContext ) } + else { + checkClassLevelFinalsAreInitialized(node) + } checkRedeclarationsInScope(node) } @@ -131,6 +136,28 @@ class PyFinalInspection : PyInspection() { } } + private fun checkClassLevelFinalsAreInitialized(cls: PyClass) { + val notInitializedFinals = mutableMapOf() + + cls.classAttributes.forEach { + if (!it.hasAssignedValue() && isFinal(it)) { + notInitializedFinals[it.name] = it + } + } + + if (notInitializedFinals.isNotEmpty()) { + cls.findMethodByName(PyNames.INIT, false, myTypeEvalContext)?.let { + val initializedAttributes = mutableMapOf() + PyClassImpl.collectInstanceAttributes(it, initializedAttributes) + notInitializedFinals -= initializedAttributes.keys + } + + notInitializedFinals.values.forEach { + registerProblem(it, "'Final' name should be initialized with a value") + } + } + } + private fun checkFinalIsOuterMost(node: PyReferenceExpression) { if (isTopLevelInAnnotationOrTypeComment(node)) return (node.parent as? PySubscriptionExpression)?.let { diff --git a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java index 7d7d34bf1eb9..84b23395a748 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -89,16 +89,19 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { () -> doTestByText("from typing_extensions import Final\n" + "\n" + "class A:\n" + - " a: Final\n" + - " b: Final[int]\n" + + " a: Final\n" + + " b: Final[int]\n" + " c: int\n" + "\n" + "MY_FINAL = Final\n" + "MY_FINAL_INT = Final[int]\n" + "\n" + "class B:\n" + - " с: MY_FINAL\n" + - " d: MY_FINAL_INT") + " с: MY_FINAL\n" + + " d: MY_FINAL_INT" + + "\n" + + " def __init__(self):\n" + + " self.d = 10") ); }