From 894acfe3fb807644148379c19b192149759ae985 Mon Sep 17 00:00:00 2001 From: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:08:15 +0000 Subject: [PATCH 1/3] Fix settype() not handled properly for non-constant type strings - When settype()'s second argument is a non-constant string, broaden the variable type to all possible settype outcomes (bool|int|float|string|array|object|null) - Previously, non-constant type strings caused the extension to return empty SpecifiedTypes, leaving the variable type unchanged - New regression test in tests/PHPStan/Analyser/nsrt/bug-3250.php --- ...SetTypeFunctionTypeSpecifyingExtension.php | 21 +++++++++++- tests/PHPStan/Analyser/nsrt/bug-3250.php | 33 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-3250.php diff --git a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php index 88c9dd86fcd..e0093a3beac 100644 --- a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php @@ -10,10 +10,16 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\ArrayType; +use PHPStan\Type\BooleanType; use PHPStan\Type\ErrorType; +use PHPStan\Type\FloatType; use PHPStan\Type\FunctionTypeSpecifyingExtension; +use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; +use PHPStan\Type\StringType; use PHPStan\Type\TypeCombinator; use stdClass; use function count; @@ -41,7 +47,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $constantStrings = $castType->getConstantStrings(); if (count($constantStrings) < 1) { - return new SpecifiedTypes(); + return $this->typeSpecifier->create( + $value, + TypeCombinator::union( + new BooleanType(), + new IntegerType(), + new FloatType(), + new StringType(), + new ArrayType(new MixedType(), new MixedType()), + new ObjectType(stdClass::class), + new NullType(), + ), + TypeSpecifierContext::createTruthy(), + $scope, + )->setAlwaysOverwriteTypes(); } $types = []; diff --git a/tests/PHPStan/Analyser/nsrt/bug-3250.php b/tests/PHPStan/Analyser/nsrt/bug-3250.php new file mode 100644 index 00000000000..0e88bf6d992 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-3250.php @@ -0,0 +1,33 @@ + Date: Sun, 5 Apr 2026 22:35:10 +0000 Subject: [PATCH 2/3] Refactor settype() extension: reuse foreach for non-constant types, fix object case Instead of returning a hardcoded union for non-constant type strings, populate $constantStrings with all valid values and let the existing foreach handle them. This avoids duplicating the type mapping logic. Fix the 'object' case to preserve the original type when the value is already an object (e.g. settype($dateTime, 'object') keeps DateTime), instead of always returning stdClass. Co-Authored-By: Claude Opus 4.6 --- ...SetTypeFunctionTypeSpecifyingExtension.php | 42 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-3250.php | 30 ++++++++++++- .../nsrt/set-type-type-specifying.php | 4 +- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php index e0093a3beac..52cd09b083b 100644 --- a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php @@ -10,16 +10,12 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\ArrayType; -use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; -use PHPStan\Type\FloatType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; -use PHPStan\Type\StringType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\TypeCombinator; use stdClass; use function count; @@ -47,20 +43,15 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $constantStrings = $castType->getConstantStrings(); if (count($constantStrings) < 1) { - return $this->typeSpecifier->create( - $value, - TypeCombinator::union( - new BooleanType(), - new IntegerType(), - new FloatType(), - new StringType(), - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(stdClass::class), - new NullType(), - ), - TypeSpecifierContext::createTruthy(), - $scope, - )->setAlwaysOverwriteTypes(); + $constantStrings = [ + new ConstantStringType('bool'), + new ConstantStringType('int'), + new ConstantStringType('float'), + new ConstantStringType('string'), + new ConstantStringType('array'), + new ConstantStringType('object'), + new ConstantStringType('null'), + ]; } $types = []; @@ -86,7 +77,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $types[] = $valueType->toArray(); break; case 'object': - $types[] = new ObjectType(stdClass::class); + if ($valueType->isObject()->yes()) { + $types[] = $valueType; + } elseif ($valueType->isObject()->no()) { + $types[] = new ObjectType(stdClass::class); + } else { + $types[] = TypeCombinator::union( + TypeCombinator::intersect($valueType, new ObjectWithoutClassType()), + new ObjectType(stdClass::class), + ); + } break; case 'null': $types[] = new NullType(); diff --git a/tests/PHPStan/Analyser/nsrt/bug-3250.php b/tests/PHPStan/Analyser/nsrt/bug-3250.php index 0e88bf6d992..ec52c27fe9f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3250.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3250.php @@ -2,6 +2,7 @@ namespace Bug3250; +use DateTime; use stdClass; use function PHPStan\Testing\assertType; @@ -10,7 +11,7 @@ function castTo(string $value, string $castTo): void $newValue = $value; settype($newValue, $castTo); - assertType('array|bool|float|int|stdClass|string|null', $newValue); + assertType('array{string}|bool|float|int|stdClass|string|null', $newValue); } function castToInt(string $value): void @@ -31,3 +32,30 @@ function castToIntOrFloat(string $value, string $castTo): void assertType('float|int', $newValue); } + +function castObjectToObject(DateTime $value): void +{ + $newValue = $value; + settype($newValue, 'object'); + + assertType('DateTime', $newValue); +} + +/** + * @param DateTime|string $value + */ +function castMixedToObject($value): void +{ + $newValue = $value; + settype($newValue, 'object'); + + assertType('DateTime|stdClass', $newValue); +} + +function castStringToObject(string $value): void +{ + $newValue = $value; + settype($newValue, 'object'); + + assertType('stdClass', $newValue); +} diff --git a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php index f7513c60450..ef0f894c639 100644 --- a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php @@ -146,7 +146,7 @@ function doObject(string $s, int $i, float $f, array $a, object $o) assertType('stdClass', $a); settype($o, 'object'); - assertType('stdClass', $o); + assertType('object', $o); } function doNull(string $s, int $i, float $f, array $a, object $o) @@ -669,5 +669,5 @@ function setTypeSpecifying($value, string $castTo): void // Mixed to non-constant. settype($value, $castTo); - assertType("array|bool|float|int|stdClass|string|null", $value); + assertType("array|bool|float|int|object|string|null", $value); } From 277b59b2ae4958e5dcba81a1ef9c3aaf9251e940 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sun, 5 Apr 2026 22:45:08 +0000 Subject: [PATCH 3/3] Cache isObject() result in local variable to avoid redundant computation Co-Authored-By: Claude Opus 4.6 --- src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php index 52cd09b083b..c14d6094402 100644 --- a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php @@ -77,9 +77,10 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $types[] = $valueType->toArray(); break; case 'object': - if ($valueType->isObject()->yes()) { + $isObject = $valueType->isObject(); + if ($isObject->yes()) { $types[] = $valueType; - } elseif ($valueType->isObject()->no()) { + } elseif ($isObject->no()) { $types[] = new ObjectType(stdClass::class); } else { $types[] = TypeCombinator::union(