Skip to content

Commit

Permalink
Handle ConstantArrayType === ConstantArrayType
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Apr 27, 2022
1 parent 9ee27df commit 6e02453
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 13 deletions.
90 changes: 77 additions & 13 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -854,19 +854,7 @@ private function resolveType(Expr $node): Type
return new BooleanType();
}

$isSuperset = $leftType->isSuperTypeOf($rightType);
if ($isSuperset->no()) {
return new ConstantBooleanType(false);
} elseif (
$isSuperset->yes()
&& $leftType instanceof ConstantScalarType
&& $rightType instanceof ConstantScalarType
&& $leftType->getValue() === $rightType->getValue()
) {
return new ConstantBooleanType(true);
}

return new BooleanType();
return $this->resolveIdenticalType($leftType, $rightType);
}

if ($node instanceof Expr\BinaryOp\NotIdentical) {
Expand Down Expand Up @@ -2347,6 +2335,82 @@ private function resolveType(Expr $node): Type
return new MixedType();
}

private function resolveIdenticalType(Type $leftType, Type $rightType): Type
{
$isSuperset = $leftType->isSuperTypeOf($rightType);
if ($isSuperset->no()) {
return new ConstantBooleanType(false);
} elseif (
$isSuperset->yes()
&& $leftType instanceof ConstantScalarType
&& $rightType instanceof ConstantScalarType
&& $leftType->getValue() === $rightType->getValue()
) {
return new ConstantBooleanType(true);
}

if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
$leftValueTypes = $leftType->getValueTypes();
$rightKeyTypes = $rightType->getKeyTypes();
$rightValueTypes = $rightType->getValueTypes();
$hasOptional = false;
foreach ($leftType->getKeyTypes() as $i => $keyType) {
if (!array_key_exists($i, $rightKeyTypes)) {
if ($leftType->isOptionalKey($i)) {
$hasOptional = true;
continue;
}
return new ConstantBooleanType(false);
}

$rightKeyType = $rightKeyTypes[$i];
if (!$keyType->equals($rightKeyType)) {
return new ConstantBooleanType(false);
}

$leftValueType = $leftValueTypes[$i];
$rightValueType = $rightValueTypes[$i];
$leftIdenticalToRight = $this->resolveIdenticalType($leftValueType, $rightValueType);
if ($leftIdenticalToRight instanceof ConstantBooleanType) {
if (!$leftIdenticalToRight->getValue()) {
return new ConstantBooleanType(false);
}
$isLeftOptional = $leftType->isOptionalKey($i);
if ($isLeftOptional || $rightType->isOptionalKey($i)) {
$hasOptional = true;
}
continue;
}

$hasOptional = true;
}

if (!isset($i)) {
$i = 0;
} else {
$i++;
}

$rightKeyTypesCount = count($rightKeyTypes);
for (; $i < $rightKeyTypesCount; $i++) {
if ($rightType->isOptionalKey($i)) {
$hasOptional = true;
continue;
}

return new ConstantBooleanType(false);
}

if ($hasOptional) {
return new BooleanType();
}

return new ConstantBooleanType(true);
}

return new BooleanType();
}

private function resolveConcatType(Expr\BinaryOp\Concat|Expr\AssignOp\Concat $node): Type
{
if ($node instanceof Node\Expr\AssignOp) {
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 @@ -901,6 +901,7 @@ public function dataFileAsserts(): iterable

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7068.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7115.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-type-identical.php');
}

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

namespace ConstantArrayTypeIdentical;

use function PHPStan\Testing\assertType;

class Foo
{


public function doFoo(string $s): void
{
assertType('true', [1] === [1]);
assertType('false', [1] === [2]);
assertType('false', [1] !== [1]);
assertType('true', [1] !== [2]);
assertType('bool', [$s] === [$s]);
assertType('bool', [$s] !== [$s]);

$a = [1];
if (doFoo()) {
$a[] = 2;
}

assertType('bool', [1] === $a);
assertType('bool', $a === [1]);
assertType('bool', [1, 2] === $a);
assertType('false', [1, 3] === $a);
assertType('false', [0] === $a);
assertType('false', [0, 2] === $a);

assertType('bool', [1] !== $a);
assertType('bool', [1, 2] !== $a);
assertType('true', [1, 3] !== $a);
assertType('true', [0] !== $a);
assertType('true', [0, 2] !== $a);

$b = [1];
if (doFoo()) {
$b[] = 2;
}

assertType('bool', $a === $b);
assertType('bool', $a !== $b);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ public function testStrictComparison(): void
'Strict comparison using === between array{X: 1, Y: 2} and array{X: 2, Y: 1} will always evaluate to false.',
300,
],
[
'Strict comparison using === between array{X: 1, Y: 2} and array{Y: 2, X: 1} will always evaluate to false.',
308,
],
[
'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.',
320,
Expand Down Expand Up @@ -300,6 +304,10 @@ public function testStrictComparisonWithoutAlwaysTrue(): void
'Strict comparison using === between array{X: 1, Y: 2} and array{X: 2, Y: 1} will always evaluate to false.',
300,
],
[
'Strict comparison using === between array{X: 1, Y: 2} and array{Y: 2, X: 1} will always evaluate to false.',
308,
],
[
'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.',
320,
Expand Down

0 comments on commit 6e02453

Please sign in to comment.