From 719330d6c5efe5ae8cbe17f3940edca00e96e1b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 00:03:31 +0000 Subject: [PATCH] Fix missing type narrowing after (string)$value comparison - Added handling in TypeSpecifier::resolveNormalizedIdentical for Cast\String_ expressions - When (string)$x === '' or (string)$x !== '', propagate narrowing to inner expression $x - Types that produce '' when cast to string (null, false, '') are used as the narrowing type - New regression test in tests/PHPStan/Analyser/nsrt/bug-8231.php --- src/Analyser/TypeSpecifier.php | 21 ++++++++++++ tests/PHPStan/Analyser/nsrt/bug-8231.php | 42 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8231.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4f3e7f9c43..95610eb2d4 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2934,6 +2934,27 @@ private function resolveNormalizedIdentical(Expr\BinaryOp\Identical $expr, Scope } } + // (string)$expr === '' - propagate narrowing to inner expression + if ( + !$context->null() + && $unwrappedLeftExpr instanceof Expr\Cast\String_ + ) { + $rightConstantStrings = $rightType->getConstantStrings(); + if (count($rightConstantStrings) === 1 && $rightConstantStrings[0]->getValue() === '') { + // Types that produce '' when cast to string: null, false, '' + $castToEmptyStringType = TypeCombinator::union( + new NullType(), + new ConstantBooleanType(false), + new ConstantStringType(''), + ); + $innerExpr = $unwrappedLeftExpr->expr; + $result = $this->create($leftExpr, $rightType, $context, $scope)->setRootExpr($expr); + return $result->unionWith( + $this->create($innerExpr, $castToEmptyStringType, $context, $scope)->setRootExpr($expr), + ); + } + } + $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); if ($expressions !== null) { $exprNode = $expressions[0]; diff --git a/tests/PHPStan/Analyser/nsrt/bug-8231.php b/tests/PHPStan/Analyser/nsrt/bug-8231.php new file mode 100644 index 0000000000..7aaac90515 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8231.php @@ -0,0 +1,42 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug8231; + +use function PHPStan\Testing\assertType; + +function foo(string $x): void {} + +function test(string|null $x): void { + if ((string)$x !== '') { + assertType('non-empty-string', $x); + foo($x); + } +} + +function testIdentical(string|null $x): void { + if ((string)$x === '') { + assertType("''|null", $x); + } else { + assertType('non-empty-string', $x); + } +} + +function testInt(int|null $x): void { + if ((string)$x !== '') { + assertType('int', $x); + } +} + +function testIntString(int|string|null $x): void { + if ((string)$x !== '') { + assertType('int|non-empty-string', $x); + } +} + +function testBool(bool|string|null $x): void { + if ((string)$x !== '') { + assertType('non-empty-string|true', $x); + } +}