From 31f6dfa417a886c3be191622f20ec90fcbbb3952 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Oct 2026 22:18:33 +0100 Subject: [PATCH] [Php80] Fix TernaryToNullsafeCoalesceRector producing redundant nullsafe property access under coalesce --- .../property_coalesce_fallback.php.inc | 31 +++++++++++++++++++ .../TernaryToNullsafeCoalesceRector.php | 24 ++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 rules-tests/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector/Fixture/property_coalesce_fallback.php.inc diff --git a/rules-tests/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector/Fixture/property_coalesce_fallback.php.inc b/rules-tests/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector/Fixture/property_coalesce_fallback.php.inc new file mode 100644 index 00000000000..77217baf639 --- /dev/null +++ b/rules-tests/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector/Fixture/property_coalesce_fallback.php.inc @@ -0,0 +1,31 @@ +name : 'fallback'; + } +} + +?> +----- +name ?? 'fallback'; + } +} + +?> diff --git a/rules/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector.php b/rules/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector.php index 19076d2d302..4f787e389cc 100644 --- a/rules/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector.php +++ b/rules/Php80/Rector/Ternary/TernaryToNullsafeCoalesceRector.php @@ -105,6 +105,13 @@ public function refactor(Node $node): ?Node return null; } + // reading a property on null is already suppressed by "??", so the nullsafe "?->" is + // redundant here and PHPStan reports it; keep the plain "->" chain for property-only + // access, while a method call must stay nullsafe to avoid a call-on-null error + if ($this->isPurePropertyChain($callExpr, $checkedExpr)) { + return new Coalesce($callExpr, $fallbackExpr); + } + return new Coalesce($nullsafeExpr, $fallbackExpr); } @@ -168,6 +175,23 @@ private function createNullsafeChain(Expr $expr, Expr $checkedExpr): ?Expr return null; } + /** + * A chain of plain property fetches rooted in the checked expression, e.g. "$a->b->c", + * with no method call in between. + */ + private function isPurePropertyChain(Expr $expr, Expr $checkedExpr): bool + { + if ($this->nodeComparator->areNodesEqual($expr, $checkedExpr)) { + return true; + } + + if ($expr instanceof PropertyFetch) { + return $this->isPurePropertyChain($expr->var, $checkedExpr); + } + + return false; + } + private function isPureExpr(Expr $expr): bool { if ($expr instanceof Variable) {