Skip to content

Commit

Permalink
Allow @psalm-assert to propagate member assertions to the caller's co…
Browse files Browse the repository at this point in the history
…ntext (#2100)

This change injects method call assertions that involve member variables
to the caller context by replacing `$this->` with the lhs of the member
call.

Fixes: #2099
  • Loading branch information
lhchavez authored and muglug committed Sep 6, 2019
1 parent 071fae9 commit ae2c671
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 0 deletions.
Expand Up @@ -670,6 +670,7 @@ public static function analyze(
if ($function_storage->assertions && $stmt->name instanceof PhpParser\Node\Name) {
self::applyAssertionsToContext(
$stmt->name,
null,
$function_storage->assertions,
$stmt->args,
$generic_params ?: [],
Expand Down
Expand Up @@ -39,11 +39,13 @@
use function is_string;
use function array_values;
use function array_shift;
use function array_unshift;
use function get_class;
use function strtolower;
use function array_map;
use function array_merge;
use function explode;
use function implode;
use function array_search;
use function array_keys;
use function in_array;
Expand Down Expand Up @@ -1306,6 +1308,7 @@ function (PhpParser\Node\Arg $arg) {
if ($method_storage->assertions) {
self::applyAssertionsToContext(
$stmt->name,
ExpressionAnalyzer::getArrayVarId($stmt->var, null, $statements_analyzer),
$method_storage->assertions,
$args,
$class_template_params ?: [],
Expand Down
Expand Up @@ -905,6 +905,7 @@ function (PhpParser\Node\Arg $arg) {
if ($method_storage->assertions) {
self::applyAssertionsToContext(
$stmt->name,
null,
$method_storage->assertions,
$stmt->args,
$found_generic_params ?: [],
Expand Down
Expand Up @@ -50,10 +50,12 @@
use function in_array;
use function array_reverse;
use function array_filter;
use function is_null;
use function is_string;
use function assert;
use function preg_match;
use function preg_replace;
use function str_replace;
use function is_int;
use function substr;
use function array_merge;
Expand Down Expand Up @@ -3187,6 +3189,7 @@ protected static function checkFunctionExists(
/**
* @param PhpParser\Node\Identifier|PhpParser\Node\Name $expr
* @param \Psalm\Storage\Assertion[] $assertions
* @param string $thisName
* @param array<int, PhpParser\Node\Arg> $args
* @param Context $context
* @param array<string, array<string, array{Type\Union}>> $template_type_map,
Expand All @@ -3196,6 +3199,7 @@ protected static function checkFunctionExists(
*/
protected static function applyAssertionsToContext(
$expr,
?string $thisName,
array $assertions,
array $args,
array $template_type_map,
Expand Down Expand Up @@ -3225,6 +3229,8 @@ protected static function applyAssertionsToContext(
}
} elseif (isset($context->vars_in_scope[$assertion->var_id])) {
$assertion_var_id = $assertion->var_id;
} elseif (strpos($assertion->var_id, '$this->') === 0 && !is_null($thisName)) {
$assertion_var_id = $thisName . str_replace('$this->', '->', $assertion->var_id);
}

if ($assertion_var_id) {
Expand Down
26 changes: 26 additions & 0 deletions tests/AssertTest.php
Expand Up @@ -685,6 +685,32 @@ public function assertProperty() : bool {
}
}'
],
'assertPropertyVisibleOutside' => [
'<?php
class A {
public ?int $x = null;
public function maybeAssignX() : void {
if (rand(0, 0) == 0) {
$this->x = 0;
}
}
/**
* @psalm-assert !null $this->x
*/
public function assertProperty() : void {
if (is_null($this->x)) {
throw new RuntimeException();
}
}
}
$a = new A();
$a->maybeAssignX();
$a->assertProperty();
echo (2 * $a->x);',
],
];
}

Expand Down

0 comments on commit ae2c671

Please sign in to comment.