From e9c69b2097be39468b2be6a2dcf4a9867abaa884 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 17 Oct 2026 17:21:24 +0200 Subject: [PATCH] skip NullToStrictString arg already cast to target type Guard against nesting a redundant cast when the converter re-processes a value already cast or literal of the target type (or a ternary whose branches are). Structural check, immune to stale resolved types on re-printed nodes. --- .../NullToStrictStringIntConverter.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php b/rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php index 6a2c3454d66..862c5a48868 100644 --- a/rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php +++ b/rules/Php81/NodeManipulator/NullToStrictStringIntConverter.php @@ -98,6 +98,11 @@ public function convertIfNull( return $funcCall; } + // already cast to the target type on a previous run, re-casting would nest a redundant cast + if ($this->isAlreadyCastToTargetType($argValue, $targetType)) { + return null; + } + $wrapInParentheses = false; if ($argValue instanceof Ternary && $argValue->cond instanceof CastString_) { $wrapInParentheses = true; @@ -118,6 +123,26 @@ public function convertIfNull( return $funcCall; } + /** + * Structural check (immune to stale resolved types on re-printed nodes): the value is already a + * cast/literal of the target type, or a ternary whose both branches are. + */ + private function isAlreadyCastToTargetType(Expr $expr, string $targetType): bool + { + if ($expr instanceof Ternary) { + // short ternary "A ?: B" keeps the left value in cond; full ternary uses if + $left = $expr->if ?? $expr->cond; + return $this->isAlreadyCastToTargetType($left, $targetType) + && $this->isAlreadyCastToTargetType($expr->else, $targetType); + } + + if ($targetType === 'string') { + return $expr instanceof CastString_ || $expr instanceof String_ || $expr instanceof InterpolatedString; + } + + return $expr instanceof CastInt_ || $expr instanceof Int_; + } + private function shouldSkipValue(Expr $expr, Scope $scope, bool $isTrait, string $targetType): bool { // array dim fetch value is mixed, null is not known - skip to avoid wrong (string) cast