From 5d1e935acb8f586fedee61367ceddd4ba3c3954d Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Wed, 14 Mar 2018 17:40:31 +0300 Subject: [PATCH] Highlight removing frozen dataclass attribute (PY-28957) --- .../inspections/PyDataclassInspection.kt | 22 ++++++++++++++++--- ...signmentsToFrozen.py => mutatingFrozen.py} | 11 +++++++++- .../PyDataclassInspectionTest.java | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) rename python/testData/inspections/PyDataclassInspection/{assignmentsToFrozen.py => mutatingFrozen.py} (69%) diff --git a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt index 8c8e9fde2b0f..2abaad0dddac 100644 --- a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt @@ -37,9 +37,18 @@ class PyDataclassInspection : PyInspection() { override fun visitPyTargetExpression(node: PyTargetExpression?) { super.visitPyTargetExpression(node) - val cls = getInstancePyClass(node?.qualifier) ?: return - if (parseDataclassParameters(cls, myTypeEvalContext)?.frozen == true) { - registerProblem(node, "'${cls.name}' object attribute '${node!!.name}' is read-only", ProblemHighlightType.GENERIC_ERROR) + if (node != null) checkMutatingFrozenAttribute(node) + } + + override fun visitPyDelStatement(node: PyDelStatement?) { + super.visitPyDelStatement(node) + + if (node != null) { + node + .targets + .asSequence() + .filterIsInstance() + .forEach { checkMutatingFrozenAttribute(it) } } } @@ -158,6 +167,13 @@ class PyDataclassInspection : PyInspection() { } } + private fun checkMutatingFrozenAttribute(expression: PyQualifiedExpression) { + val cls = getInstancePyClass(expression.qualifier) ?: return + if (parseDataclassParameters(cls, myTypeEvalContext)?.frozen == true) { + registerProblem(expression, "'${cls.name}' object attribute '${expression.name}' is read-only", ProblemHighlightType.GENERIC_ERROR) + } + } + private fun getInstancePyClass(element: PyTypedElement?): PyClass? { val type = element?.let { myTypeEvalContext.getType(it) } as? PyClassType return if (type != null && !type.isDefinition) type.pyClass else null diff --git a/python/testData/inspections/PyDataclassInspection/assignmentsToFrozen.py b/python/testData/inspections/PyDataclassInspection/mutatingFrozen.py similarity index 69% rename from python/testData/inspections/PyDataclassInspection/assignmentsToFrozen.py rename to python/testData/inspections/PyDataclassInspection/mutatingFrozen.py index 5eeaced9e009..e12465e7a35b 100644 --- a/python/testData/inspections/PyDataclassInspection/assignmentsToFrozen.py +++ b/python/testData/inspections/PyDataclassInspection/mutatingFrozen.py @@ -12,6 +12,9 @@ b1 = B1(1, "2") b1.x = 2 b1.y = "3" b1.z = 1.0 +del b1.x +del b1.y +del b1.z @dataclasses.dataclass(frozen=False) @@ -25,6 +28,9 @@ b2 = B2(1, "2") b2.x = 2 b2.y = "3" b2.z = 1.0 +del b2.x +del b2.y +del b2.z @dataclasses.dataclass(frozen=True) @@ -37,4 +43,7 @@ B3.x = 5 b3 = B3(1, "2") b3.x = 2 b3.y = "3" -b3.z = 1.0 \ No newline at end of file +b3.z = 1.0 +del b3.x +del b3.y +del b3.z \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java index aa6a3daec818..4f27c688815e 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java @@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable; public class PyDataclassInspectionTest extends PyInspectionTestCase { // PY-27398 - public void testAssignmentsToFrozen() { + public void testMutatingFrozen() { doTest(); }