Skip to content

Commit

Permalink
Detect always truthy/falsey conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 11, 2018
1 parent 254a454 commit 689e956
Show file tree
Hide file tree
Showing 54 changed files with 1,050 additions and 15 deletions.
6 changes: 6 additions & 0 deletions conf/config.level4.neon
Expand Up @@ -2,6 +2,12 @@ includes:
- config.level3.neon

rules:
- PHPStan\Rules\Comparison\BooleanAndConstantConditionRule
- PHPStan\Rules\Comparison\BooleanNotConstantConditionRule
- PHPStan\Rules\Comparison\BooleanOrConstantConditionRule
- PHPStan\Rules\Comparison\ElseIfConstantConditionRule
- PHPStan\Rules\Comparison\IfConstantConditionRule
- PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule
- PHPStan\Rules\Cast\UselessCastRule

services:
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/NodeScopeResolver.php
Expand Up @@ -507,8 +507,8 @@ private function processNode(\PhpParser\Node $node, Scope $scope, \Closure $node

$elseifScope = $ifScope->filterByFalseyValue($node->cond);
foreach ($node->elseifs as $elseif) {
$this->processNode($elseif, $scope, $nodeCallback, true);
$scope = $elseifScope;
$this->processNode($elseif, $scope, $nodeCallback, true);
$this->processNode($elseif->cond, $scope->exitFirstLevelStatements(), $nodeCallback);
$scope = $this->lookForAssigns(
$scope,
Expand Down
84 changes: 78 additions & 6 deletions src/Analyser/Scope.php
Expand Up @@ -43,6 +43,7 @@
use PHPStan\Type\IntegerType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NonexistentParentClassType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
Expand Down Expand Up @@ -325,12 +326,7 @@ public function getType(Expr $node): Type
private function resolveType(Expr $node): Type
{
if (
$node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanAnd
|| $node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanOr
|| $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalAnd
|| $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalOr
|| $node instanceof \PhpParser\Node\Expr\BooleanNot
|| $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalXor
$node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalXor
|| $node instanceof Expr\BinaryOp\Greater
|| $node instanceof Expr\BinaryOp\GreaterOrEqual
|| $node instanceof Expr\BinaryOp\Smaller
Expand All @@ -343,6 +339,79 @@ private function resolveType(Expr $node): Type
return new BooleanType();
}

if ($node instanceof \PhpParser\Node\Expr\BooleanNot) {
$exprBooleanType = $this->getType($node->expr)->toBoolean();
if ($exprBooleanType instanceof ConstantBooleanType) {
return new ConstantBooleanType(!$exprBooleanType->getValue());
}

return new BooleanType();
}

if (
$node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanAnd
|| $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalAnd
) {
$leftBooleanType = $this->getType($node->left)->toBoolean();
if (
$leftBooleanType instanceof ConstantBooleanType
&& !$leftBooleanType->getValue()
) {
return new ConstantBooleanType(false);
}

$rightBooleanType = $this->filterByTruthyValue($node->left)->getType($node->right)->toBoolean();
if (
$rightBooleanType instanceof ConstantBooleanType
&& !$rightBooleanType->getValue()
) {
return new ConstantBooleanType(false);
}

if (
$leftBooleanType instanceof ConstantBooleanType
&& $leftBooleanType->getValue()
&& $rightBooleanType instanceof ConstantBooleanType
&& $rightBooleanType->getValue()
) {
return new ConstantBooleanType(true);
}

return new BooleanType();
}

if (
$node instanceof \PhpParser\Node\Expr\BinaryOp\BooleanOr
|| $node instanceof \PhpParser\Node\Expr\BinaryOp\LogicalOr
) {
$leftBooleanType = $this->getType($node->left)->toBoolean();
if (
$leftBooleanType instanceof ConstantBooleanType
&& $leftBooleanType->getValue()
) {
return new ConstantBooleanType(true);
}

$rightBooleanType = $this->filterByFalseyValue($node->left)->getType($node->right)->toBoolean();
if (
$rightBooleanType instanceof ConstantBooleanType
&& $rightBooleanType->getValue()
) {
return new ConstantBooleanType(true);
}

if (
$leftBooleanType instanceof ConstantBooleanType
&& !$leftBooleanType->getValue()
&& $rightBooleanType instanceof ConstantBooleanType
&& !$rightBooleanType->getValue()
) {
return new ConstantBooleanType(false);
}

return new BooleanType();
}

if ($node instanceof Expr\BinaryOp\Identical) {
$leftType = $this->getType($node->left);
$rightType = $this->getType($node->right);
Expand Down Expand Up @@ -390,6 +459,9 @@ private function resolveType(Expr $node): Type
}

$expressionType = $this->getType($node->expr);
if ($expressionType instanceof NeverType) {
return new ConstantBooleanType(false);
}
$isExpressionObject = (new ObjectWithoutClassType())->isSuperTypeOf($expressionType);
if (!$isExpressionObject->no() && $type instanceof StringType) {
return new BooleanType();
Expand Down
48 changes: 48 additions & 0 deletions src/Rules/Comparison/BooleanAndConstantConditionRule.php
@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Comparison;

use PHPStan\Type\Constant\ConstantBooleanType;

class BooleanAndConstantConditionRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\Node\Expr\BinaryOp\BooleanAnd::class;
}

/**
* @param \PhpParser\Node\Expr\BinaryOp\BooleanAnd $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(
\PhpParser\Node $node,
\PHPStan\Analyser\Scope $scope
): array
{
$messages = [];
$leftType = ConstantConditionRuleHelper::getBooleanType($scope, $node->left);
if ($leftType instanceof ConstantBooleanType) {
$messages[] = sprintf(
'Left side of && is always %s.',
$leftType->getValue() ? 'true' : 'false'
);
}

$rightType = ConstantConditionRuleHelper::getBooleanType(
$scope->filterByTruthyValue($node->left),
$node->right
);
if ($rightType instanceof ConstantBooleanType) {
$messages[] = sprintf(
'Right side of && is always %s.',
$rightType->getValue() ? 'true' : 'false'
);
}

return $messages;
}

}
38 changes: 38 additions & 0 deletions src/Rules/Comparison/BooleanNotConstantConditionRule.php
@@ -0,0 +1,38 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Comparison;

use PHPStan\Type\Constant\ConstantBooleanType;

class BooleanNotConstantConditionRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\Node\Expr\BooleanNot::class;
}

/**
* @param \PhpParser\Node\Expr\BooleanNot $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(
\PhpParser\Node $node,
\PHPStan\Analyser\Scope $scope
): array
{
$exprType = ConstantConditionRuleHelper::getBooleanType($scope, $node->expr);
if ($exprType instanceof ConstantBooleanType) {
return [
sprintf(
'Negated boolean is always %s.',
$exprType->getValue() ? 'false' : 'true'
),
];
}

return [];
}

}
48 changes: 48 additions & 0 deletions src/Rules/Comparison/BooleanOrConstantConditionRule.php
@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Comparison;

use PHPStan\Type\Constant\ConstantBooleanType;

class BooleanOrConstantConditionRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\Node\Expr\BinaryOp\BooleanOr::class;
}

/**
* @param \PhpParser\Node\Expr\BinaryOp\BooleanOr $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(
\PhpParser\Node $node,
\PHPStan\Analyser\Scope $scope
): array
{
$messages = [];
$leftType = ConstantConditionRuleHelper::getBooleanType($scope, $node->left);
if ($leftType instanceof ConstantBooleanType) {
$messages[] = sprintf(
'Left side of || is always %s.',
$leftType->getValue() ? 'true' : 'false'
);
}

$rightType = ConstantConditionRuleHelper::getBooleanType(
$scope->filterByFalseyValue($node->left),
$node->right
);
if ($rightType instanceof ConstantBooleanType) {
$messages[] = sprintf(
'Right side of || is always %s.',
$rightType->getValue() ? 'true' : 'false'
);
}

return $messages;
}

}
30 changes: 30 additions & 0 deletions src/Rules/Comparison/ConstantConditionRuleHelper.php
@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Comparison;

use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
use PHPStan\Type\BooleanType;

class ConstantConditionRuleHelper
{

public static function getBooleanType(
Scope $scope,
Expr $expr
): BooleanType
{
if (
$expr instanceof Expr\Instanceof_
|| $expr instanceof Expr\FuncCall
|| $expr instanceof Expr\BinaryOp\Identical
|| $expr instanceof Expr\BinaryOp\NotIdentical
) {
// already checked by different rules
return new BooleanType();
}

return $scope->getType($expr)->toBoolean();
}

}
38 changes: 38 additions & 0 deletions src/Rules/Comparison/ElseIfConstantConditionRule.php
@@ -0,0 +1,38 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Comparison;

use PHPStan\Type\Constant\ConstantBooleanType;

class ElseIfConstantConditionRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\Node\Stmt\ElseIf_::class;
}

/**
* @param \PhpParser\Node\Stmt\ElseIf_ $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(
\PhpParser\Node $node,
\PHPStan\Analyser\Scope $scope
): array
{
$exprType = ConstantConditionRuleHelper::getBooleanType($scope, $node->cond);
if ($exprType instanceof ConstantBooleanType) {
return [
sprintf(
'Elseif condition is always %s.',
$exprType->getValue() ? 'true' : 'false'
),
];
}

return [];
}

}
38 changes: 38 additions & 0 deletions src/Rules/Comparison/IfConstantConditionRule.php
@@ -0,0 +1,38 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Comparison;

use PHPStan\Type\Constant\ConstantBooleanType;

class IfConstantConditionRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\Node\Stmt\If_::class;
}

/**
* @param \PhpParser\Node\Stmt\If_ $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(
\PhpParser\Node $node,
\PHPStan\Analyser\Scope $scope
): array
{
$exprType = ConstantConditionRuleHelper::getBooleanType($scope, $node->cond);
if ($exprType instanceof ConstantBooleanType) {
return [
sprintf(
'If condition is always %s.',
$exprType->getValue() ? 'true' : 'false'
),
];
}

return [];
}

}

0 comments on commit 689e956

Please sign in to comment.