diff --git a/src/Psalm/Internal/Diff/ClassStatementsDiffer.php b/src/Psalm/Internal/Diff/ClassStatementsDiffer.php index 5bc637b4624..65cccb3e60c 100644 --- a/src/Psalm/Internal/Diff/ClassStatementsDiffer.php +++ b/src/Psalm/Internal/Diff/ClassStatementsDiffer.php @@ -3,9 +3,11 @@ namespace Psalm\Internal\Diff; use PhpParser; +use UnexpectedValueException; use function count; use function get_class; +use function is_string; use function strpos; use function strtolower; use function substr; @@ -230,7 +232,19 @@ static function ( /** @var PhpParser\Node */ $affected_elem = $diff_elem->type === DiffElem::TYPE_REMOVE ? $diff_elem->old : $diff_elem->new; if ($affected_elem instanceof PhpParser\Node\Stmt\ClassMethod) { - $add_or_delete[] = $name_lc . '::' . strtolower((string) $affected_elem->name); + $method_name = strtolower((string) $affected_elem->name); + $add_or_delete[] = $name_lc . '::' . $method_name; + if ($method_name === '__construct') { + foreach ($affected_elem->getParams() as $param) { + if (!$param->flags || !$param->var instanceof PhpParser\Node\Expr\Variable) { + continue; + } + if ($param->var instanceof PhpParser\Node\Expr\Error || !is_string($param->var->name)) { + throw new UnexpectedValueException('Not expecting param name to be non-string'); + } + $add_or_delete[] = $name_lc . '::$' . $param->var->name; + } + } } elseif ($affected_elem instanceof PhpParser\Node\Stmt\Property) { foreach ($affected_elem->props as $prop) { $add_or_delete[] = $name_lc . '::$' . $prop->name; diff --git a/tests/Cache/CacheTest.php b/tests/Cache/CacheTest.php index 04f44eda644..8e883c65eb3 100644 --- a/tests/Cache/CacheTest.php +++ b/tests/Cache/CacheTest.php @@ -248,5 +248,51 @@ public function foo($baz): void ], ], ]; + + yield 'constructorPropertyPromotionChange' => [ + [ + [ + 'files' => [ + '/src/A.php' => <<<'PHP' + foo; + } + } + PHP, + ], + 'issues' => [], + ], + [ + 'files' => [ + '/src/A.php' => <<<'PHP' + foo; + } + } + PHP, + ], + 'issues' => [ + '/src/A.php' => [ + "UndefinedThisPropertyFetch: Instance property A::\$foo is not defined", + "MixedReturnStatement: Could not infer a return type", + "MixedInferredReturnType: Could not verify return type 'string' for A::bar", + ], + ], + ], + ], + ]; } }