From 198e0e339ebdfde82e9562641fdc2a2979638f3d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 31 Aug 2026 00:25:15 +0200 Subject: [PATCH 1/2] [DowngradePhp81] Skip PHP_VERSION_ID guarded hash() calls in DowngradeHashAlgorithmXxHashRector Mark hash() calls inside a PHP_VERSION_ID guarded ternary/if as version conditioned, so they are skipped like version_compare() guarded calls. Drops the dead scope-based check, which no longer resolves to an IntegerRangeType. Claude-Session: https://claude.ai/code/session_01UCwZigZyZFXK9SNVFLQsRi --- phpstan.neon | 1 + .../DowngradeHashAlgorithmXxHashRector.php | 54 +++++++++++++++---- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 88649322..757646d6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -103,6 +103,7 @@ parameters: - identifier: rector.avoidFeatureSetAttributeInRector paths: + - rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php - rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php - rules/DowngradePhp74/Rector/MethodCall/DowngradeReflectionGetTypeRector.php - rules/DowngradePhp80/Rector/MethodCall/DowngradeReflectionGetAttributesRector.php diff --git a/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php b/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php index 5a2304e8..d55cf996 100644 --- a/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php +++ b/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php @@ -6,14 +6,17 @@ use PhpParser\Node; use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\BinaryOp; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\Ternary; use PhpParser\Node\Scalar\String_; -use PHPStan\Type\IntegerRangeType; +use PhpParser\Node\Stmt\If_; use Rector\NodeAnalyzer\ArgsAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PhpParser\Node\Value\ValueResolver; -use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -42,6 +45,7 @@ final class DowngradeHashAlgorithmXxHashRector extends AbstractRector public function __construct( private readonly ArgsAnalyzer $argsAnalyzer, private readonly ValueResolver $valueResolver, + private readonly BetterNodeFinder $betterNodeFinder, ) { } @@ -80,14 +84,19 @@ public function run() */ public function getNodeTypes(): array { - return [FuncCall::class]; + return [Ternary::class, If_::class, FuncCall::class]; } /** - * @param FuncCall $node + * @param Ternary|If_|FuncCall $node */ public function refactor(Node $node): ?FuncCall { + if ($node instanceof Ternary || $node instanceof If_) { + $this->markGuardedHashCalls($node); + return null; + } + if ($this->shouldSkip($node)) { return null; } @@ -120,19 +129,42 @@ private function shouldSkip(FuncCall $funcCall): bool return true; } - if (! $this->isName($funcCall, 'hash')) { - return true; + return ! $this->isName($funcCall, 'hash'); + } + + /** + * Mark hash() calls guarded by a PHP_VERSION_ID check as version conditioned, + * so they are skipped like version_compare() guarded calls. + */ + private function markGuardedHashCalls(Ternary|If_ $node): void + { + if (! $this->hasPhpVersionIdCond($node->cond)) { + return; } - $scope = ScopeFetcher::fetch($funcCall); - $type = $scope->getPhpVersion() - ->getType(); + /** @var FuncCall[] $funcCalls */ + $funcCalls = $this->betterNodeFinder->findInstancesOf($node, [FuncCall::class]); + foreach ($funcCalls as $funcCall) { + if (! $this->isName($funcCall, 'hash')) { + continue; + } + + $funcCall->setAttribute(AttributeKey::PHP_VERSION_CONDITIONED, true); + } + } - if (! $type instanceof IntegerRangeType) { + private function hasPhpVersionIdCond(Expr $cond): bool + { + if (! $cond instanceof BinaryOp) { return false; } - return $type->getMin() === 80100; + return $this->isPhpVersionIdConstFetch($cond->left) || $this->isPhpVersionIdConstFetch($cond->right); + } + + private function isPhpVersionIdConstFetch(Expr $expr): bool + { + return $expr instanceof ConstFetch && $this->isName($expr, 'PHP_VERSION_ID'); } /** From d1ded5aedb4147b87af3b80c6d2365e45dca9ed6 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 30 Aug 2026 22:30:32 +0000 Subject: [PATCH 2/2] [rector] Rector fixes --- .../Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php b/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php index d55cf996..66bbce5f 100644 --- a/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php +++ b/rules/DowngradePhp81/Rector/FuncCall/DowngradeHashAlgorithmXxHashRector.php @@ -153,13 +153,13 @@ private function markGuardedHashCalls(Ternary|If_ $node): void } } - private function hasPhpVersionIdCond(Expr $cond): bool + private function hasPhpVersionIdCond(Expr $expr): bool { - if (! $cond instanceof BinaryOp) { + if (! $expr instanceof BinaryOp) { return false; } - return $this->isPhpVersionIdConstFetch($cond->left) || $this->isPhpVersionIdConstFetch($cond->right); + return $this->isPhpVersionIdConstFetch($expr->left) || $this->isPhpVersionIdConstFetch($expr->right); } private function isPhpVersionIdConstFetch(Expr $expr): bool