Skip to content

Commit 23dc4e7

Browse files
committed
When assigning nested array offset, union the value with empty array if the offset might be nonexistent
1 parent 9eb45d9 commit 23dc4e7

File tree

4 files changed

+40
-16
lines changed

4 files changed

+40
-16
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5659,12 +5659,12 @@ private function processAssignVar(
56595659
}
56605660

56615661
if ($dimExpr === null) {
5662-
$offsetTypes[] = null;
5663-
$offsetNativeTypes[] = null;
5662+
$offsetTypes[] = [null, $dimFetch];
5663+
$offsetNativeTypes[] = [null, $dimFetch];
56645664

56655665
} else {
5666-
$offsetTypes[] = $scope->getType($dimExpr);
5667-
$offsetNativeTypes[] = $scope->getNativeType($dimExpr);
5666+
$offsetTypes[] = [$scope->getType($dimExpr), $dimFetch];
5667+
$offsetNativeTypes[] = [$scope->getNativeType($dimExpr), $dimFetch];
56685668

56695669
if ($enterExpressionAssign) {
56705670
$scope->enterExpressionAssign($dimExpr);
@@ -5712,8 +5712,8 @@ private function processAssignVar(
57125712
[$nativeValueToWrite, $additionalNativeExpressions] = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope);
57135713
} else {
57145714
$rewritten = false;
5715-
foreach ($offsetTypes as $i => $offsetType) {
5716-
$offsetNativeType = $offsetNativeTypes[$i];
5715+
foreach ($offsetTypes as $i => [$offsetType]) {
5716+
[$offsetNativeType] = $offsetNativeTypes[$i];
57175717

57185718
if ($offsetType === null) {
57195719
if ($offsetNativeType !== null) {
@@ -6046,8 +6046,8 @@ static function (): void {
60466046
$offsetNativeTypes = [];
60476047
foreach (array_reverse($dimFetchStack) as $dimFetch) {
60486048
$dimExpr = $dimFetch->getDim();
6049-
$offsetTypes[] = $scope->getType($dimExpr);
6050-
$offsetNativeTypes[] = $scope->getNativeType($dimExpr);
6049+
$offsetTypes[] = [$scope->getType($dimExpr), $dimFetch];
6050+
$offsetNativeTypes[] = [$scope->getNativeType($dimExpr), $dimFetch];
60516051
}
60526052

60536053
$valueToWrite = $scope->getType($assignedExpr);
@@ -6059,21 +6059,21 @@ static function (): void {
60596059
$offsetNativeValueType = $varNativeType;
60606060
$offsetValueTypeStack = [$offsetValueType];
60616061
$offsetValueNativeTypeStack = [$offsetNativeValueType];
6062-
foreach (array_slice($offsetTypes, 0, -1) as $offsetType) {
6062+
foreach (array_slice($offsetTypes, 0, -1) as [$offsetType]) {
60636063
$offsetValueType = $offsetValueType->getOffsetValueType($offsetType);
60646064
$offsetValueTypeStack[] = $offsetValueType;
60656065
}
6066-
foreach (array_slice($offsetNativeTypes, 0, -1) as $offsetNativeType) {
6066+
foreach (array_slice($offsetNativeTypes, 0, -1) as [$offsetNativeType]) {
60676067
$offsetNativeValueType = $offsetNativeValueType->getOffsetValueType($offsetNativeType);
60686068
$offsetValueNativeTypeStack[] = $offsetNativeValueType;
60696069
}
60706070

6071-
foreach (array_reverse($offsetTypes) as $offsetType) {
6071+
foreach (array_reverse($offsetTypes) as [$offsetType]) {
60726072
/** @var Type $offsetValueType */
60736073
$offsetValueType = array_pop($offsetValueTypeStack);
60746074
$valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite);
60756075
}
6076-
foreach (array_reverse($offsetNativeTypes) as $offsetNativeType) {
6076+
foreach (array_reverse($offsetNativeTypes) as [$offsetNativeType]) {
60776077
/** @var Type $offsetNativeValueType */
60786078
$offsetNativeValueType = array_pop($offsetValueNativeTypeStack);
60796079
$nativeValueToWrite = $offsetNativeValueType->setExistingOffsetValueType($offsetNativeType, $nativeValueToWrite);
@@ -6143,7 +6143,7 @@ private function isImplicitArrayCreation(array $dimFetchStack, Scope $scope): Tr
61436143

61446144
/**
61456145
* @param list<ArrayDimFetch> $dimFetchStack
6146-
* @param list<Type|null> $offsetTypes
6146+
* @param list<array{Type|null, ArrayDimFetch}> $offsetTypes
61476147
*
61486148
* @return array{Type, list<array{Expr, Type}>}
61496149
*/
@@ -6152,21 +6152,24 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
61526152
$originalValueToWrite = $valueToWrite;
61536153

61546154
$offsetValueTypeStack = [$offsetValueType];
6155-
foreach (array_slice($offsetTypes, 0, -1) as $offsetType) {
6155+
foreach (array_slice($offsetTypes, 0, -1) as [$offsetType, $dimFetch]) {
61566156
if ($offsetType === null) {
61576157
$offsetValueType = new ConstantArrayType([], []);
61586158

61596159
} else {
6160+
$add = $offsetValueType->hasOffsetValueType($offsetType)->maybe();
61606161
$offsetValueType = $offsetValueType->getOffsetValueType($offsetType);
61616162
if ($offsetValueType instanceof ErrorType) {
61626163
$offsetValueType = new ConstantArrayType([], []);
6164+
} elseif ($add && !$scope->hasExpressionType($dimFetch)->yes()) {
6165+
$offsetValueType = TypeCombinator::union($offsetValueType, new ConstantArrayType([], []));
61636166
}
61646167
}
61656168

61666169
$offsetValueTypeStack[] = $offsetValueType;
61676170
}
61686171

6169-
foreach (array_reverse($offsetTypes) as $i => $offsetType) {
6172+
foreach (array_reverse($offsetTypes) as $i => [$offsetType]) {
61706173
/** @var Type $offsetValueType */
61716174
$offsetValueType = array_pop($offsetValueTypeStack);
61726175
if (

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ public function testBug7581(): void
983983
public function testBug7903(): void
984984
{
985985
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7903.php');
986-
$this->assertNoErrors($errors);
986+
$this->assertCount(23, $errors);
987987
}
988988

989989
public function testBug7901(): void

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,9 @@ public function testIssetAfterRememberedConstructor(): void
347347
]);
348348
}
349349

350+
public function testPr4372(): void
351+
{
352+
$this->analyse([__DIR__ . '/data/pr-4372-null-coalesce.php'], []);
353+
}
354+
350355
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Pr4372NullCoalesce;
4+
5+
function (array $matches): void {
6+
$stats = [];
7+
for ($i = 0; $i < count($matches); $i++) {
8+
$author = $matches[$i][1];
9+
$files = (int) $matches[$i][3];
10+
$insertions = (int) ($matches[$i][4] ?? 0);
11+
12+
$stats[$author]['commits'] = ($stats[$author]['commits'] ?? 0) + 1;
13+
$stats[$author]['files'] = ($stats[$author]['files'] ?? 0) + $files;
14+
$stats[$author]['insertions'] = ($stats[$author]['insertions'] ?? 0) + $insertions;
15+
}
16+
};

0 commit comments

Comments
 (0)