Skip to content

Commit b4d80f4

Browse files
authored
[TypeDeclaration] Skip redundant intersection types in union return types (#8527)
* [TypeDeclaration] Skip redundant intersection types in union return types * Drop unneeded strtolower in intersection part names
1 parent 09b71bd commit b4d80f4

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\FixtureUnionIntersection;
4+
5+
/**
6+
* "\Iterator|(\Iterator&\Countable)" is a compile error in PHP: the intersection is redundant
7+
*/
8+
final class RedundantIntersection
9+
{
10+
public function run(\Iterator $obj)
11+
{
12+
if (! $obj instanceof \Countable) {
13+
return $obj;
14+
}
15+
16+
if (rand(0, 1)) {
17+
return $obj;
18+
}
19+
20+
return '10';
21+
}
22+
}
23+
24+
?>
25+
-----
26+
<?php
27+
28+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\FixtureUnionIntersection;
29+
30+
/**
31+
* "\Iterator|(\Iterator&\Countable)" is a compile error in PHP: the intersection is redundant
32+
*/
33+
final class RedundantIntersection
34+
{
35+
public function run(\Iterator $obj): \Iterator|string
36+
{
37+
if (! $obj instanceof \Countable) {
38+
return $obj;
39+
}
40+
41+
if (rand(0, 1)) {
42+
return $obj;
43+
}
44+
45+
return '10';
46+
}
47+
}
48+
49+
?>

‎src/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)