diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php index c3f4717e3..62d63cec6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php @@ -42,10 +42,13 @@ use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Union; use UnexpectedValueException; +use function array_filter; use function array_map; use function count; use function explode; use function in_array; +use function is_string; +use function strpos; use function strtolower; /** @@ -422,30 +425,48 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer } if ($method_storage->if_true_assertions) { + $possibilities = array_map( + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( + $template_result, + $lhs_var_id, + $codebase, + ), + $method_storage->if_true_assertions, + ); + if ($lhs_var_id === null) { + $possibilities = array_filter( + $possibilities, + static fn(Possibilities $assertion): bool => !(is_string($assertion->var_id) + && strpos($assertion->var_id, '$this->') === 0 + ) + ); + } $statements_analyzer->node_data->setIfTrueAssertions( $stmt, - array_map( - static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( - $template_result, - $lhs_var_id, - $codebase, - ), - $method_storage->if_true_assertions, - ), + $possibilities, ); } if ($method_storage->if_false_assertions) { + $possibilities = array_map( + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( + $template_result, + $lhs_var_id, + $codebase, + ), + $method_storage->if_false_assertions, + ); + if ($lhs_var_id === null) { + $possibilities = array_filter( + $possibilities, + static fn(Possibilities $assertion): bool => !(is_string($assertion->var_id) + && strpos($assertion->var_id, '$this->') === 0 + ) + ); + } $statements_analyzer->node_data->setIfFalseAssertions( $stmt, - array_map( - static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( - $template_result, - $lhs_var_id, - $codebase, - ), - $method_storage->if_false_assertions, - ), + $possibilities, ); } } diff --git a/tests/AssertAnnotationTest.php b/tests/AssertAnnotationTest.php index cd69d0eb9..eb3281bcb 100644 --- a/tests/AssertAnnotationTest.php +++ b/tests/AssertAnnotationTest.php @@ -2007,6 +2007,30 @@ class AssertAnnotationTest extends TestCase function requiresString(string $_str): void {} ', ], + 'assertionOnPropertyReturnedByMethod' => [ + 'code' => 'id + */ + public function isExists(): bool { + return $this->id !== null; + } + } + + class b { + public ?int $id = null; + public function __construct(private a $a) { + if ($this->getA()->isExists()) { + /** @psalm-check-type-exact $this->id = ?int */ + } + } + public function getA(): a { return $this->a; } + }', + ], 'assertWithEmptyStringOnKeyedArray' => [ 'code' => '