Skip to content

Commit

Permalink
Empty array infers key and value type as never
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 3, 2022
1 parent 7ddd284 commit c2b9e71
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 34 deletions.
20 changes: 0 additions & 20 deletions src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
use PHPStan\Type\Traits\UndecidedBooleanTypeTrait;
use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
use function array_merge;
use function count;
use function is_float;
use function is_int;
use function key;
Expand Down Expand Up @@ -368,25 +367,6 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
return $receivedType->inferTemplateTypesOn($this);
}

if ($receivedType instanceof ConstantArrayType && count($receivedType->getKeyTypes()) === 0) {
$keyType = $this->getKeyType();
$typeMap = TemplateTypeMap::createEmpty();
if ($keyType instanceof TemplateType) {
$typeMap = new TemplateTypeMap([
$keyType->getName() => $keyType->getBound(),
]);
}

$itemType = $this->getItemType();
if ($itemType instanceof TemplateType) {
$typeMap = $typeMap->union(new TemplateTypeMap([
$itemType->getName() => $itemType->getBound(),
]));
}

return $typeMap;
}

if ($receivedType->isArray()->yes()) {
$keyTypeMap = $this->getKeyType()->inferTemplateTypes($receivedType->getIterableKeyType());
$itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getIterableValueType());
Expand Down
4 changes: 2 additions & 2 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ public function __construct(
assert(count($keyTypes) === count($valueTypes));

parent::__construct(
count($keyTypes) > 0 ? TypeCombinator::union(...$keyTypes) : new NeverType(),
count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType(),
count($keyTypes) > 0 ? TypeCombinator::union(...$keyTypes) : new NeverType(true),
count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType(true),
);
}

Expand Down
6 changes: 3 additions & 3 deletions src/Type/Generic/TemplateTypeArgumentStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace PHPStan\Type\Generic;

use PHPStan\TrinaryLogic;
use PHPStan\Type\CompoundType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;

/**
Expand All @@ -25,14 +25,14 @@ public function accepts(TemplateType $left, Type $right, bool $strictTypes): Tri
return TrinaryLogic::createNo();
}

if ($right instanceof TemplateType) {
if ($right instanceof CompoundType) {
$accepts = $right->isAcceptedBy($left, $strictTypes);
} else {
$accepts = $left->getBound()->accepts($right, $strictTypes)
->and(TrinaryLogic::createMaybe());
}

return $accepts->or(TrinaryLogic::createFromBoolean($right->equals(new MixedType())));
return $accepts;
}

public function isArgument(): bool
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Generic/TemplateTypeVariance.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;

/** @api */
Expand Down Expand Up @@ -97,6 +98,10 @@ public function compose(self $other): self

public function isValidVariance(Type $a, Type $b): TrinaryLogic
{
if ($b instanceof NeverType) {
return TrinaryLogic::createYes();
}

if ($a instanceof MixedType && !$a instanceof TemplateType) {
return TrinaryLogic::createYes();
}
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 @@ -763,6 +763,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_filter.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5759.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5668.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/generics-empty-array.php');
}

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

namespace GenericsEmptyArray;

use function PHPStan\Testing\assertType;

class Foo
{

/**
* @template TKey of array-key
* @template T
* @param array<T, TKey> $a
* @return array{TKey, T}
*/
public function doFoo(array $a = []): array
{

}

public function doBar()
{
assertType('array{*NEVER*, *NEVER*}', $this->doFoo());
assertType('array{*NEVER*, *NEVER*}', $this->doFoo([]));
}

}

/**
* @template TKey of array-key
* @template T
*/
class ArrayCollection
{

/**
* @param array<TKey, T> $items
*/
public function __construct(array $items = [])
{

}

}

class Bar
{

public function doFoo()
{
assertType('GenericsEmptyArray\\ArrayCollection<*NEVER*, *NEVER*>', new ArrayCollection());
assertType('GenericsEmptyArray\\ArrayCollection<*NEVER*, *NEVER*>', new ArrayCollection([]));
}

}
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/data/generics.php
Original file line number Diff line number Diff line change
Expand Up @@ -930,8 +930,8 @@ public function returnStatic(): self

function () {
$stdEmpty = new StdClassCollection([]);
assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection<(int|string), stdClass>', $stdEmpty);
assertType('array<stdClass>', $stdEmpty->getAll());
assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection<*NEVER*, *NEVER*>', $stdEmpty);
assertType('array<*NEVER*, *NEVER*>', $stdEmpty->getAll());

$std = new StdClassCollection([new \stdClass()]);
assertType('PHPStan\Generics\FunctionsAssertType\StdClassCollection<int, stdClass>', $std);
Expand Down
7 changes: 1 addition & 6 deletions tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,7 @@ public function testIsGenerator(): void
public function testBug2568(): void
{
require_once __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,
],
]);
$this->analyse([__DIR__ . '/data/bug-2568.php'], []);
}

public function testBug2723(): void
Expand Down
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2415,4 +2415,13 @@ public function testBug5869(): void
$this->analyse([__DIR__ . '/data/bug-5869.php'], []);
}

public function testGenericsEmptyArray(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/generics-empty-array.php'], []);
}

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

namespace GenericsEmptyArrayCall;

class Foo
{

/**
* @template TKey of array-key
* @template T
* @param array<TKey, T> $a
* @return array{TKey, T}
*/
public function doFoo(array $a = []): array
{

}

public function doBar()
{
$this->doFoo();
$this->doFoo([]);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,23 @@ protected function getRule(): Rule
public function testGenericObjectWithUnspecifiedTemplateTypes(): void
{
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], []);
$this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], [
[
'Property GenericObjectUnspecifiedTemplateTypes\Bar::$ints (GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, int>) does not accept GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, string>.',
67,
],
]);
}

public function testGenericObjectWithUnspecifiedTemplateTypesLevel8(): void
{
$this->checkExplicitMixed = false;
$this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], [
[
'Property GenericObjectUnspecifiedTemplateTypes\Bar::$ints (GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, int>) does not accept GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, string>.',
67,
],
]);
}

public static function getAdditionalConfigFiles(): array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,21 @@ public function testGenericObjectWithUnspecifiedTemplateTypes(): void
'Property GenericObjectUnspecifiedTemplateTypes\Foo::$obj (ArrayObject<int, string>) does not accept ArrayObject<(int|string), mixed>.',
13,
],
[
'Property GenericObjectUnspecifiedTemplateTypes\Bar::$ints (GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, int>) does not accept GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, string>.',
67,
],
]);
}

public function testGenericObjectWithUnspecifiedTemplateTypesLevel8(): void
{
$this->checkExplicitMixed = false;
$this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], [
[
'Property GenericObjectUnspecifiedTemplateTypes\Bar::$ints (GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, int>) does not accept GenericObjectUnspecifiedTemplateTypes\ArrayCollection<int, string>.',
67,
],
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,80 @@ public function __construct()
}

}

/**
* @template TKey of array-key
* @template T
*/
class ArrayCollection
{

/**
* @param array<TKey, T> $items
*/
public function __construct(array $items = [])
{

}

}

/**
* @template TKey of array-key
* @template T
*/
class ArrayCollection2
{

public function __construct(array $items = [])
{

}

}

class Bar
{

/** @var ArrayCollection<int, int> */
private $ints;

public function __construct()
{
$this->ints = new ArrayCollection();
}

public function doFoo()
{
$this->ints = new ArrayCollection([]);
}

public function doBar()
{
$this->ints = new ArrayCollection(['foo', 'bar']);
}

}

class Baz
{

/** @var ArrayCollection2<int, int> */
private $ints;

public function __construct()
{
$this->ints = new ArrayCollection2();
}

public function doFoo()
{
$this->ints = new ArrayCollection2([]);
}

public function doBar()
{
$this->ints = new ArrayCollection2(['foo', 'bar']);
}

}

0 comments on commit c2b9e71

Please sign in to comment.