File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -2084,7 +2084,21 @@ public static function doIntersect(Type ...$types): Type
20842084 $ accessoryBaseTypes = null ;
20852085 break ;
20862086 }
2087- $ accessoryBaseTypes [] = $ type ->getDefaultBaseType ();
2087+ // Accessory types share their default base type: every string accessory
2088+ // returns `string`, hasOffset() and hasOffsetValue() both return
2089+ // `array|ArrayAccess`. Adding the same base type again narrows nothing -
2090+ // intersection is idempotent - but the intersect() below distributes
2091+ // `A & (B | C)` one union at a time, so n copies of `array|ArrayAccess`
2092+ // would cost 2^n recursive calls before the duplicates are recognized at
2093+ // the leaves. That is why isset() with many offsets used to grow
2094+ // exponentially: each offset contributes one hasOffset().
2095+ $ baseType = $ type ->getDefaultBaseType ();
2096+ foreach ($ accessoryBaseTypes as $ addedBaseType ) {
2097+ if ($ addedBaseType ->equals ($ baseType )) {
2098+ continue 2 ;
2099+ }
2100+ }
2101+ $ accessoryBaseTypes [] = $ baseType ;
20882102 }
20892103 if ($ accessoryBaseTypes !== null ) {
20902104 // Accessory types never stand alone — supply the base type they refine.
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace Bug15061 ;
4+
5+ /**
6+ * Every isset() subject narrows the array to hasOffset(), and intersecting n of them
7+ * used to cost 2^n TypeCombinator::intersect() calls: each hasOffset() contributes the
8+ * same `array|ArrayAccess` default base type, and those n identical unions were
9+ * distributed over each other one at a time.
10+ *
11+ * @phpstan-type FooEntity array{
12+ * a?: string,
13+ * b?: string,
14+ * c?: string,
15+ * d?: string,
16+ * e?: string,
17+ * f?: string,
18+ * g?: string,
19+ * h?: string,
20+ * i?: string,
21+ * j?: string,
22+ * k?: string,
23+ * l?: string,
24+ * m?: string,
25+ * n?: string,
26+ * o?: string,
27+ * p?: string,
28+ * q?: string,
29+ * r?: string,
30+ * s?: string,
31+ * t?: string,
32+ * u?: string,
33+ * v?: string,
34+ * w?: string,
35+ * x?: string,
36+ * y?: string,
37+ * z?: string,
38+ * }
39+ */
40+ final class TestClass
41+ {
42+
43+ public function __invoke (): void
44+ {
45+ /** @var array<string, FooEntity> $entities */
46+ $ entities = [];
47+
48+ foreach ($ entities as $ entity ) {
49+ $ ok = isset (
50+ $ entity ['a ' ],
51+ $ entity ['b ' ],
52+ $ entity ['c ' ],
53+ $ entity ['d ' ],
54+ $ entity ['e ' ],
55+ $ entity ['f ' ],
56+ $ entity ['g ' ],
57+ $ entity ['h ' ],
58+ $ entity ['i ' ],
59+ $ entity ['j ' ],
60+ $ entity ['k ' ],
61+ $ entity ['l ' ],
62+ $ entity ['m ' ],
63+ $ entity ['n ' ],
64+ $ entity ['o ' ],
65+ $ entity ['p ' ],
66+ $ entity ['q ' ],
67+ $ entity ['r ' ],
68+ $ entity ['s ' ],
69+ $ entity ['t ' ],
70+ $ entity ['u ' ],
71+ $ entity ['v ' ],
72+ $ entity ['w ' ],
73+ $ entity ['x ' ],
74+ $ entity ['y ' ],
75+ $ entity ['z ' ],
76+ );
77+
78+ if (!$ ok ) {
79+ continue ;
80+ }
81+ }
82+ }
83+
84+ }
You can’t perform that action at this time.
0 commit comments