2929use function count ;
3030use function get_class ;
3131use function in_array ;
32+ use function is_array ;
3233use function is_string ;
3334use function str_contains ;
3435use function strlen ;
@@ -651,15 +652,15 @@ public static function invalidateExpressionEntries(
651652 && !str_contains ($ exprStringToInvalidate , '/* ' );
652653
653654 foreach ($ expressionTypes as $ exprString => $ exprTypeHolder ) {
654- $ exprString = (string ) $ exprString ;
655+ $ exprString = (string ) $ exprString ; // @phpstan-ignore cast.useless
655656 if (
656657 $ canUseKeyPrefilter
657658 && !str_contains ($ exprString , $ exprStringToInvalidate )
658659 && !self ::keyMayHideSubExpressions ($ exprString )
659660 ) {
660661 continue ;
661662 }
662- if (!self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ exprTypeHolder , $ exprString , $ requireMoreCharacters , $ invalidatingClass )) {
663+ if (!self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ exprTypeHolder-> getExpr () , $ exprString , $ requireMoreCharacters , $ invalidatingClass )) {
663664 continue ;
664665 }
665666
@@ -673,13 +674,14 @@ public static function invalidateExpressionEntries(
673674 if (count ($ holders ) === 0 ) {
674675 continue ;
675676 }
677+ $ conditionalExprString = (string ) $ conditionalExprString ; // @phpstan-ignore cast.useless
676678 if (
677679 !$ canUseKeyPrefilter
678- || str_contains (( string ) $ conditionalExprString , $ exprStringToInvalidate )
679- || self ::keyMayHideSubExpressions (( string ) $ conditionalExprString )
680+ || str_contains ($ conditionalExprString , $ exprStringToInvalidate )
681+ || self ::keyMayHideSubExpressions ($ conditionalExprString )
680682 ) {
681683 $ firstHolder = $ holders [array_key_first ($ holders )]->getTypeHolder ();
682- if (self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ firstHolder , self ::nodeKey ($ firstHolder ->getExpr (), $ exprPrinter ), $ requireMoreCharacters , $ invalidatingClass )) {
684+ if (self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ firstHolder-> getExpr () , self ::nodeKey ($ firstHolder ->getExpr (), $ exprPrinter ), $ requireMoreCharacters , $ invalidatingClass )) {
683685 $ invalidated = true ;
684686 continue ;
685687 }
@@ -701,7 +703,7 @@ public static function invalidateExpressionEntries(
701703 $ shouldKeep = true ;
702704 $ conditionalTypeHolders = $ holder ->getConditionExpressionTypeHolders ();
703705 foreach ($ conditionalTypeHolders as $ conditionalTypeHolderExprString => $ conditionalTypeHolder ) {
704- if (self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ conditionalTypeHolder , $ conditionalTypeHolderExprString , invalidatingClass: $ invalidatingClass )) {
706+ if (self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ conditionalTypeHolder-> getExpr () , $ conditionalTypeHolderExprString , invalidatingClass: $ invalidatingClass )) {
705707 $ invalidated = true ;
706708 $ shouldKeep = false ;
707709 break ;
@@ -728,14 +730,59 @@ public static function invalidateExpressionEntries(
728730 }
729731
730732 /**
731- * Answers one (stored holder, invalidated expression) containment check
732- * from the holder's contained-node-keys index; '$this' invalidations keep
733- * the finder because they also match self/static/parent and the current
734- * class name, whose resolution depends on the asking scope.
733+ * Depth-first pre-order search for the invalidated expression, replacing a
734+ * NodeFinder::findFirst() call - this runs for every (stored expression,
735+ * invalidated expression) pair whose keys pass the substring pre-filter,
736+ * so the traverser/visitor machinery overhead was significant.
737+ *
738+ * @param class-string<Expr> $expressionToInvalidateClass
739+ */
740+ private static function containsExpressionToInvalidate (Scope $ scope , ExprPrinter $ exprPrinter , Node $ node , string $ expressionToInvalidateClass , string $ exprStringToInvalidate ): bool
741+ {
742+ if (
743+ $ exprStringToInvalidate === '$this '
744+ && $ node instanceof Name
745+ && (
746+ in_array ($ node ->toLowerString (), ['self ' , 'static ' , 'parent ' ], true )
747+ || ($ scope ->getClassReflection () !== null && $ scope ->getClassReflection ()->is ($ scope ->resolveName ($ node )))
748+ )
749+ ) {
750+ return true ;
751+ }
752+
753+ if (
754+ $ node instanceof $ expressionToInvalidateClass
755+ && self ::nodeKey ($ node , $ exprPrinter ) === $ exprStringToInvalidate
756+ ) {
757+ return true ;
758+ }
759+
760+ foreach ($ node ->getSubNodeNames () as $ subNodeName ) {
761+ $ subNode = $ node ->$ subNodeName ;
762+ if ($ subNode instanceof Node) {
763+ if (self ::containsExpressionToInvalidate ($ scope , $ exprPrinter , $ subNode , $ expressionToInvalidateClass , $ exprStringToInvalidate )) {
764+ return true ;
765+ }
766+ } elseif (is_array ($ subNode )) {
767+ foreach ($ subNode as $ subNodeItem ) {
768+ if (
769+ $ subNodeItem instanceof Node
770+ && self ::containsExpressionToInvalidate ($ scope , $ exprPrinter , $ subNodeItem , $ expressionToInvalidateClass , $ exprStringToInvalidate )
771+ ) {
772+ return true ;
773+ }
774+ }
775+ }
776+ }
777+
778+ return false ;
779+ }
780+
781+ /**
782+ * Mirrors the former MutatingScope::shouldInvalidateExpression().
735783 */
736- public static function shouldInvalidateExpression (MutatingScope $ scope , ExprPrinter $ exprPrinter , string $ exprStringToInvalidate , Expr $ exprToInvalidate , ExpressionTypeHolder $ exprTypeHolder , string $ exprString , bool $ requireMoreCharacters = false , ?ClassReflection $ invalidatingClass = null ): bool
784+ public static function shouldInvalidateExpression (MutatingScope $ scope , ExprPrinter $ exprPrinter , string $ exprStringToInvalidate , Expr $ exprToInvalidate , Expr $ expr , string $ exprString , bool $ requireMoreCharacters = false , ?ClassReflection $ invalidatingClass = null ): bool
737785 {
738- $ expr = $ exprTypeHolder ->getExpr ();
739786 if (
740787 $ expr instanceof IntertwinedVariableByReferenceWithExpr
741788 && $ exprToInvalidate instanceof Variable
@@ -778,34 +825,7 @@ public static function shouldInvalidateExpression(MutatingScope $scope, ExprPrin
778825 return false ;
779826 }
780827
781- if ($ exprStringToInvalidate === '$this ' ) {
782- // '$this' also matches self/static/parent and the current class name -
783- // name resolution depends on this scope, so the holder-cached key
784- // index below cannot answer it
785- $ nodeFinder = new NodeFinder ();
786- $ expressionToInvalidateClass = get_class ($ exprToInvalidate );
787- $ found = $ nodeFinder ->findFirst ([$ expr ], static function (Node $ node ) use ($ scope , $ exprPrinter , $ expressionToInvalidateClass , $ exprStringToInvalidate ): bool {
788- if (
789- $ node instanceof Name
790- && (
791- in_array ($ node ->toLowerString (), ['self ' , 'static ' , 'parent ' ], true )
792- || ($ scope ->getClassReflection () !== null && $ scope ->getClassReflection ()->is ($ scope ->resolveName ($ node )))
793- )
794- ) {
795- return true ;
796- }
797-
798- if (!$ node instanceof $ expressionToInvalidateClass ) {
799- return false ;
800- }
801-
802- return self ::nodeKey ($ node , $ exprPrinter ) === $ exprStringToInvalidate ;
803- });
804-
805- if ($ found === null ) {
806- return false ;
807- }
808- } elseif (!isset ($ exprTypeHolder ->getContainedNodeKeys (static fn (Expr $ node ): string => self ::nodeKey ($ node , $ exprPrinter ))[$ exprStringToInvalidate ][get_class ($ exprToInvalidate )])) {
828+ if (!self ::containsExpressionToInvalidate ($ scope , $ exprPrinter , $ expr , get_class ($ exprToInvalidate ), $ exprStringToInvalidate )) {
809829 return false ;
810830 }
811831
0 commit comments