diff --git a/bench/remove_dead_instanceof_assert_name_type_bench.php b/bench/remove_dead_instanceof_assert_name_type_bench.php new file mode 100644 index 00000000000..f8feeb2202d --- /dev/null +++ b/bench/remove_dead_instanceof_assert_name_type_bench.php @@ -0,0 +1,95 @@ +class), which runs the type resolver over a class name + * node whose name is already known. + * + * The change reads a concrete class name straight off the node (new ObjectType($name)); the + * equals()/isSuperTypeOf() comparison against the expression type is unchanged, so the decision is + * identical. self/static/parent depend on the enclosing class, so those fall back to getType(). + * + * This bench parses a file of many functions, each with a typed param and a matching dead + * instanceof assert, attaches scope (as the real run does), then drives refactor() over every + * statement-aware node. + * + * Run on both branches to compare: + * php bench/remove_dead_instanceof_assert_name_type_bench.php + * + * Reports CPU time (user+sys) and wall time; CPU is the reliable metric, wall is noisy under + * concurrent load. + */ + +use PhpParser\Node\Stmt\Function_; +use Rector\DeadCode\Rector\StmtsAwareInterface\RemoveDeadInstanceOfAssertRector; +use Rector\DependencyInjection\LazyContainerFactory; +use Rector\PhpParser\Node\BetterNodeFinder; +use Rector\Testing\TestingParser\TestingParser; + +require __DIR__ . '/../vendor/autoload.php'; + +const FUNCTIONS = 150; // functions with a dead instanceof assert +const REPEAT = 60; // outer repeats over the whole set + +$rectorConfig = (new LazyContainerFactory())->create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +/** @var BetterNodeFinder $betterNodeFinder */ +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); + +$rector = $rectorConfig->make(RemoveDeadInstanceOfAssertRector::class); + +$source = "parseFileToDecoratedNodes($sourceFilePath); + +/** @var Function_[] $functions */ +$functions = $betterNodeFinder->findInstanceOf($stmts, Function_::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, $functions): void { + foreach ($functions as $function) { + // clone so the removal in one pass does not empty the body for the next repeat + $rector->refactor(clone $function); + } +}; + +// 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($functions); + +printf('functions: %d, repeats: %d, refactor() calls: %d%s', count($functions), 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-tests/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector/Fixture/skip_self_instanceof.php.inc b/rules-tests/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector/Fixture/skip_self_instanceof.php.inc new file mode 100644 index 00000000000..a00cbad1d19 --- /dev/null +++ b/rules-tests/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector/Fixture/skip_self_instanceof.php.inc @@ -0,0 +1,13 @@ +item instanceof self); + } +} diff --git a/rules/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector.php b/rules/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector.php index cd053d6d130..3df8ac5cc37 100644 --- a/rules/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector.php +++ b/rules/DeadCode/Rector/StmtsAwareInterface/RemoveDeadInstanceOfAssertRector.php @@ -14,6 +14,9 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Nop; +use PHPStan\Type\ObjectType; +use PHPStan\Type\Type; +use Rector\Enum\ObjectReference; use Rector\PhpParser\Enum\NodeGroup; use Rector\Rector\AbstractRector; use Rector\RuleDoc\CodeSample\CodeSample; @@ -143,7 +146,7 @@ private function isDeadInstanceOfAssert(Expr $expr): bool return false; } - $classType = $this->getType($instanceof->class); + $classType = $this->resolveClassNameType($instanceof->class); $exprType = $this->getType($instanceof->expr); if ($classType->equals($exprType)) { return true; @@ -152,4 +155,21 @@ private function isDeadInstanceOfAssert(Expr $expr): bool return $classType->isSuperTypeOf($exprType) ->yes(); } + + private function resolveClassNameType(Name $name): Type + { + // a concrete class name is resolvable straight from the node, no type inference needed; + // self/static/parent depend on the enclosing class, so fall back to scope-based type + $className = $this->getName($name); + if ($className === null) { + return $this->getType($name); + } + + $loweredClassName = strtolower($className); + if (in_array($loweredClassName, [ObjectReference::SELF, ObjectReference::STATIC, ObjectReference::PARENT], true)) { + return $this->getType($name); + } + + return new ObjectType($className); + } }