@@ -110,6 +110,13 @@ final class SpecialTypeResolver
110110 'closed-resource ' => true ,
111111 ];
112112
113+ /**
114+ * In-memory cache for Reflection instances per context string.
115+ *
116+ * @var array<string, \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod>
117+ */
118+ private static array $ reflectionContextCache = [];
119+
113120 /**
114121 * In-memory cache of file import maps keyed by filename.
115122 *
@@ -131,6 +138,17 @@ final class SpecialTypeResolver
131138 */
132139 private static array $ classTraitUseDocs = [];
133140
141+ /**
142+ * Resets internal reflection and file caches. Useful for test isolation.
143+ */
144+ public static function reset (): void
145+ {
146+ self ::$ reflectionContextCache = [];
147+ self ::$ fileUseImports = [];
148+ self ::$ fileNamespaces = [];
149+ self ::$ classTraitUseDocs = [];
150+ }
151+
134152 /**
135153 * Validates strict object identity ($value === $thisObj) when the return type node specifies $this.
136154 */
@@ -170,7 +188,7 @@ public static function resolve(TypeNode $node, \ReflectionClass|\ReflectionFunct
170188
171189 if ($ node instanceof GenericTypeNode) {
172190 $ genericType = self ::resolve ($ node ->type , $ context , $ thisObj );
173- $ innerTypes = array_map (fn ($ t ) => self ::resolve ($ t , $ context , $ thisObj ), $ node ->genericTypes );
191+ $ innerTypes = array_map (fn ($ t ) => self ::resolve ($ t , $ context , $ thisObj ), $ node ->genericTypes );
174192
175193 return new GenericTypeNode (
176194 $ genericType instanceof IdentifierTypeNode ? $ genericType : $ node ->type ,
@@ -224,11 +242,11 @@ public static function resolve(TypeNode $node, \ReflectionClass|\ReflectionFunct
224242 }
225243
226244 if ($ node instanceof UnionTypeNode) {
227- return new UnionTypeNode (array_map (fn ($ t ) => self ::resolve ($ t , $ context , $ thisObj ), $ node ->types ));
245+ return new UnionTypeNode (array_map (fn ($ t ) => self ::resolve ($ t , $ context , $ thisObj ), $ node ->types ));
228246 }
229247
230248 if ($ node instanceof IntersectionTypeNode) {
231- return new IntersectionTypeNode (array_map (fn ($ t ) => self ::resolve ($ t , $ context , $ thisObj ), $ node ->types ));
249+ return new IntersectionTypeNode (array_map (fn ($ t ) => self ::resolve ($ t , $ context , $ thisObj ), $ node ->types ));
232250 }
233251
234252 return $ node ;
@@ -262,7 +280,7 @@ public static function resolveForFile(TypeNode $node, string $file): TypeNode
262280
263281 if ($ node instanceof GenericTypeNode) {
264282 $ genericType = self ::resolveForFile ($ node ->type , $ file );
265- $ innerTypes = array_map (fn ($ t ) => self ::resolveForFile ($ t , $ file ), $ node ->genericTypes );
283+ $ innerTypes = array_map (fn ($ t ) => self ::resolveForFile ($ t , $ file ), $ node ->genericTypes );
266284
267285 return new GenericTypeNode (
268286 $ genericType instanceof IdentifierTypeNode ? $ genericType : $ node ->type ,
@@ -316,11 +334,11 @@ public static function resolveForFile(TypeNode $node, string $file): TypeNode
316334 }
317335
318336 if ($ node instanceof UnionTypeNode) {
319- return new UnionTypeNode (array_map (fn ($ t ) => self ::resolveForFile ($ t , $ file ), $ node ->types ));
337+ return new UnionTypeNode (array_map (fn ($ t ) => self ::resolveForFile ($ t , $ file ), $ node ->types ));
320338 }
321339
322340 if ($ node instanceof IntersectionTypeNode) {
323- return new IntersectionTypeNode (array_map (fn ($ t ) => self ::resolveForFile ($ t , $ file ), $ node ->types ));
341+ return new IntersectionTypeNode (array_map (fn ($ t ) => self ::resolveForFile ($ t , $ file ), $ node ->types ));
324342 }
325343
326344 return clone $ node ;
@@ -334,25 +352,36 @@ public static function resolveForFile(TypeNode $node, string $file): TypeNode
334352 private static function getReflectionContext (\ReflectionClass |\ReflectionFunction |\ReflectionMethod |string $ context ): \ReflectionClass |\ReflectionFunction |\ReflectionMethod
335353 {
336354 if (\is_string ($ context )) {
355+ if (isset (self ::$ reflectionContextCache [$ context ])) {
356+ return self ::$ reflectionContextCache [$ context ];
357+ }
358+
337359 if (str_contains ($ context , ':: ' )) {
338360 [$ className , $ methodName ] = explode (':: ' , $ context , 2 );
339361
340362 if (class_exists ($ className ) || interface_exists ($ className ) || trait_exists ($ className ) || enum_exists ($ className )) {
341363 /** @var class-string<object> $className */
342364 try {
343- return new \ReflectionMethod ($ className , $ methodName );
365+ return self :: $ reflectionContextCache [ $ context ] = new \ReflectionMethod ($ className , $ methodName );
344366 } catch (\ReflectionException $ e ) {
345- return new \ReflectionClass ($ className );
367+ return self :: $ reflectionContextCache [ $ context ] = new \ReflectionClass ($ className );
346368 }
347369 }
348370
349371 /** @var class-string<object> $fallbackClass */
350372 $ fallbackClass = \stdClass::class;
351373
352- return new \ReflectionClass ($ fallbackClass );
374+ return self :: $ reflectionContextCache [ $ context ] = new \ReflectionClass ($ fallbackClass );
353375 }
354376
355- return new \ReflectionFunction ($ context );
377+ try {
378+ return self ::$ reflectionContextCache [$ context ] = new \ReflectionFunction ($ context );
379+ } catch (\ReflectionException $ e ) {
380+ /** @var class-string<object> $fallbackClass */
381+ $ fallbackClass = \stdClass::class;
382+
383+ return self ::$ reflectionContextCache [$ context ] = new \ReflectionClass ($ fallbackClass );
384+ }
356385 }
357386
358387 return $ context ;
@@ -690,8 +719,6 @@ private static function resolveCallableForFile(CallableTypeNode $node, string $f
690719 return new CallableTypeNode ($ node ->identifier , $ resolvedParameters , $ resolvedReturnType , $ node ->templateTypes );
691720 }
692721
693- // --- Shared Utilities ---
694-
695722 private static function extractOffsetKey (TypeNode $ offsetType ): string |int |null
696723 {
697724 if ($ offsetType instanceof ConstTypeNode) {
@@ -1046,6 +1073,7 @@ private static function parseFileMetadata(string $fileName, string $source): voi
10461073 }
10471074 } elseif ($ stmt instanceof Stmt \Class_ && $ stmt ->name !== null ) {
10481075 $ className = $ namespace !== '' ? $ namespace . '\\' . $ stmt ->name ->toString () : $ stmt ->name ->toString ();
1076+ self ::$ classTraitUseDocs [$ className ] = [];
10491077 foreach ($ stmt ->stmts as $ classStmt ) {
10501078 if ($ classStmt instanceof Stmt \TraitUse) {
10511079 $ doc = $ classStmt ->getDocComment ();
0 commit comments