From fa843bfb18266a5acca204ebe916d3f97a907b7a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 26 Jul 2025 21:03:41 +0200 Subject: [PATCH] Fix object cast to array --- src/Type/ObjectType.php | 14 +++++- ...mpossibleCheckTypeFunctionCallRuleTest.php | 11 ++++ .../Rules/Comparison/data/bug-2730.php | 50 +++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-2730.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 78e4473410..d734878e69 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -36,6 +36,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; @@ -693,8 +694,17 @@ public function toArray(): Type $classReflection = $classReflection->getParentClass(); } while ($classReflection !== null); - if (!$isFinal && count($arrayKeys) === 0) { - return new ArrayType(new MixedType(), new MixedType()); + if (!$isFinal) { + if (count($arrayKeys) === 0) { + return new ArrayType(new MixedType(), new MixedType()); + } + + $types = [new ArrayType(new MixedType(), new MixedType())]; + foreach ($arrayKeys as $i => $arrayKey) { + $types[] = new HasOffsetValueType($arrayKey, $arrayValues[$i]); + } + + return new IntersectionType($types); } return new ConstantArrayType($arrayKeys, $arrayValues); diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 132ec6bfa2..de04623be6 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1025,6 +1025,17 @@ public function testBug12412(): void $this->analyse([__DIR__ . '/data/bug-12412.php'], []); } + public function testBug2730(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-2730.php'], [ + [ + 'Call to function is_object() with int will always evaluate to false.', + 43, + ], + ]); + } + #[RequiresPhp('>= 8.2')] public function testBug13291(): void { diff --git a/tests/PHPStan/Rules/Comparison/data/bug-2730.php b/tests/PHPStan/Rules/Comparison/data/bug-2730.php new file mode 100644 index 0000000000..d7e6c8801b --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-2730.php @@ -0,0 +1,50 @@ + $v){ + if(is_string($v)){ + echo "string\n"; + }elseif(is_object($v)){ + echo "object\n"; + }else{ + echo gettype($v) . "\n"; + } + } + } +} + +class B extends A{ + /** @var \stdClass */ + public $obj; + + public function __construct(){ + $this->obj = new \stdClass; + } +} + +final class C{ + /** @var string */ + public $a = "hi"; + /** @var int */ + public $b = 0; + + public function dummy() : void{ + foreach((array) $this as $k => $v){ + if(is_string($v)){ + echo "string\n"; + }elseif(is_object($v)){ + echo "object\n"; + }else{ + echo gettype($v) . "\n"; + } + } + } +}