From f53a94126629f2955602ff48404ffbfa09231340 Mon Sep 17 00:00:00 2001 From: "evgeny.bovykin" Date: Fri, 6 Feb 2026 10:36:41 +0100 Subject: [PATCH] PY-85519 Skip import statements in PyFinalInspection Renames in import statements like `from b import A as B` are considered target expressions. Since they are not annotated as `Final` (they can't be annotated), they were reported as `B could not be reassigned` GitOrigin-RevId: 0187aeb5da25dd3eb5c8284bdda5cddd30170047 --- .../com/jetbrains/python/inspections/PyFinalInspection.kt | 5 ++++- .../inspections/PyFinalInspection/ImportAsFinal/a.py | 3 +++ .../inspections/PyFinalInspection/ImportAsFinal/b.py | 3 +++ .../jetbrains/python/inspections/PyFinalInspectionTest.java | 5 +++++ 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyFinalInspection/ImportAsFinal/a.py create mode 100644 python/testData/inspections/PyFinalInspection/ImportAsFinal/b.py diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 232724b0dbcb..a6642a6342b3 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -36,6 +36,7 @@ import com.jetbrains.python.psi.PyExpressionStatement import com.jetbrains.python.psi.PyForStatement import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.PyGlobalStatement +import com.jetbrains.python.psi.PyImportStatementBase import com.jetbrains.python.psi.PyKnownDecoratorUtil import com.jetbrains.python.psi.PyLoopStatement import com.jetbrains.python.psi.PyNamedParameter @@ -157,6 +158,8 @@ class PyFinalInspection : PyInspection() { super.visitPyTargetExpression(node) val parent = PsiTreeUtil.getParentOfType(node, PyStatement::class.java) + if (parent is PyImportStatementBase) return + if (parent is PyTypeDeclarationStatement || parent is PyGlobalStatement || parent is PyNonlocalStatement) { node.annotation?.value?.let { if (PyiUtil.isInsideStub(node) || ScopeUtil.getScopeOwner(node) is PyClass) { @@ -355,7 +358,7 @@ class PyFinalInspection : PyInspection() { else -> PyUtil.multiResolveTopPriority(target, resolveContext) }.toMutableList() - val scopeOwner = ScopeUtil.getScopeOwner(target); + val scopeOwner = ScopeUtil.getScopeOwner(target) if (!target.isQualified && scopeOwner != null) { // multiResolve finds last assignments, but we need all earlier assignments val scope = ControlFlowCache.getScope(scopeOwner) diff --git a/python/testData/inspections/PyFinalInspection/ImportAsFinal/a.py b/python/testData/inspections/PyFinalInspection/ImportAsFinal/a.py new file mode 100644 index 000000000000..f59fb173dcc5 --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/ImportAsFinal/a.py @@ -0,0 +1,3 @@ +from b import A as B + +print(B) \ No newline at end of file diff --git a/python/testData/inspections/PyFinalInspection/ImportAsFinal/b.py b/python/testData/inspections/PyFinalInspection/ImportAsFinal/b.py new file mode 100644 index 000000000000..0b6aef638d37 --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/ImportAsFinal/b.py @@ -0,0 +1,3 @@ +from typing import Final + +A: Final = "example" \ 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 b8152ffbed3d..1b491f077d8a 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -695,6 +695,11 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { pass"""); } + // PY-85519 + public void testImportAsFinal() { + doMultiFileTest(); + } + @NotNull @Override protected Class getInspectionClass() {