Skip to content

Check the bound name (alias) per import in Python RemoveImport - #8407

Merged
knutwannheden merged 1 commit into
mainfrom
fix-alias-blind-python-removeimport-check
Aug 6, 2026
Merged

Check the bound name (alias) per import in Python RemoveImport#8407
knutwannheden merged 1 commit into
mainfrom
fix-alias-blind-python-removeimport-check

Conversation

@knutwannheden

Copy link
Copy Markdown
Contributor

Motivation

The Python RemoveImport visitor decided whether an import was still referenced using the imported name, but an aliased import such as from typing import List as L binds L, not List. Removing typing.List from a file whose code references L therefore found no references to List, deemed the import unused, and removed it — leaving the L usages dangling. This was reproducible end-to-end: running Java's ChangeType(typing.List -> list) over RPC on a file using the alias deleted the import and left broken code behind. With this fix, that flow becomes a conservative no-op until alias-aware re-adding lands on the Java ChangeType/ImportService side.

Summary

  • The single statement-level _is_referenced pre-check is replaced by a per-import-entry _is_removable check that judges removability by the name the entry actually binds: its alias if present, else the imported name.
  • MultiImport name lists are filtered entry by entry, so a statement mixing aliased and unaliased names (e.g. from typing import List, List as L) only drops the bindings that are unreferenced.
  • The converse also holds: an aliased import is removable when its alias is unused, even if the bare imported name appears in the code — that name is a different binding.
  • Module-only removal of a from X import a, b statement now also checks each entry's bound name instead of the module's last segment, so entries that are still in use are kept rather than deleted with the statement.
  • _binds_other additionally excludes the entry under evaluation by identity, so the "another import already binds this name" escape hatch (kept for what ChangeType leaves behind) cannot count an import against itself; the existing module/name comparison still covers duplicate statements.
  • The three near-identical MultiImport rebuild blocks are consolidated into a _prune_names helper.

Test plan

  • Six new unit tests in tests/python/test_remove_import.py: aliased from-import kept while its alias is used and removed when unused, removal proceeding when only the bare imported name appears, per-entry pruning of a mixed List, List as L statement, and the aliased plain-import (import numpy as np) equivalents. Four failed before the fix; two guard the removal direction.
  • python -m pytest tests/python/test_remove_import.py --timeout=60 — 16 passed.
  • Full non-RPC suite python -m pytest tests/python tests/recipes --timeout=60 — 1634 passed, 6 skipped.

RemoveImport decided removability from the imported name, but an aliased
import such as `from typing import List as L` binds `L`, not `List`, so
an import whose alias was still referenced could be removed, leaving
dangling references. The unused check now runs per import entry against
the name that entry actually binds (alias if present), so a MultiImport
mixing aliased and unaliased names only drops the unreferenced bindings.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Aug 6, 2026
@knutwannheden
knutwannheden merged commit 802dfe8 into main Aug 6, 2026
1 check passed
@knutwannheden
knutwannheden deleted the fix-alias-blind-python-removeimport-check branch August 6, 2026 09:19
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant