Skip to content

Commit

Permalink
Fix inferring template types on arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 19, 2021
1 parent 6d228a5 commit 19e3cde
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/Dependency/NodeDependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use PHPStan\Reflection\ReflectionWithFilename;

/**
* @implements \IteratorAggregate<int, ReflectionWithFilename>
* @implements \IteratorAggregate<int|string, ReflectionWithFilename>
*/
class NodeDependencies implements IteratorAggregate
{
Expand Down
10 changes: 3 additions & 7 deletions src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,9 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
return $typeMap;
}

if (
$receivedType instanceof ArrayType
&& !$this->getKeyType()->isSuperTypeOf($receivedType->getKeyType())->no()
&& !$this->getItemType()->isSuperTypeOf($receivedType->getItemType())->no()
) {
$keyTypeMap = $this->getKeyType()->inferTemplateTypes($receivedType->getKeyType());
$itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getItemType());
if ($receivedType->isArray()->yes()) {
$keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType());
$itemTypeMap = $this->getIterableValueType()->inferTemplateTypes($receivedType->getIterableValueType());

return $keyTypeMap->union($itemTypeMap);
}
Expand Down
11 changes: 5 additions & 6 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -753,22 +753,21 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
return $receivedType->inferTemplateTypesOn($this);
}

if ($receivedType instanceof self && !$this->isSuperTypeOf($receivedType)->no()) {
if ($receivedType instanceof self) {
$typeMap = TemplateTypeMap::createEmpty();
foreach ($this->keyTypes as $i => $keyType) {
$valueType = $this->valueTypes[$i];
if ($receivedType->hasOffsetValueType($keyType)->no()) {
continue;
}
$receivedValueType = $receivedType->getOffsetValueType($keyType);
$typeMap = $typeMap->union($valueType->inferTemplateTypes($receivedValueType));
}

return $typeMap;
}

if ($receivedType instanceof ArrayType) {
return parent::inferTemplateTypes($receivedType);
}

return TemplateTypeMap::createEmpty();
return parent::inferTemplateTypes($receivedType);
}

public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
Expand Down
12 changes: 12 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10842,6 +10842,16 @@ public function dataBug4557(): array
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4557.php');
}

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

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

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -11072,6 +11082,8 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataBug2927
* @dataProvider dataBug4558
* @dataProvider dataBug4557
* @dataProvider dataBug4209
* @dataProvider dataBug4209Two
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
69 changes: 69 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4209-2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Bug4209Two;

use function PHPStan\Analyser\assertType;

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

/**
* @template T
*/
interface Link {
/**
* @return T
*/
public function getItem();
}

/**
* @implements Link<Customer>
*/
class CustomerLink implements Link
{
/**
* @var Customer
*/
public $item;

/**
* @param Customer $item
*/
public function __construct($item) {
$this->item = $item;
}

/**
* @return Customer
*/
public function getItem()
{
return $this->item;
}
}

/**
* @return CustomerLink[]
*/
function get_links(): array {
return [new CustomerLink(new Customer())];
}

/**
* @template T
* @param Link<T>[] $links
* @return T
*/
function process_customers(array $links) {
// no-op
}

class Runner {
public function run(): void
{
assertType('Bug4209Two\Customer', process_customers(get_links()));
}
}
50 changes: 50 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4209.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Bug4209;

use function PHPStan\Analyser\assertType;

/**
* @template T
*/
class Link {
/**
* @var T
*/
public $item;

/**
* @param T $item
*/
public function __construct($item) {
$this->item = $item;
}
}

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

/**
* @return Link<Customer>[]
*/
function get_links(): array {
return [new Link(new Customer())];
}

/**
* @template T
* @param Link<T>[] $links
* @return T
*/
function process_customers(array $links) {
// no-op
}

class Runner {
public function run(): void
{
assertType('Bug4209\Customer', process_customers(get_links()));
}
}
16 changes: 16 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1826,4 +1826,20 @@ public function testBug4557(): void
$this->analyse([__DIR__ . '/../../Analyser/data/bug-4557.php'], []);
}

public function testBug4209(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/../../Analyser/data/bug-4209.php'], []);
}

public function testBug4209Two(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/../../Analyser/data/bug-4209-2.php'], []);
}

}

0 comments on commit 19e3cde

Please sign in to comment.