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
This commit is contained in:
Semyon Proshev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent 61cbd62808
commit 1dd5ee7103
2 changed files with 34 additions and 4 deletions
@@ -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<String?, PyTargetExpression>()
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<String, PyTargetExpression>()
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 {
@@ -89,16 +89,19 @@ public class PyFinalInspectionTest extends PyInspectionTestCase {
() -> doTestByText("from typing_extensions import Final\n" +
"\n" +
"class A:\n" +
" a: <warning descr=\"If assigned value is omitted, there should be an explicit type argument to 'Final'\">Final</warning>\n" +
" b: Final[int]\n" +
" <warning descr=\"'Final' name should be initialized with a value\">a</warning>: <warning descr=\"If assigned value is omitted, there should be an explicit type argument to 'Final'\">Final</warning>\n" +
" <warning descr=\"'Final' name should be initialized with a value\">b</warning>: Final[int]\n" +
" c: int\n" +
"\n" +
"MY_FINAL = Final\n" +
"MY_FINAL_INT = Final[int]\n" +
"\n" +
"class B:\n" +
" с: <warning descr=\"If assigned value is omitted, there should be an explicit type argument to 'Final'\">MY_FINAL</warning>\n" +
" d: MY_FINAL_INT")
" <warning descr=\"'Final' name should be initialized with a value\">с</warning>: <warning descr=\"If assigned value is omitted, there should be an explicit type argument to 'Final'\">MY_FINAL</warning>\n" +
" d: MY_FINAL_INT" +
"\n" +
" def __init__(self):\n" +
" self.d = 10")
);
}