Skip to content

[perf] Resolve used names in a single AST walk in UnusedImportRemovingPostRector - #8704

Merged
TomasVotruba merged 1 commit into
mainfrom
tv-unused-import-single-walk
Oct 18, 2026
Merged

TomasVotruba merged 1 commit into
mainfrom
tv-unused-import-single-walk

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

Follow-up to #8701 / #8702. End-to-end profiling of the import post-rector chain (single-process, over rector-src's own src/, 510 files) showed UnusedImportRemovingPostRector was ~61% of the remaining import cost - the guard (#8701) and the cross-pass cache (#8702) do not touch its first-pass walk cost.

Why

To find unused use imports, enterNode() resolved the referenced names with two separate full-AST traversals of the same stmts:

  • findNonUseImportNames() - PHP names
  • findNamesInDocBlocks() - docblock class names

What

Merge both into a single traversal. Each node's docblock names are collected in the same callback that gathers the PHP names. The resolved set is unchanged:

  • the Use_ subtree is still skipped for name collection (its node docblock is read first, before the skip)
  • the root node's own docblock is read up front (the former doc walk traversed the root node, not just its stmts)
  • the walk now runs over $rootNode->stmts, matching the former names walk exactly (so the namespace's own name is not collected)

The stmts are walked once instead of twice.

Bench

Adds bench/unused_import_resolve_bench.php, which bumps the stmts version per call to force the walk every time (no cache hits) and isolate the first-pass cost. 40 repeats x 400 forced walks on bench/Source/ManyNamesSource.php:

CPU
before (two walks) ~11.65s
after (one walk) ~8.90s

~24% off the resolve walk.

Tests

tests/ (701, engine + PostRector), tests/Issues (incl. NamespacedUse, NoNamespaced, NamespacedUseAutoImport), rules-tests/CodingStyle, rules-tests/Renaming all green. ECS and PHPStan level 8 clean on the changed file.

…gPostRector

To find unused `use` imports, enterNode() resolved the referenced names with two
separate full-AST traversals of the same stmts - findNonUseImportNames() for PHP
names and findNamesInDocBlocks() for docblock class names.

Merge both into a single traversal: each node's docblock names are collected in
the same callback that gathers the PHP names. The Use_ subtree is still skipped for
name collection (its node docblock is read first), and the root node's own docblock
is read up front, so the resolved set is unchanged - the stmts are walked once
instead of twice.

Adds bench/unused_import_resolve_bench.php, which bumps the stmts version per call
to force the walk each time and isolate the first-pass cost.
@TomasVotruba
TomasVotruba merged commit 3394f5b into main Oct 18, 2026
49 checks passed
@TomasVotruba
TomasVotruba deleted the tv-unused-import-single-walk branch October 18, 2026 22:36
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.

1 participant