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;
|
2021-07-13 20:54:47 +02:00
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\Equal;
|
2021-07-14 00:02:36 +02:00
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\Greater;
|
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
|
2021-07-13 20:54:47 +02:00
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\Identical;
|
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\NotEqual;
|
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
|
2021-07-14 00:02:36 +02:00
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\Smaller;
|
|
|
|
|
use PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
|
2021-08-31 23:17:44 +02:00
|
|
|
|
use PhpParser\Node\Expr\UnaryMinus;
|
|
|
|
|
use PhpParser\Node\Expr\UnaryPlus;
|
|
|
|
|
use PhpParser\Node\Scalar\LNumber;
|
2021-06-08 04:55:21 +02:00
|
|
|
|
use Psalm\CodeLocation;
|
2018-11-06 03:57:36 +01:00
|
|
|
|
use Psalm\Codebase;
|
2021-06-08 04:55:21 +02:00
|
|
|
|
use Psalm\FileSource;
|
2021-12-03 20:11:20 +01:00
|
|
|
|
use Psalm\Internal\Algebra;
|
2018-11-06 03:57:36 +01:00
|
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
2021-04-30 21:01:33 +02:00
|
|
|
|
use Psalm\Internal\Analyzer\ClassLikeNameOptions;
|
2018-11-06 03:57:36 +01:00
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2021-12-03 20:11:20 +01:00
|
|
|
|
use Psalm\Internal\Analyzer\TraitAnalyzer;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
|
|
|
|
use Psalm\Internal\Provider\NodeDataProvider;
|
2020-07-22 01:40:35 +02:00
|
|
|
|
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
2021-12-03 20:11:20 +01:00
|
|
|
|
use Psalm\Internal\Type\TypeExpander;
|
2018-04-18 18:01:13 +02:00
|
|
|
|
use Psalm\Issue\DocblockTypeContradiction;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use Psalm\Issue\InvalidDocblock;
|
2018-04-17 21:39:09 +02:00
|
|
|
|
use Psalm\Issue\RedundantCondition;
|
2018-06-26 00:02:05 +02:00
|
|
|
|
use Psalm\Issue\RedundantConditionGivenDocblockType;
|
2021-06-08 04:55:21 +02:00
|
|
|
|
use Psalm\Issue\RedundantIdentityWithTrue;
|
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;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use Psalm\Storage\PropertyStorage;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
use Psalm\Type;
|
2021-12-13 04:45:57 +01:00
|
|
|
|
use Psalm\Type\Atomic\TArray;
|
|
|
|
|
use Psalm\Type\Atomic\TClassString;
|
|
|
|
|
use Psalm\Type\Atomic\TEnumCase;
|
|
|
|
|
use Psalm\Type\Atomic\TFalse;
|
|
|
|
|
use Psalm\Type\Atomic\TKeyedArray;
|
|
|
|
|
use Psalm\Type\Atomic\TList;
|
|
|
|
|
use Psalm\Type\Atomic\TLiteralClassString;
|
|
|
|
|
use Psalm\Type\Atomic\TLiteralFloat;
|
|
|
|
|
use Psalm\Type\Atomic\TLiteralInt;
|
|
|
|
|
use Psalm\Type\Atomic\TLiteralString;
|
|
|
|
|
use Psalm\Type\Atomic\TMixed;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2021-12-13 04:45:57 +01:00
|
|
|
|
use Psalm\Type\Atomic\TNull;
|
|
|
|
|
use Psalm\Type\Atomic\TTemplateParamClass;
|
|
|
|
|
use Psalm\Type\Atomic\TTrue;
|
2021-10-11 14:38:25 +02:00
|
|
|
|
use UnexpectedValueException;
|
2020-09-22 07:10:46 +02:00
|
|
|
|
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use function array_key_exists;
|
2021-09-16 22:59:26 +02:00
|
|
|
|
use function assert;
|
2019-06-26 22:52:29 +02:00
|
|
|
|
use function count;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use function explode;
|
2019-06-26 22:52:29 +02:00
|
|
|
|
use function in_array;
|
2021-09-16 22:59:26 +02:00
|
|
|
|
use function is_callable;
|
2019-06-26 22:52:29 +02:00
|
|
|
|
use function is_int;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use function is_numeric;
|
2021-12-03 21:07:25 +01:00
|
|
|
|
use function is_string;
|
|
|
|
|
use function json_encode;
|
2021-10-17 10:29:25 +02:00
|
|
|
|
use function sprintf;
|
|
|
|
|
use function str_replace;
|
2021-06-08 04:55:21 +02:00
|
|
|
|
use function strpos;
|
|
|
|
|
use function strtolower;
|
|
|
|
|
use function substr;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
|
/**
|
|
|
|
|
* @internal
|
2021-07-13 20:54:47 +02:00
|
|
|
|
* This class transform conditions in code into "assertions" that will be reconciled with the type already known of a
|
|
|
|
|
* given variable to narrow the type or find paradox.
|
2021-07-13 22:29:27 +02:00
|
|
|
|
* For example if $a is an int, if($a > 0) will be turned into an assertion to make psalm understand that in the
|
2021-07-13 20:54:47 +02:00
|
|
|
|
* if block, $a is a positive-int
|
2018-12-02 00:37:49 +01:00
|
|
|
|
*/
|
2017-01-07 20:35:07 +01:00
|
|
|
|
class AssertionFinder
|
2016-12-28 21:52:44 +01:00
|
|
|
|
{
|
2020-09-20 18:54:46 +02:00
|
|
|
|
public const ASSIGNMENT_TO_RIGHT = 1;
|
|
|
|
|
public const ASSIGNMENT_TO_LEFT = -1;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-09-16 22:59:26 +02:00
|
|
|
|
public const IS_TYPE_CHECKS = [
|
|
|
|
|
'is_string' => ['string', [Type::class, 'getString']],
|
|
|
|
|
'is_int' => ['int', [Type::class, 'getInt']],
|
|
|
|
|
'is_integer' => ['int', [Type::class, 'getInt']],
|
|
|
|
|
'is_long' => ['int', [Type::class, 'getInt']],
|
|
|
|
|
'is_bool' => ['bool', [Type::class, 'getBool']],
|
|
|
|
|
'is_resource' => ['resource', [Type::class, 'getResource']],
|
|
|
|
|
'is_object' => ['object', [Type::class, 'getObject']],
|
2021-09-16 23:07:19 +02:00
|
|
|
|
'array_is_list' => ['list', [Type::class, 'getList']],
|
2021-09-16 22:59:26 +02:00
|
|
|
|
'is_array' => ['array', [Type::class, 'getArray']],
|
|
|
|
|
'is_numeric' => ['numeric', [Type::class, 'getNumeric']],
|
|
|
|
|
'is_null' => ['null', [Type::class, 'getNull']],
|
|
|
|
|
'is_float' => ['float', [Type::class, 'getFloat']],
|
|
|
|
|
'is_real' => ['float', [Type::class, 'getFloat']],
|
|
|
|
|
'is_double' => ['float', [Type::class, 'getFloat']],
|
|
|
|
|
'is_scalar' => ['scalar', [Type::class, 'getScalar']],
|
|
|
|
|
'is_iterable' => ['iterable'],
|
|
|
|
|
'is_countable' => ['countable'],
|
|
|
|
|
];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
/**
|
|
|
|
|
* Gets all the type assertions in a conditional
|
|
|
|
|
*
|
2020-10-25 15:49:39 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
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,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?string $this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
FileSource $source,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?Codebase $codebase = null,
|
2019-11-25 17:44:54 +01:00
|
|
|
|
bool $inside_negation = false,
|
2020-07-10 22:49:45 +02:00
|
|
|
|
bool $cache = true,
|
|
|
|
|
bool $inside_conditional = true
|
2020-09-16 23:35:55 +02:00
|
|
|
|
): array {
|
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_) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return self::getInstanceofAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$codebase,
|
|
|
|
|
$source,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$inside_negation
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 19:16:52 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\Assign) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->var,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2020-07-10 22:49:45 +02:00
|
|
|
|
$candidate_if_types = $inside_conditional
|
|
|
|
|
? self::scrapeAssertions(
|
|
|
|
|
$conditional->expr,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$inside_negation,
|
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
|
|
|
|
)
|
|
|
|
|
: [];
|
2020-06-24 19:16:52 +02:00
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($candidate_if_types) {
|
2021-12-03 21:07:25 +01:00
|
|
|
|
$if_types[$var_name] = [['@' . json_encode($candidate_if_types[0])]];
|
2020-06-24 19:16:52 +02:00
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-25 15:49:39 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2020-06-24 19:16:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$var_name = ExpressionIdentifier::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
|
|
|
|
|
2019-12-30 16:01:31 +01:00
|
|
|
|
if (!$conditional instanceof PhpParser\Node\Expr\MethodCall
|
|
|
|
|
&& !$conditional instanceof PhpParser\Node\Expr\StaticCall
|
|
|
|
|
) {
|
2020-10-25 15:49:39 +01:00
|
|
|
|
return [$if_types];
|
2019-12-30 16:01:31 +01:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) {
|
2020-11-19 22:25:53 +01:00
|
|
|
|
return [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical ||
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|
) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return self::scrapeEqualityAssertions(
|
2019-11-25 17:44:54 +01:00
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2020-07-10 22:49:45 +02:00
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
2019-11-25 17:44:54 +01:00
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical ||
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
|
|
|
|
|
) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return self::scrapeInequalityAssertions(
|
2019-11-25 17:44:54 +01:00
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2020-07-10 22:49:45 +02:00
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
2019-11-25 17:44:54 +01:00
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-15 01:21:01 +02:00
|
|
|
|
//A nullsafe method call basically adds an assertion !null for the checked variable
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\NullsafeMethodCall) {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->var,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [['!null']];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//we may throw a RedundantNullsafeMethodCall here in the future if $var_name is never null
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
|
|
|
|
|
) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return self::getGreaterAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$source,
|
|
|
|
|
$this_class_name
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
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
|
|
|
|
|
) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return self::getSmallerAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$source,
|
|
|
|
|
$this_class_name
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2017-04-06 20:53:45 +02:00
|
|
|
|
|
2021-12-09 04:26:31 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\FuncCall && !$conditional->isFirstClassCallable()) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return self::processFunctionCall(
|
2020-11-19 20:32:49 +01:00
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$inside_negation
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-12-09 04:26:31 +01:00
|
|
|
|
if (($conditional instanceof PhpParser\Node\Expr\MethodCall
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\StaticCall)
|
|
|
|
|
&& !$conditional->isFirstClassCallable()
|
2019-05-22 23:49:38 +02:00
|
|
|
|
) {
|
2020-11-19 20:32:49 +01:00
|
|
|
|
$custom_assertions = self::processCustomAssertion($conditional, $this_class_name, $source);
|
2019-12-30 16:48:50 +01:00
|
|
|
|
|
|
|
|
|
if ($custom_assertions) {
|
|
|
|
|
return $custom_assertions;
|
|
|
|
|
}
|
2019-11-25 17:44:54 +01:00
|
|
|
|
|
2020-10-25 15:49:39 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2018-07-11 17:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$var_name = ExpressionIdentifier::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) {
|
2020-10-24 18:46:27 +02:00
|
|
|
|
if ($conditional->expr instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($conditional->expr))
|
|
|
|
|
&& !$var_type->isMixed()
|
|
|
|
|
&& !$var_type->possibly_undefined
|
|
|
|
|
) {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['empty']];
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-25 15:49:39 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
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 ($conditional instanceof PhpParser\Node\Expr\Isset_) {
|
|
|
|
|
foreach ($conditional->vars as $isset_var) {
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$var_name = ExpressionIdentifier::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) {
|
2020-11-25 20:04:02 +01:00
|
|
|
|
if ($isset_var instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($isset_var))
|
|
|
|
|
&& !$var_type->isMixed()
|
|
|
|
|
&& !$var_type->possibly_undefined
|
2020-12-02 07:47:49 +01:00
|
|
|
|
&& !$var_type->possibly_undefined_from_try
|
2020-11-25 20:04:02 +01:00
|
|
|
|
) {
|
|
|
|
|
$if_types[$var_name] = [['!null']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['isset']];
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} 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;
|
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$var_name = ExpressionIdentifier::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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-25 15:49:39 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2017-10-23 01:53:53 +02:00
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
|
return [];
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-13 20:54:47 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-10-25 15:49:39 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2018-06-26 00:02:05 +02:00
|
|
|
|
*/
|
|
|
|
|
private static function scrapeEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?string $this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
FileSource $source,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?Codebase $codebase = null,
|
2020-07-10 22:49:45 +02:00
|
|
|
|
bool $cache = true,
|
|
|
|
|
bool $inside_conditional = true
|
2020-09-04 22:26:33 +02:00
|
|
|
|
): array {
|
2020-08-26 23:58:01 +02:00
|
|
|
|
$null_position = self::hasNullVariable($conditional, $source);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
|
|
|
|
if ($null_position !== null) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getNullEqualityAssertions(
|
|
|
|
|
$conditional,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$null_position
|
2018-06-26 00:02:05 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 18:50:17 +01:00
|
|
|
|
$true_position = self::hasTrueVariable($conditional);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($true_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getTrueEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$cache,
|
|
|
|
|
$true_position
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 18:50:17 +01:00
|
|
|
|
$false_position = self::hasFalseVariable($conditional);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($false_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getFalseEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional,
|
|
|
|
|
$false_position
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-11-25 17:44:54 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$empty_array_position = self::hasEmptyArrayVariable($conditional);
|
2020-10-25 15:49:39 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($empty_array_position !== null) {
|
|
|
|
|
return self::getEmptyArrayEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$empty_array_position
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-06-17 19:20:37 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$gettype_position = self::hasGetTypeCheck($conditional);
|
2020-11-20 15:56:12 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($gettype_position) {
|
|
|
|
|
return self::getGettypeEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$gettype_position
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$get_debug_type_position = self::hasGetDebugTypeCheck($conditional);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($get_debug_type_position) {
|
|
|
|
|
return self::getGetdebugtypeEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$get_debug_type_position
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-09-04 22:57:08 +02:00
|
|
|
|
if (!$source instanceof StatementsAnalyzer) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-20 07:50:44 +02:00
|
|
|
|
$count = null;
|
|
|
|
|
$count_equality_position = self::hasCountEqualityCheck($conditional, $count);
|
2020-11-10 18:50:17 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($count_equality_position) {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$count_expr = $conditional->left;
|
|
|
|
|
} elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$count_expr = $conditional->right;
|
2019-03-02 21:18:29 +01:00
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$count_equality_position value');
|
2019-03-02 21:18:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$count_expr->getArgs()[0]->value,
|
2019-03-02 21:18:29 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2021-09-04 22:57:08 +02:00
|
|
|
|
$var_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
$other_type = $source->node_data->getType($conditional->right);
|
|
|
|
|
|
|
|
|
|
if ($codebase
|
|
|
|
|
&& $other_type
|
|
|
|
|
&& $var_type
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
) {
|
|
|
|
|
self::handleParadoxicalAssertions(
|
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$other_type,
|
|
|
|
|
$codebase,
|
|
|
|
|
$conditional
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 21:18:29 +01:00
|
|
|
|
if ($var_name) {
|
2021-09-20 07:57:56 +02:00
|
|
|
|
if ($count !== 0) {
|
|
|
|
|
$if_types[$var_name] = [['=has-exactly-' . $count]];
|
2019-03-02 21:18:29 +01:00
|
|
|
|
} else {
|
2021-09-04 22:39:28 +02:00
|
|
|
|
$if_types[$var_name] = [['!non-empty-countable']];
|
2019-03-02 21:18:29 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2020-01-09 21:45:17 +01:00
|
|
|
|
$getclass_position = self::hasGetClassCheck($conditional, $source);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($getclass_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getGetclassEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$getclass_position
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2017-10-23 02:17:04 +02:00
|
|
|
|
|
2020-01-09 21:45:17 +01:00
|
|
|
|
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($typed_value_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getTypedValueEqualityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$typed_value_position
|
|
|
|
|
);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
|
$var_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
$other_type = $source->node_data->getType($conditional->right);
|
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
|
|
|
|
|
) {
|
2020-07-22 01:40:35 +02:00
|
|
|
|
if (!UnionTypeComparator::canExpressionTypesBeIdentical($codebase, $var_type, $other_type)) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2018-06-26 00:02:05 +02:00
|
|
|
|
new TypeDoesNotContainType(
|
2018-12-06 04:29:06 +01:00
|
|
|
|
$var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
|
2020-09-11 04:44:35 +02:00
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' ' . $other_type->getId()
|
2018-06-26 00:02:05 +02:00
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2021-05-18 22:14:58 +02:00
|
|
|
|
} else {
|
|
|
|
|
// both side of the Identical can be asserted to the intersection of both
|
|
|
|
|
$intersection_type = Type::intersectUnionTypes($var_type, $other_type, $codebase);
|
|
|
|
|
|
|
|
|
|
if ($intersection_type !== null && $intersection_type->isSingle()) {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $intersection_type->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
// getAssertionString can't work if the Union has more than one type
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2021-05-18 22:14:58 +02:00
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
$var_name_left = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$var_assertion_different = $var_type->getAssertionString() !== $assertion;
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
// if getAssertionString threw, it's different
|
|
|
|
|
$var_assertion_different = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name_left && $var_assertion_different) {
|
2021-09-07 03:26:43 +02:00
|
|
|
|
$if_types[$var_name_left] = [['='.$assertion]];
|
2021-05-18 22:14:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$var_name_right = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2021-10-11 14:38:25 +02:00
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$other_assertion_different = $other_type->getAssertionString() !== $assertion;
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
// if getAssertionString threw, it's different
|
|
|
|
|
$other_assertion_different = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name_right && $other_assertion_different) {
|
2021-09-07 03:26:43 +02:00
|
|
|
|
$if_types[$var_name_right] = [['='.$assertion]];
|
2021-05-18 22:14:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
|
return [];
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
2018-04-17 21:39:09 +02:00
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
/**
|
2021-07-13 20:54:47 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-10-25 15:49:39 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2018-06-26 00:02:05 +02:00
|
|
|
|
*/
|
|
|
|
|
private static function scrapeInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?string $this_class_name,
|
2018-11-06 03:57:36 +01:00
|
|
|
|
FileSource $source,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?Codebase $codebase = null,
|
2020-07-10 22:49:45 +02:00
|
|
|
|
bool $cache = true,
|
|
|
|
|
bool $inside_conditional = true
|
2020-09-04 22:26:33 +02:00
|
|
|
|
): array {
|
2020-08-27 00:16:12 +02:00
|
|
|
|
$null_position = self::hasNullVariable($conditional, $source);
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
|
|
|
|
if ($null_position !== null) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getNullInequalityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$source,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$codebase,
|
|
|
|
|
$null_position
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$false_position = self::hasFalseVariable($conditional);
|
|
|
|
|
|
|
|
|
|
if ($false_position) {
|
|
|
|
|
return self::getFalseInequalityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$cache,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$inside_conditional,
|
|
|
|
|
$codebase,
|
|
|
|
|
$false_position
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$true_position = self::hasTrueVariable($conditional);
|
|
|
|
|
|
|
|
|
|
if ($true_position) {
|
|
|
|
|
return self::getTrueInequalityAssertions(
|
|
|
|
|
$true_position,
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$count = null;
|
2021-09-04 23:44:20 +02:00
|
|
|
|
$count_inequality_position = self::hasCountEqualityCheck($conditional, $count);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($count_inequality_position) {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
if ($count_inequality_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$count_expr = $conditional->left;
|
|
|
|
|
} elseif ($count_inequality_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$count_expr = $conditional->right;
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$count_equality_position value');
|
2018-06-26 00:02:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$count_expr->getArgs()[0]->value,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($count) {
|
|
|
|
|
$if_types[$var_name] = [['!has-exactly-' . $count]];
|
2018-06-26 00:02:05 +02:00
|
|
|
|
} else {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types[$var_name] = [['non-empty-countable']];
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-25 15:49:39 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 18:50:17 +01:00
|
|
|
|
$empty_array_position = self::hasEmptyArrayVariable($conditional);
|
|
|
|
|
|
2019-03-02 21:18:29 +01:00
|
|
|
|
if ($empty_array_position !== null) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getEmptyInequalityAssertions(
|
|
|
|
|
$conditional,
|
2019-03-02 21:18:29 +01:00
|
|
|
|
$this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$empty_array_position
|
2019-03-02 21:18:29 +01:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 18:50:17 +01:00
|
|
|
|
$gettype_position = self::hasGetTypeCheck($conditional);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($gettype_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getGettypeInequalityAssertions(
|
|
|
|
|
$conditional,
|
2018-06-26 00:02:05 +02:00
|
|
|
|
$this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$source,
|
|
|
|
|
$gettype_position
|
2018-06-26 00:02:05 +02:00
|
|
|
|
);
|
2017-02-18 02:50:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 18:50:17 +01:00
|
|
|
|
$get_debug_type_position = self::hasGetDebugTypeCheck($conditional);
|
|
|
|
|
|
2020-10-03 01:15:47 +02:00
|
|
|
|
if ($get_debug_type_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getGetdebugTypeInequalityAssertions(
|
|
|
|
|
$conditional,
|
2020-10-03 01:15:47 +02:00
|
|
|
|
$this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$source,
|
|
|
|
|
$get_debug_type_position
|
2020-10-03 01:15:47 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 21:45:17 +01:00
|
|
|
|
if (!$source instanceof StatementsAnalyzer) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$getclass_position = self::hasGetClassCheck($conditional, $source);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($getclass_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getGetclassInequalityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$getclass_position
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-06-26 00:02:05 +02:00
|
|
|
|
|
2020-11-10 18:50:17 +01:00
|
|
|
|
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
|
|
|
|
|
|
2018-06-26 00:02:05 +02:00
|
|
|
|
if ($typed_value_position) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getTypedValueInequalityAssertions(
|
|
|
|
|
$conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$typed_value_position
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
|
return [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2020-10-25 15:49:39 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<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,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?string $this_class_name,
|
2018-02-23 21:39:33 +01:00
|
|
|
|
FileSource $source,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?Codebase $codebase = null,
|
|
|
|
|
bool $negate = false
|
2020-09-04 22:26:33 +02:00
|
|
|
|
): array {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$first_var_name = isset($expr->getArgs()[0]->value)
|
2020-05-18 21:13:27 +02:00
|
|
|
|
? ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$expr->getArgs()[0]->value,
|
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
|
|
|
|
)
|
|
|
|
|
: null;
|
|
|
|
|
|
2017-10-23 01:53:53 +02:00
|
|
|
|
$if_types = [];
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$first_var_type = isset($expr->getArgs()[0]->value)
|
2020-02-22 16:41:57 +01:00
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
2021-10-09 23:37:04 +02:00
|
|
|
|
? $source->node_data->getType($expr->getArgs()[0]->value)
|
2020-02-22 16:41:57 +01:00
|
|
|
|
: null;
|
|
|
|
|
|
2021-09-16 22:59:26 +02:00
|
|
|
|
if ($tmp_if_types = self::handleIsTypeCheck(
|
|
|
|
|
$codebase,
|
|
|
|
|
$source,
|
|
|
|
|
$expr,
|
|
|
|
|
$first_var_name,
|
|
|
|
|
$first_var_type,
|
|
|
|
|
$expr,
|
|
|
|
|
$negate
|
|
|
|
|
)) {
|
|
|
|
|
$if_types = $tmp_if_types;
|
2020-02-02 18:26:28 +01:00
|
|
|
|
} elseif ($source instanceof StatementsAnalyzer && self::hasIsACheck($expr, $source)) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getIsaAssertions($expr, $source, $this_class_name, $first_var_name);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
} elseif (self::hasCallableCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2020-11-19 20:32:49 +01:00
|
|
|
|
$if_types[$first_var_name] = [['callable']];
|
2021-10-09 23:37:04 +02:00
|
|
|
|
} elseif ($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\Array_
|
|
|
|
|
&& isset($expr->getArgs()[0]->value->items[0], $expr->getArgs()[0]->value->items[1])
|
|
|
|
|
&& $expr->getArgs()[0]->value->items[1]->value instanceof PhpParser\Node\Scalar\String_
|
2020-05-16 14:50:43 +02:00
|
|
|
|
) {
|
2020-05-18 21:13:27 +02:00
|
|
|
|
$first_var_name_in_array_argument = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$expr->getArgs()[0]->value->items[0]->value,
|
2020-05-16 14:50:43 +02:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
if ($first_var_name_in_array_argument) {
|
|
|
|
|
$if_types[$first_var_name_in_array_argument] = [
|
2021-10-09 23:37:04 +02:00
|
|
|
|
['hasmethod-' . $expr->getArgs()[0]->value->items[1]->value->value]
|
2020-05-16 14:50:43 +02:00
|
|
|
|
];
|
|
|
|
|
}
|
2018-06-08 19:53:42 +02:00
|
|
|
|
}
|
2019-05-28 19:16:09 +02:00
|
|
|
|
} elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) {
|
2018-11-12 18:03:55 +01:00
|
|
|
|
if ($first_var_name) {
|
2020-11-01 19:14:17 +01:00
|
|
|
|
$class_string_type = ($class_exists_check_type === 1 ? 'loaded-' : '') . 'class-string';
|
2020-11-19 20:32:49 +01:00
|
|
|
|
$if_types[$first_var_name] = [[$class_string_type]];
|
2018-11-12 18:03:55 +01:00
|
|
|
|
}
|
2019-05-31 15:43:46 +02:00
|
|
|
|
} elseif ($class_exists_check_type = self::hasTraitExistsCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2020-11-19 20:32:49 +01:00
|
|
|
|
if ($class_exists_check_type === 2) {
|
|
|
|
|
$if_types[$first_var_name] = [['trait-string']];
|
2020-04-30 18:48:21 +02:00
|
|
|
|
} else {
|
2019-05-31 15:43:46 +02:00
|
|
|
|
$if_types[$first_var_name] = [['=trait-string']];
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-21 02:57:59 +02:00
|
|
|
|
} elseif (self::hasInterfaceExistsCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2020-11-19 20:32:49 +01:00
|
|
|
|
$if_types[$first_var_name] = [['interface-string']];
|
2019-05-21 02:57:59 +02:00
|
|
|
|
}
|
2019-04-10 00:09:57 +02:00
|
|
|
|
} elseif (self::hasFunctionExistsCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
2020-11-19 20:32:49 +01:00
|
|
|
|
$if_types[$first_var_name] = [['callable-string']];
|
2019-04-10 00:09:57 +02:00
|
|
|
|
}
|
2019-08-16 17:33:58 +02:00
|
|
|
|
} elseif ($expr->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($expr->name->parts[0]) === 'method_exists'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& isset($expr->getArgs()[1])
|
|
|
|
|
&& $expr->getArgs()[1]->value instanceof PhpParser\Node\Scalar\String_
|
2019-08-16 17:33:58 +02:00
|
|
|
|
) {
|
|
|
|
|
if ($first_var_name) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$if_types[$first_var_name] = [['hasmethod-' . $expr->getArgs()[1]->value->value]];
|
2019-08-16 17:33:58 +02:00
|
|
|
|
}
|
2019-11-25 17:44:54 +01:00
|
|
|
|
} elseif (self::hasInArrayCheck($expr) && $source instanceof StatementsAnalyzer) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::getInarrayAssertions($expr, $source, $first_var_name);
|
|
|
|
|
} elseif (self::hasArrayKeyExistsCheck($expr)) {
|
|
|
|
|
return self::getArrayKeyExistsAssertions(
|
|
|
|
|
$expr,
|
|
|
|
|
$first_var_type,
|
|
|
|
|
$first_var_name,
|
|
|
|
|
$source,
|
|
|
|
|
$this_class_name
|
|
|
|
|
);
|
|
|
|
|
} elseif (self::hasNonEmptyCountCheck($expr)) {
|
|
|
|
|
if ($first_var_name) {
|
|
|
|
|
$if_types[$first_var_name] = [['non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return self::processCustomAssertion($expr, $this_class_name, $source);
|
|
|
|
|
}
|
2018-06-08 19:53:42 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
2018-06-08 19:53:42 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function processIrreconcilableFunctionCall(
|
|
|
|
|
Type\Union $first_var_type,
|
|
|
|
|
Type\Union $expected_type,
|
|
|
|
|
PhpParser\Node\Expr $expr,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
Codebase $codebase,
|
|
|
|
|
bool $negate
|
2021-12-05 18:51:26 +01:00
|
|
|
|
): void {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($first_var_type->hasMixed()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-20 00:12:14 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$first_var_type,
|
|
|
|
|
$expected_type
|
|
|
|
|
)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-20 00:12:14 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!$negate) {
|
|
|
|
|
if ($first_var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock type ' . $first_var_type . ' always contains ' . $expected_type,
|
|
|
|
|
new CodeLocation($source, $expr),
|
|
|
|
|
$first_var_type . ' ' . $expected_type
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$first_var_type . ' always contains ' . $expected_type,
|
|
|
|
|
new CodeLocation($source, $expr),
|
|
|
|
|
$first_var_type . ' ' . $expected_type
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
if ($first_var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new DocblockTypeContradiction(
|
2021-10-09 20:58:36 +02:00
|
|
|
|
'Docblock type !' . $first_var_type . ' does not contain ' . $expected_type,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new CodeLocation($source, $expr),
|
|
|
|
|
$first_var_type . ' ' . $expected_type
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new TypeDoesNotContainType(
|
2021-10-09 20:58:36 +02:00
|
|
|
|
'!' . $first_var_type . ' does not contain ' . $expected_type,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new CodeLocation($source, $expr),
|
|
|
|
|
$first_var_type . ' ' . $expected_type
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-19 21:40:27 +01:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-19 21:40:27 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $expr
|
|
|
|
|
*
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
protected static function processCustomAssertion(
|
|
|
|
|
PhpParser\Node\Expr $expr,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source
|
|
|
|
|
): array {
|
|
|
|
|
if (!$source instanceof StatementsAnalyzer) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_true_assertions = $source->node_data->getIfTrueAssertions($expr);
|
|
|
|
|
$if_false_assertions = $source->node_data->getIfFalseAssertions($expr);
|
2018-03-18 00:28:01 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($if_true_assertions === null && $if_false_assertions === null) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$first_var_name = isset($expr->getArgs()[0]->value)
|
2020-11-30 07:20:28 +01:00
|
|
|
|
? ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
)
|
|
|
|
|
: null;
|
2020-05-27 15:03:36 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$anded_types = [];
|
2020-05-27 15:03:36 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($if_true_assertions) {
|
|
|
|
|
foreach ($if_true_assertions as $assertion) {
|
|
|
|
|
$if_types = [];
|
2020-05-27 15:03:36 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$assertion = clone $assertion;
|
|
|
|
|
|
|
|
|
|
foreach ($assertion->rule as $i => $and_rules) {
|
|
|
|
|
foreach ($and_rules as $j => $rule) {
|
2021-05-03 23:54:09 +02:00
|
|
|
|
if (strpos($rule, 'class-constant(') === 0) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$codebase = $source->getCodebase();
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
2021-12-03 20:11:20 +01:00
|
|
|
|
$assertion->rule[$i][$j] = TypeExpander::expandUnion(
|
2021-10-11 14:38:25 +02:00
|
|
|
|
$codebase,
|
|
|
|
|
Type::parseString(substr($rule, 15, -1)),
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null
|
|
|
|
|
)->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if (is_int($assertion->var_id) && isset($expr->getArgs()[$assertion->var_id])) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($assertion->var_id === 0) {
|
|
|
|
|
$var_name = $first_var_name;
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$expr->getArgs()[$assertion->var_id]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [[$assertion->rule[0][0]]];
|
|
|
|
|
}
|
2021-10-17 10:29:25 +02:00
|
|
|
|
} elseif ($assertion->var_id === '$this') {
|
|
|
|
|
if (!$expr instanceof PhpParser\Node\Expr\MethodCall) {
|
|
|
|
|
IssueBuffer::add(
|
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
'Assertion of $this can be done only on method of a class',
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_id = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$expr->var,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_id) {
|
|
|
|
|
$if_types[$var_id] = [[$assertion->rule[0][0]]];
|
|
|
|
|
}
|
2021-12-03 21:07:25 +01:00
|
|
|
|
} elseif (is_string($assertion->var_id)) {
|
2021-10-17 10:29:25 +02:00
|
|
|
|
$is_function = substr($assertion->var_id, -2) === '()';
|
|
|
|
|
$exploded_id = explode('->', $assertion->var_id);
|
|
|
|
|
$var_id = $exploded_id[0] ?? null;
|
|
|
|
|
$property = $exploded_id[1] ?? null;
|
|
|
|
|
|
|
|
|
|
if (is_numeric($var_id) && null !== $property && !$is_function) {
|
|
|
|
|
$args = $expr->getArgs();
|
|
|
|
|
|
|
|
|
|
if (!array_key_exists($var_id, $args)) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
'Variable '.$var_id.' is not an argument so cannot be asserted',
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$arg_value = $args[$var_id]->value;
|
|
|
|
|
assert($arg_value instanceof PhpParser\Node\Expr\Variable);
|
|
|
|
|
|
|
|
|
|
$arg_var_id = ExpressionIdentifier::getArrayVarId($arg_value, null, $source);
|
|
|
|
|
|
|
|
|
|
if (null === $arg_var_id) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
'Variable being asserted as argument ' . ($var_id+1) . ' cannot be found
|
|
|
|
|
in local scope',
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count($exploded_id) === 2) {
|
|
|
|
|
$failedMessage = self::isPropertyImmutableOnArgument(
|
|
|
|
|
$property,
|
|
|
|
|
$source->getNodeTypeProvider(),
|
|
|
|
|
$source->getCodebase()->classlike_storage_provider,
|
|
|
|
|
$arg_value
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (null !== $failedMessage) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock($failedMessage, new CodeLocation($source, $expr))
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$assertion_var_id = str_replace($var_id, $arg_var_id, $assertion->var_id);
|
|
|
|
|
} elseif (!$expr instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$assertion_var_id = $assertion->var_id;
|
|
|
|
|
|
|
|
|
|
if (strpos($assertion_var_id, 'self::') === 0) {
|
|
|
|
|
$assertion_var_id = $this_class_name.'::'.substr($assertion_var_id, 6);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
sprintf('Assertion of variable "%s" cannot be recognized', $assertion->var_id),
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
continue;
|
2020-12-21 18:05:44 +01:00
|
|
|
|
}
|
2021-10-17 10:29:25 +02:00
|
|
|
|
$if_types[$assertion_var_id] = [[$assertion->rule[0][0]]];
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($if_types) {
|
|
|
|
|
$anded_types[] = $if_types;
|
2020-11-19 21:57:05 +01:00
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($if_false_assertions) {
|
|
|
|
|
foreach ($if_false_assertions as $assertion) {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
$assertion = clone $assertion;
|
|
|
|
|
|
|
|
|
|
foreach ($assertion->rule as $i => $and_rules) {
|
|
|
|
|
foreach ($and_rules as $j => $rule) {
|
2021-05-03 23:54:09 +02:00
|
|
|
|
if (strpos($rule, 'class-constant(') === 0) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$codebase = $source->getCodebase();
|
|
|
|
|
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
2021-12-03 20:11:20 +01:00
|
|
|
|
$assertion->rule[$i][$j] = TypeExpander::expandUnion(
|
2021-10-11 14:38:25 +02:00
|
|
|
|
$codebase,
|
|
|
|
|
Type::parseString(substr($rule, 15, -1)),
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null
|
|
|
|
|
)->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if (is_int($assertion->var_id) && isset($expr->getArgs()[$assertion->var_id])) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($assertion->var_id === 0) {
|
|
|
|
|
$var_name = $first_var_name;
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$expr->getArgs()[$assertion->var_id]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ('!' === $assertion->rule[0][0][0]) {
|
|
|
|
|
$if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!' . $assertion->rule[0][0]]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) {
|
|
|
|
|
$var_id = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$expr->var,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_id) {
|
|
|
|
|
if ('!' === $assertion->rule[0][0][0]) {
|
|
|
|
|
$if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_id] = [['!' . $assertion->rule[0][0]]];
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-03 21:07:25 +01:00
|
|
|
|
} elseif (is_string($assertion->var_id)) {
|
2021-10-17 10:29:25 +02:00
|
|
|
|
$is_function = substr($assertion->var_id, -2) === '()';
|
|
|
|
|
$exploded_id = explode('->', $assertion->var_id);
|
|
|
|
|
$var_id = $exploded_id[0] ?? null;
|
|
|
|
|
$property = $exploded_id[1] ?? null;
|
|
|
|
|
|
|
|
|
|
if (is_numeric($var_id) && null !== $property && !$is_function) {
|
|
|
|
|
$args = $expr->getArgs();
|
|
|
|
|
|
|
|
|
|
if (!array_key_exists($var_id, $args)) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
'Variable '.$var_id.' is not an argument so cannot be asserted',
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/** @var PhpParser\Node\Expr\Variable $arg_value */
|
|
|
|
|
$arg_value = $args[$var_id]->value;
|
|
|
|
|
|
|
|
|
|
$arg_var_id = ExpressionIdentifier::getArrayVarId($arg_value, null, $source);
|
|
|
|
|
|
|
|
|
|
if (null === $arg_var_id) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
'Variable being asserted as argument ' . ($var_id+1) . ' cannot be found
|
|
|
|
|
in local scope',
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count($exploded_id) === 2) {
|
|
|
|
|
$failedMessage = self::isPropertyImmutableOnArgument(
|
|
|
|
|
$property,
|
|
|
|
|
$source->getNodeTypeProvider(),
|
|
|
|
|
$source->getCodebase()->classlike_storage_provider,
|
|
|
|
|
$arg_value
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (null !== $failedMessage) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock($failedMessage, new CodeLocation($source, $expr))
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ('!' === $assertion->rule[0][0][0]) {
|
|
|
|
|
$rule = substr($assertion->rule[0][0], 1);
|
|
|
|
|
} else {
|
|
|
|
|
$rule = '!' . $assertion->rule[0][0];
|
|
|
|
|
}
|
|
|
|
|
$assertion_var_id = str_replace($var_id, $arg_var_id, $assertion->var_id);
|
|
|
|
|
|
|
|
|
|
$if_types[$assertion_var_id] = [[$rule]];
|
|
|
|
|
} elseif (!$expr instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$var_id = $assertion->var_id;
|
|
|
|
|
if (strpos($var_id, 'self::') === 0) {
|
|
|
|
|
$var_id = $this_class_name.'::'.substr($var_id, 6);
|
|
|
|
|
}
|
|
|
|
|
$if_types[$var_id] = [['!'.$assertion->rule[0][0]]];
|
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-10-17 10:29:25 +02:00
|
|
|
|
new InvalidDocblock(
|
|
|
|
|
sprintf('Assertion of variable "%s" cannot be recognized', $assertion->var_id),
|
|
|
|
|
new CodeLocation($source, $expr)
|
|
|
|
|
)
|
|
|
|
|
);
|
2020-12-21 18:05:44 +01:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($if_types) {
|
|
|
|
|
$anded_types[] = $if_types;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $anded_types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return list<string>
|
|
|
|
|
*/
|
|
|
|
|
protected static function getInstanceOfTypes(
|
|
|
|
|
PhpParser\Node\Expr\Instanceof_ $stmt,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source
|
|
|
|
|
): array {
|
|
|
|
|
if ($stmt->class instanceof PhpParser\Node\Name) {
|
|
|
|
|
if (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) {
|
|
|
|
|
$instanceof_class = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$stmt->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($source instanceof StatementsAnalyzer) {
|
|
|
|
|
$codebase = $source->getCodebase();
|
|
|
|
|
$instanceof_class = $codebase->classlikes->getUnAliasedName($instanceof_class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [$instanceof_class];
|
2021-09-25 02:34:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this_class_name
|
|
|
|
|
&& (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($stmt->class->parts[0] === 'static') {
|
|
|
|
|
return ['=' . $this_class_name . '&static'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [$this_class_name];
|
|
|
|
|
}
|
|
|
|
|
} elseif ($source instanceof StatementsAnalyzer) {
|
|
|
|
|
$stmt_class_type = $source->node_data->getType($stmt->class);
|
|
|
|
|
|
|
|
|
|
if ($stmt_class_type) {
|
|
|
|
|
$literal_class_strings = [];
|
|
|
|
|
|
|
|
|
|
foreach ($stmt_class_type->getAtomicTypes() as $atomic_type) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($atomic_type instanceof TLiteralClassString) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$literal_class_strings[] = $atomic_type->value;
|
2021-12-13 04:45:57 +01:00
|
|
|
|
} elseif ($atomic_type instanceof TTemplateParamClass) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$literal_class_strings[] = $atomic_type->param_name;
|
2021-12-13 04:45:57 +01:00
|
|
|
|
} elseif ($atomic_type instanceof TClassString && $atomic_type->as !== 'object') {
|
2021-09-17 19:52:11 +02:00
|
|
|
|
$literal_class_strings[] = $atomic_type->as;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $literal_class_strings;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 00:02:36 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
protected static function hasNullVariable(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
FileSource $source
|
|
|
|
|
): ?int {
|
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'null'
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'null'
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($right_type = $source->node_data->getType($conditional->right))
|
|
|
|
|
&& $right_type->isNull()
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 00:02:36 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
public static function hasFalseVariable(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
): ?int {
|
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'false'
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'false'
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 00:02:36 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
public static function hasTrueVariable(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
): ?int {
|
|
|
|
|
if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->right->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 00:02:36 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
protected static function hasEmptyArrayVariable(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
): ?int {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
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'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->right->getArgs()
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& $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'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->left->getArgs()
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& $conditional->right instanceof PhpParser\Node\Scalar\String_
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
protected static function hasGetDebugTypeCheck(
|
|
|
|
|
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]) === 'get_debug_type'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->right->getArgs()
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& ($conditional->left instanceof PhpParser\Node\Scalar\String_
|
|
|
|
|
|| $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch)
|
|
|
|
|
) {
|
|
|
|
|
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]) === 'get_debug_type'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->left->getArgs()
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& ($conditional->right instanceof PhpParser\Node\Scalar\String_
|
|
|
|
|
|| $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch)
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param Identical|Equal|NotIdentical|NotEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
protected static function hasGetClassCheck(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
FileSource $source
|
|
|
|
|
) {
|
|
|
|
|
if (!$source instanceof StatementsAnalyzer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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';
|
|
|
|
|
|
2021-11-04 20:33:30 +01:00
|
|
|
|
$right_variable_class_const = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $conditional->right->class instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
&& $conditional->right->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($conditional->right->name->name) === 'class';
|
|
|
|
|
|
2020-11-30 07:20:28 +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';
|
|
|
|
|
|
|
|
|
|
$left_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
|
|
|
|
|
$left_class_string_t = false;
|
|
|
|
|
|
|
|
|
|
if ($left_type && $left_type->isSingle()) {
|
|
|
|
|
foreach ($left_type->getAtomicTypes() as $type_part) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($type_part instanceof TClassString) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$left_class_string_t = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-04 20:33:30 +01:00
|
|
|
|
if (($right_get_class || $right_static_class || $right_variable_class_const)
|
|
|
|
|
&& ($left_class_string || $left_class_string_t)
|
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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';
|
|
|
|
|
|
2021-11-04 20:33:30 +01:00
|
|
|
|
$left_variable_class_const = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $conditional->left->class instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($conditional->left->name->name) === 'class';
|
|
|
|
|
|
2020-11-30 07:20:28 +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';
|
|
|
|
|
|
|
|
|
|
$right_type = $source->node_data->getType($conditional->right);
|
|
|
|
|
|
|
|
|
|
$right_class_string_t = false;
|
|
|
|
|
|
|
|
|
|
if ($right_type && $right_type->isSingle()) {
|
|
|
|
|
foreach ($right_type->getAtomicTypes() as $type_part) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($type_part instanceof TClassString) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$right_class_string_t = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-04 20:33:30 +01:00
|
|
|
|
if (($left_get_class || $left_static_class || $left_variable_class_const)
|
|
|
|
|
&& ($right_class_string || $right_class_string_t)
|
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-09-04 23:44:20 +02:00
|
|
|
|
* @param Greater|GreaterOrEqual|Smaller|SmallerOrEqual $conditional
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
protected static function hasNonEmptyCountEqualityCheck(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?int &$min_count
|
|
|
|
|
) {
|
|
|
|
|
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'count'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->left->getArgs();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
$operator_greater_than_or_equal =
|
2021-09-04 23:44:20 +02:00
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
|
|
|
|
|
|
|
|
|
|
if ($left_count
|
|
|
|
|
&& $conditional->right instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
&& $operator_greater_than_or_equal
|
|
|
|
|
&& $conditional->right->value >= (
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
|
|
|
|
? 0
|
|
|
|
|
: 1
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
$min_count = $conditional->right->value +
|
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
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'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->right->getArgs();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
$operator_less_than_or_equal =
|
2021-09-04 23:44:20 +02:00
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
|
|
|
|
|
|
|
|
|
|
if ($right_count
|
|
|
|
|
&& $conditional->left instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
&& $operator_less_than_or_equal
|
|
|
|
|
&& $conditional->left->value >= (
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
$min_count = $conditional->left->value +
|
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param Greater|GreaterOrEqual|Smaller|SmallerOrEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
protected static function hasLessThanCountEqualityCheck(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?int &$max_count
|
|
|
|
|
) {
|
|
|
|
|
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'count'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->left->getArgs();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
$operator_less_than_or_equal =
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller;
|
|
|
|
|
|
|
|
|
|
if ($left_count
|
|
|
|
|
&& $operator_less_than_or_equal
|
|
|
|
|
&& $conditional->right instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
) {
|
|
|
|
|
$max_count = $conditional->right->value -
|
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
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'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->right->getArgs();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
$operator_greater_than_or_equal =
|
|
|
|
|
$conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
|
|
|
|
|
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater;
|
|
|
|
|
|
|
|
|
|
if ($right_count
|
|
|
|
|
&& $operator_greater_than_or_equal
|
|
|
|
|
&& $conditional->left instanceof PhpParser\Node\Scalar\LNumber
|
|
|
|
|
) {
|
|
|
|
|
$max_count = $conditional->left->value -
|
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-13 20:54:47 +02:00
|
|
|
|
* @param Equal|Identical|NotEqual|NotIdentical $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
2021-09-20 07:43:30 +02:00
|
|
|
|
protected static function hasCountEqualityCheck(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?int &$count
|
|
|
|
|
) {
|
|
|
|
|
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
|
&& $conditional->left->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($conditional->left->name->parts[0]) === 'count'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->left->getArgs();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($left_count && $conditional->right instanceof PhpParser\Node\Scalar\LNumber) {
|
|
|
|
|
$count = $conditional->right->value;
|
|
|
|
|
|
|
|
|
|
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'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& $conditional->right->getArgs();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($right_count && $conditional->left instanceof PhpParser\Node\Scalar\LNumber) {
|
|
|
|
|
$count = $conditional->left->value;
|
|
|
|
|
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Greater|PhpParser\Node\Expr\BinaryOp\GreaterOrEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
2021-08-15 10:24:40 +02:00
|
|
|
|
protected static function hasSuperiorNumberCheck(
|
2021-09-12 11:33:23 +02:00
|
|
|
|
FileSource $source,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2021-09-01 22:28:05 +02:00
|
|
|
|
?int &$literal_value_comparison,
|
|
|
|
|
bool &$isset_assert
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$right_assignment = false;
|
|
|
|
|
$value_right = null;
|
2021-09-12 11:33:23 +02:00
|
|
|
|
if ($source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($type = $source->node_data->getType($conditional->right))
|
|
|
|
|
&& $type->isSingleIntLiteral()
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2021-09-12 11:33:23 +02:00
|
|
|
|
$right_assignment = true;
|
|
|
|
|
$value_right = $type->getSingleIntLiteral()->value;
|
|
|
|
|
} elseif ($conditional->right instanceof LNumber) {
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$right_assignment = true;
|
|
|
|
|
$value_right = $conditional->right->value;
|
|
|
|
|
} elseif ($conditional->right instanceof UnaryMinus && $conditional->right->expr instanceof LNumber) {
|
|
|
|
|
$right_assignment = true;
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$value_right = -$conditional->right->expr->value;
|
2021-08-31 23:17:44 +02:00
|
|
|
|
} elseif ($conditional->right instanceof UnaryPlus && $conditional->right->expr instanceof LNumber) {
|
|
|
|
|
$right_assignment = true;
|
|
|
|
|
$value_right = $conditional->right->expr->value;
|
|
|
|
|
}
|
2021-09-05 22:58:53 +02:00
|
|
|
|
if ($right_assignment === true && $value_right !== null) {
|
2021-09-01 22:49:59 +02:00
|
|
|
|
$isset_assert = $value_right === 0 && $conditional instanceof Greater;
|
2021-09-01 22:28:05 +02:00
|
|
|
|
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$literal_value_comparison = $value_right +
|
2020-11-30 07:20:28 +01:00
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$left_assignment = false;
|
|
|
|
|
$value_left = null;
|
2021-09-12 11:33:23 +02:00
|
|
|
|
if ($source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($type = $source->node_data->getType($conditional->left))
|
|
|
|
|
&& $type->isSingleIntLiteral()
|
|
|
|
|
) {
|
|
|
|
|
$left_assignment = true;
|
|
|
|
|
$value_left = $type->getSingleIntLiteral()->value;
|
|
|
|
|
} elseif ($conditional->left instanceof LNumber) {
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$left_assignment = true;
|
|
|
|
|
$value_left = $conditional->left->value;
|
|
|
|
|
} elseif ($conditional->left instanceof UnaryMinus && $conditional->left->expr instanceof LNumber) {
|
|
|
|
|
$left_assignment = true;
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$value_left = -$conditional->left->expr->value;
|
2021-08-31 23:17:44 +02:00
|
|
|
|
} elseif ($conditional->left instanceof UnaryPlus && $conditional->left->expr instanceof LNumber) {
|
|
|
|
|
$left_assignment = true;
|
|
|
|
|
$value_left = $conditional->left->expr->value;
|
|
|
|
|
}
|
2021-09-05 22:58:53 +02:00
|
|
|
|
if ($left_assignment === true && $value_left !== null) {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$isset_assert = $value_left === 0 && $conditional instanceof Greater;
|
|
|
|
|
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$literal_value_comparison = $value_left +
|
2021-09-01 21:26:56 +02:00
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? -1 : 0);
|
2021-08-15 13:44:47 +02:00
|
|
|
|
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-23 20:24:15 +02:00
|
|
|
|
/**
|
2021-08-15 10:24:40 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Smaller|PhpParser\Node\Expr\BinaryOp\SmallerOrEqual $conditional
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @return false|int
|
2021-05-23 20:24:15 +02:00
|
|
|
|
*/
|
2021-08-15 10:24:40 +02:00
|
|
|
|
protected static function hasInferiorNumberCheck(
|
2021-09-12 11:33:23 +02:00
|
|
|
|
FileSource $source,
|
2021-05-23 20:24:15 +02:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2021-09-01 22:28:05 +02:00
|
|
|
|
?int &$literal_value_comparison,
|
|
|
|
|
bool &$isset_assert
|
2021-05-23 20:24:15 +02:00
|
|
|
|
) {
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$right_assignment = false;
|
|
|
|
|
$value_right = null;
|
2021-09-12 11:33:23 +02:00
|
|
|
|
if ($source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($type = $source->node_data->getType($conditional->right))
|
|
|
|
|
&& $type->isSingleIntLiteral()
|
2021-05-23 20:24:15 +02:00
|
|
|
|
) {
|
2021-09-12 11:33:23 +02:00
|
|
|
|
$right_assignment = true;
|
|
|
|
|
$value_right = $type->getSingleIntLiteral()->value;
|
|
|
|
|
} elseif ($conditional->right instanceof LNumber) {
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$right_assignment = true;
|
|
|
|
|
$value_right = $conditional->right->value;
|
|
|
|
|
} elseif ($conditional->right instanceof UnaryMinus && $conditional->right->expr instanceof LNumber) {
|
|
|
|
|
$right_assignment = true;
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$value_right = -$conditional->right->expr->value;
|
2021-08-31 23:17:44 +02:00
|
|
|
|
} elseif ($conditional->right instanceof UnaryPlus && $conditional->right->expr instanceof LNumber) {
|
|
|
|
|
$right_assignment = true;
|
|
|
|
|
$value_right = $conditional->right->expr->value;
|
|
|
|
|
}
|
2021-09-05 22:58:53 +02:00
|
|
|
|
if ($right_assignment === true && $value_right !== null) {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$isset_assert = $value_right === 0 && $conditional instanceof Smaller;
|
|
|
|
|
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$literal_value_comparison = $value_right +
|
2021-09-01 21:26:56 +02:00
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? -1 : 0);
|
2021-05-23 20:24:15 +02:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$left_assignment = false;
|
|
|
|
|
$value_left = null;
|
2021-09-12 11:33:23 +02:00
|
|
|
|
if ($source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($type = $source->node_data->getType($conditional->left))
|
|
|
|
|
&& $type->isSingleIntLiteral()
|
|
|
|
|
) {
|
|
|
|
|
$left_assignment = true;
|
|
|
|
|
$value_left = $type->getSingleIntLiteral()->value;
|
|
|
|
|
} elseif ($conditional->left instanceof LNumber) {
|
2021-08-31 23:17:44 +02:00
|
|
|
|
$left_assignment = true;
|
|
|
|
|
$value_left = $conditional->left->value;
|
|
|
|
|
} elseif ($conditional->left instanceof UnaryMinus && $conditional->left->expr instanceof LNumber) {
|
|
|
|
|
$left_assignment = true;
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$value_left = -$conditional->left->expr->value;
|
2021-08-31 23:17:44 +02:00
|
|
|
|
} elseif ($conditional->left instanceof UnaryPlus && $conditional->left->expr instanceof LNumber) {
|
|
|
|
|
$left_assignment = true;
|
|
|
|
|
$value_left = $conditional->left->expr->value;
|
|
|
|
|
}
|
2021-09-05 22:58:53 +02:00
|
|
|
|
if ($left_assignment === true && $value_left !== null) {
|
2021-09-01 22:49:59 +02:00
|
|
|
|
$isset_assert = $value_left === 0 && $conditional instanceof Smaller;
|
2021-09-01 22:28:05 +02:00
|
|
|
|
|
2021-09-05 22:58:53 +02:00
|
|
|
|
$literal_value_comparison = $value_left +
|
2021-09-01 21:26:56 +02:00
|
|
|
|
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0);
|
2021-08-15 13:44:47 +02:00
|
|
|
|
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-23 20:24:15 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Greater|PhpParser\Node\Expr\BinaryOp\GreaterOrEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
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);
|
|
|
|
|
|
2021-07-14 00:02:36 +02:00
|
|
|
|
if ($left_count && $right_number) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-14 00:02:36 +02:00
|
|
|
|
* @param Identical|Equal|Smaller|SmallerOrEqual|NotIdentical|NotEqual $conditional
|
|
|
|
|
* @return false|int
|
2020-11-30 07:20:28 +01:00
|
|
|
|
*/
|
|
|
|
|
protected static function hasTypedValueComparison(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
FileSource $source
|
|
|
|
|
) {
|
|
|
|
|
if (!$source instanceof StatementsAnalyzer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (($right_type = $source->node_data->getType($conditional->right))
|
|
|
|
|
&& ((!$conditional->right instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
&& !$conditional->right instanceof PhpParser\Node\Expr\PropertyFetch
|
|
|
|
|
&& !$conditional->right instanceof PhpParser\Node\Expr\StaticPropertyFetch)
|
|
|
|
|
|| $conditional->left instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
|| $conditional->left instanceof PhpParser\Node\Expr\PropertyFetch
|
|
|
|
|
|| $conditional->left instanceof PhpParser\Node\Expr\StaticPropertyFetch)
|
|
|
|
|
&& count($right_type->getAtomicTypes()) === 1
|
|
|
|
|
&& !$right_type->hasMixed()
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (($left_type = $source->node_data->getType($conditional->left))
|
|
|
|
|
&& !$conditional->left instanceof PhpParser\Node\Expr\Variable
|
|
|
|
|
&& !$conditional->left instanceof PhpParser\Node\Expr\PropertyFetch
|
|
|
|
|
&& !$conditional->left instanceof PhpParser\Node\Expr\StaticPropertyFetch
|
|
|
|
|
&& count($left_type->getAtomicTypes()) === 1
|
|
|
|
|
&& !$left_type->hasMixed()
|
|
|
|
|
) {
|
|
|
|
|
return self::ASSIGNMENT_TO_LEFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static function hasIsACheck(
|
|
|
|
|
PhpParser\Node\Expr\FuncCall $stmt,
|
|
|
|
|
StatementsAnalyzer $source
|
|
|
|
|
): bool {
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& (strtolower($stmt->name->parts[0]) === 'is_a'
|
|
|
|
|
|| strtolower($stmt->name->parts[0]) === 'is_subclass_of')
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& isset($stmt->getArgs()[1])
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$second_arg = $stmt->getArgs()[1]->value;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Scalar\String_
|
|
|
|
|
|| (
|
|
|
|
|
$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'
|
|
|
|
|
)
|
|
|
|
|
|| (($second_arg_type = $source->node_data->getType($second_arg))
|
|
|
|
|
&& $second_arg_type->hasString())
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-16 22:59:26 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return array<string, non-empty-list<non-empty-list<string>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function handleIsTypeCheck(
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
PhpParser\Node\Expr\FuncCall $stmt,
|
|
|
|
|
?string $first_var_name,
|
|
|
|
|
?Type\Union $first_var_type,
|
|
|
|
|
PhpParser\Node\Expr\FuncCall $expr,
|
|
|
|
|
bool $negate
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& ($function_name = strtolower($stmt->name->parts[0]))
|
|
|
|
|
&& isset(self::IS_TYPE_CHECKS[$function_name])
|
2021-09-19 14:46:11 +02:00
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($source->getNamespace() === null //either the namespace is null
|
|
|
|
|
|| $stmt->name instanceof PhpParser\Node\Name\FullyQualified //or we have a FQ to base function
|
|
|
|
|
|| isset($source->getAliases()->functions[$function_name]) //or it is imported
|
|
|
|
|
|| ($codebase && !$codebase->functions->functionExists(
|
|
|
|
|
$source,
|
|
|
|
|
strtolower($source->getNamespace()."\\".$function_name)
|
|
|
|
|
)) //or this function name does not exist in current namespace
|
|
|
|
|
)
|
2021-09-16 22:59:26 +02:00
|
|
|
|
) {
|
|
|
|
|
if ($first_var_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[self::IS_TYPE_CHECKS[$function_name][0]]];
|
|
|
|
|
} elseif ($first_var_type
|
|
|
|
|
&& $codebase
|
|
|
|
|
) {
|
|
|
|
|
if (isset(self::IS_TYPE_CHECKS[$function_name][1])) {
|
|
|
|
|
$callable = self::IS_TYPE_CHECKS[$function_name][1];
|
|
|
|
|
assert(is_callable($callable));
|
|
|
|
|
$type = $callable();
|
|
|
|
|
assert($type instanceof Type\Union);
|
|
|
|
|
self::processIrreconcilableFunctionCall(
|
|
|
|
|
$first_var_type,
|
|
|
|
|
$type,
|
|
|
|
|
$expr,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
|
|
|
|
$negate
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-16 22:59:26 +02:00
|
|
|
|
return $if_types;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-16 22:59:26 +02:00
|
|
|
|
protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
|
2020-11-30 07:20:28 +01:00
|
|
|
|
{
|
2021-09-17 22:42:17 +02:00
|
|
|
|
return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_callable';
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return 0|1|2
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($stmt->name->parts[0]) === 'class_exists'
|
|
|
|
|
) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if (!isset($stmt->getArgs()[1])) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$second_arg = $stmt->getArgs()[1]->value;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($second_arg->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return 0|1|2
|
|
|
|
|
*/
|
|
|
|
|
protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
|
|
|
|
&& strtolower($stmt->name->parts[0]) === 'trait_exists'
|
|
|
|
|
) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if (!isset($stmt->getArgs()[1])) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$second_arg = $stmt->getArgs()[1]->value;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($second_arg->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static function hasInterfaceExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
|
|
|
|
|
{
|
2021-09-16 22:59:26 +02:00
|
|
|
|
return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'interface_exists';
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
|
|
|
|
|
{
|
2021-09-16 22:59:26 +02:00
|
|
|
|
return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'function_exists';
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
|
|
|
|
|
{
|
|
|
|
|
if ($stmt->name instanceof PhpParser\Node\Name
|
2021-09-17 22:42:17 +02:00
|
|
|
|
&& strtolower($stmt->name->parts[0]) === 'in_array'
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& isset($stmt->getArgs()[2])
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$second_arg = $stmt->getArgs()[2]->value;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
|
|
|
|
|
&& strtolower($second_arg->name->parts[0]) === 'true'
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
2018-12-19 22:15:19 +01:00
|
|
|
|
}
|
2018-07-11 17:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return false;
|
2018-07-11 17:22:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
|
|
|
|
|
{
|
2021-09-17 22:42:17 +02:00
|
|
|
|
return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'count';
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-02-22 16:41:57 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
protected static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
|
|
|
|
|
{
|
2021-09-17 22:42:17 +02:00
|
|
|
|
return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'array_key_exists';
|
2020-02-22 16:41:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-11 17:22:07 +02:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-10-25 15:49:39 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2018-07-11 17:22:07 +02:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getNullInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
FileSource $source,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?string $this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $null_position
|
2020-09-04 22:26:33 +02:00
|
|
|
|
): array {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = [];
|
2019-11-25 17:44:54 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Bad null variable position');
|
2018-07-11 17:22:07 +02:00
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2018-07-11 17:22:07 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\Assign) {
|
|
|
|
|
$var_name = '=' . $var_name;
|
|
|
|
|
}
|
2020-10-25 15:49:39 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['!null']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-12 23:03:41 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
|
|
|
|
) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$null_type = Type::getNull();
|
2020-08-12 23:03:41 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$null_type
|
|
|
|
|
) && !UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$null_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-defined type ' . $var_type . ' can never contain null',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' null'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2018-05-28 21:07:42 +02:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type . ' can never contain null',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' null'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2018-05-28 21:07:42 +02:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
2019-07-07 21:06:03 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getFalseInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
bool $cache,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
bool $inside_conditional,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $false_position
|
2021-09-26 23:01:35 +02:00
|
|
|
|
): array {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = [];
|
2020-10-25 15:49:39 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Bad false variable position');
|
2018-07-11 17:22:07 +02:00
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2020-10-25 15:49:39 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['!false']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
2020-08-12 23:03:41 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = [$if_types];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types = null;
|
2020-08-12 23:03:41 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$if_types = $source->node_data->getAssertions($base_conditional);
|
|
|
|
|
}
|
2020-08-12 23:03:41 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($if_types === null) {
|
|
|
|
|
$if_types = self::scrapeAssertions(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
false,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$source->node_data->setAssertions($base_conditional, $if_types);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-28 21:07:42 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
2020-07-27 20:26:27 +02:00
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2020-07-27 20:26:27 +02:00
|
|
|
|
$config = $source->getCodebase()->config;
|
2019-07-07 21:06:03 +02:00
|
|
|
|
|
2020-07-27 20:26:27 +02:00
|
|
|
|
if ($config->strict_binary_operands
|
|
|
|
|
&& $var_type->isSingle()
|
|
|
|
|
&& $var_type->hasBool()
|
|
|
|
|
&& !$var_type->from_docblock
|
|
|
|
|
) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-07-27 20:26:27 +02:00
|
|
|
|
new RedundantIdentityWithTrue(
|
|
|
|
|
'The "!== false" part of this comparison is redundant',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-07-27 20:26:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$false_type = Type::getFalse();
|
|
|
|
|
|
|
|
|
|
if (!UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$false_type
|
|
|
|
|
) && !UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$false_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-07-27 20:26:27 +02:00
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-defined type ' . $var_type . ' can never contain false',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' false'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-07-27 20:26:27 +02:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-07-27 20:26:27 +02:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type . ' can never contain false',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' false'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-10-25 15:49:39 +01: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
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2016-12-28 21:52:44 +01:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getTrueInequalityAssertions(
|
|
|
|
|
int $true_position,
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-09-07 01:36:47 +02:00
|
|
|
|
?string $this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
FileSource $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
bool $cache,
|
|
|
|
|
bool $inside_conditional
|
2020-09-12 17:24:05 +02:00
|
|
|
|
): array {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Bad null variable position');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-01-17 16:25:05 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$notif_types = self::processFunctionCall(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
true
|
2020-11-30 07:20:28 +01:00
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['!true']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
2019-06-20 14:37:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$notif_types = [];
|
|
|
|
|
} else {
|
|
|
|
|
$notif_types = null;
|
2019-01-27 20:10:33 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$notif_types = $source->node_data->getAssertions($base_conditional);
|
|
|
|
|
}
|
2019-11-25 17:44:54 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($notif_types === null) {
|
|
|
|
|
$notif_types = self::scrapeAssertions(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
false,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$source->node_data->setAssertions($base_conditional, $notif_types);
|
2019-11-25 17:44:54 +01:00
|
|
|
|
}
|
2019-01-27 20:10:33 +01:00
|
|
|
|
}
|
2019-11-25 17:44:54 +01:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (count($notif_types) === 1) {
|
|
|
|
|
$notif_types = $notif_types[0];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (count($notif_types) === 1) {
|
2021-12-03 20:11:20 +01:00
|
|
|
|
$if_types = Algebra::negateTypes($notif_types);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-08-26 23:58:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = $if_types ? [$if_types] : [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
2018-02-07 19:57:45 +01:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$true_type = Type::getTrue();
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$true_type
|
|
|
|
|
) && !UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$true_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-defined type ' . $var_type . ' can never contain true',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' true'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type . ' can never contain ' . $true_type,
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' true'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getEmptyInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $empty_array_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
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 {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Bad empty array variable position');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$if_types[$var_name] = [['non-empty-countable']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
2019-03-02 21:18:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
2019-03-02 21:18:29 +01:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
|
|
|
|
|
$empty_array_type = Type::getEmptyArray();
|
|
|
|
|
|
|
|
|
|
if (!UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$empty_array_type
|
|
|
|
|
) && !UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$empty_array_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
'Docblock-defined type ' . $var_type->getId() . ' can never contain null',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' null'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type->getId() . ' can never contain null',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' null'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-02 21:18:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2019-03-02 21:18:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2016-12-28 21:52:44 +01:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getGettypeInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
int $gettype_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
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 {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$gettype_position value');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$gettype_expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01: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
|
2020-10-03 01:15:47 +02:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Shouldn’t get here');
|
2020-10-03 01:15:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new UnevaluatedCode(
|
|
|
|
|
'gettype cannot return this value',
|
|
|
|
|
new CodeLocation($source, $whichclass_expr)
|
|
|
|
|
)
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
if ($var_name && $var_type) {
|
2021-07-14 12:49:46 +02:00
|
|
|
|
if ($var_type === 'class@anonymous') {
|
|
|
|
|
$if_types[$var_name] = [['!=object']];
|
|
|
|
|
} elseif ($var_type === 'resource (closed)') {
|
|
|
|
|
$if_types[$var_name] = [['!closed-resource']];
|
2021-09-26 22:51:44 +02:00
|
|
|
|
} elseif (strpos($var_type, 'resource (') === 0) {
|
2021-07-14 12:49:46 +02:00
|
|
|
|
$if_types[$var_name] = [['!=resource']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!' . $var_type]];
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-10-03 01:15:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2020-10-03 01:15:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-23 01:53:53 +02:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2017-10-23 01:53:53 +02:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getGetdebugTypeInequalityAssertions(
|
2019-11-25 17:44:54 +01:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
int $get_debug_type_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($get_debug_type_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$whichclass_expr = $conditional->left;
|
|
|
|
|
$get_debug_type_expr = $conditional->right;
|
|
|
|
|
} elseif ($get_debug_type_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$whichclass_expr = $conditional->right;
|
|
|
|
|
$get_debug_type_expr = $conditional->left;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$gettype_position value');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $get_debug_type_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$get_debug_type_expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2019-07-06 04:57:38 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01: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
|
|
|
|
|
) {
|
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Shouldn’t get here');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2019-07-06 04:57:38 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
if ($var_type === 'class@anonymous') {
|
|
|
|
|
$if_types[$var_name] = [['!=object']];
|
|
|
|
|
} elseif ($var_type === 'resource (closed)') {
|
|
|
|
|
$if_types[$var_name] = [['!closed-resource']];
|
2021-09-26 22:51:44 +02:00
|
|
|
|
} elseif (strpos($var_type, 'resource (') === 0) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types[$var_name] = [['!=resource']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!' . $var_type]];
|
2019-07-06 04:57:38 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
2017-10-23 01:53:53 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getGetclassInequalityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
int $getclass_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
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 {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$getclass_position value');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$getclass_expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2021-11-04 20:44:42 +01:00
|
|
|
|
} elseif ($getclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $getclass_expr->class instanceof PhpParser\Node\Expr
|
|
|
|
|
) {
|
2021-11-04 20:33:30 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$getclass_expr->class,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
$var_name = '$this';
|
|
|
|
|
}
|
2018-07-18 04:50:30 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01: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
|
|
|
|
|
) {
|
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
2019-07-06 04:57:38 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_type === 'self' || $var_type === 'static') {
|
|
|
|
|
$var_type = $this_class_name;
|
|
|
|
|
} elseif ($var_type === 'parent') {
|
|
|
|
|
$var_type = null;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$type = $source->node_data->getType($whichclass_expr);
|
2019-07-06 04:57:38 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($type && $var_name) {
|
|
|
|
|
foreach ($type->getAtomicTypes() as $type_part) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($type_part instanceof TTemplateParamClass) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types[$var_name] = [['!=' . $type_part->param_name]];
|
|
|
|
|
}
|
2019-07-06 04:57:38 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
2019-07-06 04:57:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_type
|
|
|
|
|
&& ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
new CodeLocation($source, $whichclass_expr),
|
|
|
|
|
null,
|
|
|
|
|
null,
|
2021-04-30 21:01:33 +02:00
|
|
|
|
$source->getSuppressedIssues()
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) === false
|
|
|
|
|
) {
|
|
|
|
|
// fall through
|
|
|
|
|
} else {
|
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$if_types[$var_name] = [['!=getclass-' . $var_type]];
|
|
|
|
|
}
|
2017-10-23 01:53:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2017-10-23 01:53:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-19 22:15:19 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2018-12-19 22:15:19 +01:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getTypedValueInequalityAssertions(
|
2019-11-26 22:34:13 +01:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
?string $this_class_name,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $typed_value_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$other_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
$var_type = $source->node_data->getType($conditional->right);
|
|
|
|
|
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2019-11-26 22:34:13 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
$other_type = $source->node_data->getType($conditional->right);
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$typed_value_position value');
|
2018-12-19 22:15:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_type) {
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
$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())
|
|
|
|
|
)
|
|
|
|
|
);
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($not_identical) {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $var_type->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
$assertion = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($assertion) {
|
|
|
|
|
$if_types[$var_name] = [['!=' . $assertion]];
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $var_type->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
$assertion = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($assertion) {
|
|
|
|
|
$if_types[$var_name] = [['!~' . $assertion]];
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $other_type
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
|
|
|
|
) {
|
2021-07-13 20:54:47 +02:00
|
|
|
|
self::handleParadoxicalAssertions(
|
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
$this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$other_type,
|
2021-07-13 20:54:47 +02:00
|
|
|
|
$codebase,
|
|
|
|
|
$conditional
|
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2020-08-30 17:32:01 +02:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getNullEqualityAssertions(
|
2020-08-30 17:32:01 +02:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $null_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$null_position value');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name && $base_conditional instanceof PhpParser\Node\Expr\Assign) {
|
|
|
|
|
$var_name = '=' . $var_name;
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['null']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
2020-08-30 17:32:01 +02:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$null_type = Type::getNull();
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$var_type,
|
|
|
|
|
$null_type
|
|
|
|
|
) && !UnionTypeComparator::isContainedBy(
|
|
|
|
|
$codebase,
|
|
|
|
|
$null_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' does not contain null',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type . ' null'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new TypeDoesNotContainNull(
|
|
|
|
|
$var_type . ' does not contain null',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId()
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2020-08-30 17:32:01 +02:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getTrueEqualityAssertions(
|
2020-08-30 17:32:01 +02:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
bool $cache,
|
|
|
|
|
int $true_position
|
2021-09-26 23:01:35 +02:00
|
|
|
|
): array {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = [];
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Unrecognised position');
|
2020-08-30 17:32:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$if_types = self::processFunctionCall(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
false
|
2020-11-30 07:20:28 +01:00
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['true']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!falsy']];
|
|
|
|
|
}
|
2020-08-30 17:32:01 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = [$if_types];
|
|
|
|
|
} else {
|
|
|
|
|
$base_assertions = null;
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$base_assertions = $source->node_data->getAssertions($base_conditional);
|
|
|
|
|
}
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($base_assertions === null) {
|
|
|
|
|
$base_assertions = self::scrapeAssertions(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
false,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$cache
|
|
|
|
|
);
|
2020-07-27 00:29:17 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$source->node_data->setAssertions($base_conditional, $base_assertions);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-27 00:29:17 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = $base_assertions;
|
|
|
|
|
}
|
2020-07-27 00:29:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
2020-07-27 00:29:17 +02:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$config = $source->getCodebase()->config;
|
2020-07-27 00:29:17 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($config->strict_binary_operands
|
|
|
|
|
&& $var_type->isSingle()
|
|
|
|
|
&& $var_type->hasBool()
|
|
|
|
|
&& !$var_type->from_docblock
|
|
|
|
|
) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new RedundantIdentityWithTrue(
|
|
|
|
|
'The "=== true" part of this comparison is redundant',
|
|
|
|
|
new CodeLocation($source, $conditional)
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$true_type = Type::getTrue();
|
|
|
|
|
|
|
|
|
|
if (!UnionTypeComparator::canExpressionTypesBeIdentical(
|
|
|
|
|
$codebase,
|
|
|
|
|
$true_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' does not contain true',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type . ' true'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new TypeDoesNotContainType(
|
|
|
|
|
$var_type . ' does not contain true',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type . ' true'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-27 00:29:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types;
|
2020-07-27 00:29:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2019-02-21 23:17:10 +01:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getFalseEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
bool $cache,
|
|
|
|
|
bool $inside_conditional,
|
|
|
|
|
int $false_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$base_conditional = $conditional->left;
|
|
|
|
|
} elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$base_conditional = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$false_position value');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
|
|
|
|
|
$notif_types = self::processFunctionCall(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
true
|
2020-11-30 07:20:28 +01:00
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['false']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notif_types = [];
|
|
|
|
|
} else {
|
|
|
|
|
$notif_types = null;
|
|
|
|
|
|
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$notif_types = $source->node_data->getAssertions($base_conditional);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($notif_types === null) {
|
|
|
|
|
$notif_types = self::scrapeAssertions(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source,
|
|
|
|
|
$codebase,
|
2021-12-04 16:40:52 +01:00
|
|
|
|
false,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$cache,
|
|
|
|
|
$inside_conditional
|
|
|
|
|
);
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($source instanceof StatementsAnalyzer && $cache) {
|
|
|
|
|
$source->node_data->setAssertions($base_conditional, $notif_types);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-21 23:17:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (count($notif_types) === 1) {
|
|
|
|
|
$notif_types = $notif_types[0];
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (count($notif_types) === 1) {
|
2021-12-03 20:11:20 +01:00
|
|
|
|
$if_types = Algebra::negateTypes($notif_types);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types = $if_types ? [$if_types] : [];
|
2019-02-21 23:17:10 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
|
|
|
|
) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$false_type = Type::getFalse();
|
|
|
|
|
|
|
|
|
|
if (!UnionTypeComparator::canExpressionTypesBeIdentical(
|
|
|
|
|
$codebase,
|
|
|
|
|
$false_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' does not contain false',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type . ' false'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new TypeDoesNotContainType(
|
|
|
|
|
$var_type . ' does not contain false',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type . ' false'
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-19 22:15:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types;
|
2018-12-19 22:15:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-28 21:52:44 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2016-12-28 21:52:44 +01:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getEmptyArrayEqualityAssertions(
|
2019-11-25 17:44:54 +01:00
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $empty_array_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
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 {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$empty_array_position value');
|
2019-11-25 17:44:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$base_conditional,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
|
|
|
|
|
$if_types[$var_name] = [['!non-empty-countable']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['falsy']];
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
&& ($var_type = $source->node_data->getType($base_conditional))
|
|
|
|
|
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
2018-01-09 21:05:48 +01:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$empty_array_type = Type::getEmptyArray();
|
|
|
|
|
|
|
|
|
|
if (!UnionTypeComparator::canExpressionTypesBeIdentical(
|
|
|
|
|
$codebase,
|
|
|
|
|
$empty_array_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type . ' does not contain an empty array',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
null
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new TypeDoesNotContainType(
|
|
|
|
|
$var_type . ' does not contain empty array',
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
null
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getGettypeEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
int $gettype_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
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 {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$gettype_position value');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$gettype_expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2020-09-11 04:44:35 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Scalar\String_ $string_expr */
|
|
|
|
|
$var_type = $string_expr->value;
|
2018-01-23 21:46:14 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2020-11-30 07:20:28 +01:00
|
|
|
|
new UnevaluatedCode(
|
|
|
|
|
'gettype cannot return this value',
|
|
|
|
|
new CodeLocation($source, $string_expr)
|
2018-01-23 21:46:14 +01:00
|
|
|
|
)
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
if ($var_name && $var_type) {
|
2021-07-14 12:49:46 +02:00
|
|
|
|
if ($var_type === 'class@anonymous') {
|
|
|
|
|
$if_types[$var_name] = [['=object']];
|
|
|
|
|
} elseif ($var_type === 'resource (closed)') {
|
|
|
|
|
$if_types[$var_name] = [['closed-resource']];
|
2021-09-26 22:51:44 +02:00
|
|
|
|
} elseif (strpos($var_type, 'resource (') === 0) {
|
2021-07-14 12:49:46 +02:00
|
|
|
|
$if_types[$var_name] = [['=resource']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [[$var_type]];
|
|
|
|
|
}
|
2018-01-23 21:46:14 +01:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getGetdebugtypeEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
int $get_debug_type_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
if ($get_debug_type_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$whichclass_expr = $conditional->left;
|
|
|
|
|
$get_debug_type_expr = $conditional->right;
|
|
|
|
|
} elseif ($get_debug_type_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$whichclass_expr = $conditional->right;
|
|
|
|
|
$get_debug_type_expr = $conditional->left;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$gettype_position value');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $get_debug_type_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$get_debug_type_expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01: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
|
|
|
|
|
) {
|
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('Shouldn’t get here');
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
if ($var_type === 'class@anonymous') {
|
|
|
|
|
$if_types[$var_name] = [['=object']];
|
|
|
|
|
} elseif ($var_type === 'resource (closed)') {
|
|
|
|
|
$if_types[$var_name] = [['closed-resource']];
|
2021-09-26 22:51:44 +02:00
|
|
|
|
} elseif (strpos($var_type, 'resource (') === 0) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types[$var_name] = [['=resource']];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [[$var_type]];
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getGetclassEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
int $getclass_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
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 {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$getclass_position value');
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall && isset($getclass_expr->getArgs()[0])) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$getclass_expr->getArgs()[0]->value,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2021-11-04 20:44:42 +01:00
|
|
|
|
} elseif ($getclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $getclass_expr->class instanceof PhpParser\Node\Expr
|
|
|
|
|
) {
|
2021-11-04 20:33:30 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$getclass_expr->class,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
$var_name = '$this';
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $whichclass_expr->class instanceof PhpParser\Node\Name
|
|
|
|
|
) {
|
|
|
|
|
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$whichclass_expr->class,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
);
|
2018-07-09 14:31:43 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_type === 'self' || $var_type === 'static') {
|
|
|
|
|
$var_type = $this_class_name;
|
|
|
|
|
} elseif ($var_type === 'parent') {
|
|
|
|
|
$var_type = null;
|
|
|
|
|
}
|
2018-07-09 14:31:43 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_type) {
|
|
|
|
|
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
new CodeLocation($source, $whichclass_expr),
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
$source->getSuppressedIssues(),
|
2021-04-30 21:01:33 +02:00
|
|
|
|
new ClassLikeNameOptions(true)
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) === false
|
|
|
|
|
) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-12 18:03:55 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$if_types[$var_name] = [['=getclass-' . $var_type]];
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$type = $source->node_data->getType($whichclass_expr);
|
|
|
|
|
|
|
|
|
|
if ($type && $var_name) {
|
|
|
|
|
foreach ($type->getAtomicTypes() as $type_part) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($type_part instanceof TTemplateParamClass) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types[$var_name] = [['=' . $type_part->param_name]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-07 21:49:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2019-06-07 21:49:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-12 18:03:55 +01:00
|
|
|
|
/**
|
2021-07-13 22:35:57 +02:00
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
|
2020-11-30 07:20:28 +01:00
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
2018-11-12 18:03:55 +01:00
|
|
|
|
*/
|
2020-11-30 07:20:28 +01:00
|
|
|
|
private static function getTypedValueEqualityAssertions(
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
int $typed_value_position
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
2019-05-26 19:16:44 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2019-05-26 19:16:44 +02:00
|
|
|
|
|
2021-09-11 15:51:18 +02:00
|
|
|
|
$other_var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$other_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
$var_type = $source->node_data->getType($conditional->right);
|
|
|
|
|
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2019-05-28 19:16:09 +02:00
|
|
|
|
|
2021-09-11 15:51:18 +02:00
|
|
|
|
$other_var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$var_type = $source->node_data->getType($conditional->left);
|
|
|
|
|
$other_type = $source->node_data->getType($conditional->right);
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$typed_value_position value');
|
2019-05-21 02:57:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($var_name && $var_type) {
|
|
|
|
|
$identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| ($other_type
|
|
|
|
|
&& (($var_type->isString(true) && $other_type->isString(true))
|
|
|
|
|
|| ($var_type->isInt(true) && $other_type->isInt(true))
|
|
|
|
|
|| ($var_type->isFloat() && $other_type->isFloat())
|
|
|
|
|
)
|
|
|
|
|
);
|
2019-05-21 02:57:59 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($identical) {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $var_type->getAssertionString(true);
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
$assertion = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($assertion) {
|
|
|
|
|
$if_types[$var_name] = [['=' . $assertion]];
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $var_type->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
$assertion = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($assertion) {
|
|
|
|
|
$if_types[$var_name] = [['~' . $assertion]];
|
|
|
|
|
}
|
2019-05-31 15:43:46 +02:00
|
|
|
|
}
|
2021-09-11 15:51:18 +02:00
|
|
|
|
|
2021-10-11 14:38:25 +02:00
|
|
|
|
|
2021-10-26 19:12:13 +02:00
|
|
|
|
if ($other_var_name && $other_type && !$other_type->isMixed()) {
|
2021-09-11 15:51:18 +02:00
|
|
|
|
if ($identical) {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $other_type->getAssertionString(true);
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
$assertion = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($assertion) {
|
|
|
|
|
$if_types[$other_var_name] = [['=' . $assertion]];
|
|
|
|
|
}
|
2021-09-11 15:51:18 +02:00
|
|
|
|
} else {
|
2021-10-11 14:38:25 +02:00
|
|
|
|
try {
|
|
|
|
|
$assertion = $other_type->getAssertionString();
|
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
|
$assertion = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($assertion) {
|
|
|
|
|
$if_types[$other_var_name] = [['~' . $assertion]];
|
|
|
|
|
}
|
2021-09-11 15:51:18 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2019-05-31 15:43:46 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($codebase
|
|
|
|
|
&& $other_type
|
|
|
|
|
&& $var_type
|
|
|
|
|
&& ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
|
|| ($other_type->isString()
|
|
|
|
|
&& $var_type->isString())
|
|
|
|
|
)
|
|
|
|
|
) {
|
2021-07-13 20:54:47 +02:00
|
|
|
|
self::handleParadoxicalAssertions(
|
|
|
|
|
$source,
|
|
|
|
|
$var_type,
|
|
|
|
|
$this_class_name,
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$other_type,
|
2021-07-13 20:54:47 +02:00
|
|
|
|
$codebase,
|
|
|
|
|
$conditional
|
|
|
|
|
);
|
2019-05-31 15:43:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2019-05-31 15:43:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $expr
|
|
|
|
|
* @param StatementsAnalyzer $source
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getIsaAssertions(
|
|
|
|
|
PhpParser\Node\Expr\FuncCall $expr,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
?string $first_var_name
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if ($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $expr->getArgs()[0]->value->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& strtolower($expr->getArgs()[0]->value->name->name) === 'class'
|
|
|
|
|
&& $expr->getArgs()[0]->value->class instanceof PhpParser\Node\Name
|
|
|
|
|
&& count($expr->getArgs()[0]->value->class->parts) === 1
|
|
|
|
|
&& strtolower($expr->getArgs()[0]->value->class->parts[0]) === 'static'
|
2019-05-19 21:56:04 +02:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$first_var_name = '$this';
|
2018-11-12 18:03:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($first_var_name) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$first_arg = $expr->getArgs()[0]->value;
|
|
|
|
|
$second_arg = $expr->getArgs()[1]->value;
|
|
|
|
|
$third_arg = $expr->getArgs()[2]->value ?? null;
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
if ($third_arg instanceof PhpParser\Node\Expr\ConstFetch) {
|
|
|
|
|
if (!in_array(strtolower($third_arg->name->parts[0]), ['true', 'false'])) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2019-04-10 00:09:57 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +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';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$is_a_prefix = $third_arg_value === 'true' ? 'isa-string-' : 'isa-';
|
|
|
|
|
|
2020-11-30 07:32:38 +01:00
|
|
|
|
if (($first_arg_type = $source->node_data->getType($first_arg))
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& $first_arg_type->isSingleStringLiteral()
|
2021-12-03 20:11:20 +01:00
|
|
|
|
&& $source->getSource()->getSource() instanceof TraitAnalyzer
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& $first_arg_type->getSingleStringLiteral()->value === $this_class_name
|
|
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
} else {
|
|
|
|
|
if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
|
$fq_class_name = $second_arg->value;
|
|
|
|
|
if ($fq_class_name[0] === '\\') {
|
|
|
|
|
$fq_class_name = substr($fq_class_name, 1);
|
|
|
|
|
}
|
2019-04-10 00:09:57 +02:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$if_types[$first_var_name] = [[$is_a_prefix . $fq_class_name]];
|
|
|
|
|
} 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'
|
|
|
|
|
) {
|
|
|
|
|
$class_node = $second_arg->class;
|
|
|
|
|
|
|
|
|
|
if ($class_node->parts === ['static']) {
|
|
|
|
|
if ($this_class_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$is_a_prefix . $this_class_name . '&static']];
|
|
|
|
|
}
|
|
|
|
|
} elseif ($class_node->parts === ['self']) {
|
|
|
|
|
if ($this_class_name) {
|
|
|
|
|
$if_types[$first_var_name] = [[$is_a_prefix . $this_class_name]];
|
|
|
|
|
}
|
|
|
|
|
} elseif ($class_node->parts === ['parent']) {
|
|
|
|
|
// do nothing
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$first_var_name] = [[
|
|
|
|
|
$is_a_prefix
|
|
|
|
|
. ClassLikeAnalyzer::getFQCLNFromNameObject(
|
|
|
|
|
$class_node,
|
|
|
|
|
$source->getAliases()
|
|
|
|
|
)
|
|
|
|
|
]];
|
|
|
|
|
}
|
|
|
|
|
} elseif (($second_arg_type = $source->node_data->getType($second_arg))
|
|
|
|
|
&& $second_arg_type->hasString()
|
|
|
|
|
) {
|
|
|
|
|
$vals = [];
|
|
|
|
|
|
|
|
|
|
foreach ($second_arg_type->getAtomicTypes() as $second_arg_atomic_type) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($second_arg_atomic_type instanceof TTemplateParamClass) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$vals[] = [$is_a_prefix . $second_arg_atomic_type->param_name];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($vals) {
|
|
|
|
|
$if_types[$first_var_name] = $vals;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $expr
|
|
|
|
|
* @param StatementsAnalyzer $source
|
|
|
|
|
* @param string|null $first_var_name
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getInarrayAssertions(
|
|
|
|
|
PhpParser\Node\Expr\FuncCall $expr,
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
?string $first_var_name
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
if ($first_var_name
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& ($second_arg_type = $source->node_data->getType($expr->getArgs()[1]->value))
|
|
|
|
|
&& isset($expr->getArgs()[0]->value)
|
|
|
|
|
&& !$expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
|
2016-12-28 21:52:44 +01:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
foreach ($second_arg_type->getAtomicTypes() as $atomic_type) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($atomic_type instanceof TArray
|
|
|
|
|
|| $atomic_type instanceof TKeyedArray
|
|
|
|
|
|| $atomic_type instanceof TList
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2021-08-03 13:03:02 +02:00
|
|
|
|
$is_sealed = false;
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($atomic_type instanceof TList) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$value_type = $atomic_type->type_param;
|
2021-12-13 04:45:57 +01:00
|
|
|
|
} elseif ($atomic_type instanceof TKeyedArray) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$value_type = $atomic_type->getGenericValueType();
|
2021-08-03 13:03:02 +02:00
|
|
|
|
$is_sealed = $atomic_type->sealed;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
$value_type = $atomic_type->type_params[1];
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-08-03 13:03:02 +02:00
|
|
|
|
$assertions = [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-08-05 15:43:39 +02:00
|
|
|
|
if (!$is_sealed) {
|
2021-09-06 22:05:56 +02:00
|
|
|
|
// `in-array-*` has special handling in the detection of paradoxical
|
|
|
|
|
// conditions and the fact the negation doesn't imply anything.
|
|
|
|
|
//
|
|
|
|
|
// In the vast majority of cases, the negation of `in-array-*`
|
|
|
|
|
// (`Algebra::negateType`) doesn't imply anything because:
|
|
|
|
|
// - The array can be empty, or
|
|
|
|
|
// - The array may have one of the types but not the others.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: the negation of the negation is the original assertion.
|
2021-09-04 18:28:34 +02:00
|
|
|
|
if ($value_type->getId() !== '' && !$value_type->isMixed()) {
|
2021-08-12 23:39:24 +02:00
|
|
|
|
$assertions[] = 'in-array-' . $value_type->getId();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2021-08-05 15:43:39 +02:00
|
|
|
|
} else {
|
|
|
|
|
foreach ($value_type->getAtomicTypes() as $atomic_value_type) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($atomic_value_type instanceof TLiteralInt
|
|
|
|
|
|| $atomic_value_type instanceof TLiteralString
|
|
|
|
|
|| $atomic_value_type instanceof TLiteralFloat
|
|
|
|
|
|| $atomic_value_type instanceof TEnumCase
|
2021-08-05 15:43:39 +02:00
|
|
|
|
) {
|
2021-08-12 23:39:24 +02:00
|
|
|
|
$assertions[] = '=' . $atomic_value_type->getAssertionString();
|
2021-12-13 04:45:57 +01:00
|
|
|
|
} elseif ($atomic_value_type instanceof TFalse
|
|
|
|
|
|| $atomic_value_type instanceof TTrue
|
|
|
|
|
|| $atomic_value_type instanceof TNull
|
2021-08-05 15:43:39 +02:00
|
|
|
|
) {
|
2021-08-12 23:39:24 +02:00
|
|
|
|
$assertions[] = $atomic_value_type->getAssertionString();
|
2021-12-13 04:45:57 +01:00
|
|
|
|
} elseif (!$atomic_value_type instanceof TMixed) {
|
2021-09-07 03:42:56 +02:00
|
|
|
|
// mixed doesn't tell us anything and can be omitted.
|
|
|
|
|
//
|
|
|
|
|
// For the meaning of in-array, see the above comment.
|
|
|
|
|
$assertions[] = 'in-array-' . $value_type->getId();
|
2021-08-05 15:43:39 +02:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
|
2021-08-05 15:43:39 +02:00
|
|
|
|
if ($assertions !== []) {
|
|
|
|
|
$if_types[$first_var_name] = [$assertions];
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\FuncCall $expr
|
|
|
|
|
* @param Type\Union|null $first_var_type
|
|
|
|
|
* @param string|null $first_var_name
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getArrayKeyExistsAssertions(
|
|
|
|
|
PhpParser\Node\Expr\FuncCall $expr,
|
|
|
|
|
?Type\Union $first_var_type,
|
|
|
|
|
?string $first_var_name,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?string $this_class_name
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
2020-12-01 04:10:17 +01:00
|
|
|
|
$literal_assertions = [];
|
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if (isset($expr->getArgs()[0])
|
|
|
|
|
&& isset($expr->getArgs()[1])
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& $first_var_type
|
|
|
|
|
&& $first_var_name
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& !$expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
|
2020-11-30 07:20:28 +01:00
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& ($second_var_type = $source->node_data->getType($expr->getArgs()[1]->value))
|
2018-06-08 19:53:42 +02:00
|
|
|
|
) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
foreach ($second_var_type->getAtomicTypes() as $atomic_type) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($atomic_type instanceof TArray
|
|
|
|
|
|| $atomic_type instanceof TKeyedArray
|
2020-11-30 07:20:28 +01:00
|
|
|
|
) {
|
2021-12-13 04:45:57 +01:00
|
|
|
|
if ($atomic_type instanceof TKeyedArray) {
|
2020-12-02 20:49:30 +01:00
|
|
|
|
$key_possibly_undefined = false;
|
|
|
|
|
|
|
|
|
|
foreach ($atomic_type->properties as $property_type) {
|
|
|
|
|
if ($property_type->possibly_undefined) {
|
|
|
|
|
$key_possibly_undefined = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
$key_type = $atomic_type->getGenericKeyType();
|
2020-12-02 20:49:30 +01:00
|
|
|
|
|
|
|
|
|
if ($key_possibly_undefined) {
|
|
|
|
|
$key_type->possibly_undefined = true;
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
$key_type = $atomic_type->type_params[0];
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 20:49:30 +01:00
|
|
|
|
if ($key_type->allStringLiterals() && !$key_type->possibly_undefined) {
|
2020-11-30 07:20:28 +01:00
|
|
|
|
foreach ($key_type->getLiteralStrings() as $array_literal_type) {
|
2021-01-26 05:41:42 +01:00
|
|
|
|
$literal_assertions[] = '=' . $array_literal_type->getAssertionString();
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2020-12-02 23:13:45 +01:00
|
|
|
|
} elseif ($key_type->allIntLiterals() && !$key_type->possibly_undefined) {
|
|
|
|
|
foreach ($key_type->getLiteralInts() as $array_literal_type) {
|
2021-02-12 23:00:38 +01:00
|
|
|
|
$literal_assertions[] = '~' . $array_literal_type->getAssertionString();
|
2020-12-02 23:13:45 +01:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-08 19:53:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-01 06:48:09 +01:00
|
|
|
|
if ($literal_assertions && $first_var_name) {
|
2020-12-01 04:10:17 +01:00
|
|
|
|
$if_types[$first_var_name] = [$literal_assertions];
|
|
|
|
|
} else {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$array_root = isset($expr->getArgs()[1]->value)
|
2020-12-01 04:10:17 +01:00
|
|
|
|
? ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$expr->getArgs()[1]->value,
|
2020-12-01 04:10:17 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
)
|
|
|
|
|
: null;
|
2018-06-08 19:53:42 +02:00
|
|
|
|
|
2020-12-01 04:10:17 +01:00
|
|
|
|
if ($array_root) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if ($first_var_name === null && isset($expr->getArgs()[0])) {
|
|
|
|
|
$first_arg = $expr->getArgs()[0];
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2020-12-01 04:10:17 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2018-12-19 22:15:19 +01:00
|
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
|
if ($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
|
|
|
|
|
&& $expr->getArgs()[0]->value->name instanceof PhpParser\Node\Identifier
|
|
|
|
|
&& $expr->getArgs()[0]->value->name->name !== 'class'
|
2020-12-01 04:10:17 +01:00
|
|
|
|
) {
|
|
|
|
|
$const_type = null;
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
2020-12-01 04:10:17 +01:00
|
|
|
|
if ($source instanceof StatementsAnalyzer) {
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$const_type = $source->node_data->getType($expr->getArgs()[0]->value);
|
2020-12-01 04:10:17 +01:00
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
|
2020-12-01 04:10:17 +01:00
|
|
|
|
if ($const_type) {
|
|
|
|
|
if ($const_type->isSingleStringLiteral()) {
|
|
|
|
|
$first_var_name = $const_type->getSingleStringLiteral()->value;
|
|
|
|
|
} elseif ($const_type->isSingleIntLiteral()) {
|
|
|
|
|
$first_var_name = (string)$const_type->getSingleIntLiteral()->value;
|
|
|
|
|
} else {
|
|
|
|
|
$first_var_name = null;
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
} else {
|
|
|
|
|
$first_var_name = null;
|
|
|
|
|
}
|
2021-10-09 23:37:04 +02:00
|
|
|
|
} elseif ($expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\Variable
|
2021-04-09 17:29:45 +02:00
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
2021-10-09 23:37:04 +02:00
|
|
|
|
&& ($first_var_type = $source->node_data->getType($expr->getArgs()[0]->value))
|
2021-04-09 17:29:45 +02:00
|
|
|
|
) {
|
|
|
|
|
foreach ($first_var_type->getLiteralStrings() as $array_literal_type) {
|
|
|
|
|
$if_types[$array_root . "['" . $array_literal_type->value . "']"] = [['array-key-exists']];
|
|
|
|
|
}
|
|
|
|
|
foreach ($first_var_type->getLiteralInts() as $array_literal_type) {
|
|
|
|
|
$if_types[$array_root . "[" . $array_literal_type->value . "]"] = [['array-key-exists']];
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-01 04:10:17 +01:00
|
|
|
|
if ($first_var_name !== null
|
|
|
|
|
&& !strpos($first_var_name, '->')
|
|
|
|
|
&& !strpos($first_var_name, '[')
|
|
|
|
|
) {
|
|
|
|
|
$if_types[$array_root . '[' . $first_var_name . ']'] = [['array-key-exists']];
|
|
|
|
|
}
|
2020-11-30 07:20:28 +01:00
|
|
|
|
}
|
2018-03-18 00:03:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 07:20:28 +01:00
|
|
|
|
return $if_types ? [$if_types] : [];
|
2018-03-18 00:03:46 +01:00
|
|
|
|
}
|
2021-03-18 20:09:03 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Greater|PhpParser\Node\Expr\BinaryOp\GreaterOrEqual $conditional
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getGreaterAssertions(
|
|
|
|
|
PhpParser\Node\Expr $conditional,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?string $this_class_name
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
$min_count = null;
|
|
|
|
|
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
|
|
|
|
|
$max_count = null;
|
|
|
|
|
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$isset_assert = false;
|
2021-08-15 10:24:40 +02:00
|
|
|
|
$superior_value_comparison = null;
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$superior_value_position = self::hasSuperiorNumberCheck(
|
2021-09-12 11:33:23 +02:00
|
|
|
|
$source,
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$conditional,
|
|
|
|
|
$superior_value_comparison,
|
|
|
|
|
$isset_assert
|
|
|
|
|
);
|
2021-03-18 20:09:03 +01:00
|
|
|
|
|
|
|
|
|
if ($count_equality_position) {
|
|
|
|
|
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$counted_expr = $conditional->left;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$count_equality_position value');
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $counted_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$counted_expr->getArgs()[0]->value,
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if (self::hasReconcilableNonEmptyCountEqualityCheck($conditional)) {
|
|
|
|
|
$if_types[$var_name] = [['non-empty-countable']];
|
|
|
|
|
} else {
|
|
|
|
|
if ($min_count) {
|
|
|
|
|
$if_types[$var_name] = [['=has-at-least-' . $min_count]];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['=non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($count_inequality_position) {
|
|
|
|
|
if ($count_inequality_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$count_expr = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$count_inequality_position value');
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$count_expr->getArgs()[0]->value,
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($max_count) {
|
|
|
|
|
$if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-15 10:24:40 +02:00
|
|
|
|
if ($superior_value_position) {
|
|
|
|
|
if ($superior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-15 10:24:40 +02:00
|
|
|
|
if ($var_name !== null) {
|
2021-08-15 13:44:47 +02:00
|
|
|
|
if ($superior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
if ($superior_value_comparison === 0) {
|
2021-09-05 19:58:39 +02:00
|
|
|
|
$if_types[$var_name] = [['=positive-numeric', '=int(0)']];
|
2021-08-15 13:44:47 +02:00
|
|
|
|
} elseif ($superior_value_comparison === 1) {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$if_types[$var_name] = [['positive-numeric']];
|
2021-08-15 13:44:47 +02:00
|
|
|
|
} else {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$if_types[$var_name] = [['>' . $superior_value_comparison]];
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$if_types[$var_name] = [['<' . $superior_value_comparison]];
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 22:28:05 +02:00
|
|
|
|
if ($isset_assert) {
|
|
|
|
|
$if_types[$var_name][] = ['=isset'];
|
2021-05-23 20:24:15 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-18 20:09:03 +01:00
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param PhpParser\Node\Expr\BinaryOp\Smaller|PhpParser\Node\Expr\BinaryOp\SmallerOrEqual $conditional
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getSmallerAssertions(
|
|
|
|
|
PhpParser\Node\Expr $conditional,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?string $this_class_name
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
$min_count = null;
|
|
|
|
|
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
|
|
|
|
|
$max_count = null;
|
|
|
|
|
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$isset_assert = false;
|
2021-08-15 10:24:40 +02:00
|
|
|
|
$inferior_value_comparison = null;
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$inferior_value_position = self::hasInferiorNumberCheck(
|
2021-09-12 11:33:23 +02:00
|
|
|
|
$source,
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$conditional,
|
|
|
|
|
$inferior_value_comparison,
|
|
|
|
|
$isset_assert
|
|
|
|
|
);
|
2021-03-18 20:09:03 +01:00
|
|
|
|
|
|
|
|
|
if ($count_equality_position) {
|
|
|
|
|
if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
|
|
|
|
|
$count_expr = $conditional->right;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$count_equality_position value');
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$count_expr->getArgs()[0]->value,
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($min_count) {
|
|
|
|
|
$if_types[$var_name] = [['=has-at-least-' . $min_count]];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['=non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($count_inequality_position) {
|
|
|
|
|
if ($count_inequality_position === self::ASSIGNMENT_TO_RIGHT) {
|
|
|
|
|
$count_expr = $conditional->left;
|
|
|
|
|
} else {
|
2021-12-03 21:40:18 +01:00
|
|
|
|
throw new UnexpectedValueException('$count_inequality_position value');
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
2021-10-09 23:37:04 +02:00
|
|
|
|
$count_expr->getArgs()[0]->value,
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
if ($max_count) {
|
|
|
|
|
$if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]];
|
|
|
|
|
} else {
|
|
|
|
|
$if_types[$var_name] = [['!non-empty-countable']];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-15 10:24:40 +02:00
|
|
|
|
if ($inferior_value_position) {
|
|
|
|
|
if ($inferior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->left,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
2021-08-15 10:24:40 +02:00
|
|
|
|
} else {
|
2021-03-18 20:09:03 +01:00
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->right,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-08-15 13:44:47 +02:00
|
|
|
|
if ($var_name !== null) {
|
|
|
|
|
if ($inferior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$if_types[$var_name] = [['<' . $inferior_value_comparison]];
|
2021-08-15 13:44:47 +02:00
|
|
|
|
} else {
|
|
|
|
|
if ($inferior_value_comparison === 0) {
|
2021-09-05 19:58:39 +02:00
|
|
|
|
$if_types[$var_name] = [['=positive-numeric', '=int(0)']];
|
2021-08-15 13:44:47 +02:00
|
|
|
|
} elseif ($inferior_value_comparison === 1) {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$if_types[$var_name] = [['positive-numeric']];
|
2021-08-15 13:44:47 +02:00
|
|
|
|
} else {
|
2021-09-01 22:28:05 +02:00
|
|
|
|
$if_types[$var_name] = [['>' . $inferior_value_comparison]];
|
2021-08-15 13:44:47 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-01 22:28:05 +02:00
|
|
|
|
|
|
|
|
|
if ($isset_assert) {
|
|
|
|
|
$if_types[$var_name][] = ['=isset'];
|
|
|
|
|
}
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
|
|
|
|
|
*/
|
|
|
|
|
private static function getInstanceofAssertions(
|
|
|
|
|
PhpParser\Node\Expr\Instanceof_ $conditional,
|
|
|
|
|
?Codebase $codebase,
|
|
|
|
|
FileSource $source,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
bool $inside_negation
|
|
|
|
|
): array {
|
|
|
|
|
$if_types = [];
|
|
|
|
|
|
|
|
|
|
$instanceof_types = self::getInstanceOfTypes($conditional, $this_class_name, $source);
|
|
|
|
|
|
|
|
|
|
if ($instanceof_types) {
|
|
|
|
|
$var_name = ExpressionIdentifier::getArrayVarId(
|
|
|
|
|
$conditional->expr,
|
|
|
|
|
$this_class_name,
|
|
|
|
|
$source
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($var_name) {
|
|
|
|
|
$if_types[$var_name] = [$instanceof_types];
|
|
|
|
|
|
|
|
|
|
$var_type = $source instanceof StatementsAnalyzer
|
|
|
|
|
? $source->node_data->getType($conditional->expr)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
foreach ($instanceof_types as $instanceof_type) {
|
|
|
|
|
if ($instanceof_type[0] === '=') {
|
|
|
|
|
$instanceof_type = substr($instanceof_type, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($codebase
|
|
|
|
|
&& $var_type
|
|
|
|
|
&& $inside_negation
|
|
|
|
|
&& $source instanceof StatementsAnalyzer
|
|
|
|
|
) {
|
|
|
|
|
if ($codebase->interfaceExists($instanceof_type)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$instanceof_type = Type::parseString(
|
|
|
|
|
$instanceof_type,
|
|
|
|
|
null,
|
|
|
|
|
$source->getTemplateTypeMap() ?: []
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!UnionTypeComparator::canExpressionTypesBeIdentical(
|
|
|
|
|
$codebase,
|
|
|
|
|
$instanceof_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-03-18 20:09:03 +01:00
|
|
|
|
new RedundantConditionGivenDocblockType(
|
|
|
|
|
$var_type->getId() . ' does not contain '
|
|
|
|
|
. $instanceof_type->getId(),
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' ' . $instanceof_type->getId()
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2021-03-18 20:09:03 +01:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-03-18 20:09:03 +01:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type->getId() . ' cannot be identical to '
|
|
|
|
|
. $instanceof_type->getId(),
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' ' . $instanceof_type->getId()
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2021-03-18 20:09:03 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $if_types ? [$if_types] : [];
|
|
|
|
|
}
|
2021-07-13 20:54:47 +02:00
|
|
|
|
|
2021-07-13 22:35:57 +02:00
|
|
|
|
/**
|
|
|
|
|
* @param NotIdentical|NotEqual|Identical|Equal $conditional
|
|
|
|
|
*/
|
2021-07-13 20:54:47 +02:00
|
|
|
|
private static function handleParadoxicalAssertions(
|
|
|
|
|
StatementsAnalyzer $source,
|
|
|
|
|
Type\Union $var_type,
|
|
|
|
|
?string $this_class_name,
|
|
|
|
|
Type\Union $other_type,
|
|
|
|
|
Codebase $codebase,
|
|
|
|
|
PhpParser\Node\Expr\BinaryOp $conditional
|
|
|
|
|
): void {
|
|
|
|
|
$parent_source = $source->getSource();
|
|
|
|
|
|
2021-12-03 20:11:20 +01:00
|
|
|
|
if ($parent_source->getSource() instanceof TraitAnalyzer
|
2021-07-13 20:54:47 +02:00
|
|
|
|
&& (($var_type->isSingleStringLiteral()
|
|
|
|
|
&& $var_type->getSingleStringLiteral()->value === $this_class_name)
|
|
|
|
|
|| ($other_type->isSingleStringLiteral()
|
|
|
|
|
&& $other_type->getSingleStringLiteral()->value === $this_class_name))
|
|
|
|
|
) {
|
|
|
|
|
// do nothing
|
|
|
|
|
} elseif (!UnionTypeComparator::canExpressionTypesBeIdentical(
|
|
|
|
|
$codebase,
|
|
|
|
|
$other_type,
|
|
|
|
|
$var_type
|
|
|
|
|
)) {
|
|
|
|
|
if ($var_type->from_docblock || $other_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-07-13 20:54:47 +02:00
|
|
|
|
new DocblockTypeContradiction(
|
|
|
|
|
$var_type->getId() . ' does not contain ' . $other_type->getId(),
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' ' . $other_type->getId()
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2021-07-13 20:54:47 +02:00
|
|
|
|
} else {
|
|
|
|
|
if ($conditional instanceof NotEqual || $conditional instanceof NotIdentical) {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-07-13 20:54:47 +02:00
|
|
|
|
new RedundantCondition(
|
|
|
|
|
$var_type->getId() . ' can never contain ' . $other_type->getId(),
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' ' . $other_type->getId()
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2021-07-13 20:54:47 +02:00
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
|
IssueBuffer::maybeAdd(
|
2021-07-13 20:54:47 +02:00
|
|
|
|
new TypeDoesNotContainType(
|
|
|
|
|
$var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
|
|
|
|
|
new CodeLocation($source, $conditional),
|
|
|
|
|
$var_type->getId() . ' ' . $other_type->getId()
|
|
|
|
|
),
|
|
|
|
|
$source->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
|
);
|
2021-07-13 20:54:47 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-17 10:29:25 +02:00
|
|
|
|
|
|
|
|
|
public static function isPropertyImmutableOnArgument(
|
|
|
|
|
string $property,
|
|
|
|
|
NodeDataProvider $node_provider,
|
|
|
|
|
ClassLikeStorageProvider $class_provider,
|
|
|
|
|
PhpParser\Node\Expr\Variable $arg_expr
|
|
|
|
|
): ?string {
|
|
|
|
|
$type = $node_provider->getType($arg_expr);
|
|
|
|
|
/** @var string $name */
|
|
|
|
|
$name = $arg_expr->name;
|
|
|
|
|
|
|
|
|
|
if (null === $type) {
|
|
|
|
|
return 'Cannot resolve a type of variable ' . $name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($type->getAtomicTypes() as $type) {
|
|
|
|
|
if (!$type instanceof TNamedObject) {
|
|
|
|
|
return 'Variable ' . $name . ' is not an object so assertion cannot be applied';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$class_definition = $class_provider->get($type->value);
|
|
|
|
|
$property_definition = $class_definition->properties[$property] ?? null;
|
|
|
|
|
|
|
|
|
|
if (!$property_definition instanceof PropertyStorage) {
|
|
|
|
|
return sprintf(
|
|
|
|
|
'Property %s is not defined on variable %s so assertion cannot be applied',
|
|
|
|
|
$property,
|
|
|
|
|
$name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$property_definition->readonly) {
|
|
|
|
|
return sprintf(
|
|
|
|
|
'Property %s of variable %s is not read-only/immutable so assertion cannot be applied',
|
|
|
|
|
$property,
|
|
|
|
|
$name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2016-12-28 21:52:44 +01:00
|
|
|
|
}
|