1515use PHPStan \Type \IntegerRangeType ;
1616use PHPStan \Type \IntegerType ;
1717use PHPStan \Type \NullType ;
18- use PHPStan \Type \Regex \RegexAlternation ;
1918use PHPStan \Type \Regex \RegexCapturingGroup ;
2019use PHPStan \Type \Regex \RegexExpressionHelper ;
20+ use PHPStan \Type \Regex \RegexGroupList ;
2121use PHPStan \Type \Regex \RegexGroupParser ;
2222use PHPStan \Type \StringType ;
2323use PHPStan \Type \Type ;
2424use PHPStan \Type \TypeCombinator ;
25- use function array_reverse ;
2625use function count ;
2726use function in_array ;
2827use function is_string ;
@@ -115,16 +114,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
115114 }
116115 [$ groupList , $ markVerbs ] = $ parseResult ;
117116
118- $ trailingOptionals = 0 ;
119- foreach (array_reverse ($ groupList ) as $ captureGroup ) {
120- if (!$ captureGroup ->isOptional ()) {
121- break ;
122- }
123- $ trailingOptionals ++;
124- }
125-
126- $ onlyOptionalTopLevelGroup = $ this ->getOnlyOptionalTopLevelGroup ($ groupList );
127- $ onlyTopLevelAlternation = $ this ->getOnlyTopLevelAlternation ($ groupList );
117+ $ regexGroupList = new RegexGroupList ($ groupList );
118+ $ trailingOptionals = $ regexGroupList ->countTrailingOptionals ();
119+ $ onlyOptionalTopLevelGroup = $ regexGroupList ->getOnlyOptionalTopLevelGroup ();
120+ $ onlyTopLevelAlternation = $ regexGroupList ->getOnlyTopLevelAlternation ();
128121 $ flags ??= 0 ;
129122
130123 if (
@@ -134,11 +127,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
134127 ) {
135128 // if only one top level capturing optional group exists
136129 // we build a more precise tagged union of a empty-match and a match with the group
137-
138- $ onlyOptionalTopLevelGroup ->forceNonOptional ();
130+ $ regexGroupList = $ regexGroupList ->forceGroupNonOptional ($ onlyOptionalTopLevelGroup );
139131
140132 $ combiType = $ this ->buildArrayType (
141- $ groupList ,
133+ $ regexGroupList ,
142134 $ wasMatched ,
143135 $ trailingOptionals ,
144136 $ flags ,
@@ -154,8 +146,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
154146 );
155147 }
156148
157- $ onlyOptionalTopLevelGroup ->clearOverrides ();
158-
159149 return $ combiType ;
160150 } elseif (
161151 !$ matchesAll
@@ -168,24 +158,24 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
168158 $ combiTypes = [];
169159 $ isOptionalAlternation = false ;
170160 foreach ($ onlyTopLevelAlternation ->getGroupCombinations () as $ groupCombo ) {
171- $ comboList = $ groupList ;
161+ $ comboList = new RegexGroupList ( $ groupList) ;
172162
173163 $ beforeCurrentCombo = true ;
174- foreach ($ comboList as $ groupId => $ group ) {
175- if (in_array ($ groupId , $ groupCombo , true )) {
164+ foreach ($ comboList as $ group ) {
165+ if (in_array ($ group -> getId () , $ groupCombo , true )) {
176166 $ isOptionalAlternation = $ group ->inOptionalAlternation ();
177- $ group -> forceNonOptional ( );
167+ $ comboList = $ comboList -> forceGroupNonOptional ( $ group );
178168 $ beforeCurrentCombo = false ;
179169 } elseif ($ beforeCurrentCombo && !$ group ->resetsGroupCounter ()) {
180- $ group -> forceNonOptional ();
181- $ group-> forceType (
170+ $ comboList = $ comboList -> forceGroupTypeAndNonOptional (
171+ $ group,
182172 $ this ->containsUnmatchedAsNull ($ flags , $ matchesAll ) ? new NullType () : new ConstantStringType ('' ),
183173 );
184174 } elseif (
185175 $ group ->getAlternationId () === $ onlyTopLevelAlternation ->getId ()
186176 && !$ this ->containsUnmatchedAsNull ($ flags , $ matchesAll )
187177 ) {
188- unset( $ comboList[ $ groupId ] );
178+ $ comboList = $ comboList -> removeGroup ( $ group );
189179 }
190180 }
191181
@@ -199,11 +189,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
199189 );
200190
201191 $ combiTypes [] = $ combiType ;
202-
203- foreach ($ groupCombo as $ groupId ) {
204- $ group = $ comboList [$ groupId ];
205- $ group ->clearOverrides ();
206- }
207192 }
208193
209194 if (
@@ -223,7 +208,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
223208 // the general case, which should work in all cases but does not yield the most
224209 // precise result possible in some cases
225210 return $ this ->buildArrayType (
226- $ groupList ,
211+ $ regexGroupList ,
227212 $ wasMatched ,
228213 $ trailingOptionals ,
229214 $ flags ,
@@ -233,65 +218,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
233218 }
234219
235220 /**
236- * @param array<int, RegexCapturingGroup> $captureGroups
237- */
238- private function getOnlyOptionalTopLevelGroup (array $ captureGroups ): ?RegexCapturingGroup
239- {
240- $ group = null ;
241- foreach ($ captureGroups as $ captureGroup ) {
242- if (!$ captureGroup ->isTopLevel ()) {
243- continue ;
244- }
245-
246- if (!$ captureGroup ->isOptional ()) {
247- return null ;
248- }
249-
250- if ($ group !== null ) {
251- return null ;
252- }
253-
254- $ group = $ captureGroup ;
255- }
256-
257- return $ group ;
258- }
259-
260- /**
261- * @param array<int, RegexCapturingGroup> $captureGroups
262- */
263- private function getOnlyTopLevelAlternation (array $ captureGroups ): ?RegexAlternation
264- {
265- $ alternation = null ;
266- foreach ($ captureGroups as $ captureGroup ) {
267- if (!$ captureGroup ->isTopLevel ()) {
268- continue ;
269- }
270-
271- if (!$ captureGroup ->inAlternation ()) {
272- return null ;
273- }
274-
275- if ($ captureGroup ->inOptionalQuantification ()) {
276- return null ;
277- }
278-
279- if ($ alternation === null ) {
280- $ alternation = $ captureGroup ->getAlternation ();
281- } elseif ($ alternation ->getId () !== $ captureGroup ->getAlternation ()->getId ()) {
282- return null ;
283- }
284- }
285-
286- return $ alternation ;
287- }
288-
289- /**
290- * @param array<RegexCapturingGroup> $captureGroups
291221 * @param list<string> $markVerbs
292222 */
293223 private function buildArrayType (
294- array $ captureGroups ,
224+ RegexGroupList $ captureGroups ,
295225 TrinaryLogic $ wasMatched ,
296226 int $ trailingOptionals ,
297227 int $ flags ,
0 commit comments