Treat first-class callable NullsafeMethodCall as a MethodCall instead of throwing#5888
Treat first-class callable NullsafeMethodCall as a MethodCall instead of throwing#5888phpstan-bot wants to merge 2 commits into
NullsafeMethodCall as a MethodCall instead of throwing#5888Conversation
…tead of throwing
- `NodeScopeResolver::processExprNode()` handled the first-class callable
syntax (`foo(...)`) for `FuncCall`, `MethodCall`, `StaticCall` and `New_`,
but `NullsafeMethodCall` (`$foo?->bar(...)`) fell through to the `else`
branch and threw `ShouldNotHappenException`, surfacing as an internal error.
- `NullsafeMethodCall` is the only `Expr\CallLike` subclass that was missing
from this branch. It is added by wrapping the call in a `MethodCallableNode`
built from an equivalent `MethodCall`, since `$foo?->bar(...)` is a fatal
error in PHP ("Cannot combine nullsafe operator with Closure creation") and
must not crash the analyser.
- Type resolution already routed correctly through `NullsafeMethodCallHandler`,
so no internal error occurs anywhere the expression is typed (assignment,
argument, array element, return).
staabm
left a comment
There was a problem hiding this comment.
Crash bug fixes need a AnalyserIntegrationTest
…ll crash Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done. I addressed staabm's review by adding the requested What I did
Verification
Committed as |
Summary
Using the first-class callable syntax together with the nullsafe operator
(
$foo?->bar(...)) crashed PHPStan withInternal error.. While this is afatal error in PHP itself ("Cannot combine nullsafe operator with Closure
creation"), PHPStan must not crash on it. This change makes the analyser treat
such an expression as a regular method-call first-class callable.
Changes
src/Analyser/NodeScopeResolver.php: in the first-class callable branch ofprocessExprNode(), handleExpr\NullsafeMethodCallby building aMethodCallableNodefrom an equivalentMethodCall, instead of fallingthrough to the
throw new ShouldNotHappenException().tests/PHPStan/Analyser/nsrt/bug-9746.php: regression test asserting theinferred types and (implicitly) that no internal error is thrown.
Root cause
processExprNode()enumerated theExpr\CallLikesubclasses that support thefirst-class callable syntax —
FuncCall,MethodCall,StaticCall,New_—and threw
ShouldNotHappenExceptionfor anything else.NullsafeMethodCallisalso an
Expr\CallLikewhoseisFirstClassCallable()can betrue(the parseraccepts
$foo?->bar(...)even though PHP rejects it at runtime), so it hit theelsebranch and crashed. It was the onlyExpr\CallLikesubclass not covered.Test
tests/PHPStan/Analyser/nsrt/bug-9746.phpreproduces the original crash via$foo?->doFoo(...)(nullable and non-nullable receivers, plus an assignment)and asserts
(Closure(): int)|null/Closure(): int. It errors with aninternal error before the fix and passes after.
(statement, assignment, call argument, array element, return).
Analogous cases probed
Expr\CallLikesubclasses (FuncCall,MethodCall,StaticCall,New_) were already handled;NullsafeMethodCallwas the only missing one.
MutatingScope::getKeepVoidType()already listsNullsafeMethodCallin itsfirst-class-callable guard, and type resolution flows through
NullsafeMethodCallHandler, so no internal error occurs when the expressionis typed.
Fixes phpstan/phpstan#9746