Skip to content

Commit 1ecf6e0

Browse files
ondrejmirtesclaude
andcommitted
Let the compositional-key shortcut clear virtual-node keys on the invalidated side
The invalidation pre-filters treated every '__phpstan' occurrence in the INVALIDATED expression's key as non-compositional: invalidating a virtual-keyed expression disabled the substring shortcut and swept the scope's whole holder population through the per-holder containment work. The holder side already consulted keyMayHideSubExpressions(); the invalidated side now does too, and ForeachValueByRef, IntertwinedVariableByReference and PropertyInitialization join COMPOSITIONAL_VIRTUAL_KEY_PREFIXES - their printers emit every walked sub-node (or the node walks none). The foreach and parameter original-value markers stay out deliberately: they hide a synthesized Variable child that containment-based invalidation must keep finding when the variable is reassigned. src/Analyser+src/Rules run: shouldInvalidateExpression() calls 759k -> 433k, containment scans 629k -> 297k. Mirrored in the native twin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
1 parent 4c8b9ac commit 1ecf6e0

2 files changed

Lines changed: 19 additions & 7 deletions

File tree

src/Analyser/ScopeOps.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@ final class ScopeOps
4949
private const CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME = 'containsSuperGlobal';
5050

5151
/** Virtual-node key prefixes whose printers include all children verbatim. */
52-
private const COMPOSITIONAL_VIRTUAL_KEY_PREFIXES = ['__phpstanPossiblyImpure(', '__phpstanRemembered('];
52+
// A prefix may be listed only when the printer emits every getSubNodeNames()
53+
// sub-node verbatim (or the node walks no sub-nodes at all). The foreach/
54+
// parameter original-value markers (__phpstanOriginalForeachKey etc.) hide a
55+
// synthesized Variable child on purpose - reassigning the variable must
56+
// invalidate them through containment - so they can never be listed here.
57+
private const COMPOSITIONAL_VIRTUAL_KEY_PREFIXES = ['__phpstanForeachValueByRef(', '__phpstanIntertwinedVariableByReference(', '__phpstanPossiblyImpure(', '__phpstanPropertyInitialization(', '__phpstanRemembered('];
5358

5459
/**
5560
* Mirrors MutatingScope::getNodeKey().
@@ -611,7 +616,7 @@ public static function invalidateExpressionEntries(
611616
// a substring cannot belong to an expression containing the invalidated one, so
612617
// the much more expensive per-expression check can be skipped without being called.
613618
$canUseKeyPrefilter = $exprStringToInvalidate !== '$this'
614-
&& !str_contains($exprStringToInvalidate, '__phpstan')
619+
&& !self::keyMayHideSubExpressions($exprStringToInvalidate)
615620
&& !str_contains($exprStringToInvalidate, '/*');
616621

617622
foreach ($expressionTypes as $exprString => $exprTypeHolder) {
@@ -735,7 +740,7 @@ public static function invalidateMethodsOnExpression(
735740
// call's key embeds its receiver's key verbatim, so when the invalidated key
736741
// does not occur in the entry's key, the receiver cannot match and the entry
737742
// can be kept without re-printing the receiver.
738-
$canUseKeyPrefilter = !str_contains($exprStringToInvalidate, '__phpstan')
743+
$canUseKeyPrefilter = !self::keyMayHideSubExpressions($exprStringToInvalidate)
739744
&& !str_contains($exprStringToInvalidate, '/*');
740745

741746
foreach ($expressionTypes as $exprString => $exprTypeHolder) {
@@ -844,7 +849,7 @@ public static function shouldInvalidateExpression(MutatingScope $scope, ExprPrin
844849
// - keys carrying a getNodeKey() suffix ('/*…*/') are not plain substrings.
845850
if (
846851
$exprStringToInvalidate !== '$this'
847-
&& !str_contains($exprStringToInvalidate, '__phpstan')
852+
&& !self::keyMayHideSubExpressions($exprStringToInvalidate)
848853
&& !str_contains($exprStringToInvalidate, '/*')
849854
&& !str_contains($exprString, $exprStringToInvalidate)
850855
&& !self::keyMayHideSubExpressions($exprString)

turbo-ext/src/ScopeOps.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ class ScopeOps
795795
* containing the invalidated one, so the much more expensive
796796
* per-expression check can be skipped without being called. */
797797
const bool canUseKeyPrefilter = !query.isThis
798-
&& !strContains(exprStringToInvalidate, "__phpstan", sizeof("__phpstan") - 1)
798+
&& !keyMayHideSubExpressions(exprStringToInvalidate)
799799
&& !strContains(exprStringToInvalidate, "/*", 2);
800800

801801
bool invalidated = false;
@@ -1006,7 +1006,7 @@ class ScopeOps
10061006
* method call's key embeds its receiver's key verbatim, so when the
10071007
* invalidated key does not occur in the entry's key, the receiver cannot
10081008
* match and the entry can be kept without re-printing the receiver. */
1009-
const bool canUseKeyPrefilter = !strContains(exprStringToInvalidate, "__phpstan", sizeof("__phpstan") - 1)
1009+
const bool canUseKeyPrefilter = !keyMayHideSubExpressions(exprStringToInvalidate)
10101010
&& !strContains(exprStringToInvalidate, "/*", 2);
10111011

10121012
for (auto entry : expressionTypes) {
@@ -1657,8 +1657,15 @@ class ScopeOps
16571657
*/
16581658
static bool keyMayHideSubExpressions(zend_string *key)
16591659
{
1660+
/* Mirror of ScopeOps::COMPOSITIONAL_VIRTUAL_KEY_PREFIXES - a prefix may
1661+
* be listed only when the printer emits every getSubNodeNames() sub-node
1662+
* verbatim (or the node walks no sub-nodes at all); the foreach/parameter
1663+
* original-value markers hide a synthesized Variable child on purpose. */
16601664
static const struct { const char *prefix; size_t len; } compositionalPrefixes[] = {
1665+
{ "__phpstanForeachValueByRef(", sizeof("__phpstanForeachValueByRef(") - 1 },
1666+
{ "__phpstanIntertwinedVariableByReference(", sizeof("__phpstanIntertwinedVariableByReference(") - 1 },
16611667
{ "__phpstanPossiblyImpure(", sizeof("__phpstanPossiblyImpure(") - 1 },
1668+
{ "__phpstanPropertyInitialization(", sizeof("__phpstanPropertyInitialization(") - 1 },
16621669
{ "__phpstanRemembered(", sizeof("__phpstanRemembered(") - 1 },
16631670
};
16641671

@@ -1901,7 +1908,7 @@ class ScopeOps
19011908

19021909
/* Compositional-key substring gate */
19031910
if (!query.isThis
1904-
&& !strContains(query.exprStringToInvalidate, "__phpstan", sizeof("__phpstan") - 1)
1911+
&& !keyMayHideSubExpressions(query.exprStringToInvalidate)
19051912
&& !strContains(query.exprStringToInvalidate, "/*", 2)
19061913
&& !strContainsStr(exprString, query.exprStringToInvalidate)
19071914
&& !keyMayHideSubExpressions(exprString)) {

0 commit comments

Comments
 (0)