1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 03:17:02 +01:00
psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php

243 lines
8.4 KiB
PHP
Raw Normal View History

2018-01-14 18:09:40 +01:00
<?php
2018-11-06 03:57:36 +01:00
namespace Psalm\Internal\Analyzer\Statements\Expression;
2018-01-14 18:09:40 +01:00
use PhpParser;
2018-11-06 03:57:36 +01:00
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\ArrayAssignmentAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Block\IfAnalyzer;
2018-11-06 03:57:36 +01:00
use Psalm\Internal\Analyzer\TypeAnalyzer;
2018-01-14 18:09:40 +01:00
use Psalm\CodeLocation;
use Psalm\Config;
use Psalm\Context;
use Psalm\Issue\FalseOperand;
use Psalm\Issue\ImplicitToStringCast;
use Psalm\Issue\ImpureMethodCall;
2018-01-14 18:09:40 +01:00
use Psalm\Issue\InvalidOperand;
use Psalm\Issue\MixedOperand;
use Psalm\Issue\NullOperand;
use Psalm\Issue\PossiblyFalseOperand;
use Psalm\Issue\PossiblyInvalidOperand;
2018-01-14 18:09:40 +01:00
use Psalm\Issue\PossiblyNullOperand;
use Psalm\Issue\StringIncrement;
2018-01-14 18:09:40 +01:00
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
use Psalm\Type;
2018-05-07 07:26:06 +02:00
use Psalm\Type\Algebra;
2018-01-14 18:09:40 +01:00
use Psalm\Type\Atomic\ObjectLike;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TFalse;
2018-01-14 18:09:40 +01:00
use Psalm\Type\Atomic\TFloat;
2019-10-09 00:44:46 +02:00
use Psalm\Type\Atomic\TList;
2019-02-22 03:40:06 +01:00
use Psalm\Type\Atomic\TTemplateParam;
2018-01-14 18:09:40 +01:00
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TMixed;
2018-05-05 18:59:30 +02:00
use Psalm\Type\Atomic\TNamedObject;
2018-01-14 18:09:40 +01:00
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TNumeric;
2018-01-14 18:09:40 +01:00
use Psalm\Type\Reconciler;
2019-08-10 19:22:21 +02:00
use Psalm\Internal\Type\AssertionReconciler;
use Psalm\Internal\Type\TypeCombination;
use function array_merge;
use function array_diff_key;
use function array_filter;
use function array_intersect_key;
use function array_values;
use function array_map;
use function array_keys;
use function preg_match;
use function preg_quote;
use function strtolower;
use function strlen;
2018-01-14 18:09:40 +01:00
/**
* @internal
*/
2018-11-06 03:57:36 +01:00
class BinaryOpAnalyzer
2018-01-14 18:09:40 +01:00
{
public static function analyze(
2018-11-11 18:01:14 +01:00
StatementsAnalyzer $statements_analyzer,
2018-01-14 18:09:40 +01:00
PhpParser\Node\Expr\BinaryOp $stmt,
Context $context,
int $nesting = 0,
bool $from_stmt = false
2020-05-18 21:13:27 +02:00
) : bool {
2018-01-14 18:09:40 +01:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat && $nesting > 20) {
// ignore deeply-nested string concatenation
2020-05-19 00:57:02 +02:00
return true;
}
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd ||
2018-01-14 18:09:40 +01:00
$stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd
) {
2020-05-19 00:57:02 +02:00
return BinaryOp\AndAnalyzer::analyze(
$statements_analyzer,
2020-05-19 00:57:02 +02:00
$stmt,
$context,
$from_stmt
2018-01-14 18:09:40 +01:00
);
2020-05-19 00:57:02 +02:00
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr ||
2018-01-14 18:09:40 +01:00
$stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr
) {
2020-05-19 00:57:02 +02:00
return BinaryOp\OrAnalyzer::analyze(
2018-11-11 18:01:14 +01:00
$statements_analyzer,
2020-05-19 00:57:02 +02:00
$stmt,
$context,
$from_stmt
2018-01-14 18:09:40 +01:00
);
2020-05-19 00:57:02 +02:00
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) {
return BinaryOp\CoalesceAnalyzer::analyze(
$statements_analyzer,
$stmt,
$context
);
2020-05-19 00:57:02 +02:00
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp) {
if (self::analyze($statements_analyzer, $stmt->left, $context, ++$nesting) === false) {
2018-01-14 18:09:40 +01:00
return false;
}
2020-05-19 00:57:02 +02:00
} else {
2018-11-11 18:01:14 +01:00
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $context) === false) {
2018-01-14 18:09:40 +01:00
return false;
}
2020-05-19 00:57:02 +02:00
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt->right instanceof PhpParser\Node\Expr\BinaryOp) {
if (self::analyze($statements_analyzer, $stmt->right, $context, ++$nesting) === false) {
return false;
}
} else {
2018-11-11 18:01:14 +01:00
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $context) === false) {
2018-01-14 18:09:40 +01:00
return false;
}
2020-05-19 00:57:02 +02:00
}
2020-05-19 00:57:02 +02:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
BinaryOp\ConcatAnalyzer::analyze(
$statements_analyzer,
$stmt->left,
$stmt->right,
2020-05-19 00:57:02 +02:00
$context
);
2020-05-19 00:57:02 +02:00
return true;
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) {
$statements_analyzer->node_data->setType($stmt, Type::getInt());
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
return true;
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
) {
$statements_analyzer->node_data->setType($stmt, Type::getBool());
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
$stmt_left_type = $statements_analyzer->node_data->getType($stmt->left);
$stmt_right_type = $statements_analyzer->node_data->getType($stmt->right);
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
&& $stmt_left_type
&& $stmt_right_type
&& $context->mutation_free
) {
self::checkForImpureEqualityComparison(
$statements_analyzer,
2020-05-19 00:57:02 +02:00
$stmt,
$stmt_left_type,
$stmt_right_type
);
}
2018-01-14 18:09:40 +01:00
2020-05-19 00:57:02 +02:00
return true;
}
2019-08-20 04:45:24 +02:00
2020-05-19 00:57:02 +02:00
BinaryOp\NonComparisonOpAnalyzer::analyze(
$statements_analyzer,
$stmt,
$context
);
2019-08-20 04:45:24 +02:00
2020-05-19 00:57:02 +02:00
return true;
}
2019-08-20 04:45:24 +02:00
2020-05-19 00:57:02 +02:00
private static function checkForImpureEqualityComparison(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\BinaryOp\Equal $stmt,
Type\Union $stmt_left_type,
Type\Union $stmt_right_type
) : void {
$codebase = $statements_analyzer->getCodebase();
2019-08-20 04:45:24 +02:00
2020-05-19 00:57:02 +02:00
if ($stmt_left_type->hasString() && $stmt_right_type->hasObjectType()) {
foreach ($stmt_right_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof TNamedObject) {
try {
$storage = $codebase->methods->getStorage(
new \Psalm\Internal\MethodIdentifier(
$atomic_type->value,
'__tostring'
)
);
} catch (\UnexpectedValueException $e) {
continue;
}
if (!$storage->mutation_free) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call a possibly-mutating method '
. $atomic_type->value . '::__toString from a pure context',
new CodeLocation($statements_analyzer, $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
2019-08-20 04:45:24 +02:00
}
}
}
}
2020-05-19 00:57:02 +02:00
} elseif ($stmt_right_type->hasString() && $stmt_left_type->hasObjectType()) {
foreach ($stmt_left_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof TNamedObject) {
try {
$storage = $codebase->methods->getStorage(
new \Psalm\Internal\MethodIdentifier(
$atomic_type->value,
'__tostring'
)
);
} catch (\UnexpectedValueException $e) {
continue;
}
if (!$storage->mutation_free) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call a possibly-mutating method '
. $atomic_type->value . '::__toString from a pure context',
new CodeLocation($statements_analyzer, $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
2018-01-14 18:09:40 +01:00
}
}
}
2018-01-14 18:09:40 +01:00
}
}