Skip to content

Commit

Permalink
IteratorAggregate - read key and value type from generics if getItera…
Browse files Browse the repository at this point in the history
…tor() doesn't have PHPDoc type
  • Loading branch information
ondrejmirtes committed Jan 22, 2021
1 parent f69bd3e commit c75b0ea
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 4 deletions.
10 changes: 8 additions & 2 deletions src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,14 @@ public function getIterableKeyType(): Type
}

if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) {
return RecursionGuard::run($this, static function () use ($classReflection): Type {
$keyType = RecursionGuard::run($this, static function () use ($classReflection): Type {
return ParametersAcceptorSelector::selectSingle(
$classReflection->getNativeMethod('getIterator')->getVariants()
)->getReturnType()->getIterableKeyType();
});
if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
return $keyType;
}
}

if ($this->isInstanceOf(\Traversable::class)->yes()) {
Expand Down Expand Up @@ -558,11 +561,14 @@ public function getIterableValueType(): Type
}

if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) {
return RecursionGuard::run($this, static function () use ($classReflection): Type {
$valueType = RecursionGuard::run($this, static function () use ($classReflection): Type {
return ParametersAcceptorSelector::selectSingle(
$classReflection->getNativeMethod('getIterator')->getVariants()
)->getReturnType()->getIterableValueType();
});
if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
return $valueType;
}
}

if ($this->isInstanceOf(\Traversable::class)->yes()) {
Expand Down
10 changes: 8 additions & 2 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4893,13 +4893,13 @@ public function dataForeachObjectType(): array
],
[
__DIR__ . '/data/foreach/object-type.php',
'*ERROR*',
'mixed',
'$keyFromRecursiveAggregate',
"'insideThirdForeach'",
],
[
__DIR__ . '/data/foreach/object-type.php',
'*ERROR*',
'mixed',
'$valueFromRecursiveAggregate',
"'insideThirdForeach'",
],
Expand Down Expand Up @@ -10726,6 +10726,11 @@ public function dataBug4398(): array
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4398.php');
}

public function dataBug4415(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4415.php');
}

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -10937,6 +10942,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataVarAboveDeclare
* @dataProvider dataClosureReturnType
* @dataProvider dataBug4398
* @dataProvider dataBug4415
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
25 changes: 25 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4415.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Bug4415;

use function PHPStan\Analyser\assertType;

/**
* @implements \IteratorAggregate<int, string>
*/
class Foo implements \IteratorAggregate
{

public function getIterator(): \Iterator
{

}

}

function (Foo $foo): void {
foreach ($foo as $k => $v) {
assertType('int', $k);
assertType('string', $v);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,15 @@ public function testArrayTypehintWithoutNullInPhpDoc(): void
$this->analyse([__DIR__ . '/../../Analyser/data/array-typehint-without-null-in-phpdoc.php'], []);
}

public function testBug4415(): void
{
$this->analyse([__DIR__ . '/data/bug-4415.php'], [
[
'Method Bug4415Rule\CategoryCollection::getIterator() return type has no value type specified in iterable type Iterator.',
76,
"Consider adding something like <fg=cyan>Iterator<Foo></> to the PHPDoc.\nYou can turn off this check by setting <fg=cyan>checkMissingIterableValueType: false</> in your <fg=cyan>%configurationFile%</>.",
],
]);
}

}
87 changes: 87 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4415.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Bug4415Rule;

/**
* @template T
* @extends \IteratorAggregate<T>
*/
interface CollectionInterface extends \IteratorAggregate
{
/**
* @param T $item
*/
public function has($item): bool;

/**
* @return self<T>
*/
public function sort(): self;
}

/**
* @template T
* @extends CollectionInterface<T>
*/
interface MutableCollectionInterface extends CollectionInterface
{
/**
* @param T $item
* @phpstan-return self<T>
*/
public function add($item): self;
}

/**
* @extends CollectionInterface<Category>
*/
interface CategoryCollectionInterface extends CollectionInterface
{
public function has($item): bool;

/**
* @phpstan-return \Iterator<Category>
*/
public function getIterator(): \Iterator;
}

/**
* @extends MutableCollectionInterface<Category>
*/
interface MutableCategoryCollectionInterface extends CategoryCollectionInterface, MutableCollectionInterface
{
}

class CategoryCollection implements MutableCategoryCollectionInterface
{
/** @var array<Category> */
private array $categories = [];

public function add($item): self
{
$this->categories[$item->getName()] = $item;
return $this;
}

public function has($item): bool
{
return isset($this->categories[$item->getName()]);
}

public function sort(): self
{
return $this;
}

public function getIterator(): \Iterator
{
return new \ArrayIterator($this->categories);
}
}

class Category {
public function getName(): string
{
return '';
}
}

0 comments on commit c75b0ea

Please sign in to comment.