mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 13:51:54 +01:00
4.x - add support for the nullsafe operator
This commit is contained in:
parent
780bc14de3
commit
956199c688
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements\Expression;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class NullsafeAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\NullsafePropertyFetch|PhpParser\Node\Expr\NullsafeMethodCall $stmt
|
||||
*/
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr $stmt,
|
||||
Context $context
|
||||
) : bool {
|
||||
if (!$stmt->var instanceof PhpParser\Node\Expr\Variable) {
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $stmt->var, $context);
|
||||
|
||||
$tmp_name = '__tmp_nullsafe__' . (int) $stmt->var->getAttribute('startFilePos');
|
||||
|
||||
$condition_type = $statements_analyzer->node_data->getType($stmt->var);
|
||||
|
||||
if ($condition_type) {
|
||||
$context->vars_in_scope['$' . $tmp_name] = $condition_type;
|
||||
|
||||
$tmp_var = new PhpParser\Node\Expr\Variable($tmp_name, $stmt->var->getAttributes());
|
||||
} else {
|
||||
$tmp_var = $stmt->var;
|
||||
}
|
||||
} else {
|
||||
$tmp_var = $stmt->var;
|
||||
}
|
||||
|
||||
$old_node_data = $statements_analyzer->node_data;
|
||||
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
|
||||
|
||||
$null_value1 = new PhpParser\Node\Expr\ConstFetch(
|
||||
new PhpParser\Node\Name('null'),
|
||||
$stmt->var->getAttributes()
|
||||
);
|
||||
|
||||
$null_comparison = new PhpParser\Node\Expr\BinaryOp\Identical(
|
||||
$tmp_var,
|
||||
$null_value1,
|
||||
$stmt->var->getAttributes()
|
||||
);
|
||||
|
||||
$null_value2 = new PhpParser\Node\Expr\ConstFetch(
|
||||
new PhpParser\Node\Name('null'),
|
||||
$stmt->var->getAttributes()
|
||||
);
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\NullsafePropertyFetch) {
|
||||
$ternary = new PhpParser\Node\Expr\Ternary(
|
||||
$null_comparison,
|
||||
$null_value2,
|
||||
new PhpParser\Node\Expr\PropertyFetch($tmp_var, $stmt->name, $stmt->getAttributes()),
|
||||
$stmt->getAttributes()
|
||||
);
|
||||
} else {
|
||||
$ternary = new PhpParser\Node\Expr\Ternary(
|
||||
$null_comparison,
|
||||
$null_value2,
|
||||
new PhpParser\Node\Expr\MethodCall($tmp_var, $stmt->name, $stmt->args, $stmt->getAttributes()),
|
||||
$stmt->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $ternary, $context);
|
||||
|
||||
$ternary_type = $statements_analyzer->node_data->getType($ternary);
|
||||
|
||||
$statements_analyzer->node_data = $old_node_data;
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $ternary_type ?: Type::getMixed());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -375,6 +375,12 @@ class ExpressionAnalyzer
|
||||
return ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\NullsafePropertyFetch
|
||||
|| $stmt instanceof PhpParser\Node\Expr\NullsafeMethodCall
|
||||
) {
|
||||
return Expression\NullsafeAnalyzer::analyze($statements_analyzer, $stmt, $context);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Error) {
|
||||
// do nothing
|
||||
return true;
|
||||
|
@ -2822,6 +2822,50 @@ class ConditionalTest extends \Psalm\Tests\TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'nullsafePropertyAccess' => [
|
||||
'<?php
|
||||
class IntLinkedList {
|
||||
public function __construct(
|
||||
public int $value,
|
||||
public ?self $next
|
||||
) {}
|
||||
}
|
||||
|
||||
function skipOn(IntLinkedList $l) : ?int {
|
||||
return $l->next?->value;
|
||||
}
|
||||
|
||||
function skipTwo(IntLinkedList $l) : ?int {
|
||||
return $l->next?->next?->value;
|
||||
}',
|
||||
[],
|
||||
[],
|
||||
'8.0'
|
||||
],
|
||||
'nullsafeMethodCall' => [
|
||||
'<?php
|
||||
class IntLinkedList {
|
||||
public function __construct(
|
||||
public int $value,
|
||||
private ?self $next
|
||||
) {}
|
||||
|
||||
public function getNext() : ?self {
|
||||
return $this->next;
|
||||
}
|
||||
}
|
||||
|
||||
function skipOne(IntLinkedList $l) : ?int {
|
||||
return $l->getNext()?->value;
|
||||
}
|
||||
|
||||
function skipTwo(IntLinkedList $l) : ?int {
|
||||
return $l->getNext()?->getNext()?->value;
|
||||
}',
|
||||
[],
|
||||
[],
|
||||
'8.0'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user