@@ -225,6 +225,7 @@ private function matchPhpParserUnionType(UnionType $unionType, string $typeKind)
225225
226226 /** @var Identifier[]|Name[] $phpParserUnionedTypes */
227227 $ phpParserUnionedTypes = array_unique ($ phpParserUnionedTypes , SORT_REGULAR );
228+ $ phpParserUnionedTypes = $ this ->removeRedundantIntersectionTypes ($ phpParserUnionedTypes );
228229
229230 $ countPhpParserUnionedTypes = count ($ phpParserUnionedTypes );
230231 if ($ countPhpParserUnionedTypes === 1 ) {
@@ -234,6 +235,72 @@ private function matchPhpParserUnionType(UnionType $unionType, string $typeKind)
234235 return $ this ->resolveTypeWithNullablePHPParserUnionType (new PhpParserUnionType ($ phpParserUnionedTypes ));
235236 }
236237
238+ /**
239+ * PHP rejects e.g. "A|(A&B)" with "Type A&B is redundant as it is more restrictive than type A",
240+ * so drop intersections that contain all parts of another member of the union
241+ *
242+ * @param array<Identifier|Name|PHPParserNodeIntersectionType> $phpParserUnionedTypes
243+ * @return list<Identifier|Name|PHPParserNodeIntersectionType>
244+ */
245+ private function removeRedundantIntersectionTypes (array $ phpParserUnionedTypes ): array
246+ {
247+ $ phpParserUnionedTypes = array_values ($ phpParserUnionedTypes );
248+
249+ $ typeNames = [];
250+ foreach ($ phpParserUnionedTypes as $ key => $ phpParserUnionedType ) {
251+ $ typeNames [$ key ] = $ this ->resolveIntersectionPartNames ($ phpParserUnionedType );
252+ }
253+
254+ foreach ($ phpParserUnionedTypes as $ key => $ phpParserUnionedType ) {
255+ if (! $ phpParserUnionedType instanceof PHPParserNodeIntersectionType) {
256+ continue ;
257+ }
258+
259+ foreach ($ typeNames as $ otherKey => $ otherTypeNames ) {
260+ if ($ otherKey === $ key || $ otherTypeNames === []) {
261+ continue ;
262+ }
263+
264+ // already removed
265+ if (! isset ($ phpParserUnionedTypes [$ otherKey ])) {
266+ continue ;
267+ }
268+
269+ if (array_diff ($ otherTypeNames , $ typeNames [$ key ]) === []) {
270+ unset($ phpParserUnionedTypes [$ key ]);
271+ continue 2 ;
272+ }
273+ }
274+ }
275+
276+ return array_values ($ phpParserUnionedTypes );
277+ }
278+
279+ /**
280+ * @return string[]
281+ */
282+ private function resolveIntersectionPartNames (Node $ node ): array
283+ {
284+ if ($ node instanceof Name) {
285+ return [$ node ->toString ()];
286+ }
287+
288+ if (! $ node instanceof PHPParserNodeIntersectionType) {
289+ return [];
290+ }
291+
292+ $ names = [];
293+ foreach ($ node ->types as $ type ) {
294+ if (! $ type instanceof Name) {
295+ return [];
296+ }
297+
298+ $ names [] = $ type ->toString ();
299+ }
300+
301+ return $ names ;
302+ }
303+
237304 private function resolveUnionTypeNode (PhpParserUnionType $ phpParserUnionType ): ?PhpParserUnionType
238305 {
239306 if (! $ this ->phpVersionProvider ->isAtLeastPhpVersion (PhpVersionFeature::UNION_TYPES )) {
0 commit comments