From 14b4672ba9680d916dd30685f49e5db71c916826 Mon Sep 17 00:00:00 2001 From: David Scandurra Date: Thu, 28 Aug 2025 16:41:16 +0200 Subject: [PATCH 1/2] Fix plus operator with plus --- src/Reflection/InitializerExprTypeResolver.php | 11 +++++++++++ tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-11912.php | 14 ++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-11912.php diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index af6302edfc..58f87e93dc 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -34,6 +34,7 @@ use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; +use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; @@ -1078,6 +1079,16 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()), ); + foreach ($leftType->getConstantArrays() as $type) { + foreach ($type->getKeyTypes() as $i => $offsetType) { + if ($type->isOptionalKey($i)) { + continue; + } + $valueType = $type->getValueTypes()[$i]; + $arrayType = TypeCombinator::intersect($arrayType, new HasOffsetValueType($offsetType, $valueType)); + } + } + if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index a2a5ce4889..d30a6d4576 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1516,6 +1516,12 @@ public function testBug13310(): void $this->assertNoErrors($errors); } + public function testBug11912(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-11912.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-11912.php b/tests/PHPStan/Analyser/data/bug-11912.php new file mode 100644 index 0000000000..69d65ee01c --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11912.php @@ -0,0 +1,14 @@ + $results + * @param list $names + */ +function appendResults(array $results, array $names): bool { + // Make sure 'names' comes first in array + $results = ['names' => $names] + $results; + \PHPStan\Testing\assertType("list", $results['names']); + return true; +} From 5e0125b0f29ac18fffb17161b8117477ebff28e2 Mon Sep 17 00:00:00 2001 From: David Scandurra Date: Fri, 29 Aug 2025 19:17:20 +0200 Subject: [PATCH 2/2] Move test for bug-11912 --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 6 ------ tests/PHPStan/Analyser/{data => nsrt}/bug-11912.php | 0 2 files changed, 6 deletions(-) rename tests/PHPStan/Analyser/{data => nsrt}/bug-11912.php (100%) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index d30a6d4576..a2a5ce4889 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1516,12 +1516,6 @@ public function testBug13310(): void $this->assertNoErrors($errors); } - public function testBug11912(): void - { - $errors = $this->runAnalyse(__DIR__ . '/data/bug-11912.php'); - $this->assertNoErrors($errors); - } - /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-11912.php b/tests/PHPStan/Analyser/nsrt/bug-11912.php similarity index 100% rename from tests/PHPStan/Analyser/data/bug-11912.php rename to tests/PHPStan/Analyser/nsrt/bug-11912.php