From 3cb186667eca7b6507e0bf08eedcf7223816308b Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 31 May 2019 16:16:18 +0300 Subject: [PATCH] Raise a warning on inherited `Final`s reassignment on class level (PEP 591) (PY-34945) GitOrigin-RevId: 8d06617819ab21c83a053a0c36da5495b503288b --- .../python/inspections/PyFinalInspection.kt | 21 +++++++++++++++++++ .../ImportedClassFinalReassignment/a.py | 5 ++++- .../inspections/PyFinalInspectionTest.java | 6 ++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 0d92b56764e3..7a3eeeeb3d36 100644 --- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -111,6 +111,14 @@ class PyFinalInspection : PyInspection() { else if (PyUtil.multiResolveTopPriority(node, resolveContext).any { it != node && it is PyTargetExpression && isFinal(it) }) { registerProblem(node, "'${node.name}' is 'Final' and could not be reassigned") } + else { + if (!node.isQualified) { + val scopeOwner = ScopeUtil.getScopeOwner(node) + if (scopeOwner is PyClass) { + checkInheritedClassFinalReassignmentOnClassLevel(node, scopeOwner) + } + } + } } if (isFinal(node) && PyUtil.multiResolveTopPriority(node, resolveContext).any { it != node }) { @@ -265,6 +273,19 @@ class PyFinalInspection : PyInspection() { } } + private fun checkInheritedClassFinalReassignmentOnClassLevel(target: PyTargetExpression, cls: PyClass) { + val name = target.name ?: return + + for (ancestor in cls.getAncestorClasses(myTypeEvalContext)) { + val ancestorClassAttribute = ancestor.findClassAttribute(name, false, myTypeEvalContext) + + if (ancestorClassAttribute != null && ancestorClassAttribute.hasAssignedValue() && isFinal(ancestorClassAttribute)) { + registerProblem(target, "'${ancestor.name}.$name' is 'Final' and could not be reassigned") + break + } + } + } + private fun checkFinalIsOuterMost(node: PyReferenceExpression) { if (isTopLevelInAnnotationOrTypeComment(node)) return (node.parent as? PySubscriptionExpression)?.let { diff --git a/python/testData/inspections/PyFinalInspection/ImportedClassFinalReassignment/a.py b/python/testData/inspections/PyFinalInspection/ImportedClassFinalReassignment/a.py index 50d82886c862..fd060b61e280 100644 --- a/python/testData/inspections/PyFinalInspection/ImportedClassFinalReassignment/a.py +++ b/python/testData/inspections/PyFinalInspection/ImportedClassFinalReassignment/a.py @@ -7,4 +7,7 @@ class B(A): def my_cls_method(cls): cls.a = 6 -B.a = 7 \ No newline at end of file +B.a = 7 + +class C(A): + a = 8 \ 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 db2b254ca8bb..cc325b3df6c8 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -350,8 +350,10 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { " def my_cls_method(cls):\n" + " cls.a = 6\n" + "\n" + - "" + - "B.a = 7") + "B.a = 7\n" + + "\n" + + "class C(A):\n" + + " a = 8\n") ); }