From 5e252bd06b7acf3c37ae7cefa5f2a1e2122ba739 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 3 Aug 2025 21:21:20 +0200 Subject: [PATCH 1/3] Fix 10367 --- src/Type/Php/ArrayColumnHelper.php | 2 +- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 7 +++++ .../Rules/Variables/data/bug-10367.php | 30 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-10367.php diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index 22e7eb69d0..48710fbf92 100644 --- a/src/Type/Php/ArrayColumnHelper.php +++ b/src/Type/Php/ArrayColumnHelper.php @@ -145,7 +145,7 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ if (!$type->canAccessProperties()->no()) { $propertyTypes = $offsetOrProperty->getConstantStrings(); if ($propertyTypes === []) { - return new MixedType(); + return $allowMaybe ? new MixedType() : null; } foreach ($propertyTypes as $propertyType) { $propertyName = $propertyType->getValue(); diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index f0c0343340..98a52dfae7 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -207,6 +207,13 @@ public function testBug12658(): void $this->analyse([__DIR__ . '/data/bug-12658.php'], []); } + public function testBug10367(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-10367.php'], []); + } + #[RequiresPhp('>= 8.0')] public function testIssetAfterRememberedConstructor(): void { diff --git a/tests/PHPStan/Rules/Variables/data/bug-10367.php b/tests/PHPStan/Rules/Variables/data/bug-10367.php new file mode 100644 index 0000000000..a9a433ea55 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-10367.php @@ -0,0 +1,30 @@ + 123, 2 => 413, 4 => 132], + [1 => 123, 2 => 413, 4 => 132], +]; + +$packingSlipPdf = new PackingSlipPdf($testArray, "TestName"); From 26476e034f688b32be8b73db7dcc044b195effd3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 3 Aug 2025 21:36:18 +0200 Subject: [PATCH 2/3] Avoid duplicate calls --- src/Type/Php/ArrayColumnHelper.php | 52 +++++++++++++----------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index 48710fbf92..f153e1bbbc 100644 --- a/src/Type/Php/ArrayColumnHelper.php +++ b/src/Type/Php/ArrayColumnHelper.php @@ -39,14 +39,10 @@ public function getReturnValueType(Type $arrayType, Type $columnType, Scope $sco } $iterableValueType = $arrayType->getIterableValueType(); - $returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false); + [$returnValueType, $certainty] = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope); - if ($returnValueType === null) { - $returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, true); + if (!$certainty->yes()) { $iterableAtLeastOnce = TrinaryLogic::createMaybe(); - if ($returnValueType === null) { - throw new ShouldNotHappenException(); - } } return [$returnValueType, $iterableAtLeastOnce]; @@ -57,15 +53,12 @@ public function getReturnIndexType(Type $arrayType, Type $indexType, Scope $scop if (!$indexType->isNull()->yes()) { $iterableValueType = $arrayType->getIterableValueType(); - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); - if ($type !== null) { + [$type, $certainty] = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope); + if ($certainty->yes()) { return $type; } - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true); - if ($type !== null) { - return TypeCombinator::union($type, new IntegerType()); - } + return TypeCombinator::union($type, new IntegerType()); } return new IntegerType(); @@ -96,8 +89,8 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy $builder = ConstantArrayTypeBuilder::createEmpty(); foreach ($arrayType->getValueTypes() as $i => $iterableValueType) { - $valueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false); - if ($valueType === null) { + [$valueType, $certainty] = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope); + if (!$certainty->yes()) { return null; } if ($valueType instanceof NeverType) { @@ -105,16 +98,11 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy } if (!$indexType->isNull()->yes()) { - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); - if ($type !== null) { + [$type, $certainty] = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope); + if ($certainty->yes()) { $keyType = $type; } else { - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true); - if ($type !== null) { - $keyType = TypeCombinator::union($type, new IntegerType()); - } else { - $keyType = null; - } + $keyType = TypeCombinator::union($type, new IntegerType()); } } else { $keyType = null; @@ -129,11 +117,14 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy return $builder->getArray(); } - private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope, bool $allowMaybe): ?Type + /** + * @return array{Type, TrinaryLogic} + */ + private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope): array { $offsetIsNull = $offsetOrProperty->isNull(); if ($offsetIsNull->yes()) { - return $type; + return [$type, TrinaryLogic::createYes()]; } $returnTypes = []; @@ -145,13 +136,13 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ if (!$type->canAccessProperties()->no()) { $propertyTypes = $offsetOrProperty->getConstantStrings(); if ($propertyTypes === []) { - return $allowMaybe ? new MixedType() : null; + return [new MixedType(), TrinaryLogic::createMaybe()]; } foreach ($propertyTypes as $propertyType) { $propertyName = $propertyType->getValue(); $hasProperty = $type->hasProperty($propertyName); if ($hasProperty->maybe()) { - return $allowMaybe ? new MixedType() : null; + return [new MixedType(), TrinaryLogic::createMaybe()]; } if (!$hasProperty->yes()) { continue; @@ -161,10 +152,11 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ } } + $certainty = TrinaryLogic::createYes(); if ($type->isOffsetAccessible()->yes()) { $hasOffset = $type->hasOffsetValueType($offsetOrProperty); - if (!$allowMaybe && $hasOffset->maybe()) { - return null; + if ($hasOffset->maybe()) { + $certainty = TrinaryLogic::createMaybe(); } if (!$hasOffset->no()) { $returnTypes[] = $type->getOffsetValueType($offsetOrProperty); @@ -172,10 +164,10 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ } if ($returnTypes === []) { - return new NeverType(); + return [new NeverType(), TrinaryLogic::createYes()]; } - return TypeCombinator::union(...$returnTypes); + return [TypeCombinator::union(...$returnTypes), $certainty]; } private function castToArrayKeyType(Type $type): Type From 082440e1dd108523603f47ad3bb2e5d53655eb9f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 3 Aug 2025 21:39:20 +0200 Subject: [PATCH 3/3] Cs --- src/Type/Php/ArrayColumnHelper.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index f153e1bbbc..d1d40e8cd2 100644 --- a/src/Type/Php/ArrayColumnHelper.php +++ b/src/Type/Php/ArrayColumnHelper.php @@ -5,7 +5,6 @@ use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; -use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\NonEmptyArrayType;