From 74b7109c914ac26494509f7b55376c143cd85aaa Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Oct 2026 16:46:09 +0200 Subject: [PATCH 1/2] [perf] Speed up CodeQuality rules with cheap guards before type resolution, reflection and subtree walks --- .../RepeatedAndNotEqualToNotInArrayRector.php | 14 +++++++------- .../BooleanOr/RepeatedOrEqualToInArrayRector.php | 2 +- .../Catch_/ThrowWithPreviousExceptionRector.php | 6 +++--- .../SimplifyEmptyCheckOnEmptyArrayRector.php | 8 ++++---- .../Rector/FuncCall/InlineIsAInstanceOfRector.php | 13 +++++++++---- ...IssetOnPropertyObjectToPropertyExistsRector.php | 10 +++++----- ...naryEmptyArrayArrayDimFetchToCoalesceRector.php | 8 ++++---- 7 files changed, 33 insertions(+), 28 deletions(-) diff --git a/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php b/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php index 5b9daf0dd9c..f5f5cee6cfd 100644 --- a/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php +++ b/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php @@ -169,14 +169,14 @@ private function matchComparedAndDesiredValues(BooleanAnd $booleanAnd): ?array private function isStrictComparison(BooleanAnd $booleanAnd): bool { $notIdenticals = $this->betterNodeFinder->findInstanceOf($booleanAnd, NotIdentical::class); - $notEquals = $this->betterNodeFinder->findInstanceOf($booleanAnd, NotEqual::class); - - if ($notIdenticals !== []) { - // mix not identical and not equals, keep as is - // @see https://3v4l.org/2SoHZ - return $notEquals === []; + if ($notIdenticals === []) { + return false; } - return false; + $notEquals = $this->betterNodeFinder->findInstanceOf($booleanAnd, NotEqual::class); + + // mix not identical and not equals, keep as is + // @see https://3v4l.org/2SoHZ + return $notEquals === []; } } diff --git a/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php b/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php index f49fa8fab74..7a9fa75425a 100644 --- a/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php +++ b/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php @@ -93,9 +93,9 @@ public function refactor(Node $node): ?FuncCall } $identicals = $this->betterNodeFinder->findInstanceOf($node, Identical::class); - $equals = $this->betterNodeFinder->findInstanceOf($node, Equal::class); if ($identicals !== []) { + $equals = $this->betterNodeFinder->findInstanceOf($node, Equal::class); if ($equals !== []) { // mix identical and equals, keep as is // @see https://3v4l.org/24cFl diff --git a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php index 17216fc046c..73b177e3038 100644 --- a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php +++ b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php @@ -130,12 +130,12 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) return null; } - $exceptionArgumentPosition = $this->resolveExceptionArgumentPosition($new->class); - if ($exceptionArgumentPosition === null) { + if ($new->isFirstClassCallable()) { return null; } - if ($new->isFirstClassCallable()) { + $exceptionArgumentPosition = $this->resolveExceptionArgumentPosition($new->class); + if ($exceptionArgumentPosition === null) { return null; } diff --git a/rules/CodeQuality/Rector/Empty_/SimplifyEmptyCheckOnEmptyArrayRector.php b/rules/CodeQuality/Rector/Empty_/SimplifyEmptyCheckOnEmptyArrayRector.php index b6f7bf2452b..933c7b321ac 100644 --- a/rules/CodeQuality/Rector/Empty_/SimplifyEmptyCheckOnEmptyArrayRector.php +++ b/rules/CodeQuality/Rector/Empty_/SimplifyEmptyCheckOnEmptyArrayRector.php @@ -106,6 +106,10 @@ private function isAllowedVariable(Variable $variable): bool private function isAllowedExpr(Expr $expr, Scope $scope): bool { + if (! $expr instanceof Variable && ! $expr instanceof PropertyFetch && ! $expr instanceof StaticPropertyFetch) { + return false; + } + if (! $scope->getType($expr)->isArray()->yes()) { return false; } @@ -114,10 +118,6 @@ private function isAllowedExpr(Expr $expr, Scope $scope): bool return $this->isAllowedVariable($expr); } - if (! $expr instanceof PropertyFetch && ! $expr instanceof StaticPropertyFetch) { - return false; - } - if (! $expr->name instanceof Identifier) { return false; } diff --git a/rules/CodeQuality/Rector/FuncCall/InlineIsAInstanceOfRector.php b/rules/CodeQuality/Rector/FuncCall/InlineIsAInstanceOfRector.php index d1cd7d1954d..509979bb0b0 100644 --- a/rules/CodeQuality/Rector/FuncCall/InlineIsAInstanceOfRector.php +++ b/rules/CodeQuality/Rector/FuncCall/InlineIsAInstanceOfRector.php @@ -73,8 +73,13 @@ public function refactor(Node $node): ?Node } $args = $node->getArgs(); - $firstArgValue = $args[0]->value; + $secondArgValue = $args[1]->value; + if (! $secondArgValue instanceof Variable && ! $secondArgValue instanceof ClassConstFetch) { + return null; + } + + $firstArgValue = $args[0]->value; if (! $this->isFirstObjectType($firstArgValue)) { return null; } @@ -82,11 +87,11 @@ public function refactor(Node $node): ?Node /** * instanceof with Variable is ok, while on FuncCal with instanceof cause fatal error, see https://3v4l.org/IHb30 */ - if ($args[1]->value instanceof Variable) { - return new Instanceof_($firstArgValue, $args[1]->value); + if ($secondArgValue instanceof Variable) { + return new Instanceof_($firstArgValue, $secondArgValue); } - $className = $this->resolveClassName($args[1]->value); + $className = $this->resolveClassName($secondArgValue); if ($className === null) { return null; } diff --git a/rules/CodeQuality/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php b/rules/CodeQuality/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php index 7facda73b16..a2cd4c7679a 100644 --- a/rules/CodeQuality/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php +++ b/rules/CodeQuality/Rector/Isset_/IssetOnPropertyObjectToPropertyExistsRector.php @@ -106,6 +106,11 @@ public function refactor(Node $node): ?Node continue; } + // possibly by docblock, dynamic array access + if ($issetExpr->var instanceof ArrayDimFetch) { + continue; + } + // has property PHP 7.4 type? if ($this->shouldSkipForPropertyTypeDeclaration($issetExpr)) { continue; @@ -126,11 +131,6 @@ public function refactor(Node $node): ?Node continue; } - // possibly by docblock - if ($issetExpr->var instanceof ArrayDimFetch) { - continue; - } - if (! $classReflection->hasInstanceProperty($propertyFetchName) || $classReflection->isBuiltin()) { $newNodes[] = $this->replaceToPropertyExistsWithNullCheck( $issetExpr->var, diff --git a/rules/CodeQuality/Rector/Ternary/TernaryEmptyArrayArrayDimFetchToCoalesceRector.php b/rules/CodeQuality/Rector/Ternary/TernaryEmptyArrayArrayDimFetchToCoalesceRector.php index 03aad4914bc..363a6e6eabc 100644 --- a/rules/CodeQuality/Rector/Ternary/TernaryEmptyArrayArrayDimFetchToCoalesceRector.php +++ b/rules/CodeQuality/Rector/Ternary/TernaryEmptyArrayArrayDimFetchToCoalesceRector.php @@ -77,13 +77,13 @@ public function refactor(Node $node): ?Node return null; } - $emptyExprType = $this->getType($negagedExpr->expr); - if (! $emptyExprType->isArray()->yes()) { + $dimFetchVar = $node->if->var; + if (! $this->nodeComparator->areNodesEqual($negagedExpr->expr, $dimFetchVar)) { return null; } - $dimFetchVar = $node->if->var; - if (! $this->nodeComparator->areNodesEqual($negagedExpr->expr, $dimFetchVar)) { + $emptyExprType = $this->getType($negagedExpr->expr); + if (! $emptyExprType->isArray()->yes()) { return null; } From 58dce6e9bcc2d65a9ed67c49e58b60cf02366592 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Oct 2026 16:46:13 +0200 Subject: [PATCH 2/2] [Php83] Skip ReadOnlyAnonymousClassRector on anonymous class with attributes --- .../skip_anonymous_class_with_attribute.php.inc | 15 +++++++++++++++ .../Class_/ReadOnlyAnonymousClassRector.php | 5 +++++ 2 files changed, 20 insertions(+) create mode 100644 rules-tests/Php83/Rector/Class_/ReadOnlyAnonymousClassRector/Fixture/skip_anonymous_class_with_attribute.php.inc diff --git a/rules-tests/Php83/Rector/Class_/ReadOnlyAnonymousClassRector/Fixture/skip_anonymous_class_with_attribute.php.inc b/rules-tests/Php83/Rector/Class_/ReadOnlyAnonymousClassRector/Fixture/skip_anonymous_class_with_attribute.php.inc new file mode 100644 index 00000000000..a64c6cc563d --- /dev/null +++ b/rules-tests/Php83/Rector/Class_/ReadOnlyAnonymousClassRector/Fixture/skip_anonymous_class_with_attribute.php.inc @@ -0,0 +1,15 @@ +attempts; + } +}; diff --git a/rules/Php83/Rector/Class_/ReadOnlyAnonymousClassRector.php b/rules/Php83/Rector/Class_/ReadOnlyAnonymousClassRector.php index e1206e3df49..c12646158be 100644 --- a/rules/Php83/Rector/Class_/ReadOnlyAnonymousClassRector.php +++ b/rules/Php83/Rector/Class_/ReadOnlyAnonymousClassRector.php @@ -68,6 +68,11 @@ public function refactor(Node $node): ?Node return null; } + // attribute between "new" and "class" cannot be format-preserving printed with an added readonly modifier + if ($node->attrGroups !== []) { + return null; + } + return $this->readonlyClassManipulator->process($node); }