1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 11:26:55 +01:00
psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php
2020-07-03 01:07:50 -04:00

3033 lines
112 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\TypeAnalyzer;
use Psalm\CodeLocation;
use Psalm\FileSource;
use Psalm\Issue\DocblockTypeContradiction;
use Psalm\Issue\RedundantCondition;
use Psalm\Issue\RedundantConditionGivenDocblockType;
use Psalm\Issue\TypeDoesNotContainNull;
use Psalm\Issue\TypeDoesNotContainType;
use Psalm\Issue\UnevaluatedCode;
use Psalm\IssueBuffer;
use Psalm\Type;
use function substr;
use function count;
use function strtolower;
use function in_array;
use function array_merge;
use function strpos;
use function is_int;
/**
* @internal
*/
class AssertionFinder
{
const ASSIGNMENT_TO_RIGHT = 1;
const ASSIGNMENT_TO_LEFT = -1;
/**
* Gets all the type assertions in a conditional
*
* @param string|null $this_class_name
*
* @return array<string, non-empty-list<non-empty-list<string>>>|null
*/
public static function scrapeAssertions(
PhpParser\Node\Expr $conditional,
$this_class_name,
FileSource $source,
Codebase $codebase = null,
bool $inside_negation = false,
bool $cache = true
) {
$if_types = [];
if ($conditional instanceof PhpParser\Node\Expr\Instanceof_) {
$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 (!TypeAnalyzer::canExpressionTypesBeIdentical(
$codebase,
$instanceof_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
$var_type->getId() . ' does not contain '
. $instanceof_type->getId(),
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type->getId() . ' cannot be identical to '
. $instanceof_type->getId(),
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
}
}
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\Assign) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->var,
$this_class_name,
$source
);
$candidate_if_types = self::scrapeAssertions(
$conditional->expr,
$this_class_name,
$source,
$codebase,
$inside_negation,
$cache
);
if ($var_name) {
if ($candidate_if_types) {
$if_types[$var_name] = [['>' . \json_encode($candidate_if_types)]];
} else {
$if_types[$var_name] = [['!falsy']];
}
}
return $if_types;
}
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional,
$this_class_name,
$source
);
if ($var_name) {
$if_types[$var_name] = [['!falsy']];
if (!$conditional instanceof PhpParser\Node\Expr\MethodCall
&& !$conditional instanceof PhpParser\Node\Expr\StaticCall
) {
return $if_types;
}
}
if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) {
$expr_assertions = null;
if ($cache && $source instanceof StatementsAnalyzer) {
$expr_assertions = $source->node_data->getAssertions($conditional->expr);
}
if ($expr_assertions === null) {
$expr_assertions = self::scrapeAssertions(
$conditional->expr,
$this_class_name,
$source,
$codebase,
!$inside_negation,
$cache
);
if ($cache && $source instanceof StatementsAnalyzer) {
$source->node_data->setAssertions($conditional->expr, $expr_assertions);
}
}
if ($expr_assertions === null) {
throw new \UnexpectedValueException('Assertions should be set');
}
$if_types = \Psalm\Type\Algebra::negateTypes($expr_assertions);
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical ||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
) {
$if_types = self::scrapeEqualityAssertions(
$conditional,
$this_class_name,
$source,
$codebase,
false,
$cache
);
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical ||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
) {
$if_types = self::scrapeInequalityAssertions(
$conditional,
$this_class_name,
$source,
$codebase,
false,
$cache
);
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
) {
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
$counted_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $counted_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$counted_expr->args[0]->value,
$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 ($typed_value_position) {
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
/** @var PhpParser\Node\Expr $conditional->right */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = null;
} else {
throw new \UnexpectedValueException('$typed_value_position value');
}
if ($var_name) {
$if_types[$var_name] = [['=isset']];
}
return $if_types;
}
return [];
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
) {
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
$count_expr = $conditional->right;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$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 ($typed_value_position) {
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
/** @var PhpParser\Node\Expr $conditional->left */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$expr = $conditional->right;
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
/** @var PhpParser\Node\Expr $conditional->left */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$typed_value_position value');
}
$expr_type = $source instanceof StatementsAnalyzer
? $source->node_data->getType($expr)
: null;
if ($var_name
&& $expr_type
&& $expr_type->isSingleIntLiteral()
&& ($expr_type->getSingleIntLiteral()->value === 0)
) {
$if_types[$var_name] = [['=isset']];
}
return $if_types;
}
return [];
}
if ($conditional instanceof PhpParser\Node\Expr\FuncCall) {
$if_types = self::processFunctionCall($conditional, $this_class_name, $source, $codebase, false);
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\MethodCall
|| $conditional instanceof PhpParser\Node\Expr\StaticCall
) {
$custom_assertions = self::processCustomAssertion($conditional, $this_class_name, $source, false);
if ($custom_assertions) {
return $custom_assertions;
}
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->expr,
$this_class_name,
$source
);
if ($var_name) {
$if_types[$var_name] = [['empty']];
}
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\Isset_) {
foreach ($conditional->vars as $isset_var) {
$var_name = ExpressionIdentifier::getArrayVarId(
$isset_var,
$this_class_name,
$source
);
if ($var_name) {
$if_types[$var_name] = [['isset']];
} else {
// look for any variables we *can* use for an isset assertion
$array_root = $isset_var;
while ($array_root instanceof PhpParser\Node\Expr\ArrayDimFetch && !$var_name) {
$array_root = $array_root->var;
$var_name = ExpressionIdentifier::getArrayVarId(
$array_root,
$this_class_name,
$source
);
}
if ($var_name) {
$if_types[$var_name] = [['=isset']];
}
}
}
return $if_types;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) {
return self::scrapeAssertions(
new PhpParser\Node\Expr\Ternary(
new PhpParser\Node\Expr\Isset_(
[$conditional->left]
),
$conditional->left,
$conditional->right,
$conditional->getAttributes()
),
$this_class_name,
$source,
$codebase,
$inside_negation,
false
);
}
return [];
}
/**
* @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
* @param string|null $this_class_name
*
* @return array<string, non-empty-list<non-empty-list<string>>>
*/
private static function scrapeEqualityAssertions(
PhpParser\Node\Expr\BinaryOp $conditional,
$this_class_name,
FileSource $source,
Codebase $codebase = null,
bool $inside_negation = false,
bool $cache = true
) {
$if_types = [];
$null_position = self::hasNullVariable($conditional);
$false_position = self::hasFalseVariable($conditional);
$true_position = self::hasTrueVariable($conditional);
$empty_array_position = self::hasEmptyArrayVariable($conditional);
$gettype_position = self::hasGetTypeCheck($conditional);
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
if ($null_position !== null) {
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('$null_position value');
}
$var_name = ExpressionIdentifier::getArrayVarId(
$base_conditional,
$this_class_name,
$source
);
if ($var_name && $base_conditional instanceof PhpParser\Node\Expr\Assign) {
$var_name = '=' . $var_name;
}
if ($var_name) {
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
$if_types[$var_name] = [['null']];
} else {
$if_types[$var_name] = [['falsy']];
}
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($base_conditional))
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
) {
$null_type = Type::getNull();
if (!TypeAnalyzer::isContainedBy(
$codebase,
$var_type,
$null_type
) && !TypeAnalyzer::isContainedBy(
$codebase,
$null_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' does not contain null',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainNull(
$var_type . ' does not contain null',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
return $if_types;
}
if ($true_position) {
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('Unrecognised position');
}
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
$if_types = self::processFunctionCall(
$base_conditional,
$this_class_name,
$source,
$codebase,
false
);
} 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] = [['true']];
} else {
$if_types[$var_name] = [['!falsy']];
}
} else {
$base_assertions = null;
if ($source instanceof StatementsAnalyzer && $cache) {
$base_assertions = $source->node_data->getAssertions($base_conditional);
}
if ($base_assertions === null) {
$base_assertions = self::scrapeAssertions(
$base_conditional,
$this_class_name,
$source,
$codebase,
$inside_negation,
$cache
);
if ($source instanceof StatementsAnalyzer && $cache) {
$source->node_data->setAssertions($base_conditional, $base_assertions);
}
}
if ($base_assertions === null) {
throw new \UnexpectedValueException('Assertions should be set');
}
$if_types = $base_assertions;
}
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($base_conditional))
) {
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
$true_type = Type::getTrue();
if (!TypeAnalyzer::isContainedBy(
$codebase,
$var_type,
$true_type
) && !TypeAnalyzer::isContainedBy(
$codebase,
$true_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' does not contain true',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type . ' does not contain true',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
if ($false_position) {
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('$false_position value');
}
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
$if_types = self::processFunctionCall(
$base_conditional,
$this_class_name,
$source,
$codebase,
true
);
} 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']];
}
} else {
$base_assertions = null;
if ($source instanceof StatementsAnalyzer && $cache) {
$base_assertions = $source->node_data->getAssertions($base_conditional);
}
if ($base_assertions === null) {
$base_assertions = self::scrapeAssertions(
$base_conditional,
$this_class_name,
$source,
$codebase,
$inside_negation,
$cache
);
if ($source instanceof StatementsAnalyzer && $cache) {
$source->node_data->setAssertions($base_conditional, $base_assertions);
}
}
if ($base_assertions === null) {
throw new \UnexpectedValueException('Assertions should be set');
}
$notif_types = $base_assertions;
if (count($notif_types) === 1) {
$if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
}
}
}
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 (!TypeAnalyzer::canExpressionTypesBeIdentical(
$codebase,
$false_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' does not contain false',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type . ' does not contain false',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
if ($empty_array_position !== null) {
if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('$empty_array_position value');
}
$var_name = 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']];
}
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($base_conditional))
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
) {
$empty_array_type = Type::getEmptyArray();
if (!TypeAnalyzer::canExpressionTypesBeIdentical(
$codebase,
$empty_array_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' does not contain an empty array',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type . ' does not contain empty array',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
return $if_types;
}
if ($gettype_position) {
if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
$string_expr = $conditional->left;
$gettype_expr = $conditional->right;
} elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
$string_expr = $conditional->right;
$gettype_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$gettype_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$gettype_expr->args[0]->value,
$this_class_name,
$source
);
/** @var PhpParser\Node\Scalar\String_ $string_expr */
$var_type = $string_expr->value;
if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
if (IssueBuffer::accepts(
new UnevaluatedCode(
'gettype cannot return this value',
new CodeLocation($source, $string_expr)
)
)) {
// fall through
}
} else {
if ($var_name && $var_type) {
$if_types[$var_name] = [[$var_type]];
}
}
return $if_types;
}
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
$count_expr = $conditional->left;
} elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
$count_expr = $conditional->right;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$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 (!$source instanceof StatementsAnalyzer) {
return [];
}
$getclass_position = self::hasGetClassCheck($conditional, $source);
if ($getclass_position) {
if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
$whichclass_expr = $conditional->left;
$getclass_expr = $conditional->right;
} elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) {
$whichclass_expr = $conditional->right;
$getclass_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$getclass_position value');
}
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall && isset($getclass_expr->args[0])) {
$var_name = ExpressionIdentifier::getArrayVarId(
$getclass_expr->args[0]->value,
$this_class_name,
$source
);
} else {
$var_name = '$this';
}
if ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
&& $whichclass_expr->class instanceof PhpParser\Node\Name
) {
$var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
$whichclass_expr->class,
$source->getAliases()
);
if ($var_type === 'self' || $var_type === 'static') {
$var_type = $this_class_name;
} elseif ($var_type === 'parent') {
$var_type = null;
}
if ($var_type) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$source,
$var_type,
new CodeLocation($source, $whichclass_expr),
null,
null,
$source->getSuppressedIssues(),
true
) === false
) {
return $if_types;
}
}
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) {
if ($type_part instanceof Type\Atomic\TTemplateParamClass) {
$if_types[$var_name] = [['=' . $type_part->param_name]];
}
}
}
}
return $if_types;
}
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
if ($typed_value_position) {
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
/** @var PhpParser\Node\Expr $conditional->right */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$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 PhpParser\Node\Expr $conditional->left */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$var_type = $source->node_data->getType($conditional->left);
$other_type = $source->node_data->getType($conditional->right);
} else {
throw new \UnexpectedValueException('$typed_value_position value');
}
if ($var_name && $var_type) {
$identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|| ($other_type
&& (($var_type->isString(true) && $other_type->isString(true))
|| ($var_type->isInt(true) && $other_type->isInt(true))
|| ($var_type->isFloat() && $other_type->isFloat())
)
);
if ($identical) {
$if_types[$var_name] = [['=' . $var_type->getAssertionString()]];
} else {
$if_types[$var_name] = [['~' . $var_type->getAssertionString()]];
}
}
if ($codebase
&& $other_type
&& $var_type
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
) {
$parent_source = $source->getSource();
if ($parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
&& (($var_type->isSingleStringLiteral()
&& $var_type->getSingleStringLiteral()->value === $this_class_name)
|| ($other_type->isSingleStringLiteral()
&& $other_type->getSingleStringLiteral()->value === $this_class_name))
) {
// do nothing
} elseif (!TypeAnalyzer::canExpressionTypesBeIdentical(
$codebase,
$other_type,
$var_type
)) {
if ($var_type->from_docblock || $other_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type->getId() . ' does not contain ' . $other_type->getId(),
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
return $if_types;
}
$var_type = $source->node_data->getType($conditional->left);
$other_type = $source->node_data->getType($conditional->right);
if ($codebase
&& $var_type
&& $other_type
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
) {
if (!TypeAnalyzer::canExpressionTypesBeIdentical($codebase, $var_type, $other_type)) {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
return [];
}
/**
* @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
* @param string|null $this_class_name
*
* @return array<string, non-empty-list<non-empty-list<string>>>
*/
private static function scrapeInequalityAssertions(
PhpParser\Node\Expr\BinaryOp $conditional,
$this_class_name,
FileSource $source,
Codebase $codebase = null,
bool $inside_negation = false,
bool $cache = true
) {
$if_types = [];
$null_position = self::hasNullVariable($conditional);
$false_position = self::hasFalseVariable($conditional);
$true_position = self::hasTrueVariable($conditional);
$empty_array_position = self::hasEmptyArrayVariable($conditional);
$gettype_position = self::hasGetTypeCheck($conditional);
if ($null_position !== null) {
if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('Bad null variable position');
}
$var_name = ExpressionIdentifier::getArrayVarId(
$base_conditional,
$this_class_name,
$source
);
if ($var_name) {
if ($base_conditional instanceof PhpParser\Node\Expr\Assign) {
$var_name = '=' . $var_name;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
$if_types[$var_name] = [['!null']];
} else {
$if_types[$var_name] = [['!falsy']];
}
}
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();
if (!TypeAnalyzer::isContainedBy(
$codebase,
$var_type,
$null_type
) && !TypeAnalyzer::isContainedBy(
$codebase,
$null_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
'Docblock-asserted type ' . $var_type . ' can never contain null',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type . ' can never contain null',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
if ($false_position) {
if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('Bad false variable position');
}
$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] = [['!false']];
} else {
$if_types[$var_name] = [['!falsy']];
}
} else {
$base_assertions = null;
if ($source instanceof StatementsAnalyzer && $cache) {
$base_assertions = $source->node_data->getAssertions($base_conditional);
}
if ($base_assertions === null) {
$base_assertions = self::scrapeAssertions(
$base_conditional,
$this_class_name,
$source,
$codebase,
$inside_negation,
$cache
);
if ($source instanceof StatementsAnalyzer && $cache) {
$source->node_data->setAssertions($base_conditional, $base_assertions);
}
}
if ($base_assertions === null) {
throw new \UnexpectedValueException('Assertions should be set');
}
$notif_types = $base_assertions;
if (count($notif_types) === 1) {
$if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
}
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($base_conditional))
) {
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
$false_type = Type::getFalse();
if (!TypeAnalyzer::isContainedBy(
$codebase,
$var_type,
$false_type
) && !TypeAnalyzer::isContainedBy(
$codebase,
$false_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
'Docblock-asserted type ' . $var_type . ' can never contain false',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type . ' can never contain false',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
if ($true_position) {
if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('Bad null variable position');
}
if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
$if_types = self::processFunctionCall(
$base_conditional,
$this_class_name,
$source,
$codebase,
true
);
} 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']];
}
} else {
$base_assertions = null;
if ($source instanceof StatementsAnalyzer && $cache) {
$base_assertions = $source->node_data->getAssertions($base_conditional);
}
if ($base_assertions === null) {
$base_assertions = self::scrapeAssertions(
$base_conditional,
$this_class_name,
$source,
$codebase,
$inside_negation,
$cache
);
if ($source instanceof StatementsAnalyzer && $cache) {
$source->node_data->setAssertions($base_conditional, $base_assertions);
}
}
if ($base_assertions === null) {
throw new \UnexpectedValueException('Assertions should be set');
}
$notif_types = $base_assertions;
if (count($notif_types) === 1) {
$if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
}
}
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($base_conditional))
) {
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
$true_type = Type::getTrue();
if (!TypeAnalyzer::isContainedBy(
$codebase,
$var_type,
$true_type
) && !TypeAnalyzer::isContainedBy(
$codebase,
$true_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
'Docblock-asserted type ' . $var_type . ' can never contain true',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type . ' can never contain ' . $true_type,
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
if ($empty_array_position !== null) {
if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) {
$base_conditional = $conditional->left;
} elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) {
$base_conditional = $conditional->right;
} else {
throw new \UnexpectedValueException('Bad empty array variable position');
}
$var_name = ExpressionIdentifier::getArrayVarId(
$base_conditional,
$this_class_name,
$source
);
if ($var_name) {
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
$if_types[$var_name] = [['non-empty-countable']];
} else {
$if_types[$var_name] = [['!falsy']];
}
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($base_conditional))
) {
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
$empty_array_type = Type::getEmptyArray();
if (!TypeAnalyzer::isContainedBy(
$codebase,
$var_type,
$empty_array_type
) && !TypeAnalyzer::isContainedBy(
$codebase,
$empty_array_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
'Docblock-asserted type ' . $var_type . ' can never contain null',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type . ' can never contain null',
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
if ($gettype_position) {
if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
$whichclass_expr = $conditional->left;
$gettype_expr = $conditional->right;
} elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
$whichclass_expr = $conditional->right;
$gettype_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$gettype_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$gettype_expr->args[0]->value,
$this_class_name,
$source
);
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 {
throw new \UnexpectedValueException('Shouldnt get here');
}
if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
if (IssueBuffer::accepts(
new UnevaluatedCode(
'gettype cannot return this value',
new CodeLocation($source, $whichclass_expr)
)
)) {
// fall through
}
} else {
if ($var_name && $var_type) {
$if_types[$var_name] = [['!' . $var_type]];
}
}
return $if_types;
}
if (!$source instanceof StatementsAnalyzer) {
return [];
}
$getclass_position = self::hasGetClassCheck($conditional, $source);
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
if ($getclass_position) {
if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
$whichclass_expr = $conditional->left;
$getclass_expr = $conditional->right;
} elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) {
$whichclass_expr = $conditional->right;
$getclass_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$getclass_position value');
}
if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
$var_name = ExpressionIdentifier::getArrayVarId(
$getclass_expr->args[0]->value,
$this_class_name,
$source
);
} else {
$var_name = '$this';
}
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()
);
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);
if ($type && $var_name) {
foreach ($type->getAtomicTypes() as $type_part) {
if ($type_part instanceof Type\Atomic\TTemplateParamClass) {
$if_types[$var_name] = [['!=' . $type_part->param_name]];
}
}
}
return $if_types;
}
if ($var_type
&& ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$source,
$var_type,
new CodeLocation($source, $whichclass_expr),
null,
null,
$source->getSuppressedIssues(),
false
) === false
) {
// fall through
} else {
if ($var_name && $var_type) {
$if_types[$var_name] = [['!=getclass-' . $var_type]];
}
}
return $if_types;
}
if ($typed_value_position) {
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
/** @var PhpParser\Node\Expr $conditional->right */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$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 PhpParser\Node\Expr $conditional->left */
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$var_type = $source->node_data->getType($conditional->left);
$other_type = $source->node_data->getType($conditional->right);
} else {
throw new \UnexpectedValueException('$typed_value_position value');
}
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())
)
);
if ($not_identical) {
$if_types[$var_name] = [['!=' . $var_type->getAssertionString()]];
} else {
$if_types[$var_name] = [['!~' . $var_type->getAssertionString()]];
}
}
if ($codebase
&& $other_type
&& $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
) {
$parent_source = $source->getSource();
if ($parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
&& (($var_type->isSingleStringLiteral()
&& $var_type->getSingleStringLiteral()->value === $this_class_name)
|| ($other_type->isSingleStringLiteral()
&& $other_type->getSingleStringLiteral()->value === $this_class_name))
) {
// do nothing
} elseif (!TypeAnalyzer::canExpressionTypesBeIdentical(
$codebase,
$other_type,
$var_type
)) {
if ($var_type->from_docblock || $other_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' can never contain ' . $other_type,
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type->getId() . ' can never contain ' . $other_type->getId(),
new CodeLocation($source, $conditional)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
return $if_types;
}
return [];
}
/**
* @param PhpParser\Node\Expr\FuncCall $expr
* @param string|null $this_class_name
* @param FileSource $source
* @param bool $negate
*
* @return array<string, non-empty-list<non-empty-list<string>>>
*/
public static function processFunctionCall(
PhpParser\Node\Expr\FuncCall $expr,
$this_class_name,
FileSource $source,
Codebase $codebase = null,
$negate = false
) {
$prefix = $negate ? '!' : '';
$first_var_name = isset($expr->args[0]->value)
? ExpressionIdentifier::getArrayVarId(
$expr->args[0]->value,
$this_class_name,
$source
)
: null;
$if_types = [];
$first_var_type = isset($expr->args[0]->value)
&& $source instanceof StatementsAnalyzer
? $source->node_data->getType($expr->args[0]->value)
: null;
if (self::hasNullCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'null']];
}
} elseif ($source instanceof StatementsAnalyzer && self::hasIsACheck($expr, $source)) {
if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
&& $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
&& strtolower($expr->args[0]->value->name->name) === 'class'
&& $expr->args[0]->value->class instanceof PhpParser\Node\Name
&& count($expr->args[0]->value->class->parts) === 1
&& strtolower($expr->args[0]->value->class->parts[0]) === 'static'
) {
$first_var_name = '$this';
}
if ($first_var_name) {
$first_arg = $expr->args[0]->value;
$second_arg = $expr->args[1]->value;
$third_arg = isset($expr->args[2]->value) ? $expr->args[2]->value : null;
if ($third_arg instanceof PhpParser\Node\Expr\ConstFetch) {
if (!in_array(strtolower($third_arg->name->parts[0]), ['true', 'false'])) {
return $if_types;
}
$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-';
if ($first_arg
&& ($first_arg_type = $source->node_data->getType($first_arg))
&& $first_arg_type->isSingleStringLiteral()
&& $source->getSource()->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
&& $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);
}
$if_types[$first_var_name] = [[$prefix . $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] = [[$prefix . $is_a_prefix . $this_class_name . '&static']];
}
} elseif ($class_node->parts === ['self']) {
if ($this_class_name) {
$if_types[$first_var_name] = [[$prefix . $is_a_prefix . $this_class_name]];
}
} elseif ($class_node->parts === ['parent']) {
// do nothing
} else {
$if_types[$first_var_name] = [[
$prefix . $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) {
if ($second_arg_atomic_type instanceof Type\Atomic\TTemplateParamClass) {
$vals[] = [$prefix . $is_a_prefix . $second_arg_atomic_type->param_name];
}
}
if ($vals) {
$if_types[$first_var_name] = $vals;
}
}
}
}
} elseif (self::hasArrayCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'array']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getArray(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasBoolCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'bool']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getBool(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasStringCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'string']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getString(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasObjectCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'object']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getObject(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasNumericCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'numeric']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getNumeric(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasIntCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'int']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getInt(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasFloatCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'float']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getFloat(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasResourceCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'resource']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getResource(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasScalarCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'scalar']];
} elseif ($first_var_type
&& $codebase
&& $source instanceof StatementsAnalyzer
) {
self::processIrreconcilableFunctionCall(
$first_var_type,
Type::getScalar(),
$expr,
$source,
$codebase,
$negate
);
}
} elseif (self::hasCallableCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'callable']];
} elseif ($expr->args[0]->value instanceof PhpParser\Node\Expr\Array_
&& isset($expr->args[0]->value->items[0], $expr->args[0]->value->items[1])
&& $expr->args[0]->value->items[1]->value instanceof PhpParser\Node\Scalar\String_
) {
$first_var_name_in_array_argument = ExpressionIdentifier::getArrayVarId(
$expr->args[0]->value->items[0]->value,
$this_class_name,
$source
);
if ($first_var_name_in_array_argument) {
$if_types[$first_var_name_in_array_argument] = [
[$prefix . 'hasmethod-' . $expr->args[0]->value->items[1]->value->value]
];
}
}
} elseif (self::hasIterableCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'iterable']];
}
} elseif (self::hasCountableCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'countable']];
}
} elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) {
if ($first_var_name) {
if ($class_exists_check_type === 2 || $prefix) {
$if_types[$first_var_name] = [[$prefix . 'class-string']];
} else {
$if_types[$first_var_name] = [['=class-string']];
}
}
} elseif ($class_exists_check_type = self::hasTraitExistsCheck($expr)) {
if ($first_var_name) {
if ($class_exists_check_type === 2 || $prefix) {
$if_types[$first_var_name] = [[$prefix . 'trait-string']];
} else {
$if_types[$first_var_name] = [['=trait-string']];
}
}
} elseif (self::hasInterfaceExistsCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'interface-string']];
}
} elseif (self::hasFunctionExistsCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'callable-string']];
}
} elseif ($expr->name instanceof PhpParser\Node\Name
&& strtolower($expr->name->parts[0]) === 'method_exists'
&& isset($expr->args[1])
&& $expr->args[1]->value instanceof PhpParser\Node\Scalar\String_
) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'hasmethod-' . $expr->args[1]->value->value]];
}
} elseif (self::hasInArrayCheck($expr) && $source instanceof StatementsAnalyzer) {
if ($first_var_name
&& ($second_arg_type = $source->node_data->getType($expr->args[1]->value))
) {
foreach ($second_arg_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Type\Atomic\TArray
|| $atomic_type instanceof Type\Atomic\ObjectLike
) {
if ($atomic_type instanceof Type\Atomic\ObjectLike) {
$atomic_type = $atomic_type->getGenericArrayType();
}
$array_literal_types = array_merge(
$atomic_type->type_params[1]->getLiteralStrings(),
$atomic_type->type_params[1]->getLiteralInts(),
$atomic_type->type_params[1]->getLiteralFloats()
);
if ($array_literal_types
&& count($atomic_type->type_params[1]->getAtomicTypes())
) {
$literal_assertions = [];
foreach ($array_literal_types as $array_literal_type) {
$literal_assertions[] = '=' . $array_literal_type->getId();
}
if ($negate) {
$if_types = \Psalm\Type\Algebra::negateTypes([
$first_var_name => [$literal_assertions]
]);
} else {
$if_types[$first_var_name] = [$literal_assertions];
}
}
}
}
}
} elseif (self::hasArrayKeyExistsCheck($expr)) {
$array_root = isset($expr->args[1]->value)
? ExpressionIdentifier::getArrayVarId(
$expr->args[1]->value,
$this_class_name,
$source
)
: null;
if ($first_var_name === null && isset($expr->args[0])) {
$first_arg = $expr->args[0];
if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) {
$first_var_name = '\'' . $first_arg->value->value . '\'';
} elseif ($first_arg->value instanceof PhpParser\Node\Scalar\LNumber) {
$first_var_name = (string) $first_arg->value->value;
}
}
if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
&& $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
&& $expr->args[0]->value->name->name !== 'class'
) {
$const_type = null;
if ($source instanceof StatementsAnalyzer) {
$const_type = $source->node_data->getType($expr->args[0]->value);
}
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;
}
} else {
$first_var_name = null;
}
}
if ($first_var_name !== null
&& $array_root
&& !strpos($first_var_name, '->')
&& !strpos($first_var_name, '[')
) {
$if_types[$array_root . '[' . $first_var_name . ']'] = [[$prefix . 'array-key-exists']];
}
} elseif (self::hasNonEmptyCountCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'non-empty-countable']];
}
} else {
$if_types = self::processCustomAssertion($expr, $this_class_name, $source, $negate);
}
return $if_types;
}
private static function processIrreconcilableFunctionCall(
Type\Union $first_var_type,
Type\Union $expected_type,
PhpParser\Node\Expr $expr,
StatementsAnalyzer $source,
Codebase $codebase,
bool $negate
) : void {
if ($first_var_type->hasMixed()) {
return;
}
if (!TypeAnalyzer::isContainedBy(
$codebase,
$first_var_type,
$expected_type
)) {
return;
}
if (!$negate) {
if ($first_var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
'Docblock type ' . $first_var_type . ' always contains ' . $expected_type,
new CodeLocation($source, $expr)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$first_var_type . ' always contains ' . $expected_type,
new CodeLocation($source, $expr)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
} else {
if ($first_var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$first_var_type . ' does not contain ' . $expected_type,
new CodeLocation($source, $expr)
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$first_var_type . ' does not contain ' . $expected_type,
new CodeLocation($source, $expr)
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
/**
* @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $expr
* @param string|null $this_class_name
* @param FileSource $source
* @param bool $negate
*
* @return array<string, non-empty-list<non-empty-list<string>>>
*/
protected static function processCustomAssertion(
$expr,
$this_class_name,
FileSource $source,
$negate = false
) {
if (!$source instanceof StatementsAnalyzer) {
return [];
}
$if_true_assertions = $source->node_data->getIfTrueAssertions($expr);
$if_false_assertions = $source->node_data->getIfFalseAssertions($expr);
if ($if_true_assertions === null && $if_false_assertions === null) {
return [];
}
$prefix = $negate ? '!' : '';
$first_var_name = isset($expr->args[0]->value)
? ExpressionIdentifier::getArrayVarId(
$expr->args[0]->value,
$this_class_name,
$source
)
: null;
$if_types = [];
if ($if_true_assertions) {
foreach ($if_true_assertions as $assertion) {
if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) {
if ($assertion->var_id === 0) {
$var_name = $first_var_name;
} else {
$var_name = ExpressionIdentifier::getArrayVarId(
$expr->args[$assertion->var_id]->value,
$this_class_name,
$source
);
}
if ($var_name) {
if ($prefix === $assertion->rule[0][0][0]) {
$if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
} else {
$if_types[$var_name] = [[$prefix . $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 ($prefix === $assertion->rule[0][0][0]) {
$if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]];
} else {
$if_types[$var_id] = [[$prefix . $assertion->rule[0][0]]];
}
}
} elseif (\is_string($assertion->var_id)
&& $expr instanceof PhpParser\Node\Expr\MethodCall
) {
if ($prefix === $assertion->rule[0][0][0]) {
$if_types[$assertion->var_id] = [[substr($assertion->rule[0][0], 1)]];
} else {
$if_types[$assertion->var_id] = [[$prefix . $assertion->rule[0][0]]];
}
}
}
}
if ($if_false_assertions) {
$negated_prefix = !$negate ? '!' : '';
foreach ($if_false_assertions as $assertion) {
if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) {
if ($assertion->var_id === 0) {
$var_name = $first_var_name;
} else {
$var_name = ExpressionIdentifier::getArrayVarId(
$expr->args[$assertion->var_id]->value,
$this_class_name,
$source
);
}
if ($var_name) {
if ($negated_prefix === $assertion->rule[0][0][0]) {
$if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
} else {
$if_types[$var_name] = [[$negated_prefix . $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 ($negated_prefix === $assertion->rule[0][0][0]) {
$if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]];
} else {
$if_types[$var_id] = [[$negated_prefix . $assertion->rule[0][0]]];
}
}
} elseif (\is_string($assertion->var_id)
&& $expr instanceof PhpParser\Node\Expr\MethodCall
) {
if ($prefix === $assertion->rule[0][0][0]) {
$if_types[$assertion->var_id] = [[substr($assertion->rule[0][0], 1)]];
} else {
$if_types[$assertion->var_id] = [[$negated_prefix . $assertion->rule[0][0]]];
}
}
}
}
return $if_types;
}
/**
* @param PhpParser\Node\Expr\Instanceof_ $stmt
* @param string|null $this_class_name
* @param FileSource $source
*
* @return list<string>
*/
protected static function getInstanceOfTypes(
PhpParser\Node\Expr\Instanceof_ $stmt,
$this_class_name,
FileSource $source
) {
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];
} elseif ($this_class_name
&& (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))
) {
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) {
if ($atomic_type instanceof Type\Atomic\TLiteralClassString) {
$literal_class_strings[] = $atomic_type->value;
} elseif ($atomic_type instanceof Type\Atomic\TTemplateParamClass) {
$literal_class_strings[] = $atomic_type->param_name;
}
}
return $literal_class_strings;
}
}
return [];
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return int|null
*/
protected static function hasNullVariable(PhpParser\Node\Expr\BinaryOp $conditional)
{
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;
}
return null;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return int|null
*/
public static function hasFalseVariable(PhpParser\Node\Expr\BinaryOp $conditional)
{
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;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return int|null
*/
public static function hasTrueVariable(PhpParser\Node\Expr\BinaryOp $conditional)
{
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;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return int|null
*/
protected static function hasEmptyArrayVariable(PhpParser\Node\Expr\BinaryOp $conditional)
{
if ($conditional->right instanceof PhpParser\Node\Expr\Array_
&& !$conditional->right->items
) {
return self::ASSIGNMENT_TO_RIGHT;
}
if ($conditional->left instanceof PhpParser\Node\Expr\Array_
&& !$conditional->left->items
) {
return self::ASSIGNMENT_TO_LEFT;
}
return null;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return false|int
*/
protected static function hasGetTypeCheck(PhpParser\Node\Expr\BinaryOp $conditional)
{
if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->right->name instanceof PhpParser\Node\Name
&& strtolower($conditional->right->name->parts[0]) === 'gettype'
&& $conditional->right->args
&& $conditional->left instanceof PhpParser\Node\Scalar\String_
) {
return self::ASSIGNMENT_TO_RIGHT;
}
if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->left->name instanceof PhpParser\Node\Name
&& strtolower($conditional->left->name->parts[0]) === 'gettype'
&& $conditional->left->args
&& $conditional->right instanceof PhpParser\Node\Scalar\String_
) {
return self::ASSIGNMENT_TO_LEFT;
}
return false;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return false|int
*/
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';
$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) {
if ($type_part instanceof Type\Atomic\TClassString) {
$left_class_string_t = true;
}
}
}
if (($right_get_class || $right_static_class) && ($left_class_string || $left_class_string_t)) {
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';
$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) {
if ($type_part instanceof Type\Atomic\TClassString) {
$right_class_string_t = true;
}
}
}
if (($left_get_class || $left_static_class) && ($right_class_string || $right_class_string_t)) {
return self::ASSIGNMENT_TO_LEFT;
}
return false;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return false|int
*/
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'
&& $conditional->left->args;
$operator_greater_than_or_equal =
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
if ($left_count
&& $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'
&& $conditional->right->args;
$operator_less_than_or_equal =
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
if ($right_count
&& $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;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return false|int
*/
protected static function hasReconcilableNonEmptyCountEqualityCheck(PhpParser\Node\Expr\BinaryOp $conditional)
{
$left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->left->name instanceof PhpParser\Node\Name
&& strtolower($conditional->left->name->parts[0]) === 'count';
$right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber
&& $conditional->right->value === (
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1);
$operator_greater_than_or_equal =
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
if ($left_count && $right_number && $operator_greater_than_or_equal) {
return self::ASSIGNMENT_TO_RIGHT;
}
$right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
&& $conditional->right->name instanceof PhpParser\Node\Name
&& strtolower($conditional->right->name->parts[0]) === 'count';
$left_number = $conditional->left instanceof PhpParser\Node\Scalar\LNumber
&& $conditional->left->value === (
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1);
$operator_less_than_or_equal =
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
if ($right_count && $left_number && $operator_less_than_or_equal) {
return self::ASSIGNMENT_TO_LEFT;
}
return false;
}
/**
* @param PhpParser\Node\Expr\BinaryOp $conditional
*
* @return false|int
*/
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;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasNullCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_null') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasIsACheck(
PhpParser\Node\Expr\FuncCall $stmt,
StatementsAnalyzer $source
) {
if ($stmt->name instanceof PhpParser\Node\Name
&& (strtolower($stmt->name->parts[0]) === 'is_a'
|| strtolower($stmt->name->parts[0]) === 'is_subclass_of')
&& isset($stmt->args[1])
) {
$second_arg = $stmt->args[1]->value;
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;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasArrayCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_array') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasStringCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_string') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasBoolCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_bool') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasObjectCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_object']) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasNumericCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_numeric']) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasIterableCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_iterable') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasCountableCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_countable') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return 0|1|2
*/
protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name
&& strtolower($stmt->name->parts[0]) === 'class_exists'
) {
if (!isset($stmt->args[1])) {
return 2;
}
$second_arg = $stmt->args[1]->value;
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
&& strtolower($second_arg->name->parts[0]) === 'true'
) {
return 2;
}
return 1;
}
return 0;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return 0|1|2
*/
protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name
&& strtolower($stmt->name->parts[0]) === 'trait_exists'
) {
if (!isset($stmt->args[1])) {
return 2;
}
$second_arg = $stmt->args[1]->value;
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
&& strtolower($second_arg->name->parts[0]) === 'true'
) {
return 2;
}
return 1;
}
return 0;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasInterfaceExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name
&& strtolower($stmt->name->parts[0]) === 'interface_exists'
) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'function_exists') {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasIntCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name &&
($stmt->name->parts === ['is_int'] ||
$stmt->name->parts === ['is_integer'] ||
$stmt->name->parts === ['is_long'])
) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasFloatCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name &&
($stmt->name->parts === ['is_float'] ||
$stmt->name->parts === ['is_real'] ||
$stmt->name->parts === ['is_double'])
) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasResourceCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_resource']) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasScalarCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_scalar']) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_callable']) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name
&& $stmt->name->parts === ['in_array']
&& isset($stmt->args[2])
) {
$second_arg = $stmt->args[2]->value;
if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
&& strtolower($second_arg->name->parts[0]) === 'true'
) {
return true;
}
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name
&& $stmt->name->parts === ['count']
) {
return true;
}
return false;
}
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['array_key_exists']) {
return true;
}
return false;
}
}