Raise a warning on same name class and instance level Finals (PEP 591) (PY-34945)

GitOrigin-RevId: e8eaad47c5a06cb8471e06d7c1e0890f02266820
This commit is contained in:
Semyon Proshev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent 66defb9207
commit f2ca243d2e
2 changed files with 50 additions and 14 deletions
@@ -54,7 +54,9 @@ class PyFinalInspection : PyInspection() {
)
}
else {
checkClassLevelFinalsAreInitialized(node)
val (classLevelFinals, initAttributes) = getClassLevelFinalsAndInitAttributes(node)
checkClassLevelFinalsAreInitialized(classLevelFinals, initAttributes)
checkSameNameClassAndInstanceFinals(classLevelFinals, initAttributes)
}
checkRedeclarationsInScope(node)
@@ -140,24 +142,39 @@ class PyFinalInspection : PyInspection() {
}
}
private fun checkClassLevelFinalsAreInitialized(cls: PyClass) {
val notInitializedFinals = mutableMapOf<String?, PyTargetExpression>()
private fun getClassLevelFinalsAndInitAttributes(cls: PyClass): Pair<Map<String?, PyTargetExpression>, Map<String, PyTargetExpression>> {
val classLevelFinals = mutableMapOf<String?, PyTargetExpression>()
cls.classAttributes.forEach { if (isFinal(it)) classLevelFinals[it.name] = it }
cls.classAttributes.forEach {
if (!it.hasAssignedValue() && isFinal(it)) {
notInitializedFinals[it.name] = it
val initAttributes = mutableMapOf<String, PyTargetExpression>()
cls.findMethodByName(PyNames.INIT, false, myTypeEvalContext)?.let { PyClassImpl.collectInstanceAttributes(it, initAttributes) }
return Pair(classLevelFinals, initAttributes)
}
private fun checkClassLevelFinalsAreInitialized(classLevelFinals: Map<String?, PyTargetExpression>,
initAttributes: Map<String, PyTargetExpression>) {
classLevelFinals.forEach { (name, psi) ->
if (!psi.hasAssignedValue() && name !in initAttributes) {
registerProblem(psi, "'Final' name should be initialized with a value")
}
}
}
if (notInitializedFinals.isNotEmpty()) {
cls.findMethodByName(PyNames.INIT, false, myTypeEvalContext)?.let {
val initializedAttributes = mutableMapOf<String, PyTargetExpression>()
PyClassImpl.collectInstanceAttributes(it, initializedAttributes)
notInitializedFinals -= initializedAttributes.keys
}
private fun checkSameNameClassAndInstanceFinals(classLevelFinals: Map<String?, PyTargetExpression>,
initAttributes: Map<String, PyTargetExpression>) {
initAttributes.forEach { (name, initAttribute) ->
val sameNameClassLevelFinal = classLevelFinals[name]
notInitializedFinals.values.forEach {
registerProblem(it, "'Final' name should be initialized with a value")
if (sameNameClassLevelFinal != null && isFinal(initAttribute)) {
if (sameNameClassLevelFinal.hasAssignedValue()) {
registerProblem(initAttribute, "Already declared name could not be redefined as 'Final'")
}
else {
val message = "Either instance attribute or class attribute could be type hinted as 'Final'"
registerProblem(sameNameClassLevelFinal, message)
registerProblem(initAttribute, message)
}
}
}
}
@@ -288,6 +288,25 @@ public class PyFinalInspectionTest extends PyInspectionTestCase {
);
}
// PY-34945
public void testSameNameClassAndInstanceLevelFinals() {
runWithLanguageLevel(
LanguageLevel.PYTHON36,
() -> doTestByText(
"from typing_extensions import Final\n" +
"\n" +
"class A:\n" +
" a: Final[int] = 1\n" +
" b: Final[str] = \"1\"\n" +
" <warning descr=\"Either instance attribute or class attribute could be type hinted as 'Final'\">c</warning>: Final[int]\n" +
"\n" +
" def __init__(self):\n" +
" <warning descr=\"Already declared name could not be redefined as 'Final'\">self.a</warning>: Final[int] = 2\n" +
" self.b = \"2\"\n" +
" <warning descr=\"Either instance attribute or class attribute could be type hinted as 'Final'\">self.c</warning>: Final[int] = 2")
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {