From 66defb92077fe949dc7e4850b43a753373ab40b5 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Tue, 28 May 2019 18:23:03 +0300 Subject: [PATCH] Raise a warning on `Final` instance attributes outside `__init__` (PEP 591) (PY-34945) GitOrigin-RevId: 74f4aab3a879d74815945a6f33d5a66afabd25c7 --- .../python/inspections/PyFinalInspection.kt | 18 ++++++++++++++++-- .../inspections/PyFinalInspectionTest.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 230356970816..e49b7aab5b80 100644 --- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -71,8 +71,12 @@ class PyFinalInspection : PyInspection() { registerProblem(node.nameIdentifier, "'${(it as PyFunction).qualifiedName}' is marked as '@final' and should not be overridden") } - if (!PyiUtil.isInsideStub(node) && isFinal(node) && PyiUtil.isOverload(node, myTypeEvalContext)) { - registerProblem(node.nameIdentifier, "'@final' should be placed on the implementation") + if (!PyiUtil.isInsideStub(node)) { + if (isFinal(node) && PyiUtil.isOverload(node, myTypeEvalContext)) { + registerProblem(node.nameIdentifier, "'@final' should be placed on the implementation") + } + + checkInstanceFinalsOutsideInit(node) } } else if (isFinal(node)) { @@ -158,6 +162,16 @@ class PyFinalInspection : PyInspection() { } } + private fun checkInstanceFinalsOutsideInit(method: PyFunction) { + if (PyUtil.isInit(method)) return + + val instanceAttributes = mutableMapOf() + PyClassImpl.collectInstanceAttributes(method, instanceAttributes) + instanceAttributes.values.forEach { + if (isFinal(it)) registerProblem(it, "'Final' attribute should be declared in class body or '__init__'") + } + } + 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 84b23395a748..b8275ea4953e 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -272,6 +272,22 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { ); } + // PY-34945 + public void testFinalInstanceAttributes() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> doTestByText("from typing_extensions import Final\n" + + "\n" + + "class A:\n" + + " def __init__(self):\n" + + " self.a: Final[str] = \"str\"\n" + + "\n" + + " def method(self):\n" + + " self.a: Final[int] = 10\n" + + " self.b: Final[int] = 10") + ); + } + @NotNull @Override protected Class getInspectionClass() {