@@ -153,6 +153,14 @@ public static function union(Type ...$types): Type
153153 return new NeverType ();
154154 }
155155
156+ // Fast path for single non-union type
157+ if ($ typesCount === 1 ) {
158+ $ singleType = $ types [0 ];
159+ if (!$ singleType instanceof UnionType && !$ singleType ->isArray ()->yes ()) {
160+ return $ singleType ;
161+ }
162+ }
163+
156164 // Fast path for common 2-type cases
157165 if ($ typesCount === 2 ) {
158166 $ a = $ types [0 ];
@@ -180,6 +188,53 @@ public static function union(Type ...$types): Type
180188 }
181189 }
182190
191+ // Fast path for N>2: strip implicit NeverTypes and short-circuit on mixed
192+ if ($ typesCount > 2 ) {
193+ $ neverCount = 0 ;
194+ $ hasUnionOrBenevolent = false ;
195+ for ($ i = 0 ; $ i < $ typesCount ; $ i ++) {
196+ $ t = $ types [$ i ];
197+ if (
198+ $ t instanceof MixedType
199+ && !$ t ->isExplicitMixed ()
200+ && !$ t instanceof TemplateMixedType
201+ && $ t ->getSubtractedType () === null
202+ ) {
203+ return $ t ;
204+ }
205+ if ($ t instanceof NeverType && !$ t ->isExplicit ()) {
206+ $ neverCount ++;
207+ } elseif ($ t instanceof UnionType && !$ t instanceof TemplateType) {
208+ $ hasUnionOrBenevolent = true ;
209+ }
210+ }
211+
212+ if ($ neverCount > 0 && !$ hasUnionOrBenevolent ) {
213+ if ($ neverCount === $ typesCount ) {
214+ return new NeverType ();
215+ }
216+
217+ $ filtered = [];
218+ for ($ i = 0 ; $ i < $ typesCount ; $ i ++) {
219+ if ($ types [$ i ] instanceof NeverType && !$ types [$ i ]->isExplicit ()) {
220+ continue ;
221+ }
222+
223+ $ filtered [] = $ types [$ i ];
224+ }
225+ $ filteredCount = count ($ filtered );
226+
227+ if ($ filteredCount === 1 && !$ filtered [0 ]->isArray ()->yes ()) {
228+ return $ filtered [0 ];
229+ }
230+ if ($ filteredCount === 2 ) {
231+ return self ::union ($ filtered [0 ], $ filtered [1 ]);
232+ }
233+ $ types = $ filtered ;
234+ $ typesCount = $ filteredCount ;
235+ }
236+ }
237+
183238 $ alreadyNormalized = [];
184239 $ alreadyNormalizedCounter = 0 ;
185240
0 commit comments