Skip to content

Commit 393f970

Browse files
ondrejmirtesclaude
andcommitted
Keep conditional holder keys when entering a closure
enterAnonymousFunctionWithoutReflection() rebuilt each filtered holder list with `[]`, renumbering the holders 0, 1, ... instead of keeping their ConditionalExpressionHolder::getKey() keys. invalidateExpressionEntries() pre-filters holders by whether their key mentions the invalidated expression, so inside the closure a reassigned use variable never dropped the conditionals it guards - a later match narrowed by a stale guard. The native twin skipped the pre-filter for integer keys, so the two diverged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEjx9cAdSfGZDQdTWKaX64
1 parent a491b72 commit 393f970

3 files changed

Lines changed: 53 additions & 3 deletions

File tree

‎src/Analyser/MutatingScope.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,13 +2597,13 @@ public function enterAnonymousFunctionWithoutReflection(
25972597
continue;
25982598
}
25992599
$filteredHolders = [];
2600-
foreach ($holders as $holder) {
2600+
foreach ($holders as $key => $holder) {
26012601
foreach (array_keys($holder->getConditionExpressionTypeHolders()) as $holderExprString) {
26022602
if (!array_key_exists($holderExprString, $useVariableNames)) {
26032603
continue 2;
26042604
}
26052605
}
2606-
$filteredHolders[] = $holder;
2606+
$filteredHolders[$key] = $holder;
26072607
}
26082608
if ($filteredHolders === []) {
26092609
continue;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace ClosureUseConditionalGuardReassigned;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function reassignedGuard(bool $c): void
8+
{
9+
if ($c) {
10+
$a = 1;
11+
$b = 'x';
12+
} else {
13+
$a = 2;
14+
$b = 'y';
15+
}
16+
17+
$f = function () use ($a, $b): void {
18+
if ($a === 1) {
19+
assertType("'x'", $b);
20+
}
21+
22+
$a = rand(1, 2);
23+
if ($a === 1) {
24+
assertType("'x'|'y'", $b);
25+
}
26+
};
27+
}
28+
29+
function guardOffsetWritten(bool $c): void
30+
{
31+
if ($c) {
32+
$item = ['nested' => null, 'k' => 1];
33+
$walk = 'x';
34+
} else {
35+
$item = ['nested' => null, 'k' => 2];
36+
$walk = 'y';
37+
}
38+
39+
$f = function () use ($item, $walk): void {
40+
$item['nested'] = 1;
41+
if ($item['k'] === 1) {
42+
assertType("'x'|'y'", $walk);
43+
}
44+
if ($item === ['nested' => 1, 'k' => 1]) {
45+
assertType("'x'|'y'", $walk);
46+
}
47+
};
48+
}

‎turbo-ext/src/MutatingScope.cpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5380,7 +5380,9 @@ class MutatingScope
53805380
}
53815381
}
53825382
if (!allUsed) continue;
5383-
filteredHolders.push(holderEntry.value().deref());
5383+
zval copy;
5384+
ZVAL_COPY(&copy, holderEntry.value().deref().raw());
5385+
pt_ht_update(filteredHolders.table(), holderEntry.stringKeyOrNull(), holderEntry.indexKey(), &copy);
53845386
}
53855387
if (zend_hash_num_elements(filteredHolders.table()) == 0) continue;
53865388
filteredConditionalExpressions.set(conditionalExprString, zv::Val(std::move(filteredHolders)));

0 commit comments

Comments
 (0)