@@ -224,17 +224,14 @@ fn type_hint_to_classes_typed_depth(
224224 ) ,
225225
226226 // ── Generic type ───────────────────────────────────────────
227- PhpType :: Generic ( name, args) => {
228- let arg_strings: Vec < String > = args. iter ( ) . map ( |a| a. to_string ( ) ) . collect ( ) ;
229- resolve_named_type (
230- name,
231- & arg_strings,
232- owning_class_name,
233- all_classes,
234- class_loader,
235- depth,
236- )
237- }
227+ PhpType :: Generic ( name, args) => resolve_named_type (
228+ name,
229+ args,
230+ owning_class_name,
231+ all_classes,
232+ class_loader,
233+ depth,
234+ ) ,
238235
239236 // ── Array slice (T[]) ──────────────────────────────────────
240237 // Not a class type itself; skip.
@@ -260,7 +257,7 @@ fn type_hint_to_classes_typed_depth(
260257/// substitution.
261258fn resolve_named_type (
262259 name : & str ,
263- generic_arg_strings : & [ String ] ,
260+ generic_args : & [ PhpType ] ,
264261 owning_class_name : & str ,
265262 all_classes : & [ Arc < ClassInfo > ] ,
266263 class_loader : & dyn Fn ( & str ) -> Option < Arc < ClassInfo > > ,
@@ -280,11 +277,10 @@ fn resolve_named_type(
280277
281278 // ── self / static / $this ──────────────────────────────────────
282279 if matches ! ( name, "self" | "static" | "$this" ) {
283- if !generic_arg_strings . is_empty ( ) {
280+ if !generic_args . is_empty ( ) {
284281 // `self<RuleError>` → rewrite to `OwningClass<RuleError>`.
285- let args_str = generic_arg_strings. join ( ", " ) ;
286- let rewritten = format ! ( "{}<{}>" , owning_class_name, args_str) ;
287- return type_hint_to_classes_depth (
282+ let rewritten = PhpType :: Generic ( owning_class_name. to_string ( ) , generic_args. to_vec ( ) ) ;
283+ return type_hint_to_classes_typed_depth (
288284 & rewritten,
289285 owning_class_name,
290286 all_classes,
@@ -321,18 +317,26 @@ fn resolve_named_type(
321317 }
322318
323319 // ── Resolve static/self/$this inside generic arguments ────────
324- let resolved_generic_args: Vec < String > = generic_arg_strings
325- . iter ( )
326- . map ( |arg| {
327- let trimmed = arg. trim ( ) ;
328- if trimmed == "static" || trimmed == "self" || trimmed == "$this" {
329- owning_class_name. to_string ( )
330- } else {
331- trimmed. to_string ( )
332- }
333- } )
334- . collect ( ) ;
335- let generic_args: Vec < & str > = resolved_generic_args. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
320+ let resolved_generic_args: Vec < PhpType > ;
321+ let generic_args: & [ PhpType ] = if generic_args. iter ( ) . any (
322+ |a| matches ! ( a, PhpType :: Named ( n) if matches!( n. as_str( ) , "static" | "self" | "$this" ) ) ,
323+ ) {
324+ resolved_generic_args = generic_args
325+ . iter ( )
326+ . map ( |arg| {
327+ if let PhpType :: Named ( n) = arg
328+ && matches ! ( n. as_str( ) , "static" | "self" | "$this" )
329+ {
330+ PhpType :: Named ( owning_class_name. to_string ( ) )
331+ } else {
332+ arg. clone ( )
333+ }
334+ } )
335+ . collect ( ) ;
336+ & resolved_generic_args
337+ } else {
338+ generic_args
339+ } ;
336340
337341 let short = short_name ( name) ;
338342
@@ -347,7 +351,7 @@ fn resolve_named_type(
347351 let cls = laravel:: try_swap_custom_collection (
348352 cls,
349353 name,
350- & generic_args,
354+ generic_args,
351355 all_classes,
352356 class_loader,
353357 ) ;
@@ -360,7 +364,7 @@ fn resolve_named_type(
360364 } else {
361365 virtual_members:: resolve_class_fully ( & cls, class_loader)
362366 } ;
363- let mut result = apply_generic_args ( & resolved, & generic_args) ;
367+ let mut result = apply_generic_args ( & resolved, generic_args) ;
364368
365369 // ── Template-param mixin resolution ────────────────
366370 // When a class declares `@mixin TParam` where `TParam`
@@ -369,7 +373,7 @@ fn resolve_named_type(
369373 // is not yet known. Now that generic args are concrete,
370374 // resolve those mixins and merge their members.
371375 if cls. mixins . iter ( ) . any ( |m| cls. template_params . contains ( m) ) {
372- let subs = build_generic_subs ( & cls, & generic_args) ;
376+ let subs = build_generic_subs ( & cls, generic_args) ;
373377 if !subs. is_empty ( ) {
374378 let mixin_members = virtual_members:: phpdoc:: resolve_template_param_mixins (
375379 & cls,
@@ -387,7 +391,7 @@ fn resolve_named_type(
387391 & mut result,
388392 & cls,
389393 name,
390- & generic_args,
394+ generic_args,
391395 class_loader,
392396 ) ;
393397
@@ -401,7 +405,7 @@ fn resolve_named_type(
401405 laravel:: try_inject_mixin_builder_scopes (
402406 & mut result,
403407 & cls,
404- & generic_args,
408+ generic_args,
405409 class_loader,
406410 ) ;
407411
@@ -425,9 +429,8 @@ fn resolve_named_type(
425429 && owner. template_params . contains ( & short. to_string ( ) )
426430 && let Some ( bound) = owner. template_param_bounds . get ( short)
427431 {
428- let bound_str = bound. to_string ( ) ;
429- return type_hint_to_classes_depth (
430- & bound_str,
432+ return type_hint_to_classes_typed_depth (
433+ bound,
431434 owning_class_name,
432435 all_classes,
433436 class_loader,
0 commit comments