44
55use PhpParser \Node ;
66use PhpParser \Node \Expr ;
7- use PhpParser \Node \Expr \Array_ ;
87use PhpParser \Node \Expr \FuncCall ;
98use PhpParser \Node \Expr \MethodCall ;
10- use PhpParser \Node \Expr \PropertyFetch ;
119use PhpParser \Node \Expr \Variable ;
12- use PhpParser \Node \Name ;
1310use PhpParser \NodeFinder ;
14- use PHPStan \Node \Expr \IntertwinedVariableByReferenceWithExpr ;
1511use PHPStan \Node \Printer \ExprPrinter ;
1612use PHPStan \Node \VirtualNode ;
1713use PHPStan \Parser \ArrayMapArgVisitor ;
18- use PHPStan \Reflection \ClassReflection ;
1914use PHPStan \Reflection \FunctionReflection ;
2015use PHPStan \Reflection \MethodReflection ;
2116use PHPStan \Reflection \ParameterReflection ;
2419use PHPStan \Type \Type ;
2520use function array_filter ;
2621use function array_key_exists ;
27- use function array_key_first ;
22+ use function array_keys ;
2823use function count ;
29- use function get_class ;
3024use function in_array ;
31- use function is_array ;
3225use function is_string ;
33- use function str_contains ;
34- use function strlen ;
35- use function usort ;
3626
3727/**
3828 * Hot scope-table operations extracted from MutatingScope.
@@ -374,7 +364,7 @@ public static function createConditionalExpressions(
374364 // branch — but it remains a valid conditional *target*, so only exclude
375365 // it from guard selection instead of dropping it entirely.
376366 $ guardsToExclude = [];
377- foreach ($ differingKeys as $ exprString => $ unusedDiffMarker ) {
367+ foreach (array_keys ( $ differingKeys) as $ exprString ) {
378368 if (!array_key_exists ($ exprString , $ theirExpressionTypes )) {
379369 continue ;
380370 }
@@ -399,7 +389,7 @@ public static function createConditionalExpressions(
399389 }
400390
401391 $ typeGuards = [];
402- foreach ($ differingKeys as $ exprString => $ unusedDiffMarker ) {
392+ foreach (array_keys ( $ differingKeys) as $ exprString ) {
403393 if (!array_key_exists ($ exprString , $ newVariableTypes )) {
404394 continue ;
405395 }
@@ -443,7 +433,7 @@ public static function createConditionalExpressions(
443433 $ guardIsSuperTypeOfTheirExprCache = [];
444434 $ theirExprIsSuperTypeOfGuardCache = [];
445435
446- foreach ($ differingKeys as $ exprString => $ unusedDiffMarker ) {
436+ foreach (array_keys ( $ differingKeys) as $ exprString ) {
447437 if (!array_key_exists ($ exprString , $ newVariableTypes )) {
448438 continue ;
449439 }
@@ -508,7 +498,7 @@ public static function createConditionalExpressions(
508498 }
509499 }
510500
511- foreach ($ differingKeys as $ exprString => $ unusedDiffMarker ) {
501+ foreach (array_keys ( $ differingKeys) as $ exprString ) {
512502 if (!array_key_exists ($ exprString , $ mergedExpressionTypes )) {
513503 continue ;
514504 }
@@ -526,130 +516,6 @@ public static function createConditionalExpressions(
526516 return $ conditionalExpressions ;
527517 }
528518
529- /**
530- * Depth-first pre-order search for the invalidated expression, replacing a
531- * NodeFinder::findFirst() call - this runs for every (stored expression,
532- * invalidated expression) pair whose keys pass the substring pre-filter,
533- * so the traverser/visitor machinery overhead was significant.
534- *
535- * @param class-string<Expr> $expressionToInvalidateClass
536- */
537- private static function containsExpressionToInvalidate (Scope $ scope , ExprPrinter $ exprPrinter , Node $ node , string $ expressionToInvalidateClass , string $ exprStringToInvalidate ): bool
538- {
539- if (
540- $ exprStringToInvalidate === '$this '
541- && $ node instanceof Name
542- && (
543- in_array ($ node ->toLowerString (), ['self ' , 'static ' , 'parent ' ], true )
544- || ($ scope ->getClassReflection () !== null && $ scope ->getClassReflection ()->is ($ scope ->resolveName ($ node )))
545- )
546- ) {
547- return true ;
548- }
549-
550- if (
551- $ node instanceof $ expressionToInvalidateClass
552- && self ::nodeKey ($ node , $ exprPrinter ) === $ exprStringToInvalidate
553- ) {
554- return true ;
555- }
556-
557- foreach ($ node ->getSubNodeNames () as $ subNodeName ) {
558- $ subNode = $ node ->$ subNodeName ;
559- if ($ subNode instanceof Node) {
560- if (self ::containsExpressionToInvalidate ($ scope , $ exprPrinter , $ subNode , $ expressionToInvalidateClass , $ exprStringToInvalidate )) {
561- return true ;
562- }
563- } elseif (is_array ($ subNode )) {
564- foreach ($ subNode as $ subNodeItem ) {
565- if (
566- $ subNodeItem instanceof Node
567- && self ::containsExpressionToInvalidate ($ scope , $ exprPrinter , $ subNodeItem , $ expressionToInvalidateClass , $ exprStringToInvalidate )
568- ) {
569- return true ;
570- }
571- }
572- }
573- }
574-
575- return false ;
576- }
577-
578- /**
579- * The scan of MutatingScope::invalidateExpression(): computes the tables
580- * with the invalidated entries removed, or null when nothing changed.
581- *
582- * @param array<string, ExpressionTypeHolder> $expressionTypes
583- * @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
584- * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
585- * @return array{array<string, ExpressionTypeHolder>, array<string, ExpressionTypeHolder>, array<string, ConditionalExpressionHolder[]>}|null
586- */
587- public static function invalidateExpressionEntries (
588- MutatingScope $ scope ,
589- ExprPrinter $ exprPrinter ,
590- string $ exprStringToInvalidate ,
591- Expr $ expressionToInvalidate ,
592- bool $ requireMoreCharacters ,
593- ?ClassReflection $ invalidatingClass ,
594- array $ expressionTypes ,
595- array $ nativeExpressionTypes ,
596- array $ conditionalExpressions ,
597- ): ?array
598- {
599- $ invalidated = false ;
600-
601- foreach ($ expressionTypes as $ exprString => $ exprTypeHolder ) {
602- $ exprExpr = $ exprTypeHolder ->getExpr ();
603- if (!self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ exprExpr , (string ) $ exprString , $ requireMoreCharacters , $ invalidatingClass )) {
604- continue ;
605- }
606-
607- unset($ expressionTypes [$ exprString ]);
608- unset($ nativeExpressionTypes [$ exprString ]);
609- $ invalidated = true ;
610- }
611-
612- $ newConditionalExpressions = [];
613- foreach ($ conditionalExpressions as $ conditionalExprString => $ holders ) {
614- if (count ($ holders ) === 0 ) {
615- continue ;
616- }
617- $ firstExpr = $ holders [array_key_first ($ holders )]->getTypeHolder ()->getExpr ();
618- if (self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ firstExpr , self ::nodeKey ($ firstExpr , $ exprPrinter ), $ requireMoreCharacters , $ invalidatingClass )) {
619- $ invalidated = true ;
620- continue ;
621- }
622- $ filteredHolders = [];
623- foreach ($ holders as $ key => $ holder ) {
624- $ shouldKeep = true ;
625- $ conditionalTypeHolders = $ holder ->getConditionExpressionTypeHolders ();
626- foreach ($ conditionalTypeHolders as $ conditionalTypeHolderExprString => $ conditionalTypeHolder ) {
627- if (self ::shouldInvalidateExpression ($ scope , $ exprPrinter , $ exprStringToInvalidate , $ expressionToInvalidate , $ conditionalTypeHolder ->getExpr (), (string ) $ conditionalTypeHolderExprString , invalidatingClass: $ invalidatingClass )) {
628- $ invalidated = true ;
629- $ shouldKeep = false ;
630- break ;
631- }
632- }
633- if (!$ shouldKeep ) {
634- continue ;
635- }
636-
637- $ filteredHolders [$ key ] = $ holder ;
638- }
639- if (count ($ filteredHolders ) <= 0 ) {
640- continue ;
641- }
642-
643- $ newConditionalExpressions [$ conditionalExprString ] = $ filteredHolders ;
644- }
645-
646- if (!$ invalidated ) {
647- return null ;
648- }
649-
650- return [$ expressionTypes , $ nativeExpressionTypes , $ newConditionalExpressions ];
651- }
652-
653519 /**
654520 * The scan of MutatingScope::invalidateMethodsOnExpression(): drops tracked
655521 * MethodCall expressions whose var matches the invalidated key, or returns
@@ -689,76 +555,6 @@ public static function invalidateMethodsOnExpression(
689555 return [$ expressionTypes , $ nativeExpressionTypes ];
690556 }
691557
692- /**
693- * Mirrors the former MutatingScope::shouldInvalidateExpression().
694- */
695- public static function shouldInvalidateExpression (MutatingScope $ scope , ExprPrinter $ exprPrinter , string $ exprStringToInvalidate , Expr $ exprToInvalidate , Expr $ expr , string $ exprString , bool $ requireMoreCharacters = false , ?ClassReflection $ invalidatingClass = null ): bool
696- {
697- if (
698- $ expr instanceof IntertwinedVariableByReferenceWithExpr
699- && $ exprToInvalidate instanceof Variable
700- && is_string ($ exprToInvalidate ->name )
701- && (
702- $ expr ->getVariableName () === $ exprToInvalidate ->name
703- || self ::getIntertwinedRefRootVariableName ($ expr ->getExpr ()) === $ exprToInvalidate ->name
704- || self ::getIntertwinedRefRootVariableName ($ expr ->getAssignedExpr ()) === $ exprToInvalidate ->name
705- )
706- ) {
707- return false ;
708- }
709-
710- if ($ requireMoreCharacters && $ exprStringToInvalidate === $ exprString ) {
711- return false ;
712- }
713-
714- // Variables will not contain traversable expressions. skip the NodeFinder overhead
715- if ($ expr instanceof Variable && is_string ($ expr ->name ) && !$ requireMoreCharacters ) {
716- return $ exprStringToInvalidate === $ exprString ;
717- }
718-
719- // getNodeKey() is the pretty-printed expression, and the standard printer is
720- // compositional: the key of any sub-expression appears verbatim as a substring of
721- // the key of the expression containing it. So if the invalidated expression's key
722- // does not appear anywhere in this expression's key, this expression cannot contain
723- // it and we can skip the expensive AST traversal below.
724- // Carve-outs where that invariant does not hold:
725- // - '$this' is special-cased in the visitor to also match self/static/parent,
726- // - PHPStan's virtual nodes (printed as '__phpstan…') use non-compositional printers
727- // (e.g. a wrapped variable is printed by name, not as '$name'),
728- // - keys carrying a getNodeKey() suffix ('/*…*/') are not plain substrings.
729- if (
730- $ exprStringToInvalidate !== '$this '
731- && !str_contains ($ exprStringToInvalidate , '__phpstan ' )
732- && !str_contains ($ exprStringToInvalidate , '/* ' )
733- && !str_contains ($ exprString , '__phpstan ' )
734- && !str_contains ($ exprString , $ exprStringToInvalidate )
735- ) {
736- return false ;
737- }
738-
739- if (!self ::containsExpressionToInvalidate ($ scope , $ exprPrinter , $ expr , get_class ($ exprToInvalidate ), $ exprStringToInvalidate )) {
740- return false ;
741- }
742-
743- if (
744- $ expr instanceof PropertyFetch
745- && $ requireMoreCharacters
746- && $ scope ->isReadonlyPropertyFetch ($ expr , false )
747- ) {
748- return false ;
749- }
750-
751- if (
752- $ invalidatingClass !== null
753- && $ requireMoreCharacters
754- && $ scope ->isPrivatePropertyOfDifferentClass ($ expr , $ invalidatingClass )
755- ) {
756- return false ;
757- }
758-
759- return true ;
760- }
761-
762558 public static function getIntertwinedRefRootVariableName (Expr $ expr ): ?string
763559 {
764560 if ($ expr instanceof Variable && is_string ($ expr ->name )) {
@@ -770,51 +566,6 @@ public static function getIntertwinedRefRootVariableName(Expr $expr): ?string
770566 return null ;
771567 }
772568
773- /**
774- * The sorted type-specification list of MutatingScope::filterBySpecifiedTypes().
775- *
776- * @param array<string|int, array{Expr, Type}> $sureTypes
777- * @param array<string|int, array{Expr, Type}> $sureNotTypes
778- * @return list<array{sure: bool, exprString: string, expr: Expr, type: Type}>
779- */
780- public static function buildTypeSpecifications (array $ sureTypes , array $ sureNotTypes ): array
781- {
782- $ typeSpecifications = [];
783- foreach ($ sureTypes as $ exprString => [$ expr , $ type ]) {
784- if ($ expr instanceof Node \Scalar || $ expr instanceof Array_ || $ expr instanceof Expr \UnaryMinus && $ expr ->expr instanceof Node \Scalar) {
785- continue ;
786- }
787- $ typeSpecifications [] = [
788- 'sure ' => true ,
789- 'exprString ' => (string ) $ exprString ,
790- 'expr ' => $ expr ,
791- 'type ' => $ type ,
792- ];
793- }
794- foreach ($ sureNotTypes as $ exprString => [$ expr , $ type ]) {
795- if ($ expr instanceof Node \Scalar || $ expr instanceof Array_ || $ expr instanceof Expr \UnaryMinus && $ expr ->expr instanceof Node \Scalar) {
796- continue ;
797- }
798- $ typeSpecifications [] = [
799- 'sure ' => false ,
800- 'exprString ' => (string ) $ exprString ,
801- 'expr ' => $ expr ,
802- 'type ' => $ type ,
803- ];
804- }
805-
806- usort ($ typeSpecifications , static function (array $ a , array $ b ): int {
807- $ length = strlen ($ a ['exprString ' ]) - strlen ($ b ['exprString ' ]);
808- if ($ length !== 0 ) {
809- return $ length ;
810- }
811-
812- return $ b ['sure ' ] - $ a ['sure ' ]; // @phpstan-ignore minus.leftNonNumeric, minus.rightNonNumeric
813- });
814-
815- return $ typeSpecifications ;
816- }
817-
818569 /**
819570 * The conditional-expressions fixed-point matching of
820571 * MutatingScope::filterBySpecifiedTypes().
0 commit comments