Skip to content

Commit

Permalink
Fixed accepting ConstantArrayType
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 3, 2020
1 parent 61cb5aa commit 34edb50
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 71 deletions.
50 changes: 2 additions & 48 deletions src/Rules/RuleLevelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\CompoundType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
Expand Down Expand Up @@ -63,52 +62,6 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp
$acceptedType = TypeCombinator::removeNull($acceptedType);
}

if (
$acceptedType->isArray()->yes()
&& $acceptingType->isArray()->yes()
&& !$acceptingType instanceof ConstantArrayType
) {
$acceptedConstantArrays = TypeUtils::getConstantArrays($acceptedType);
if (count($acceptedConstantArrays) > 0) {
foreach ($acceptedConstantArrays as $acceptedConstantArray) {
foreach ($acceptedConstantArray->getKeyTypes() as $i => $keyType) {
$valueType = $acceptedConstantArray->getValueTypes()[$i];
if (
!self::accepts(
$acceptingType->getIterableKeyType(),
$keyType,
$strictTypes
) || !self::accepts(
$acceptingType->getIterableValueType(),
$valueType,
$strictTypes
)
) {
return false;
}
}
}

return true;
}

if (
!self::accepts(
$acceptingType->getIterableKeyType(),
$acceptedType->getIterableKeyType(),
$strictTypes
) || !self::accepts(
$acceptingType->getIterableValueType(),
$acceptedType->getIterableValueType(),
$strictTypes
)
) {
return false;
}

return true;
}

if ($acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) {
foreach ($acceptingType->getTypes() as $innerType) {
if (self::accepts($innerType, $acceptedType, $strictTypes)) {
Expand All @@ -122,7 +75,8 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp
if (
$acceptedType->isArray()->yes()
&& $acceptingType->isArray()->yes()
&& !$acceptingType instanceof ConstantArrayType
&& count(TypeUtils::getConstantArrays($acceptedType)) === 0
&& count(TypeUtils::getConstantArrays($acceptingType)) === 0
) {
return self::accepts(
$acceptingType->getIterableKeyType(),
Expand Down
21 changes: 13 additions & 8 deletions src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,25 @@ public function getReferencedClasses(): array

public function accepts(Type $type, bool $strictTypes): TrinaryLogic
{
$arrays = TypeUtils::getArrays($type);
if (count($arrays) > 0) {
if ($type instanceof CompoundType) {
return CompoundTypeHelper::accepts($type, $this, $strictTypes);
}

if ($type instanceof ConstantArrayType) {
$result = TrinaryLogic::createYes();
foreach ($arrays as $array) {
$result = $result
->and($this->getItemType()->accepts($array->getItemType(), $strictTypes))
->and($this->keyType->accepts($array->keyType, $strictTypes));
$thisKeyType = $this->keyType;
$itemType = $this->getItemType();
foreach ($type->getKeyTypes() as $i => $keyType) {
$valueType = $type->getValueTypes()[$i];
$result = $result->and($thisKeyType->accepts($keyType, $strictTypes))->and($itemType->accepts($valueType, $strictTypes));
}

return $result;
}

if ($type instanceof CompoundType) {
return CompoundTypeHelper::accepts($type, $this, $strictTypes);
if ($type instanceof ArrayType) {
return $this->getItemType()->accepts($type->getItemType(), $strictTypes)
->and($this->keyType->accepts($type->keyType, $strictTypes));
}

return TrinaryLogic::createNo();
Expand Down
10 changes: 0 additions & 10 deletions tests/PHPStan/Levels/data/acceptTypes-5.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,6 @@
"line": 118,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, int|null> given.",
"line": 119,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, float|int> given.",
"line": 120,
"ignorable": true
},
{
"message": "Parameter #1 $callable of method Levels\\AcceptTypes\\Foo::expectCallable() expects callable(): mixed, 'nonexistentFunction' given.",
"line": 144,
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Levels/data/acceptTypes-7.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@
"line": 92,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, int|null> given.",
"line": 119,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, float|int> given.",
"line": 120,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, int|null> given.",
"line": 131,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, float|int> given.",
"line": 132,
Expand Down
5 changes: 0 additions & 5 deletions tests/PHPStan/Levels/data/acceptTypes-8.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
"line": 91,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Foo::doBarArray() expects array<int>, array<int, int|null> given.",
"line": 131,
"ignorable": true
},
{
"message": "Parameter #1 $i of method Levels\\AcceptTypes\\Baz::doBar() expects int, int|null given.",
"line": 405,
Expand Down
42 changes: 42 additions & 0 deletions tests/PHPStan/Rules/Methods/data/call-methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -1598,3 +1598,45 @@ public function doBar(array $members)
}

}

class ConstantArrayAccepts
{

/**
* @param array{
* name: string,
* color: string,
* year: int,
* } $param
*/
public function doFoo(array $param): void
{
$this->doBar($param);
}

/**
* @param array{
* name: string,
* color?: string,
* } $param
*/
public function doBar(array $param): void
{

}

}

class ConstantArrayAcceptsOptionalKey
{

/**
* @param array{wrapperClass?: class-string} $params
*/
public function doFoo(array $params)
{
$this->doFoo(['wrapperClass' => \stdClass::class, 'undocumented' => 42]);
$this->doFoo([]);
}

}
23 changes: 23 additions & 0 deletions tests/PHPStan/Type/ArrayTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,29 @@ public function dataAccepts(): array
]),
TrinaryLogic::createYes(),
],
[
new ArrayType(new MixedType(), new CallableType()),
new ConstantArrayType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
], [
new ConstantArrayType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
], [
new ThisType(self::class),
new ConstantStringType('dataAccepts'),
]),
new ConstantArrayType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
], [
new ThisType(self::class),
new ConstantStringType('dataIsSuperTypeOf'),
]),
]),
TrinaryLogic::createYes(),
],
];
}

Expand Down

0 comments on commit 34edb50

Please sign in to comment.