2016-12-28 21:52:44 +01:00
|
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
|
|
|
|
use PhpParser;
|
2018-11-06 03:57:36 +01:00
|
|
|
|
use Psalm\Codebase;
|
|
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
|
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
|
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
use Psalm\CodeLocation;
|
2018-02-23 21:39:33 +01:00
|
|
|
|
use Psalm\FileSource;
|
2018-04-18 18:01:13 +02:00
|
|
|
|
use Psalm\Issue\DocblockTypeContradiction;
|
2018-04-17 21:39:09 +02:00
|
|
|
|
use Psalm\Issue\RedundantCondition;
|
2018-06-26 00:02:05 +02:00
|
|
|
|
use Psalm\Issue\RedundantConditionGivenDocblockType;
|
2017-04-06 21:36:22 +02:00
|
|
|
|
use Psalm\Issue\TypeDoesNotContainNull;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
use Psalm\Issue\TypeDoesNotContainType;
|
2017-10-23 01:53:53 +02:00
|
|
|
|
use Psalm\Issue\UnevaluatedCode;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
use Psalm\IssueBuffer;
|
2017-01-07 20:35:07 +01:00
|
|
|
|
use Psalm\StatementsSource;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
use Psalm\Type;
|
|
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
2017-01-07 20:35:07 +01:00
|
|
|
|
class AssertionFinder
|
2016-12-28 21:52:44 +01:00
|
|
|
|
{
|
|
|
|
|
const ASSIGNMENT_TO_RIGHT = 1;
|
|
|
|
|
const ASSIGNMENT_TO_LEFT = -1;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets all the type assertions in a conditional
|
|
|
|
|
*
|
2018-06-26 00:02:05 +02:00
|
|
|
|
* @param string|null $this_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2018-06-26 00:02:05 +02:00
|
|
|
|
* @return void
|
2016-12-28 21:52:44 +01:00
|
|
|
|
*/
|
2018-06-26 00:02:05 +02:00
|
|
|
|
public static function scrapeAssertions(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
PhpParser\Node\Expr $conditional,
|
|
|
|
|
$this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
FileSource $source,
|
2019-03-16 17:34:48 +01:00
|
|
|
|
Codebase $codebase = null,
|
|
|
|
|
bool $inside_negation = false
|
2016-12-28 21:52:44 +01:00
|
|
|
|
) {
|
2018-07-03 18:27:14 +02:00
|
|
|
|
if (isset($conditional->assertions)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$if_types = [];
|
2017-07-29 21:05:06 +02:00
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\Instanceof_) {
|
2019-01-27 20:10:33 +01:00
|
|
|
|
$instanceof_types = self::getInstanceOfTypes($conditional, $this_class_name, $source);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2019-01-27 20:10:33 +01:00
|
|
|
|
if ($instanceof_types) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$conditional->expr,
|
|
|
|
|
$this_class_name,
|
2017-01-07 20:35:07 +01:00
|
|
|
|
$source
|
2016-12-28 21:52:44 +01:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2019-01-27 20:10:33 +01:00
|
|
|
|
$if_types[$var_name] = [$instanceof_types];
|
2019-03-16 17:34:48 +01:00
|
|
|
|
|
|
|
|
|
$var_type = $conditional->expr->inferredType ?? null;
|
|
|
|
|
|
|
|
|
|
foreach ($instanceof_types as $instanceof_type) {
|
|
|
|
|
if ($codebase
|
|
|
|
|
&& $var_type
|
|
|
|
|
&& $inside_negation
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
if ($codebase->interfaceExists($instanceof_type)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$instanceof_type = new Type\Union([
|
|
|
|
|
new Type\Atomic\TNamedObject($instanceof_type)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!TypeAnalyzer::canExpressionTypesBeIdentical(
|
|
|
|
|
$codebase,
|
|
|
|
|
$instanceof_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
$var_type->getId() . ' does not contain ' . $instanceof_type,
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type->getId() . ' cannot be identical to ' . $instanceof_type,
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
2017-01-07 20:35:07 +01:00
|
|
|
|
$source
|
2018-05-07 07:26:06 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\Assign) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$conditional->var,
|
|
|
|
|
$this_class_name,
|
2017-01-07 20:35:07 +01:00
|
|
|
|
$source
|
2016-12-28 21:52:44 +01:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
self::scrapeAssertions(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$conditional->expr,
|
|
|
|
|
$this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$source,
|
2019-03-16 17:34:48 +01:00
|
|
|
|
$codebase,
|
|
|
|
|
!$inside_negation
|
2016-12-28 21:52:44 +01:00
|
|
|
|
);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if (!isset($conditional->expr->assertions)) {
|
|
|
|
|
throw new \UnexpectedValueException('Assertions should be set');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = \Psalm\Type\Algebra::negateTypes($conditional->expr->assertions);
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical ||
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
self::scrapeEqualityAssertions($conditional, $this_class_name, $source, $codebase);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical ||
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
|
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
self::scrapeInequalityAssertions($conditional, $this_class_name, $source, $codebase);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
|
|
|
|
|
) {
|
|
|
|
|
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional);
|
2017-01-07 20:35:07 +01:00
|
|
|
|
$typed_value_position = self::hasTypedValueComparison($conditional);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($count_equality_position) {
|
|
|
|
|
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
2019-02-21 23:17:10 +01:00
|
|
|
|
$counted_expr = $conditional->left;
|
2018-12-19 22:15:19 +01:00
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$count_equality_position value');
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-21 23:17:10 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $counted_expr */
|
2018-12-19 22:15:19 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2019-02-21 23:17:10 +01:00
|
|
|
|
$counted_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($var_name) {
|
2019-02-21 23:17:10 +01:00
|
|
|
|
if (self::hasReconcilableNonEmptyCountEqualityCheck($conditional)) {
|
|
|
|
|
$if_types[$var_name] = [['non-empty-countable']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['=non-empty-countable']];
|
|
|
|
|
}
|
2018-12-19 22:15:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($typed_value_position) {
|
|
|
|
|
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
/** @var PhpParser\Node\Expr $conditional->right */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$var_name = null;
|
2017-10-23 01:11:28 +02:00
|
|
|
|
} else {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
throw new \UnexpectedValueException('$typed_value_position value');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-11-16 16:13:52 +01:00
|
|
|
|
$if_types[$var_name] = [['=isset']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-10-22 18:09:22 +02:00
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
|
|
|
|
|
) {
|
|
|
|
|
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$typed_value_position = self::hasTypedValueComparison($conditional);
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($count_equality_position) {
|
2019-03-26 03:30:40 +01:00
|
|
|
|
if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
2018-12-19 22:15:19 +01:00
|
|
|
|
$count_expr = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$count_equality_position value');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$count_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [['=non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($typed_value_position) {
|
|
|
|
|
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$var_name = null;
|
|
|
|
|
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
/** @var PhpParser\Node\Expr $conditional->left */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->right,
|
2017-10-23 01:11:28 +02:00
|
|
|
|
$this_class_name,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$source
|
2017-10-23 01:11:28 +02:00
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$typed_value_position value');
|
2017-10-22 18:09:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-11-16 16:13:52 +01:00
|
|
|
|
$if_types[$var_name] = [['=isset']];
|
2017-10-22 18:09:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2017-10-22 18:09:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-04-06 20:53:45 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$conditional->assertions = self::processFunctionCall($conditional, $this_class_name, $source, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\MethodCall) {
|
|
|
|
|
$conditional->assertions = self::processCustomAssertion($conditional, $this_class_name, $source, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->expr,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2017-10-23 01:53:53 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [['empty']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\Isset_) {
|
|
|
|
|
foreach ($conditional->vars as $isset_var) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$isset_var,
|
2017-10-23 01:53:53 +02:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [['isset']];
|
|
|
|
|
} else {
|
|
|
|
|
// look for any variables we *can* use for an isset assertion
|
|
|
|
|
$array_root = $isset_var;
|
2017-10-23 01:53:53 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
while ($array_root instanceof PhpParser\Node\Expr\ArrayDimFetch && !$var_name) {
|
|
|
|
|
$array_root = $array_root->var;
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$array_root,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2017-10-23 01:53:53 +02:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-11-16 16:13:52 +01:00
|
|
|
|
$if_types[$var_name] = [['=isset']];
|
2017-10-23 01:53:53 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-10-23 01:53:53 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [['isset']];
|
|
|
|
|
} else {
|
|
|
|
|
// look for any variables we *can* use for an isset assertion
|
|
|
|
|
$array_root = $conditional->left;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
while ($array_root instanceof PhpParser\Node\Expr\ArrayDimFetch && !$var_name) {
|
|
|
|
|
$array_root = $array_root->var;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$array_root,
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$this_class_name,
|
2017-01-07 20:35:07 +01:00
|
|
|
|
$source
|
2016-12-28 21:52:44 +01:00
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($var_name) {
|
2018-11-16 16:13:52 +01:00
|
|
|
|
$if_types[$var_name] = [['=isset']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-05-13 00:46:47 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
|
|
|
|
* @param string|null $this_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private static function scrapeEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
$this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
FileSource $source,
|
2019-03-16 17:34:48 +01:00
|
|
|
|
Codebase $codebase = null,
|
|
|
|
|
bool $inside_negation = false
|
2018-06-26 00:02:05 +02:00
|
|
|
|
) {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
$null_position = self::hasNullVariable($conditional);
|
|
|
|
|
$false_position = self::hasFalseVariable($conditional);
|
|
|
|
|
$true_position = self::hasTrueVariable($conditional);
|
2019-03-02 21:18:29 +01:00
|
|
|
|
$empty_array_position = self::hasEmptyArrayVariable($conditional);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$gettype_position = self::hasGetTypeCheck($conditional);
|
|
|
|
|
$getclass_position = self::hasGetClassCheck($conditional);
|
2018-12-19 22:15:19 +01:00
|
|
|
|
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$typed_value_position = self::hasTypedValueComparison($conditional);
|
|
|
|
|
|
|
|
|
|
if ($null_position !== null) {
|
|
|
|
|
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$null_position value');
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['null']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
2018-05-03 19:56:30 +02:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2018-04-18 18:01:13 +02:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $var_type
|
2018-06-26 00:02:05 +02:00
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$null_type = Type::getNull();
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type,
|
|
|
|
|
$null_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$null_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' does not contain null',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new TypeDoesNotContainNull(
|
|
|
|
|
$var_type . ' does not contain null',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($true_position) {
|
|
|
|
|
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Unrecognised position');
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-12 15:38:56 +02:00
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
2019-04-12 15:38:56 +02:00
|
|
|
|
$if_types = self::processFunctionCall(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
false
|
|
|
|
|
);
|
2019-04-12 15:38:56 +02:00
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
2019-04-12 15:38:56 +02:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['true']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
2019-04-12 15:38:56 +02:00
|
|
|
|
self::scrapeAssertions(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$inside_negation
|
|
|
|
|
);
|
|
|
|
|
$if_types = $base_conditional->assertions;
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase && $var_type) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
2018-05-03 19:56:30 +02:00
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$true_type = Type::getTrue();
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-05-03 19:56:30 +02:00
|
|
|
|
$var_type,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$true_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$true_type,
|
|
|
|
|
$var_type
|
2018-05-03 19:56:30 +02:00
|
|
|
|
)) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($var_type->from_docblock) {
|
2018-05-03 19:56:30 +02:00
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new DocblockTypeContradiction(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type . ' does not contain true',
|
2018-05-03 19:56:30 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2018-05-18 17:02:50 +02:00
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new TypeDoesNotContainType(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type . ' does not contain true',
|
2018-05-18 17:02:50 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
2017-04-06 20:53:45 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2017-04-06 20:53:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($false_position) {
|
|
|
|
|
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$false_position value');
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2019-04-12 16:30:56 +02:00
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
2019-04-12 16:30:56 +02:00
|
|
|
|
$if_types = self::processFunctionCall(
|
2017-10-23 01:11:28 +02:00
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$source,
|
|
|
|
|
true
|
2017-10-23 01:11:28 +02:00
|
|
|
|
);
|
2019-04-12 16:30:56 +02:00
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2019-04-12 16:30:56 +02:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['false']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
}
|
|
|
|
|
} elseif ($var_type) {
|
|
|
|
|
self::scrapeAssertions($base_conditional, $this_class_name, $source, $codebase, $inside_negation);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2019-04-12 16:30:56 +02:00
|
|
|
|
if (!isset($base_conditional->assertions)) {
|
|
|
|
|
throw new \UnexpectedValueException('Assertions should be set');
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2019-04-12 16:30:56 +02:00
|
|
|
|
$notif_types = $base_conditional->assertions;
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2019-04-12 16:30:56 +02:00
|
|
|
|
if (count($notif_types) === 1) {
|
|
|
|
|
$if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase && $var_type) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$false_type = Type::getFalse();
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type,
|
|
|
|
|
$false_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$false_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2018-06-17 19:20:37 +02:00
|
|
|
|
if (IssueBuffer::accepts(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' does not contain false',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new TypeDoesNotContainType(
|
|
|
|
|
$var_type . ' does not contain false',
|
2018-06-17 19:20:37 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2019-03-02 21:18:29 +01:00
|
|
|
|
if ($empty_array_position !== null) {
|
|
|
|
|
if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$empty_array_position value');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['!non-empty-countable']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($codebase
|
|
|
|
|
&& $var_type
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$null_type = Type::getEmptyArray();
|
|
|
|
|
|
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$null_type
|
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$null_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new DocblockTypeContradiction(
|
2019-03-02 21:19:59 +01:00
|
|
|
|
$var_type . ' does not contain an empty array',
|
2019-03-02 21:18:29 +01:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
2019-03-26 03:30:40 +01:00
|
|
|
|
new TypeDoesNotContainType(
|
2019-03-02 21:19:59 +01:00
|
|
|
|
$var_type . ' does not contain empty array',
|
2019-03-02 21:18:29 +01:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($gettype_position) {
|
|
|
|
|
if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$string_expr = $conditional->left;
|
|
|
|
|
$gettype_expr = $conditional->right;
|
|
|
|
|
} elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$string_expr = $conditional->right;
|
|
|
|
|
$gettype_expr = $conditional->left;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$gettype_position value');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$gettype_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
/** @var PhpParser\Node\Scalar\String_ $string_expr */
|
|
|
|
|
$var_type = $string_expr->value;
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2019-01-05 22:23:18 +01:00
|
|
|
|
if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])
|
2018-06-26 00:02:05 +02:00
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new UnevaluatedCode(
|
|
|
|
|
'gettype cannot return this value',
|
|
|
|
|
new CodeLocation($source, $string_expr)
|
|
|
|
|
)
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$if_types[$var_name] = [[$var_type]];
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($count_equality_position) {
|
|
|
|
|
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$count_expr = $conditional->left;
|
|
|
|
|
} elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$count_expr = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$count_equality_position value');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$count_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [['=non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($getclass_position) {
|
|
|
|
|
if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$whichclass_expr = $conditional->left;
|
|
|
|
|
$getclass_expr = $conditional->right;
|
|
|
|
|
} elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$whichclass_expr = $conditional->right;
|
|
|
|
|
$getclass_expr = $conditional->left;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$getclass_position value');
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2018-07-18 04:50:30 +02:00
|
|
|
|
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-07-18 04:50:30 +02:00
|
|
|
|
$getclass_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = '$this';
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2018-11-29 05:59:43 +01:00
|
|
|
|
if ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
|
2018-06-26 00:02:05 +02:00
|
|
|
|
&& $whichclass_expr->class instanceof PhpParser\Node\Name
|
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
2018-07-13 15:52:15 +02:00
|
|
|
|
|
|
|
|
|
if ($var_type === 'self') {
|
|
|
|
|
$var_type = $this_class_name;
|
|
|
|
|
} elseif ($var_type === 'parent' || $var_type === 'static') {
|
|
|
|
|
$var_type = null;
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Shouldn’t get here');
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($source instanceof StatementsSource
|
2018-07-13 15:58:35 +02:00
|
|
|
|
&& $var_type
|
2018-11-29 05:59:43 +01:00
|
|
|
|
) {
|
|
|
|
|
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
new CodeLocation($source, $whichclass_expr),
|
|
|
|
|
$source->getSuppressedIssues(),
|
|
|
|
|
false
|
|
|
|
|
) === false
|
2018-11-29 05:59:43 +01:00
|
|
|
|
) {
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2018-06-17 19:20:37 +02:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-29 05:59:43 +01:00
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$if_types[$var_name] = [['=getclass-' . $var_type]];
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-10-23 02:17:04 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($typed_value_position) {
|
|
|
|
|
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
/** @var PhpParser\Node\Expr $conditional->right */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->left,
|
2017-10-23 02:17:04 +02:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$other_type = isset($conditional->left->inferredType) ? $conditional->left->inferredType : null;
|
|
|
|
|
$var_type = $conditional->right->inferredType;
|
|
|
|
|
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
/** @var PhpParser\Node\Expr $conditional->left */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->right,
|
2017-10-23 02:17:04 +02:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type = $conditional->left->inferredType;
|
|
|
|
|
$other_type = isset($conditional->right->inferredType) ? $conditional->right->inferredType : null;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$typed_value_position value');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| ($other_type
|
|
|
|
|
&& (($var_type->isString() && $other_type->isString())
|
|
|
|
|
|| ($var_type->isInt() && $other_type->isInt())
|
|
|
|
|
|| ($var_type->isFloat() && $other_type->isFloat())
|
|
|
|
|
)
|
2017-11-06 18:04:38 +01:00
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
|
|
|
|
if ($identical) {
|
2019-01-02 17:35:49 +01:00
|
|
|
|
$if_types[$var_name] = [['=' . $var_type->getAssertionString()]];
|
2017-11-06 18:04:38 +01:00
|
|
|
|
} else {
|
2019-01-02 17:35:49 +01:00
|
|
|
|
$if_types[$var_name] = [['~' . $var_type->getAssertionString()]];
|
2017-11-06 18:04:38 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2017-10-23 02:17:04 +02:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $other_type
|
2018-06-26 00:02:05 +02:00
|
|
|
|
&& $var_type
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
2019-01-02 03:00:34 +01:00
|
|
|
|
$parent_source = $source->getSource();
|
|
|
|
|
|
|
|
|
|
if ($parent_source
|
|
|
|
|
&& $parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
|
|
|
|
|
&& (($var_type->isSingleStringLiteral()
|
|
|
|
|
&& $var_type->getSingleStringLiteral()->value === $this_class_name)
|
|
|
|
|
|| ($other_type->isSingleStringLiteral()
|
|
|
|
|
&& $other_type->getSingleStringLiteral()->value === $this_class_name))
|
|
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
} elseif (!TypeAnalyzer::canExpressionTypesBeIdentical(
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$other_type,
|
2018-09-09 18:20:49 +02:00
|
|
|
|
$var_type
|
2018-06-26 00:02:05 +02:00
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock || $other_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new DocblockTypeContradiction(
|
2019-01-13 20:40:21 +01:00
|
|
|
|
$var_type->getId() . ' does not contain ' . $other_type->getId(),
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new TypeDoesNotContainType(
|
2018-12-06 04:29:06 +01:00
|
|
|
|
$var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
2017-10-23 02:17:04 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type = isset($conditional->left->inferredType) ? $conditional->left->inferredType : null;
|
|
|
|
|
$other_type = isset($conditional->right->inferredType) ? $conditional->right->inferredType : null;
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $var_type
|
2018-06-26 00:02:05 +02:00
|
|
|
|
&& $other_type
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
2018-11-16 17:04:45 +01:00
|
|
|
|
if (!TypeAnalyzer::canExpressionTypesBeIdentical($codebase, $var_type, $other_type)) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new TypeDoesNotContainType(
|
2018-12-06 04:29:06 +01:00
|
|
|
|
$var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
|
|
|
|
* @param string|null $this_class_name
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private static function scrapeInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
$this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
FileSource $source,
|
2019-03-16 17:34:48 +01:00
|
|
|
|
Codebase $codebase = null,
|
|
|
|
|
bool $inside_negation = false
|
2018-06-26 00:02:05 +02:00
|
|
|
|
) {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$null_position = self::hasNullVariable($conditional);
|
|
|
|
|
$false_position = self::hasFalseVariable($conditional);
|
|
|
|
|
$true_position = self::hasTrueVariable($conditional);
|
2019-03-02 21:18:29 +01:00
|
|
|
|
$empty_array_position = self::hasEmptyArrayVariable($conditional);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$gettype_position = self::hasGetTypeCheck($conditional);
|
|
|
|
|
$getclass_position = self::hasGetClassCheck($conditional);
|
|
|
|
|
$typed_value_position = self::hasTypedValueComparison($conditional);
|
|
|
|
|
|
|
|
|
|
if ($null_position !== null) {
|
|
|
|
|
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Bad null variable position');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['!null']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase && $var_type) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$null_type = Type::getNull();
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type,
|
|
|
|
|
$null_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$null_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantConditionGivenDocblockType(
|
2019-04-25 19:43:05 +02:00
|
|
|
|
'Docblock-asserted type ' . $var_type . ' can never contain null',
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantCondition(
|
2019-04-25 19:43:05 +02:00
|
|
|
|
$var_type . ' can never contain null',
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
2018-04-17 21:39:09 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($false_position) {
|
|
|
|
|
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Bad false variable position');
|
|
|
|
|
}
|
2017-12-14 02:06:19 +01:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['!false']];
|
2017-12-14 02:48:01 +01:00
|
|
|
|
} else {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
2017-12-14 02:48:01 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} elseif ($var_type) {
|
2019-03-16 17:34:48 +01:00
|
|
|
|
self::scrapeAssertions($base_conditional, $this_class_name, $source, $codebase, $inside_negation);
|
2017-12-14 02:48:01 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if (!isset($base_conditional->assertions)) {
|
|
|
|
|
throw new \UnexpectedValueException('Assertions should be set');
|
2017-12-14 02:48:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$notif_types = $base_conditional->assertions;
|
|
|
|
|
|
|
|
|
|
if (count($notif_types) === 1) {
|
|
|
|
|
$if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
|
|
|
|
|
}
|
2017-12-14 02:48:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase && $var_type) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$false_type = Type::getFalse();
|
2017-12-14 02:48:01 +01:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type,
|
|
|
|
|
$false_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$false_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-asserted type ' . $var_type . ' can never contain false',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type . ' can never contain false',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-14 02:48:01 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($true_position) {
|
|
|
|
|
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Bad null variable position');
|
2017-12-14 02:06:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
2019-04-12 15:38:56 +02:00
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$if_types = self::processFunctionCall(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2017-12-05 18:14:10 +01:00
|
|
|
|
|
2019-04-12 15:38:56 +02:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['!true']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
}
|
|
|
|
|
} elseif ($var_type) {
|
|
|
|
|
self::scrapeAssertions(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$inside_negation
|
|
|
|
|
);
|
2017-12-05 18:14:10 +01:00
|
|
|
|
|
2019-04-12 15:38:56 +02:00
|
|
|
|
if (!isset($base_conditional->assertions)) {
|
|
|
|
|
throw new \UnexpectedValueException('Assertions should be set');
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
2019-04-12 15:38:56 +02:00
|
|
|
|
$notif_types = $base_conditional->assertions;
|
|
|
|
|
|
|
|
|
|
if (count($notif_types) === 1) {
|
|
|
|
|
$if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
|
|
|
|
|
}
|
2017-12-05 18:14:10 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2017-12-05 18:14:10 +01:00
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase && $var_type) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$true_type = Type::getTrue();
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$var_type,
|
|
|
|
|
$true_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$true_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-asserted type ' . $var_type . ' can never contain true',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type . ' can never contain ' . $true_type,
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-05 18:14:10 +01:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 21:18:29 +01:00
|
|
|
|
if ($empty_array_position !== null) {
|
|
|
|
|
if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Bad empty array variable position');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$var_type = isset($base_conditional->inferredType) ? $base_conditional->inferredType : null;
|
|
|
|
|
|
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['non-empty-countable']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($codebase && $var_type) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
|
|
|
|
$empty_array_type = Type::getEmptyArray();
|
|
|
|
|
|
|
|
|
|
if (!TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$empty_array_type
|
|
|
|
|
) && !TypeAnalyzer::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$empty_array_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-asserted type ' . $var_type . ' can never contain null',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type . ' can never contain null',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($gettype_position) {
|
|
|
|
|
if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$whichclass_expr = $conditional->left;
|
|
|
|
|
$gettype_expr = $conditional->right;
|
|
|
|
|
} elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$whichclass_expr = $conditional->right;
|
|
|
|
|
$gettype_expr = $conditional->left;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$gettype_position value');
|
|
|
|
|
}
|
2017-01-31 07:35:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$gettype_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2017-01-31 07:35:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
|
$var_type = $whichclass_expr->value;
|
|
|
|
|
} elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $whichclass_expr->class instanceof PhpParser\Node\Name
|
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('Shouldn’t get here');
|
|
|
|
|
}
|
2017-01-31 07:35:44 +01:00
|
|
|
|
|
2019-01-05 22:23:18 +01:00
|
|
|
|
if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new UnevaluatedCode(
|
|
|
|
|
'gettype cannot return this value',
|
|
|
|
|
new CodeLocation($source, $whichclass_expr)
|
|
|
|
|
)
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$if_types[$var_name] = [['!' . $var_type]];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2017-02-18 02:50:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($getclass_position) {
|
|
|
|
|
if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$whichclass_expr = $conditional->left;
|
|
|
|
|
$getclass_expr = $conditional->right;
|
|
|
|
|
} elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$whichclass_expr = $conditional->right;
|
|
|
|
|
$getclass_expr = $conditional->left;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$getclass_position value');
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-18 04:50:30 +02:00
|
|
|
|
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-07-18 04:50:30 +02:00
|
|
|
|
$getclass_expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = '$this';
|
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
|
$var_type = $whichclass_expr->value;
|
|
|
|
|
} elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $whichclass_expr->class instanceof PhpParser\Node\Name
|
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
2018-07-13 15:52:15 +02:00
|
|
|
|
|
|
|
|
|
if ($var_type === 'self') {
|
|
|
|
|
$var_type = $this_class_name;
|
|
|
|
|
} elseif ($var_type === 'parent' || $var_type === 'static') {
|
|
|
|
|
$var_type = null;
|
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
|
} else {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
throw new \UnexpectedValueException('Shouldn’t get here');
|
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($source instanceof StatementsSource
|
2018-07-13 15:58:35 +02:00
|
|
|
|
&& $var_type
|
2018-11-06 03:57:36 +01:00
|
|
|
|
&& ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
new CodeLocation($source, $whichclass_expr),
|
|
|
|
|
$source->getSuppressedIssues(),
|
|
|
|
|
false
|
|
|
|
|
) === false
|
|
|
|
|
) {
|
|
|
|
|
// fall through
|
|
|
|
|
} else {
|
|
|
|
|
if ($var_name && $var_type) {
|
2018-11-16 16:13:52 +01:00
|
|
|
|
$if_types[$var_name] = [['!=getclass-' . $var_type]];
|
2017-02-18 02:50:47 +01:00
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($typed_value_position) {
|
|
|
|
|
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
/** @var PhpParser\Node\Expr $conditional->right */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$other_type = isset($conditional->left->inferredType) ? $conditional->left->inferredType : null;
|
|
|
|
|
$var_type = isset($conditional->right->inferredType) ? $conditional->right->inferredType : null;
|
|
|
|
|
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
/** @var PhpParser\Node\Expr $conditional->left */
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$var_type = isset($conditional->left->inferredType) ? $conditional->left->inferredType : null;
|
|
|
|
|
$other_type = isset($conditional->right->inferredType) ? $conditional->right->inferredType : null;
|
|
|
|
|
} else {
|
|
|
|
|
throw new \UnexpectedValueException('$typed_value_position value');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_type) {
|
2017-02-18 02:50:47 +01:00
|
|
|
|
if ($var_name) {
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$not_identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
|| ($other_type
|
|
|
|
|
&& (($var_type->isString() && $other_type->isString())
|
|
|
|
|
|| ($var_type->isInt() && $other_type->isInt())
|
|
|
|
|
|| ($var_type->isFloat() && $other_type->isFloat())
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($not_identical) {
|
2019-01-02 17:35:49 +01:00
|
|
|
|
$if_types[$var_name] = [['!=' . $var_type->getAssertionString()]];
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
2019-01-02 17:35:49 +01:00
|
|
|
|
$if_types[$var_name] = [['!~' . $var_type->getAssertionString()]];
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $other_type
|
2018-06-26 00:02:05 +02:00
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
&& $source instanceof StatementsSource
|
|
|
|
|
) {
|
2019-01-02 03:00:34 +01:00
|
|
|
|
$parent_source = $source->getSource();
|
|
|
|
|
|
|
|
|
|
if ($parent_source
|
|
|
|
|
&& $parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
|
|
|
|
|
&& (($var_type->isSingleStringLiteral()
|
|
|
|
|
&& $var_type->getSingleStringLiteral()->value === $this_class_name)
|
|
|
|
|
|| ($other_type->isSingleStringLiteral()
|
|
|
|
|
&& $other_type->getSingleStringLiteral()->value === $this_class_name))
|
|
|
|
|
) {
|
|
|
|
|
// do nothing
|
2019-03-21 22:26:10 +01:00
|
|
|
|
} elseif (!TypeAnalyzer::canExpressionTypesBeIdentical(
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$codebase,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$other_type,
|
2019-03-21 22:26:10 +01:00
|
|
|
|
$var_type
|
2018-06-26 00:02:05 +02:00
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock || $other_type->from_docblock) {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' can never contain ' . $other_type,
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type->getId() . ' can never contain ' . $other_type->getId(),
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
|
|
|
|
)) {
|
|
|
|
|
// fall through
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = $if_types;
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$conditional->assertions = [];
|
|
|
|
|
return;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $expr
|
2017-01-07 20:35:07 +01:00
|
|
|
|
* @param string|null $this_class_name
|
2018-02-23 21:39:33 +01:00
|
|
|
|
* @param FileSource $source
|
|
|
|
|
* @param bool $negate
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2018-06-08 19:53:42 +02:00
|
|
|
|
* @return array<string, array<int, array<int, string>>>
|
2016-12-28 21:52:44 +01:00
|
|
|
|
*/
|
2019-02-26 07:03:33 +01:00
|
|
|
|
public static function processFunctionCall(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
PhpParser\Node\Expr\FuncCall $expr,
|
|
|
|
|
$this_class_name,
|
2018-02-23 21:39:33 +01:00
|
|
|
|
FileSource $source,
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$negate = false
|
|
|
|
|
) {
|
|
|
|
|
$prefix = $negate ? '!' : '';
|
|
|
|
|
|
|
|
|
|
$first_var_name = isset($expr->args[0]->value)
|
2018-11-06 03:57:36 +01:00
|
|
|
|
? ExpressionAnalyzer::getArrayVarId(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
2017-01-07 20:35:07 +01:00
|
|
|
|
$source
|
2016-12-28 21:52:44 +01:00
|
|
|
|
)
|
|
|
|
|
: null;
|
|
|
|
|
|
2017-10-23 01:53:53 +02:00
|
|
|
|
$if_types = [];
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
if (self::hasNullCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'null']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasIsACheck($expr)) {
|
2018-07-18 04:50:30 +02:00
|
|
|
|
if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($expr->args[0]->value->name->name) === 'class'
|
|
|
|
|
&& $expr->args[0]->value->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& count($expr->args[0]->value->class->parts) === 1
|
|
|
|
|
&& strtolower($expr->args[0]->value->class->parts[0]) === 'static'
|
|
|
|
|
) {
|
|
|
|
|
$first_var_name = '$this';
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
if ($first_var_name) {
|
2018-05-30 16:13:55 +02:00
|
|
|
|
$second_arg = $expr->args[1]->value;
|
2018-01-23 21:46:14 +01:00
|
|
|
|
|
2019-01-05 22:58:34 +01:00
|
|
|
|
$third_arg = isset($expr->args[2]->value) ? $expr->args[2]->value : null;
|
2018-04-04 05:14:23 +02:00
|
|
|
|
|
2019-01-05 22:58:34 +01:00
|
|
|
|
if ($third_arg instanceof PhpParser\Node\Expr\ConstFetch) {
|
|
|
|
|
if (!in_array(strtolower($third_arg->name->parts[0]), ['true', 'false'])) {
|
2018-04-04 05:14:23 +02:00
|
|
|
|
return $if_types;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-05 22:58:34 +01:00
|
|
|
|
$third_arg_value = strtolower($third_arg->name->parts[0]);
|
|
|
|
|
} else {
|
|
|
|
|
$third_arg_value = $expr->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($expr->name->parts[0]) === 'is_subclass_of'
|
|
|
|
|
? 'true'
|
|
|
|
|
: 'false';
|
2018-04-04 05:14:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-05 22:58:34 +01:00
|
|
|
|
$is_a_prefix = $third_arg_value === 'true' ? 'isa-string-' : 'isa-';
|
|
|
|
|
|
2018-05-30 16:13:55 +02:00
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
|
2019-01-09 14:42:27 +01:00
|
|
|
|
$fq_class_name = $second_arg->value;
|
|
|
|
|
if ($fq_class_name[0] === '\\') {
|
|
|
|
|
$fq_class_name = substr($fq_class_name, 1);
|
|
|
|
|
}
|
2019-02-26 07:03:33 +01:00
|
|
|
|
|
|
|
|
|
$first_arg = $expr->args[0]->value;
|
|
|
|
|
|
|
|
|
|
if (isset($first_arg->inferredType)
|
|
|
|
|
&& $first_arg->inferredType->isSingleStringLiteral()
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& $source->getSource()->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
|
|
|
|
|
&& $first_arg->inferredType->getSingleStringLiteral()->value === $this_class_name
|
|
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$first_var_name] = [[$prefix . $is_a_prefix . $fq_class_name]];
|
|
|
|
|
}
|
2018-05-30 16:13:55 +02:00
|
|
|
|
} elseif ($second_arg instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $second_arg->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& $second_arg->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($second_arg->name->name) === 'class'
|
2018-01-23 21:46:14 +01:00
|
|
|
|
) {
|
2018-05-30 16:13:55 +02:00
|
|
|
|
$first_arg = $expr->args[0]->value;
|
2018-01-23 21:46:14 +01:00
|
|
|
|
|
2018-05-30 16:13:55 +02:00
|
|
|
|
if (isset($first_arg->inferredType)
|
|
|
|
|
&& $first_arg->inferredType->isSingleStringLiteral()
|
2018-11-06 03:57:36 +01:00
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& $source->getSource()->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
|
2018-08-09 03:31:13 +02:00
|
|
|
|
&& $first_arg->inferredType->getSingleStringLiteral()->value === $this_class_name
|
2018-05-30 16:13:55 +02:00
|
|
|
|
) {
|
2018-01-23 21:46:14 +01:00
|
|
|
|
// do nothing
|
|
|
|
|
} else {
|
2018-05-30 16:13:55 +02:00
|
|
|
|
$class_node = $second_arg->class;
|
|
|
|
|
|
|
|
|
|
if ($class_node->parts === ['static'] || $class_node->parts === ['self']) {
|
2018-06-15 16:33:51 +02:00
|
|
|
|
if ($this_class_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$prefix . $is_a_prefix . $this_class_name]];
|
|
|
|
|
}
|
2018-05-30 16:13:55 +02:00
|
|
|
|
} elseif ($class_node->parts === ['parent']) {
|
|
|
|
|
// do nothing
|
|
|
|
|
} else {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[
|
|
|
|
|
$prefix . $is_a_prefix
|
2018-11-06 03:57:36 +01:00
|
|
|
|
. ClassLikeAnalyzer::getFQCLNFromNameObject(
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$class_node,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
)
|
|
|
|
|
]];
|
2018-05-30 16:13:55 +02:00
|
|
|
|
}
|
2018-01-23 21:46:14 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasArrayCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'array']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasBoolCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'bool']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasStringCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'string']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasObjectCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'object']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasNumericCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'numeric']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasIntCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'int']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasFloatCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'float']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasResourceCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'resource']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasScalarCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'scalar']];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
} elseif (self::hasCallableCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'callable']];
|
|
|
|
|
}
|
2018-07-09 14:31:43 +02:00
|
|
|
|
} elseif (self::hasIterableCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'iterable']];
|
|
|
|
|
}
|
2018-11-12 18:03:55 +01:00
|
|
|
|
} elseif (self::hasClassExistsCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'class-string']];
|
|
|
|
|
}
|
2019-04-10 00:09:57 +02:00
|
|
|
|
} elseif (self::hasFunctionExistsCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'callable-string']];
|
|
|
|
|
}
|
2018-06-08 19:53:42 +02:00
|
|
|
|
} elseif (self::hasInArrayCheck($expr)) {
|
|
|
|
|
if ($first_var_name && isset($expr->args[1]->value->inferredType)) {
|
|
|
|
|
foreach ($expr->args[1]->value->inferredType->getTypes() as $atomic_type) {
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TArray
|
|
|
|
|
|| $atomic_type instanceof Type\Atomic\ObjectLike
|
|
|
|
|
) {
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\ObjectLike) {
|
|
|
|
|
$atomic_type = $atomic_type->getGenericArrayType();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$array_literal_types = array_merge(
|
|
|
|
|
$atomic_type->type_params[1]->getLiteralStrings(),
|
|
|
|
|
$atomic_type->type_params[1]->getLiteralInts(),
|
|
|
|
|
$atomic_type->type_params[1]->getLiteralFloats()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (count($atomic_type->type_params[1]->getTypes()) === count($array_literal_types)) {
|
|
|
|
|
$literal_assertions = [];
|
|
|
|
|
|
|
|
|
|
foreach ($array_literal_types as $array_literal_type) {
|
2018-11-16 16:13:52 +01:00
|
|
|
|
$literal_assertions[] = '=' . $array_literal_type->getId();
|
2018-06-08 19:53:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($negate) {
|
|
|
|
|
$if_types = \Psalm\Type\Algebra::negateTypes([
|
|
|
|
|
$first_var_name => [$literal_assertions]
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$first_var_name] = [$literal_assertions];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
} elseif (self::hasArrayKeyExistsCheck($expr)) {
|
|
|
|
|
$array_root = isset($expr->args[1]->value)
|
2018-11-06 03:57:36 +01:00
|
|
|
|
? ExpressionAnalyzer::getArrayVarId(
|
2018-03-18 00:03:46 +01:00
|
|
|
|
$expr->args[1]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
)
|
|
|
|
|
: null;
|
|
|
|
|
|
2018-03-18 00:28:01 +01:00
|
|
|
|
if ($first_var_name === null && isset($expr->args[0])) {
|
|
|
|
|
$first_arg = $expr->args[0];
|
|
|
|
|
|
|
|
|
|
if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
|
$first_var_name = '"' . $first_arg->value->value . '"';
|
|
|
|
|
} elseif ($first_arg->value instanceof PhpParser\Node\Scalar\LNumber) {
|
|
|
|
|
$first_var_name = (string) $first_arg->value->value;
|
2018-03-18 00:03:46 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-20 00:14:55 +01:00
|
|
|
|
if ($first_var_name !== null
|
|
|
|
|
&& $array_root
|
|
|
|
|
&& !strpos($first_var_name, '->')
|
|
|
|
|
&& !strpos($first_var_name, '[')
|
|
|
|
|
) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$array_root . '[' . $first_var_name . ']'] = [[$prefix . 'array-key-exists']];
|
2018-03-18 00:03:46 +01:00
|
|
|
|
}
|
2018-12-19 22:15:19 +01:00
|
|
|
|
} elseif (self::hasNonEmptyCountCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$prefix . 'non-empty-countable']];
|
|
|
|
|
}
|
2018-07-11 17:22:07 +02:00
|
|
|
|
} else {
|
|
|
|
|
$if_types = self::processCustomAssertion($expr, $this_class_name, $source, $negate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall $expr
|
|
|
|
|
* @param string|null $this_class_name
|
|
|
|
|
* @param FileSource $source
|
|
|
|
|
* @param bool $negate
|
|
|
|
|
*
|
|
|
|
|
* @return array<string, array<int, array<int, string>>>
|
|
|
|
|
*/
|
|
|
|
|
protected static function processCustomAssertion(
|
|
|
|
|
$expr,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
$negate = false
|
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
if (!$source instanceof StatementsAnalyzer
|
2018-07-11 17:22:07 +02:00
|
|
|
|
|| (!isset($expr->ifTrueAssertions) && !isset($expr->ifFalseAssertions))
|
2018-05-28 21:07:42 +02:00
|
|
|
|
) {
|
2018-07-11 17:22:07 +02:00
|
|
|
|
return [];
|
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
$prefix = $negate ? '!' : '';
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
$first_var_name = isset($expr->args[0]->value)
|
2018-11-06 03:57:36 +01:00
|
|
|
|
? ExpressionAnalyzer::getArrayVarId(
|
2018-07-11 17:22:07 +02:00
|
|
|
|
$expr->args[0]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
)
|
|
|
|
|
: null;
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
if (isset($expr->ifTrueAssertions)) {
|
|
|
|
|
foreach ($expr->ifTrueAssertions as $assertion) {
|
2018-05-28 21:07:42 +02:00
|
|
|
|
if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) {
|
|
|
|
|
if ($assertion->var_id === 0) {
|
|
|
|
|
$var_name = $first_var_name;
|
|
|
|
|
} else {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-05-28 21:07:42 +02:00
|
|
|
|
$expr->args[$assertion->var_id]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-06-01 04:46:22 +02:00
|
|
|
|
if ($prefix === $assertion->rule[0][0][0]) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
|
2018-06-01 03:59:55 +02:00
|
|
|
|
} else {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$var_name] = [[$prefix . $assertion->rule[0][0]]];
|
2018-06-01 03:59:55 +02:00
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-11 17:22:07 +02:00
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
if (isset($expr->ifFalseAssertions)) {
|
2018-05-28 21:07:42 +02:00
|
|
|
|
$negated_prefix = !$negate ? '!' : '';
|
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
foreach ($expr->ifFalseAssertions as $assertion) {
|
2018-05-28 21:07:42 +02:00
|
|
|
|
if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) {
|
|
|
|
|
if ($assertion->var_id === 0) {
|
|
|
|
|
$var_name = $first_var_name;
|
|
|
|
|
} else {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$var_name = ExpressionAnalyzer::getArrayVarId(
|
2018-05-28 21:07:42 +02:00
|
|
|
|
$expr->args[$assertion->var_id]->value,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2018-06-01 04:46:22 +02:00
|
|
|
|
if ($negated_prefix === $assertion->rule[0][0][0]) {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
|
2018-06-01 03:59:55 +02:00
|
|
|
|
} else {
|
2018-06-08 19:53:42 +02:00
|
|
|
|
$if_types[$var_name] = [[$negated_prefix . $assertion->rule[0][0]]];
|
2018-06-01 03:59:55 +02:00
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2017-10-23 01:53:53 +02:00
|
|
|
|
|
|
|
|
|
return $if_types;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\Instanceof_ $stmt
|
2017-01-07 20:35:07 +01:00
|
|
|
|
* @param string|null $this_class_name
|
2018-02-23 21:39:33 +01:00
|
|
|
|
* @param FileSource $source
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2019-01-27 20:10:33 +01:00
|
|
|
|
* @return array<int, string>
|
2016-12-28 21:52:44 +01:00
|
|
|
|
*/
|
|
|
|
|
protected static function getInstanceOfTypes(
|
|
|
|
|
PhpParser\Node\Expr\Instanceof_ $stmt,
|
|
|
|
|
$this_class_name,
|
2018-02-23 21:39:33 +01:00
|
|
|
|
FileSource $source
|
2016-12-28 21:52:44 +01:00
|
|
|
|
) {
|
|
|
|
|
if ($stmt->class instanceof PhpParser\Node\Name) {
|
2017-11-15 03:56:29 +01:00
|
|
|
|
if (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) {
|
2018-11-06 03:57:36 +01:00
|
|
|
|
$instanceof_class = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$stmt->class,
|
2017-07-25 22:11:02 +02:00
|
|
|
|
$source->getAliases()
|
2016-12-28 21:52:44 +01:00
|
|
|
|
);
|
|
|
|
|
|
2019-01-27 20:10:33 +01:00
|
|
|
|
return [$instanceof_class];
|
2018-01-23 20:46:46 +01:00
|
|
|
|
} elseif ($this_class_name
|
|
|
|
|
&& (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))
|
|
|
|
|
) {
|
2019-01-27 20:10:33 +01:00
|
|
|
|
return [$this_class_name];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2019-01-27 20:10:33 +01:00
|
|
|
|
} elseif (isset($stmt->class->inferredType)) {
|
|
|
|
|
$literal_class_strings = [];
|
|
|
|
|
|
|
|
|
|
foreach ($stmt->class->inferredType->getTypes() as $atomic_type) {
|
|
|
|
|
if ($atomic_type instanceof Type\Atomic\TLiteralClassString) {
|
|
|
|
|
$literal_class_strings[] = $atomic_type->value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $literal_class_strings;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-27 20:10:33 +01:00
|
|
|
|
return [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return int|null
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasNullVariable(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
2018-02-07 19:57:45 +01:00
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'null'
|
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 19:57:45 +01:00
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'null'
|
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return int|null
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasFalseVariable(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
2018-02-07 19:57:45 +01:00
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'false'
|
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 19:57:45 +01:00
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'false'
|
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return int|null
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasTrueVariable(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
2018-02-07 19:57:45 +01:00
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 19:57:45 +01:00
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 21:18:29 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
*
|
|
|
|
|
* @return int|null
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasEmptyArrayVariable(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\Array_
|
|
|
|
|
&& !$conditional->right->items
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\Array_
|
|
|
|
|
&& !$conditional->left->items
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return false|int
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasGetTypeCheck(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall &&
|
|
|
|
|
$conditional->right->name instanceof PhpParser\Node\Name &&
|
|
|
|
|
strtolower($conditional->right->name->parts[0]) === 'gettype' &&
|
|
|
|
|
$conditional->left instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall &&
|
|
|
|
|
$conditional->left->name instanceof PhpParser\Node\Name &&
|
|
|
|
|
strtolower($conditional->left->name->parts[0]) === 'gettype' &&
|
|
|
|
|
$conditional->right instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-23 01:53:53 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
*
|
|
|
|
|
* @return false|int
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasGetClassCheck(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
2018-07-18 04:50:30 +02:00
|
|
|
|
$right_get_class = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->right->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'get_class';
|
|
|
|
|
|
|
|
|
|
$right_static_class = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $conditional->right->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& $conditional->right->class->parts === ['static']
|
|
|
|
|
&& $conditional->right->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($conditional->right->name->name) === 'class';
|
|
|
|
|
|
2018-11-29 05:59:43 +01:00
|
|
|
|
$left_class_string = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $conditional->left->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($conditional->left->name->name) === 'class';
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
|
|
|
|
if (($right_get_class || $right_static_class) && $left_class_string) {
|
2017-10-23 01:53:53 +02:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-18 04:50:30 +02:00
|
|
|
|
$left_get_class = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'get_class';
|
|
|
|
|
|
|
|
|
|
$left_static_class = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $conditional->left->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& $conditional->left->class->parts === ['static']
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($conditional->left->name->name) === 'class';
|
|
|
|
|
|
2018-11-29 05:59:43 +01:00
|
|
|
|
$right_class_string = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $conditional->right->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& $conditional->right->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($conditional->right->name->name) === 'class';
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
|
|
|
|
if (($left_get_class || $left_static_class) && $right_class_string) {
|
2017-10-23 01:53:53 +02:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
*
|
|
|
|
|
* @return false|int
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasNonEmptyCountEqualityCheck(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
|
|
|
|
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'count';
|
|
|
|
|
|
|
|
|
|
$right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
&& $conditional->right->value >= (
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1);
|
|
|
|
|
|
|
|
|
|
$operator_greater_than_or_equal =
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
|
|
|
|
|
|
|
|
|
|
if ($left_count && $right_number && $operator_greater_than_or_equal) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->right->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'count';
|
|
|
|
|
|
|
|
|
|
$left_number = $conditional->left instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
&& $conditional->left->value >= (
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1);
|
|
|
|
|
|
|
|
|
|
$operator_less_than_or_equal =
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
|
|
|
|
|
|
|
|
|
|
if ($right_count && $left_number && $operator_less_than_or_equal) {
|
2019-02-21 23:17:10 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
*
|
|
|
|
|
* @return false|int
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasReconcilableNonEmptyCountEqualityCheck(PhpParser\Node\Expr\BinaryOp $conditional)
|
|
|
|
|
{
|
|
|
|
|
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'count';
|
|
|
|
|
|
|
|
|
|
$right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
&& $conditional->right->value === (
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1);
|
|
|
|
|
|
|
|
|
|
$operator_greater_than_or_equal =
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
|
|
|
|
|
|
|
|
|
|
if ($left_count && $right_number && $operator_greater_than_or_equal) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->right->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'count';
|
|
|
|
|
|
|
|
|
|
$left_number = $conditional->left instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
&& $conditional->left->value === (
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1);
|
|
|
|
|
|
|
|
|
|
$operator_less_than_or_equal =
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
|
|
|
|
|
|
|
|
|
|
if ($right_count && $left_number && $operator_less_than_or_equal) {
|
2018-12-19 22:15:19 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp $conditional
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return false|int
|
|
|
|
|
*/
|
2017-01-07 20:35:07 +01:00
|
|
|
|
protected static function hasTypedValueComparison(PhpParser\Node\Expr\BinaryOp $conditional)
|
2016-12-28 21:52:44 +01:00
|
|
|
|
{
|
2018-01-09 21:05:48 +01:00
|
|
|
|
if (isset($conditional->right->inferredType)
|
|
|
|
|
&& count($conditional->right->inferredType->getTypes()) === 1
|
2018-12-08 19:18:55 +01:00
|
|
|
|
&& !$conditional->right->inferredType->hasMixed()
|
2018-01-09 21:05:48 +01:00
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-09 21:05:48 +01:00
|
|
|
|
if (isset($conditional->left->inferredType)
|
|
|
|
|
&& count($conditional->left->inferredType->getTypes()) === 1
|
2018-12-08 19:18:55 +01:00
|
|
|
|
&& !$conditional->left->inferredType->hasMixed()
|
2018-01-09 21:05:48 +01:00
|
|
|
|
) {
|
2016-12-28 21:52:44 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasNullCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_null') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasIsACheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
2018-01-23 21:46:14 +01:00
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
2019-01-05 22:58:34 +01:00
|
|
|
|
&& (strtolower($stmt->name->parts[0]) === 'is_a'
|
|
|
|
|
|| strtolower($stmt->name->parts[0]) === 'is_subclass_of')
|
2018-01-23 21:46:14 +01:00
|
|
|
|
&& isset($stmt->args[1])
|
|
|
|
|
) {
|
2018-01-30 22:45:29 +01:00
|
|
|
|
$second_arg = $stmt->args[1]->value;
|
2018-01-23 21:46:14 +01:00
|
|
|
|
|
2018-01-30 22:45:29 +01:00
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Scalar\String_
|
2018-01-23 21:46:14 +01:00
|
|
|
|
|| (
|
2018-01-30 22:45:29 +01:00
|
|
|
|
$second_arg instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $second_arg->class instanceof PhpParser\Node\Name
|
2018-04-17 18:16:25 +02:00
|
|
|
|
&& $second_arg->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($second_arg->name->name) === 'class'
|
2018-01-23 21:46:14 +01:00
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasArrayCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_array') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasStringCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_string') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasBoolCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_bool') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasObjectCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_object']) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasNumericCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_numeric']) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-07-09 14:31:43 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasIterableCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_iterable') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-11-12 18:03:55 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'class_exists') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2019-04-10 00:09:57 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'function_exists') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasIntCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name &&
|
|
|
|
|
($stmt->name->parts === ['is_int'] ||
|
2017-05-25 04:07:49 +02:00
|
|
|
|
$stmt->name->parts === ['is_integer'] ||
|
2016-12-28 21:52:44 +01:00
|
|
|
|
$stmt->name->parts === ['is_long'])
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasFloatCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name &&
|
|
|
|
|
($stmt->name->parts === ['is_float'] ||
|
|
|
|
|
$stmt->name->parts === ['is_real'] ||
|
|
|
|
|
$stmt->name->parts === ['is_double'])
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasResourceCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_resource']) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasScalarCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_scalar']) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
2017-05-27 02:16:18 +02:00
|
|
|
|
*
|
2016-12-28 21:52:44 +01:00
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_callable']) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
|
2018-06-08 19:53:42 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& $stmt->name->parts === ['in_array']
|
|
|
|
|
&& isset($stmt->args[2])
|
|
|
|
|
) {
|
|
|
|
|
$second_arg = $stmt->args[2]->value;
|
|
|
|
|
|
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& $second_arg->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($second_arg->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& $stmt->name->parts === ['count']
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-18 00:03:46 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $stmt
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['array_key_exists']) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|