Skip to content

Commit

Permalink
Fix TemplateUnionType as part of intersection
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 14, 2021
1 parent a3f587f commit 4a45db5
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 14 deletions.
45 changes: 31 additions & 14 deletions src/Type/TypeCombinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeFactory;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateUnionType;

class TypeCombinator
{
Expand Down Expand Up @@ -603,23 +606,37 @@ public static function intersect(Type ...$types): Type
});
// transform A & (B | C) to (A & B) | (A & C)
foreach ($types as $i => $type) {
if ($type instanceof UnionType) {
$topLevelUnionSubTypes = [];
foreach ($type->getTypes() as $innerUnionSubType) {
$topLevelUnionSubTypes[] = self::intersect(
$innerUnionSubType,
...array_slice($types, 0, $i),
...array_slice($types, $i + 1)
);
}
if (!$type instanceof UnionType) {
continue;
}

$union = self::union(...$topLevelUnionSubTypes);
if ($type instanceof BenevolentUnionType) {
return TypeUtils::toBenevolentUnion($union);
}
$topLevelUnionSubTypes = [];
foreach ($type->getTypes() as $innerUnionSubType) {
$topLevelUnionSubTypes[] = self::intersect(
$innerUnionSubType,
...array_slice($types, 0, $i),
...array_slice($types, $i + 1)
);
}

$union = self::union(...$topLevelUnionSubTypes);
if ($type instanceof BenevolentUnionType) {
$union = TypeUtils::toBenevolentUnion($union);
}

return $union;
if ($type instanceof TemplateUnionType) {
$union = TemplateTypeFactory::create(
$type->getScope(),
$type->getName(),
$union,
$type->getVariance()
);
if ($type->isArgument()) {
return TemplateTypeHelper::toArgument($union);
}
}

return $union;
}

// transform A & (B & C) to A & B & C
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/getopt.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/generics-default.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4985.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5000.php');
}

/**
Expand Down
53 changes: 53 additions & 0 deletions tests/PHPStan/Analyser/data/bug-5000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Bug5000;

use function PHPStan\Testing\assertType;

class A {

}

class B {

}

/**
* @template T of A|B
*/
interface F
{

/**
* @return T[]
*/
function getValues(): array;

/**
* @phpstan-param T $v
* @return T
*/
function getDetail(object $v);
}

class Foo
{

/**
* @template T of A|B
* @phpstan-param F<T> $f
*/
function foo(F $f): void {
$values = $f->getValues();
assertType('array<T of Bug5000\A|Bug5000\B (method Bug5000\Foo::foo(), argument)>', $values);

$f->getDetail($values[0]);
assertType('array<T of Bug5000\A|Bug5000\B (method Bug5000\Foo::foo(), argument)>', $values);
foreach($values as $val) {
assertType('T of Bug5000\A|Bug5000\B (method Bug5000\Foo::foo(), argument)', $val);
$f->getDetail($val);
}
}


}

0 comments on commit 4a45db5

Please sign in to comment.