diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index 22e7eb69d0..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; @@ -39,14 +38,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 +52,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 +88,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 +97,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 +116,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 +135,13 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ if (!$type->canAccessProperties()->no()) { $propertyTypes = $offsetOrProperty->getConstantStrings(); if ($propertyTypes === []) { - return new MixedType(); + 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 +151,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 +163,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 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");