Skip to content

Commit 1c5af0d

Browse files
committed
Fix more cases
1 parent a79f4ca commit 1c5af0d

File tree

6 files changed

+64
-129
lines changed

6 files changed

+64
-129
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,11 @@ private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameSc
624624
private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, NameScope $nameScope): Type
625625
{
626626
$types = $this->resolveMultiple($typeNode->types, $nameScope);
627-
return TypeCombinator::intersect(...$types);
627+
$result = $types[0];
628+
for ($i = 1, $count = count($types); $i < $count; $i++) {
629+
$result = TypeCombinator::intersect($result, $types[$i]);
630+
}
631+
return $result;
628632
}
629633

630634
private function resolveConditionalTypeNode(ConditionalTypeNode $typeNode, NameScope $nameScope): Type

src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ private function computeNeedleNarrowingType(TypeSpecifierContext $context, Type
222222
return null;
223223
}
224224

225-
$guaranteedValueType = TypeCombinator::intersect(...$guaranteedValueTypePerArray);
225+
$guaranteedValueType = $guaranteedValueTypePerArray[0];
226+
for ($i = 1, $count = count($guaranteedValueTypePerArray); $i < $count; $i++) {
227+
$guaranteedValueType = TypeCombinator::intersect($guaranteedValueType, $guaranteedValueTypePerArray[$i]);
228+
}
226229
if (count($guaranteedValueType->getFiniteTypes()) === 0) {
227230
return null;
228231
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,12 +1529,6 @@ public function testBug14439(): void
15291529
$this->assertNoErrors($errors);
15301530
}
15311531

1532-
public function testBug14475(): void
1533-
{
1534-
$errors = $this->runAnalyse(__DIR__ . '/data/bug-14475.php');
1535-
$this->assertNoErrors($errors);
1536-
}
1537-
15381532
/**
15391533
* @param string[]|null $allAnalysedFiles
15401534
* @return list<Error>

tests/PHPStan/Analyser/data/bug-14475.php

Lines changed: 0 additions & 121 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace BenchInArrayIntersectBlowup;
4+
5+
/**
6+
* Regression test for exponential blowup in InArrayFunctionTypeSpecifyingExtension::computeNeedleNarrowingType().
7+
*
8+
* The method intersects per-array guaranteed value types. When the array parameter is a union of N constant
9+
* arrays, each with M guaranteed values, an N-ary TypeCombinator::intersect() call causes M^N combinations
10+
* via the distributive law. Pairwise folding avoids this by letting intermediate intersections simplify early.
11+
*
12+
* @param 'v1'|'v2'|'v3' $needle
13+
* @param 'a'|'b'|'c'|'d'|'e' $variant
14+
*/
15+
function testInArray(string $needle, string $variant): void
16+
{
17+
$arr = match($variant) {
18+
'a' => ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'a10', 'a11', 'a12', 'a13', 'a14', 'a15', 'a16', 'a17', 'a18', 'a19', 'a20', 'a21', 'a22', 'a23', 'a24', 'a25'],
19+
'b' => ['b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', 'b11', 'b12', 'b13', 'b14', 'b15', 'b16', 'b17', 'b18', 'b19', 'b20', 'b21', 'b22', 'b23', 'b24', 'b25'],
20+
'c' => ['c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'c10', 'c11', 'c12', 'c13', 'c14', 'c15', 'c16', 'c17', 'c18', 'c19', 'c20', 'c21', 'c22', 'c23', 'c24', 'c25'],
21+
'd' => ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'd10', 'd11', 'd12', 'd13', 'd14', 'd15', 'd16', 'd17', 'd18', 'd19', 'd20', 'd21', 'd22', 'd23', 'd24', 'd25'],
22+
'e' => ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'e10', 'e11', 'e12', 'e13', 'e14', 'e15', 'e16', 'e17', 'e18', 'e19', 'e20', 'e21', 'e22', 'e23', 'e24', 'e25'],
23+
};
24+
25+
if (!in_array($needle, $arr, true)) {
26+
echo $needle;
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace BenchPhpDocIntersectionBlowup;
4+
5+
/**
6+
* Regression test for exponential blowup in TypeNodeResolver::resolveIntersectionTypeNode().
7+
*
8+
* When a PHPDoc intersection type contains multiple union type aliases, the N-ary
9+
* TypeCombinator::intersect() call causes M^N combinations via the distributive law.
10+
* Pairwise folding avoids this by letting intermediate intersections simplify early.
11+
*
12+
* @phpstan-type TypeA 'a1'|'a2'|'a3'|'a4'|'a5'|'a6'|'a7'|'a8'|'a9'|'a10'|'a11'|'a12'|'a13'|'a14'|'a15'|'a16'|'a17'|'a18'|'a19'|'a20'|'a21'|'a22'|'a23'|'a24'|'a25'
13+
* @phpstan-type TypeB 'b1'|'b2'|'b3'|'b4'|'b5'|'b6'|'b7'|'b8'|'b9'|'b10'|'b11'|'b12'|'b13'|'b14'|'b15'|'b16'|'b17'|'b18'|'b19'|'b20'|'b21'|'b22'|'b23'|'b24'|'b25'
14+
* @phpstan-type TypeC 'c1'|'c2'|'c3'|'c4'|'c5'|'c6'|'c7'|'c8'|'c9'|'c10'|'c11'|'c12'|'c13'|'c14'|'c15'|'c16'|'c17'|'c18'|'c19'|'c20'|'c21'|'c22'|'c23'|'c24'|'c25'
15+
* @phpstan-type TypeD 'd1'|'d2'|'d3'|'d4'|'d5'|'d6'|'d7'|'d8'|'d9'|'d10'|'d11'|'d12'|'d13'|'d14'|'d15'|'d16'|'d17'|'d18'|'d19'|'d20'|'d21'|'d22'|'d23'|'d24'|'d25'
16+
* @phpstan-type TypeE 'e1'|'e2'|'e3'|'e4'|'e5'|'e6'|'e7'|'e8'|'e9'|'e10'|'e11'|'e12'|'e13'|'e14'|'e15'|'e16'|'e17'|'e18'|'e19'|'e20'|'e21'|'e22'|'e23'|'e24'|'e25'
17+
*/
18+
class Foo
19+
{
20+
/**
21+
* @param TypeA&TypeB&TypeC&TypeD&TypeE $x
22+
*/
23+
public function bar($x): void
24+
{
25+
echo $x;
26+
}
27+
}

0 commit comments

Comments
 (0)