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\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
2020-02-24 00:18:25 +01:00
|
|
|
use Psalm\Issue\ImpureMethodCall;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Type;
|
2018-05-05 18:59:30 +02:00
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2018-12-02 00:37:49 +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,
|
2019-12-16 14:32:00 +01:00
|
|
|
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 05:00:53 +02:00
|
|
|
$expr_result = BinaryOp\AndAnalyzer::analyze(
|
2020-01-01 18:45:17 +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 05:00:53 +02:00
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
|
|
|
|
|
|
|
return $expr_result;
|
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 05:00:53 +02:00
|
|
|
$expr_result = 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 05:00:53 +02:00
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
|
|
|
|
|
|
|
return $expr_result;
|
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
|
2019-12-08 22:35:56 +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->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
|
|
|
}
|
2019-08-04 16:37:36 +02:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
|
2020-05-19 05:00:53 +02:00
|
|
|
$stmt_type = Type::getString();
|
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
BinaryOp\ConcatAnalyzer::analyze(
|
2020-01-07 07:44:12 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->left,
|
|
|
|
$stmt->right,
|
2020-05-19 05:00:53 +02:00
|
|
|
$context,
|
|
|
|
$result_type
|
2018-05-31 00:56:44 +02:00
|
|
|
);
|
|
|
|
|
2020-05-19 05:00:53 +02:00
|
|
|
if ($result_type) {
|
|
|
|
$stmt_type = $result_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
|
|
|
|
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(
|
2018-12-17 21:23:56 +01:00
|
|
|
$statements_analyzer,
|
2020-05-19 00:57:02 +02:00
|
|
|
$stmt,
|
|
|
|
$stmt_left_type,
|
|
|
|
$stmt_right_type
|
2018-12-17 21:23:56 +01:00
|
|
|
);
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|
2020-01-07 14:50:31 +01:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
}
|