Skip to content

Commit

Permalink
ObjectType - different priority in iterable key and iterable value
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 22, 2022
1 parent a0d1248 commit 1256192
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 46 deletions.
67 changes: 23 additions & 44 deletions src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -686,42 +686,32 @@ public function isIterableAtLeastOnce(): TrinaryLogic
public function getIterableKeyType(): Type
{
$isTraversable = false;
if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
$keyType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
$this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
)->getReturnType()->getIterableKeyType());
$isTraversable = true;
if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
return $keyType;
}
}

if ($this->isInstanceOf(Traversable::class)->yes()) {
$isTraversable = true;
$tKey = GenericTypeVariableResolver::getType($this, Traversable::class, 'TKey');
if ($tKey !== null) {
if (!$tKey instanceof MixedType || $tKey->isExplicitMixed()) {
$classReflection = $this->getClassReflection();
if ($classReflection === null) {
return $tKey;
}

return TypeTraverser::map($tKey, static function (Type $type, callable $traverse) use ($classReflection): Type {
if ($type instanceof StaticType) {
return $type->changeBaseClass($classReflection)->getStaticObjectType();
}

return $traverse($type);
});
return $tKey;
}
}
}

if ($this->isInstanceOf(Iterator::class)->yes()) {
return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
$this->getMethod('key', new OutOfClassScope())->getVariants(),
)->getReturnType());
}

if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
$keyType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
$this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
)->getReturnType()->getIterableKeyType());
$isTraversable = true;
if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) {
return $keyType;
}
}

if ($isTraversable) {
return new MixedType();
}
Expand All @@ -732,23 +722,22 @@ public function getIterableKeyType(): Type
public function getIterableValueType(): Type
{
$isTraversable = false;
if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
$valueType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
$this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
)->getReturnType()->getIterableValueType());
$isTraversable = true;
if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
return $valueType;
}
}

if ($this->isInstanceOf(Traversable::class)->yes()) {
$isTraversable = true;
$tValue = GenericTypeVariableResolver::getType($this, Traversable::class, 'TValue');
if ($tValue !== null) {
if (!$tValue instanceof MixedType || $tValue->isExplicitMixed()) {
$classReflection = $this->getClassReflection();
if ($classReflection === null) {
return $tValue;
}

return TypeTraverser::map($tValue, static function (Type $type, callable $traverse) use ($classReflection): Type {
if ($type instanceof StaticType) {
return $type->changeBaseClass($classReflection)->getStaticObjectType();
}

return $traverse($type);
});
return $tValue;
}
}
}
Expand All @@ -759,16 +748,6 @@ public function getIterableValueType(): Type
)->getReturnType());
}

if ($this->isInstanceOf(IteratorAggregate::class)->yes()) {
$valueType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle(
$this->getMethod('getIterator', new OutOfClassScope())->getVariants(),
)->getReturnType()->getIterableValueType());
$isTraversable = true;
if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) {
return $valueType;
}
}

if ($isTraversable) {
return new MixedType();
}
Expand Down
9 changes: 9 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,15 @@ public function testBug6466(): void
$this->assertNoErrors($errors);
}

public function testBug6494(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}
$errors = $this->runAnalyse(__DIR__ . '/data/bug-6494.php');
$this->assertNoErrors($errors);
}

public function testBug6253(): void
{
$errors = $this->runAnalyse(
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/data/bug-4415.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function getIterator(): \Iterator

function (Foo $foo): void {
foreach ($foo as $k => $v) {
assertType('int', $k);
assertType('string', $v);
assertType('mixed', $k); // should be int
assertType('mixed', $v); // should be string
}
};
38 changes: 38 additions & 0 deletions tests/PHPStan/Analyser/data/bug-6494.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types = 1); // lint >= 8.0

namespace Bug6494;

use function PHPStan\Testing\assertType;

// To get rid of warnings about using new static()
interface SomeInterface {
public function __construct();
}

class Base implements SomeInterface {

public function __construct() {}

/**
* @return \Generator<int, static, void, void>
*/
public static function instances() {
yield new static();
}
}

function (): void {
foreach ((new Base())::instances() as $h) {
assertType(Base::class, $h);
}
};

class Extension extends Base {

}

function (): void {
foreach ((new Extension())::instances() as $h) {
assertType(Extension::class, $h);
}
};

0 comments on commit 1256192

Please sign in to comment.