@@ -249,59 +249,10 @@ protected function getResult(): Type
249249 }
250250
251251 if ($ nConstantArrays === count ($ types )) {
252- $ normalizedTypes = [];
253- $ allNormalizedTypesConstant = true ;
252+ $ result = self ::tryMergeConstantArrays ($ types );
254253
255- foreach ($ types as $ type ) {
256- $ normalizedType = self ::normalizeConstantArrayIntegerKeys ($ type );
257- if (null === $ normalizedType ) {
258- return new MixedType ();
259- }
260-
261- $ normalizedTypes [] = $ normalizedType ;
262- if (!$ normalizedType ->isConstantArray ()->yes ()) {
263- $ allNormalizedTypesConstant = false ;
264- }
265-
266- foreach ($ type ->getConstantArrays () as $ constantArrayType ) {
267- if (self ::hasUnknownExtraOffsets ($ constantArrayType )) {
268- $ allNormalizedTypesConstant = false ;
269- break ;
270- }
271- }
272- }
273-
274- if ($ allNormalizedTypesConstant ) {
275- $ builder = ConstantArrayTypeBuilder::createEmpty ();
276-
277- foreach ($ normalizedTypes as $ normalizedType ) {
278- foreach (self ::getConstantArrayKeyTypes ($ normalizedType ) as $ keyType ) {
279- $ builder ->setOffsetValueType (
280- $ keyType instanceof ConstantIntegerType ? null : $ keyType ,
281- $ normalizedType ->getOffsetValueType ($ keyType ),
282- !$ normalizedType ->hasOffsetValueType ($ keyType )->yes (),
283- );
284- }
285- }
286-
287- $ result = $ builder ->getArray ();
288- $ constantResults = $ result ->getConstantArrays ();
289-
290- if (count ($ constantResults ) !== 1 || self ::hasConsistentKeyOrder ($ types , $ constantResults [0 ])) {
291- $ emptyArray = ConstantArrayTypeBuilder::createEmpty ()->getArray ();
292-
293- foreach ($ types as $ type ) {
294- // Benevolent unions can report nonempty while permitting an empty branch.
295- if ($ type ->isSuperTypeOf ($ emptyArray )->no ()) {
296- return TypeCombinator::intersect ($ result , new NonEmptyArrayType ());
297- }
298- }
299-
300- return $ result ;
301- }
302-
303- // Use the generic fallback when order cannot be proven. Combining shapes
304- // with TypeCombinator::union() can erase their different key orders.
254+ if (null !== $ result ) {
255+ return $ result ;
305256 }
306257 }
307258
@@ -378,6 +329,71 @@ protected function getResult(): Type
378329 return new ArrayType (new MixedType (true ), new MixedType (true ));
379330 }
380331
332+ /**
333+ * Returns null when the operands require the generic fallback.
334+ *
335+ * @param non-empty-list<Type> $types
336+ */
337+ private static function tryMergeConstantArrays (array $ types ): ?Type
338+ {
339+ $ normalizedTypes = [];
340+ $ allNormalizedTypesConstant = true ;
341+
342+ foreach ($ types as $ type ) {
343+ $ normalizedType = self ::normalizeConstantArrayIntegerKeys ($ type );
344+ if (null === $ normalizedType ) {
345+ return new MixedType ();
346+ }
347+
348+ $ normalizedTypes [] = $ normalizedType ;
349+ if (!$ normalizedType ->isConstantArray ()->yes ()) {
350+ $ allNormalizedTypesConstant = false ;
351+ }
352+
353+ foreach ($ type ->getConstantArrays () as $ constantArrayType ) {
354+ if (self ::hasUnknownExtraOffsets ($ constantArrayType )) {
355+ $ allNormalizedTypesConstant = false ;
356+ break ;
357+ }
358+ }
359+ }
360+
361+ if ($ allNormalizedTypesConstant ) {
362+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
363+
364+ foreach ($ normalizedTypes as $ normalizedType ) {
365+ foreach (self ::getConstantArrayKeyTypes ($ normalizedType ) as $ keyType ) {
366+ $ builder ->setOffsetValueType (
367+ $ keyType instanceof ConstantIntegerType ? null : $ keyType ,
368+ $ normalizedType ->getOffsetValueType ($ keyType ),
369+ !$ normalizedType ->hasOffsetValueType ($ keyType )->yes (),
370+ );
371+ }
372+ }
373+
374+ $ result = $ builder ->getArray ();
375+ $ constantResults = $ result ->getConstantArrays ();
376+
377+ if (count ($ constantResults ) !== 1 || self ::hasConsistentKeyOrder ($ types , $ constantResults [0 ])) {
378+ $ emptyArray = ConstantArrayTypeBuilder::createEmpty ()->getArray ();
379+
380+ foreach ($ types as $ type ) {
381+ // Benevolent unions can report nonempty while permitting an empty branch.
382+ if ($ type ->isSuperTypeOf ($ emptyArray )->no ()) {
383+ return TypeCombinator::intersect ($ result , new NonEmptyArrayType ());
384+ }
385+ }
386+
387+ return $ result ;
388+ }
389+
390+ // Use the generic fallback when order cannot be proven. Combining shapes
391+ // with TypeCombinator::union() can erase their different key orders.
392+ }
393+
394+ return null ;
395+ }
396+
381397 private static function removeTopLevelNeverAlternatives (Type $ type ): Type
382398 {
383399 if (!($ type instanceof UnionType) || $ type instanceof TemplateType) {
0 commit comments