Merge scope after ?-> method call to account for argument short-circuiting#5456
Merged
VincentLanglet merged 4 commits intophpstan:2.1.xfrom Apr 22, 2026
Merged
Conversation
…cuiting - In NullsafeMethodCallHandler::processExpr(), merge the post-call scope with the pre-call scope when the var type is nullable, so variables assigned in arguments become "maybe defined" rather than "definitely defined" - When the var type is always null, use the pre-call scope directly since arguments are never evaluated - Add DefinedVariableRule regression tests for nullable, always-null, and multi-argument cases - Add NSRT type inference tests verifying correct types after nullsafe calls
…ll() Address review feedback: cache $varType->isNull() in a variable to avoid computing it twice, and use ->maybe() instead of TypeCombinator::containsNull() for the nullable check. Update throw-points test: doesntThrow() returns mixed which can be null, so ?-> may short-circuit and variables assigned in the method name/arguments should be "maybe defined", not "yes defined". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a companion test case where the left operand of ?-> is mixed~null (obtained via null-narrowing). Since the value can never be null, ?-> never short-circuits and variables assigned in arguments have Yes certainty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12bdb77 to
25f307f
Compare
25f307f to
709ba5f
Compare
VincentLanglet
approved these changes
Apr 22, 2026
staabm
approved these changes
Apr 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PHP's nullsafe operator
?->short-circuits evaluation when the left operand is null, meaning method arguments are not evaluated. PHPStan was not accounting for this: variables assigned within nullsafe method call arguments were treated as "definitely defined" even though they might not be evaluated at all.Changes
src/Analyser/ExprHandler/NullsafeMethodCallHandler.php:Root cause
NullsafeMethodCallHandler::processExpr()converts$date?->format($format = "Y-m-d")into a regularMethodCalland processes it unconditionally. This means$formatgets added to the scope as "definitely defined". But in PHP, when$dateis null, the entire method call including argument evaluation is short-circuited —$formatis never assigned.The fix mirrors how
CoalesceHandlerandBooleanAndHandleralready handle conditional evaluation: by merging the scope from the "executed" path with the scope from the "not executed" path, making any newly-introduced variables "maybe defined".Analogous cases probed:
NullsafePropertyFetchHandler: Property names are almost alwaysIdentifiernodes (not expressions), so there are no side effects to short-circuit. The rare case of$obj?->$namewhere$namehas side effects is theoretical — skipped.$a?->b()?->c($x = 1)): Each?->is a separateNullsafeMethodCallnode, so the fix applies independently at each level — works correctly.Test
tests/PHPStan/Rules/Variables/data/bug-10729.php— DefinedVariableRule test covering:$formatreported as "might not be defined" (the reported bug)$formatreported as "Undefined variable" (arguments never evaluated)$formatand$valuereported as "might not be defined"$result = $date?->format($format = ...)with$formatcorrectly "might not be defined"tests/PHPStan/Analyser/nsrt/bug-10729.php— Type inference test verifying correct types for variables assigned in nullsafe argumentsFixes phpstan/phpstan#10729