diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 1a1ed14bae..b64b82936b 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -948,7 +948,8 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar $offsetValueTypeStack = [$offsetValueType]; $generalizeOnWrite = $offsetTypes[array_key_last($offsetTypes)][0] !== null; - foreach (array_slice($offsetTypes, 0, -1) as [$offsetType, $dimFetch]) { + $slicedOffsets = array_slice($offsetTypes, 0, -1); + foreach ($slicedOffsets as $k => [$offsetType, $dimFetch]) { if ($offsetType === null) { $offsetValueType = new ConstantArrayType([], []); $generalizeOnWrite = false; @@ -959,7 +960,13 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar } elseif ($has->maybe()) { if ($scope->hasExpressionType($dimFetch)->yes()) { $generalizeOnWrite = false; - $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); + $scopeType = $scope->getType($dimFetch); + $nextOffsetType = $offsetTypes[$k + 1][0] ?? null; + if ($nextOffsetType !== null && $scopeType->hasOffsetValueType($nextOffsetType)->yes()) { + $offsetValueType = $scopeType; + } else { + $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); + } } else { $offsetValueType = TypeCombinator::union($offsetValueType->getOffsetValueType($offsetType), new ConstantArrayType([], [])); } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 42fe96957d..778aef896c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3852,6 +3852,18 @@ private function generalizeVariableTypeHolders( continue; } + if (isset($otherVariableTypeHolders[$variableExprString])) { + $thisType = $variableTypeHolder->getType(); + $otherType = $otherVariableTypeHolders[$variableExprString]->getType(); + if ( + $thisType->isConstantArray()->yes() + && $otherType->isConstantArray()->yes() + && $thisType->getIterableKeyType()->equals($otherType->getIterableKeyType()) + ) { + break; + } + } + continue 2; } if (!isset($otherVariableTypeHolders[$variableExprString])) { diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index e239384855..f202fb6583 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -1263,4 +1263,11 @@ public function testBug14308(): void $this->analyse([__DIR__ . '/data/bug-14308.php'], []); } + public function testBug13669(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-13669.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-13669.php b/tests/PHPStan/Rules/Arrays/data/bug-13669.php new file mode 100644 index 0000000000..4b2b674c06 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-13669.php @@ -0,0 +1,48 @@ +> + */ + private array $mailCounts; + + /** @var array> */ + private array $sources; + + /** @param array> $sources */ + private function __construct(array $sources) + { + $this->mailCounts = []; + $this->sources = $sources; + } + + public function countMailStates(): void + { + foreach ($this->sources as $templateId => $mails) { + $this->mailCounts[$templateId] = [ + MailStatus::CODE_DELETED => 0, + MailStatus::CODE_NOT_ACTIVE => 0, + MailStatus::CODE_ACTIVE => 0, + MailStatus::CODE_SIMULATION => 0, + ]; + + foreach ($mails as $mail) { + ++$this->mailCounts[$templateId][$mail]; + } + } + } +} + +final class MailStatus +{ + public const CODE_DELETED = -1; + + public const CODE_NOT_ACTIVE = 0; + + public const CODE_SIMULATION = 1; + + public const CODE_ACTIVE = 2; +}