Skip to content

Commit

Permalink
TypeSpecifier - understand Equal operator for the same types on both …
Browse files Browse the repository at this point in the history
…sides
  • Loading branch information
ondrejmirtes committed Jan 15, 2022
1 parent cbb7963 commit e40eff0
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 38 deletions.
3 changes: 3 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,9 @@ private function resolveType(Expr $node): Type
$referencedClasses = TypeUtils::getDirectClassNames($constantClassType);
if (strtolower($constantName) === 'class') {
if (count($referencedClasses) === 0) {
if ((new ObjectWithoutClassType())->isSuperTypeOf($constantClassType)->yes()) {
return new ClassStringType();
}
return new ErrorType();
}
$classTypes = [];
Expand Down
52 changes: 14 additions & 38 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\Enum\EnumCaseObjectType;
use PHPStan\Type\FloatType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\IntegerRangeType;
Expand Down Expand Up @@ -342,44 +343,10 @@ public function specifyTypesInCondition(
$context->true() ? TypeSpecifierContext::createTruthy() : TypeSpecifierContext::createTruthy()->negate(),
);
}

if (
!$context->null()
&& $exprNode instanceof FuncCall
&& count($exprNode->getArgs()) === 1
&& $exprNode->name instanceof Name
&& in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true)
&& $constantType instanceof ConstantIntegerType
) {
if ($context->truthy() || $constantType->getValue() === 0) {
$newContext = $context;
if ($constantType->getValue() === 0) {
$newContext = $newContext->negate();
}
$argType = $scope->getType($exprNode->getArgs()[0]->value);
if ($argType->isArray()->yes()) {
return $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope);
}
}
}
}

$leftType = $scope->getType($expr->left);
$rightType = $scope->getType($expr->right);
if (
$expr->left instanceof ClassConstFetch &&
$expr->left->class instanceof Expr &&
$expr->left->name instanceof Node\Identifier &&
$expr->right instanceof ClassConstFetch &&
$rightType instanceof ConstantStringType &&
strtolower($expr->left->name->toString()) === 'class'
) {
return $this->specifyTypesInCondition(
$scope,
new Expr\BinaryOp\Identical($expr->left, $expr->right),
$context,
);
}

$leftBooleanType = $leftType->toBoolean();
if ($leftBooleanType instanceof ConstantBooleanType && $rightType instanceof BooleanType) {
Expand All @@ -406,16 +373,14 @@ public function specifyTypesInCondition(
}

if (
$context->falsey()
&& $rightType->isArray()->yes()
$rightType->isArray()->yes()
&& $leftType instanceof ConstantArrayType && $leftType->isEmpty()
) {
return $this->create($expr->right, new NonEmptyArrayType(), $context->negate(), false, $scope);
}

if (
$context->falsey()
&& $leftType->isArray()->yes()
$leftType->isArray()->yes()
&& $rightType instanceof ConstantArrayType && $rightType->isEmpty()
) {
return $this->create($expr->left, new NonEmptyArrayType(), $context->negate(), false, $scope);
Expand Down Expand Up @@ -454,6 +419,17 @@ public function specifyTypesInCondition(
$context,
);
}

$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->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context);
}
} elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) {
return $this->specifyTypesInCondition(
$scope,
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 @@ -608,6 +608,7 @@ public function dataFileAsserts(): iterable
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/weird-array_key_exists-issue.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/equal.php');
}

/**
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/data/bug-5843.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Foo

function doFoo(object $object): void
{
assertType('class-string', $object::class);
switch ($object::class) {
case \DateTime::class:
assertType(\DateTime::class, $object);
Expand Down
68 changes: 68 additions & 0 deletions tests/PHPStan/Analyser/data/equal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace TypeSpecifierEqual;

use function PHPStan\Testing\assertType;

class Foo
{

public function doFoo(string $s): void
{
assertType("string", $s);
if ($s == 'one') {
assertType("'one'", $s);
} else {
assertType("string", $s);
}
assertType("string", $s);
}

/** @param 'one'|'two' $s */
public function doBar(string $s): void
{
assertType("'one'|'two'", $s);
if ($s == 'one') {
assertType("'one'", $s);
} else {
assertType("'two'", $s);
}
assertType("'one'|'two'", $s);
}

/** @param int<1, 3>|int<8, 13> $i */
public function doBaz(int $i): void
{
assertType('int<1, 3>|int<8, 13>', $i);
if ($i == 3) {
assertType('3', $i);
} else {
assertType('int<1, 2>|int<8, 13>', $i);
}
assertType('int<1, 3>|int<8, 13>', $i);
}

public function doLorem(float $f): void
{
assertType('float', $f);
if ($f == 3.5) {
assertType('3.5', $f);
} else {
assertType('float', $f);
}

assertType('float', $f);
}

public function doIpsum(array $a): void
{
assertType('array', $a);
if ($a == []) {
assertType('array{}', $a);
} else {
assertType('non-empty-array', $a);
}
assertType('array', $a);
}

}

0 comments on commit e40eff0

Please sign in to comment.