Skip to content

[perf] Skip unused-import name resolution when the node has no use statement - #8701

Merged
TomasVotruba merged 2 commits into
mainfrom
tv-skip-unused-import-resolve-without-use
Oct 18, 2026
Merged

TomasVotruba merged 2 commits into
mainfrom
tv-skip-unused-import-resolve-without-use

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

Why

To drop unused use imports, UnusedImportRemovingPostRector::enterNode() resolves every name referenced in the file via two full-AST traversals (findNonUseImportNames() + findNamesInDocBlocks()), and that resolution is not cached.

enterNode() fires for both the FileNode root and the inner Namespace_. In a namespaced file the use statements live inside the Namespace_, so the FileNode-level call did the full double walk and then found no use statement to act on - pure wasted work, on every post-rector pass.

What

Add a cheap guard: if the node has no direct use statement, there is nothing to remove, so return before the resolution walks. Behavior is unchanged - non-namespaced files keep their use at FileNode level and still resolve.

Bench

Adds bench/unused_import_removing_bench.php, which drives the real enterNode() on the root nodes a file actually hits (FileNode + Namespace_). Measured on bench/Source/ManyNamesSource.php (namespaced, all imports used), 40 repeats x 400 enters:

CPU
before ~23.8s
after ~11.9s

~2x on a namespaced file - the wasted FileNode-level double walk is gone.

Tests

rules-tests/CodingStyle, tests/Issues (incl. NamespacedUse, NoNamespaced, NamespacedUseAutoImport), and the Import/UnusedImport filters all green. ECS and PHPStan level 8 clean.

TomasVotruba and others added 2 commits October 18, 2026 16:08
…atement

UnusedImportRemovingPostRector::enterNode() fires for both the FileNode root
and the inner Namespace_. To find which imports are unused it resolves every
referenced name via two full-AST traversals (findNonUseImportNames +
findNamesInDocBlocks), and that resolution is not cached.

In a namespaced file the `use` statements live inside the Namespace_, so the
FileNode-level call did the full double walk and then found no `use` statement
to act on - pure waste.

Add a cheap guard: if the node has no direct `use` statement there is nothing
to remove, so return before the resolution walks. Behavior is unchanged
(non-namespaced files keep their `use` at FileNode level and still resolve).

Adds bench/unused_import_removing_bench.php.
@TomasVotruba
TomasVotruba merged commit 3693a3c into main Oct 18, 2026
49 checks passed
@TomasVotruba
TomasVotruba deleted the tv-skip-unused-import-resolve-without-use branch October 18, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants