1
0
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:
Matt Brown 2020-10-03 20:21:44 -04:00 committed by Daniil Gentili
parent 780bc14de3
commit 956199c688
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
3 changed files with 137 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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'
]
];
}