From b96ca49db4ce5cd7d396fbb28fbf67d42adba81e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 19 Oct 2026 10:58:33 +0200 Subject: [PATCH] [perf] Skip ancestor reflection when no parent:: call to trim in RemoveArgumentFromDefaultParentCallRector --- ...rgument_from_default_parent_call_bench.php | 100 ++++++++++++++++++ ...oveArgumentFromDefaultParentCallRector.php | 34 ++++++ 2 files changed, 134 insertions(+) create mode 100644 bench/remove_argument_from_default_parent_call_bench.php diff --git a/bench/remove_argument_from_default_parent_call_bench.php b/bench/remove_argument_from_default_parent_call_bench.php new file mode 100644 index 00000000000..2811e2dcb6a --- /dev/null +++ b/bench/remove_argument_from_default_parent_call_bench.php @@ -0,0 +1,100 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(RemoveArgumentFromDefaultParentCallRector::class); + +// generate a file: one parent with a defaulted constructor param, many children that extend it +// but only call an ordinary function - no parent:: call, so the rule has nothing to trim +$source = "parseFileToDecoratedNodes($sourceFilePath); + +/** @var Class_[] $classes */ +$classes = $betterNodeFinder->findInstanceOf($stmts, Class_::class); + +$cpuSeconds = static function (): float { + $usage = getrusage(); + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; +}; + +$run = static function () use ($rector, $classes): void { + foreach ($classes as $class) { + $rector->refactor($class); + } +}; + +// warm up autoloading/reflection caches once, excluded from timing +$run(); + +$wallStart = microtime(true); +$cpuStart = $cpuSeconds(); + +for ($repeat = 0; $repeat < REPEAT; ++$repeat) { + $run(); +} + +$cpu = $cpuSeconds() - $cpuStart; +$wall = microtime(true) - $wallStart; + +@unlink($sourceFilePath); + +$totalCalls = REPEAT * count($classes); + +printf('classes: %d, repeats: %d, refactor() calls: %d%s', count($classes), REPEAT, $totalCalls, PHP_EOL); +printf('CPU: %.4f s (%.3f us/call)%s', $cpu, $cpu / $totalCalls * 1_000_000, PHP_EOL); +printf('wall: %.4f s%s', $wall, PHP_EOL); diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveArgumentFromDefaultParentCallRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveArgumentFromDefaultParentCallRector.php index 00c46be33e8..8b772a358f5 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveArgumentFromDefaultParentCallRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveArgumentFromDefaultParentCallRector.php @@ -96,6 +96,11 @@ public function refactor(Node $node): ?Class_ return null; } + // no parent:: call to trim? skip the ancestor reflection lookup + if (! $this->hasParentStaticCall($node)) { + return null; + } + $classReflection = $this->reflectionResolver->resolveClassReflection($node); if (! $classReflection instanceof ClassReflection) { @@ -204,4 +209,33 @@ public function refactor(Node $node): ?Class_ return $node; } + + private function hasParentStaticCall(Class_ $class): bool + { + foreach ($class->getMethods() as $classMethod) { + if ($classMethod->isPrivate()) { + continue; + } + + if ($classMethod->isAbstract()) { + continue; + } + + foreach ((array) $classMethod->stmts as $stmt) { + if (! $stmt instanceof Expression) { + continue; + } + + if (! $stmt->expr instanceof StaticCall) { + continue; + } + + if ($this->isName($stmt->expr->class, 'parent')) { + return true; + } + } + } + + return false; + } }