3636use PHPStan \Type \Type ;
3737use function array_key_exists ;
3838use function count ;
39+ use function get_class ;
3940use function in_array ;
4041use function is_file ;
4142use function spl_object_id ;
4445final class DependencyResolver
4546{
4647
48+ private const PROFILE_VAR_TAGS = 1 ;
49+
50+ private const PROFILE_CHAIN = 2 ;
51+
52+ private const PROFILE_EXPORT = 4 ;
53+
54+ private const PROFILE_NAME_SCOPE = 8 ;
55+
56+ /** Node classes the branch chain in collectNodeDependencies() reacts to */
57+ private const CHAIN_NODE_TYPES = [
58+ Node \Stmt \Class_::class,
59+ Node \Stmt \Interface_::class,
60+ Node \Stmt \Enum_::class,
61+ InClassMethodNode::class,
62+ InPropertyHookNode::class,
63+ ClassPropertyNode::class,
64+ InFunctionNode::class,
65+ Closure::class,
66+ Node \Expr \ArrowFunction::class,
67+ Node \Expr \FuncCall::class,
68+ Node \Expr \MethodCall::class,
69+ Node \Expr \PropertyFetch::class,
70+ Node \Expr \StaticCall::class,
71+ Node \Expr \ClassConstFetch::class,
72+ Node \Expr \ConstFetch::class,
73+ Node \Expr \StaticPropertyFetch::class,
74+ Node \Expr \New_::class,
75+ Node \Stmt \Trait_::class,
76+ Node \Stmt \TraitUse::class,
77+ Node \Expr \Instanceof_::class,
78+ Node \Expr \Include_::class,
79+ Node \Stmt \Catch_::class,
80+ ArrayDimFetch::class,
81+ Foreach_::class,
82+ Array_::class,
83+ StaticMethodCallableNode::class,
84+ MethodCallableNode::class,
85+ FunctionCallableNode::class,
86+ InstantiationCallableNode::class,
87+ ];
88+
89+ /**
90+ * Node classes ExportedNodeResolver::resolve() reacts to. A class member is not among them: it is
91+ * exported as part of the class declaring it, through exportClassStatement(), never on its own.
92+ */
93+ private const EXPORT_NODE_TYPES = [
94+ Node \Stmt \Class_::class,
95+ Node \Stmt \Interface_::class,
96+ Node \Stmt \Enum_::class,
97+ Node \Stmt \Trait_::class,
98+ Node \Stmt \Function_::class,
99+ Node \Stmt \Const_::class,
100+ Node \Expr \FuncCall::class,
101+ ];
102+
103+ /** Node classes ExportedNameScopeTracker::enterNode() reacts to */
104+ private const NAME_SCOPE_NODE_TYPES = [
105+ Node \Stmt \Namespace_::class,
106+ Node \Stmt \Use_::class,
107+ Node \Stmt \GroupUse::class,
108+ ];
109+
47110 /** @var array<string, array<int, ClassReflection|FunctionReflection|ConstantReflection>> reflections keyed by spl_object_id() */
48111 private array $ classDependencies = [];
49112
113+ /** @var array<class-string, int> */
114+ private array $ nodeProfiles = [];
115+
50116 private ExportedNameScopeTracker $ nameScopeTracker ;
51117
52118 private ?string $ nameScopeFile = null ;
@@ -72,27 +138,42 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
72138 $ this ->nameScopeFile = $ file ;
73139 $ this ->nameScopeTracker ->reset ();
74140 }
75- $ this ->nameScopeTracker ->enterNode ($ node );
141+ $ nodeClass = get_class ($ node );
142+ $ nodeProfile = $ this ->nodeProfiles [$ nodeClass ] ??= $ this ->resolveNodeProfile ($ node );
143+
144+ if (($ nodeProfile & self ::PROFILE_NAME_SCOPE ) !== 0 ) {
145+ $ this ->nameScopeTracker ->enterNode ($ node );
146+ }
76147
77148 // Keyed by spl_object_id(), so that a reflection collected again - every level of a class hierarchy
78149 // repeats the interfaces it inherits, and the classes a node references share most of their
79150 // ancestors - is kept only once instead of being resolved to its file and package once more.
80151 $ dependenciesReflections = [];
81152 $ dependenciesFilePaths = [];
82153
83- if (
84- $ node instanceof Node \Stmt
85- && !$ node instanceof VirtualNode
86- && !$ node instanceof Node \Stmt \ClassLike
87- && !$ node instanceof Node \Stmt \ClassMethod
88- && !$ node instanceof Node \Stmt \Function_
89- && !$ node instanceof Node \Stmt \Property
90- && !$ node instanceof Node \Stmt \ClassConst
91- && !$ node instanceof Node \Stmt \Const_
92- ) {
154+ if (($ nodeProfile & self ::PROFILE_VAR_TAGS ) !== 0 && $ node instanceof Node \Stmt) {
93155 $ this ->extractStmtVarTags ($ node , $ scope , $ dependenciesReflections );
94156 }
95157
158+ if (($ nodeProfile & self ::PROFILE_CHAIN ) !== 0 ) {
159+ $ this ->collectNodeDependencies ($ node , $ scope , $ dependenciesReflections , $ dependenciesFilePaths );
160+ }
161+
162+ $ exportedNode = ($ nodeProfile & self ::PROFILE_EXPORT ) !== 0
163+ ? $ this ->exportedNodeResolver ->resolve ($ node , $ this ->nameScopeTracker ->getNameScope ())
164+ : null ;
165+
166+ return new NodeDependencies ($ this ->fileHelper , $ dependenciesReflections , $ exportedNode , $ dependenciesFilePaths );
167+ }
168+
169+ /**
170+ * The node-kind branches. Only entered when resolveNodeProfile() says a branch can match.
171+ *
172+ * @param array<ClassReflection|FunctionReflection|ConstantReflection> $dependenciesReflections
173+ * @param list<string> $dependenciesFilePaths
174+ */
175+ private function collectNodeDependencies (Node $ node , Scope $ scope , array &$ dependenciesReflections , array &$ dependenciesFilePaths ): void
176+ {
96177 if ($ node instanceof Node \Stmt \Class_) {
97178 if (isset ($ node ->namespacedName )) {
98179 $ this ->addClassToDependencies ($ node ->namespacedName ->toString (), $ dependenciesReflections );
@@ -562,8 +643,6 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
562643 } elseif ($ node instanceof InstantiationCallableNode) {
563644 $ dependenciesReflections += $ this ->resolveDependencies (new Node \Expr \New_ ($ node ->getClass ()), $ scope )->getReflections ();
564645 }
565-
566- return new NodeDependencies ($ this ->fileHelper , $ dependenciesReflections , $ this ->exportedNodeResolver ->resolve ($ node , $ this ->nameScopeTracker ->getNameScope ()), $ dependenciesFilePaths );
567646 }
568647
569648 public function resolveUsedTraitDependencies (InClassNode $ inClassNode ): NodeDependencies
@@ -606,6 +685,45 @@ private function getClassNamesFromClassString(Type $type): array
606685 return $ classNames ;
607686 }
608687
688+ /**
689+ * Which parts of resolveDependencies() a node of this class can reach. Depends only on the class,
690+ * so it is computed once per class and reused for every node of it.
691+ */
692+ private function resolveNodeProfile (Node $ node ): int
693+ {
694+ $ profile = 0 ;
695+ if (
696+ $ node instanceof Node \Stmt
697+ && !$ node instanceof VirtualNode
698+ && !$ node instanceof Node \Stmt \ClassLike
699+ && !$ node instanceof Node \Stmt \ClassMethod
700+ && !$ node instanceof Node \Stmt \Function_
701+ && !$ node instanceof Node \Stmt \Property
702+ && !$ node instanceof Node \Stmt \ClassConst
703+ && !$ node instanceof Node \Stmt \Const_
704+ ) {
705+ $ profile |= self ::PROFILE_VAR_TAGS ;
706+ }
707+
708+ $ lists = [
709+ self ::PROFILE_CHAIN => self ::CHAIN_NODE_TYPES ,
710+ self ::PROFILE_EXPORT => self ::EXPORT_NODE_TYPES ,
711+ self ::PROFILE_NAME_SCOPE => self ::NAME_SCOPE_NODE_TYPES ,
712+ ];
713+ foreach ($ lists as $ bit => $ nodeTypes ) {
714+ foreach ($ nodeTypes as $ nodeType ) {
715+ if (!$ node instanceof $ nodeType ) {
716+ continue ;
717+ }
718+
719+ $ profile |= $ bit ;
720+ break ;
721+ }
722+ }
723+
724+ return $ profile ;
725+ }
726+
609727 /**
610728 * Extracts the classes referenced from a variable-level var-tag PHPDoc attached to a statement.
611729 *
0 commit comments