Skip to content

Commit

Permalink
Bleeding edge - intersect array key type with int|string
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 9, 2021
1 parent 57e3cbf commit 724c8ba
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 3 deletions.
2 changes: 2 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ services:

-
class: PHPStan\PhpDoc\TypeNodeResolver
arguments:
deepInspectTypes: %featureToggles.deepInspectTypes%

-
class: PHPStan\PhpDoc\TypeNodeResolverExtensionRegistryProvider
Expand Down
15 changes: 13 additions & 2 deletions src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,17 @@ class TypeNodeResolver

private Container $container;

private bool $deepInspectTypes;

public function __construct(
TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider,
Container $container
Container $container,
bool $deepInspectTypes = false
)
{
$this->extensionRegistryProvider = $extensionRegistryProvider;
$this->container = $container;
$this->deepInspectTypes = $deepInspectTypes;
}

/** @api */
Expand Down Expand Up @@ -417,7 +421,14 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na
if (count($genericTypes) === 1) { // array<ValueType>
$arrayType = new ArrayType(new MixedType(true), $genericTypes[0]);
} elseif (count($genericTypes) === 2) { // array<KeyType, ValueType>
$arrayType = new ArrayType($genericTypes[0], $genericTypes[1]);
$keyType = $genericTypes[0];
if ($this->deepInspectTypes) {
$keyType = TypeCombinator::intersect($keyType, new UnionType([
new IntegerType(),
new StringType(),
]));
}
$arrayType = new ArrayType($keyType, $genericTypes[1]);
} else {
return new ErrorType();
}
Expand Down
7 changes: 6 additions & 1 deletion tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ public function testIsGenerator(): void
public function testBug2568(): void
{
require_once __DIR__ . '/data/bug-2568.php';
$this->analyse([__DIR__ . '/data/bug-2568.php'], []);
$this->analyse([__DIR__ . '/data/bug-2568.php'], [
[
'Function Bug2568\my_array_keys() should return array<int, T> but returns array<int, (int&T)|(string&T)>.',
12,
],
]);
}

public function testBug2723(): void
Expand Down
20 changes: 20 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-2568.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,23 @@
function my_array_keys($arr) {
return array_keys($arr);
}

/**
* @template T of array-key
*
* @param array<T, mixed> $arr
* @return array<int, T>
*/
function my_array_keys2($arr) {
return array_keys($arr);
}

/**
* @template T of int|string
*
* @param array<T, mixed> $arr
* @return array<int, T>
*/
function my_array_keys3($arr) {
return array_keys($arr);
}
76 changes: 76 additions & 0 deletions tests/PHPStan/Rules/Methods/data/infer-array-key.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,79 @@ public function getIterator()
}

}

/**
* @implements \IteratorAggregate<int, \stdClass>
*/
class Bar implements \IteratorAggregate
{

/** @var array<int, \stdClass> */
private $items;

public function getIterator()
{
$it = new \ArrayIterator($this->items);
assertType('int', $it->key());

return $it;
}

}

/**
* @implements \IteratorAggregate<string, \stdClass>
*/
class Baz implements \IteratorAggregate
{

/** @var array<string, \stdClass> */
private $items;

public function getIterator()
{
$it = new \ArrayIterator($this->items);
assertType('string', $it->key());

return $it;
}

}

/**
* @implements \IteratorAggregate<int, \stdClass>
*/
class Lorem implements \IteratorAggregate
{

/** @var array<\stdClass> */
private $items;

public function getIterator()
{
$it = new \ArrayIterator($this->items);
assertType('(int|string)', $it->key());

return $it;
}

}

/**
* @implements \IteratorAggregate<int|string, \stdClass>
*/
class Ipsum implements \IteratorAggregate
{

/** @var array<int|string, \stdClass> */
private $items;

public function getIterator()
{
$it = new \ArrayIterator($this->items);
assertType('int|string', $it->key());

return $it;
}

}
4 changes: 4 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ public function testBug3753(): void
'PHPDoc tag @param for parameter $foo contains unresolvable type.',
20,
],
[
'PHPDoc tag @param for parameter $bars contains unresolvable type.',
28,
],
]);
}

Expand Down
41 changes: 41 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateBenevolentUnionType;
use PHPStan\Type\Generic\TemplateObjectType;
use PHPStan\Type\Generic\TemplateObjectWithoutClassType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeFactory;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Generic\TemplateUnionType;

class TypeCombinatorTest extends \PHPStan\Testing\TestCase
{
Expand Down Expand Up @@ -2945,6 +2947,45 @@ public function dataIntersect(): array
NeverType::class,
'*NEVER*',
],
[
[
TemplateTypeFactory::create(
TemplateTypeScope::createWithFunction('my_array_keys'),
'T',
new BenevolentUnionType([new IntegerType(), new StringType()]),
TemplateTypeVariance::createInvariant(),
),
new UnionType([new IntegerType(), new StringType()]),
],
TemplateBenevolentUnionType::class,
'T of (int|string) (function my_array_keys(), parameter)',
],
[
[
TemplateTypeFactory::create(
TemplateTypeScope::createWithFunction('my_array_keys'),
'T',
new BenevolentUnionType([new IntegerType(), new StringType()]),
TemplateTypeVariance::createInvariant(),
),
new BenevolentUnionType([new IntegerType(), new StringType()]),
],
TemplateBenevolentUnionType::class,
'T of (int|string) (function my_array_keys(), parameter)',
],
[
[
TemplateTypeFactory::create(
TemplateTypeScope::createWithFunction('my_array_keys'),
'T',
new UnionType([new IntegerType(), new StringType()]),
TemplateTypeVariance::createInvariant(),
),
new UnionType([new IntegerType(), new StringType()]),
],
UnionType::class,
'T of int|string (function my_array_keys(), parameter)',
],
];
}

Expand Down

0 comments on commit 724c8ba

Please sign in to comment.