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 6e02453 commit c9ae89c
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 57 deletions.
129 changes: 73 additions & 56 deletions src/Analyser/MutatingScope.php
Expand Up @@ -628,18 +628,7 @@ private function resolveType(Expr $node): Type
$leftType = $this->getType($node->left);
$rightType = $this->getType($node->right);

$stringType = new StringType();
$integerType = new IntegerType();
$floatType = new FloatType();
if (
($stringType->isSuperTypeOf($leftType)->yes() && $stringType->isSuperTypeOf($rightType)->yes())
|| ($integerType->isSuperTypeOf($leftType)->yes() && $integerType->isSuperTypeOf($rightType)->yes())
|| ($floatType->isSuperTypeOf($leftType)->yes() && $floatType->isSuperTypeOf($rightType)->yes())
) {
return $this->getType(new Expr\BinaryOp\Identical($node->left, $node->right));
}

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

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

private function resolveIdenticalType(Type $leftType, Type $rightType): Type
private function resolveIdenticalType(Type $leftType, Type $rightType): BooleanType
{
$isSuperset = $leftType->isSuperTypeOf($rightType);
if ($isSuperset->no()) {
Expand All @@ -2350,65 +2339,93 @@ private function resolveIdenticalType(Type $leftType, Type $rightType): Type
}

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);
}
return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveIdenticalType($leftValueType, $rightValueType));
}

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

$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;
}
private function resolveEqualType(Type $leftType, Type $rightType): BooleanType
{
$stringType = new StringType();
$integerType = new IntegerType();
$floatType = new FloatType();
if (
($stringType->isSuperTypeOf($leftType)->yes() && $stringType->isSuperTypeOf($rightType)->yes())
|| ($integerType->isSuperTypeOf($leftType)->yes() && $integerType->isSuperTypeOf($rightType)->yes())
|| ($floatType->isSuperTypeOf($leftType)->yes() && $floatType->isSuperTypeOf($rightType)->yes())
) {
return $this->resolveIdenticalType($leftType, $rightType);
}

$hasOptional = true;
}
if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) {
return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveEqualType($leftValueType, $rightValueType));
}

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

$rightKeyTypesCount = count($rightKeyTypes);
for (; $i < $rightKeyTypesCount; $i++) {
if ($rightType->isOptionalKey($i)) {
/**
* @param callable(Type, Type): BooleanType $valueComparisonCallback
*/
private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): BooleanType
{
$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);
}

if ($hasOptional) {
return new BooleanType();
$leftValueType = $leftValueTypes[$i];
$rightValueType = $rightValueTypes[$i];
$leftIdenticalToRight = $valueComparisonCallback($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;
}

return new ConstantBooleanType(true);
$hasOptional = true;
}

return new BooleanType();
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);
}

private function resolveConcatType(Expr\BinaryOp\Concat|Expr\AssignOp\Concat $node): Type
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-6940.php
Expand Up @@ -10,7 +10,7 @@ class Bug6940
public function foo(): void
{
$b = [] == [];
assertType('bool', $b);
assertType('true', $b);
}

}
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/data/constant-array-type-identical.php
Expand Up @@ -11,6 +11,13 @@ class Foo
public function doFoo(string $s): void
{
assertType('true', [1] === [1]);
assertType('true', [1] == [1]);
assertType('false', [1] != [1]);
assertType('false', [1] == [2]);
assertType('true', [1] != [2]);
assertType('bool', [1] == ["1"]);
assertType('bool', [1] != ["1"]);

assertType('false', [1] === [2]);
assertType('false', [1] !== [1]);
assertType('true', [1] !== [2]);
Expand Down

0 comments on commit c9ae89c

Please sign in to comment.