Skip to content

Commit 787ffe1

Browse files
phpstan-botclaude
andcommitted
Use separate attribute for callCallbackImmediately to avoid breaking by-ref closure loop
The previous commit reused ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME for callbacks that are invoked immediately (e.g. array_map). This caused a regression because NodeScopeResolver::processClosureNode uses that same attribute to break the do-while loop after one iteration (correct for IIFEs called once, but wrong for array_map callbacks called multiple times with by-ref variables like &$isVariadic). Introduces a new CALL_CALLBACK_IMMEDIATELY_ATTRIBUTE_NAME constant to distinguish "callback invoked immediately by a function" from "IIFE called exactly once". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2e7e5ef commit 787ffe1

2 files changed

Lines changed: 4 additions & 2 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ class MutatingScope implements Scope, NodeCallbackInvoker
146146
{
147147

148148
public const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
149+
public const CALL_CALLBACK_IMMEDIATELY_ATTRIBUTE_NAME = 'callCallbackImmediately';
149150
private const CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME = 'containsSuperGlobal';
150151

151152
/** @var Type[] */
@@ -2155,6 +2156,7 @@ public function enterAnonymousFunctionWithoutReflection(
21552156

21562157
if (
21572158
$closure->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) !== true
2159+
&& $closure->getAttribute(self::CALL_CALLBACK_IMMEDIATELY_ATTRIBUTE_NAME) !== true
21582160
&& (
21592161
$expr instanceof PropertyFetch
21602162
|| $expr instanceof MethodCall

src/Analyser/NodeScopeResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3408,7 +3408,7 @@ public function processArgs(
34083408

34093409
$callCallbackImmediately = $this->callCallbackImmediately($parameter, $parameterType, $calleeReflection);
34103410
if ($callCallbackImmediately) {
3411-
$arg->value->setAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME, true);
3411+
$arg->value->setAttribute(MutatingScope::CALL_CALLBACK_IMMEDIATELY_ATTRIBUTE_NAME, true);
34123412
}
34133413

34143414
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);
@@ -3471,7 +3471,7 @@ public function processArgs(
34713471

34723472
$callCallbackImmediately = $this->callCallbackImmediately($parameter, $parameterType, $calleeReflection);
34733473
if ($callCallbackImmediately) {
3474-
$arg->value->setAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME, true);
3474+
$arg->value->setAttribute(MutatingScope::CALL_CALLBACK_IMMEDIATELY_ATTRIBUTE_NAME, true);
34753475
}
34763476

34773477
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $storage, $context);

0 commit comments

Comments
 (0)