@@ -52,14 +52,24 @@ public static function checkParams(
5252 return null ;
5353 }
5454
55- $ templates = $ contract ['templates ' ];
55+ $ methodTemplates = $ contract ['templates ' ];
56+ $ classTemplates = $ contract ['classTemplates ' ] ?? [];
5657 $ aliases = $ contract ['aliases ' ];
5758
58- self ::initializeCallContext ($ effectiveFunction , $ thisObj , $ templates );
59- self ::preInferGenericArrayTemplates ($ contract ['types ' ], $ vars , $ effectiveFunction , $ thisObj , $ templates );
59+ if (\count ($ methodTemplates ) > 0 ) {
60+ TemplateManager::clearCallBindings ($ effectiveFunction , $ methodTemplates );
61+ }
6062
61- $ boundTemplates = TemplateManager::getBoundTemplates ($ effectiveFunction , $ thisObj , $ templates );
62- $ declaredTemplates = $ templates ;
63+ if ($ thisObj !== null && \count ($ classTemplates ) > 0 && str_contains ($ effectiveFunction , ':: ' )) {
64+ $ declaringClass = explode (':: ' , $ effectiveFunction , 2 )[0 ];
65+ TemplateManager::resolveInheritedTemplates ($ thisObj , $ declaringClass );
66+ }
67+
68+ $ allTemplates = [...$ classTemplates , ...$ methodTemplates ];
69+ self ::preInferGenericArrayTemplates ($ contract ['types ' ], $ vars , $ effectiveFunction , $ thisObj , $ allTemplates );
70+
71+ $ boundTemplates = TemplateManager::getBoundTemplates ($ effectiveFunction , $ thisObj , $ allTemplates );
72+ $ declaredTemplates = $ allTemplates ;
6373
6474 foreach ($ contract ['types ' ] as $ paramName => $ typeNode ) {
6575 if (! \array_key_exists ($ paramName , $ vars )) {
@@ -72,7 +82,7 @@ public static function checkParams(
7282 $ vars [$ paramName ],
7383 $ effectiveFunction ,
7484 $ thisObj ,
75- $ templates ,
85+ $ allTemplates ,
7686 $ aliases ,
7787 $ boundTemplates ,
7888 $ declaredTemplates ,
@@ -170,21 +180,6 @@ private static function handleMagicCall(
170180 return self ::validateMagicArguments ($ magicContract , $ magicArgs , $ magicFunction , $ thisObj , $ registry );
171181 }
172182
173- /**
174- * Initializes call stack frames or resolves inherited template bounds.
175- *
176- * @param array<string, TemplateTagValueNode> $templates
177- */
178- private static function initializeCallContext (string $ effectiveFunction , ?object $ thisObj , array $ templates ): void
179- {
180- if ($ thisObj === null && \count ($ templates ) > 0 ) {
181- TemplateManager::clearCallBindings ($ effectiveFunction , $ templates );
182- } elseif ($ thisObj !== null && str_contains ($ effectiveFunction , ':: ' )) {
183- $ declaringClass = explode (':: ' , $ effectiveFunction , 2 )[0 ];
184- TemplateManager::resolveInheritedTemplates ($ thisObj , $ declaringClass );
185- }
186- }
187-
188183 /**
189184 * Pre-infers generic template parameters from array arguments before callback wrapping.
190185 *
@@ -227,7 +222,7 @@ private static function inferFromTypeNode(
227222 ): void {
228223 if ($ typeNode instanceof GenericTypeNode) {
229224 $ baseType = strtolower ($ typeNode ->type ->name );
230- if (! \in_array ($ baseType , ['array ' , 'list ' , 'iterable ' , 'traversable ' ], strict: true )) {
225+ if (! \in_array ($ baseType , ['array ' , 'list ' , 'iterable ' , 'traversable ' ], true )) {
231226 return ;
232227 }
233228
@@ -259,8 +254,13 @@ private static function bindTemplateIfUnbound(
259254 ?object $ thisObj ,
260255 array $ templates
261256 ): void {
262- if (isset ($ templates [$ templateName ]) && ! TemplateManager::isBound ($ effectiveFunction , $ thisObj , $ templateName )) {
263- TemplateManager::bindTemplate ($ effectiveFunction , $ thisObj , $ templateName , TemplateManager::inferTypeFromValue ($ sampleValue ));
257+ $ contract = ContractParser::parse ($ effectiveFunction );
258+ $ classTemplates = $ contract ['classTemplates ' ] ?? [];
259+ $ isClassLevelTemplate = isset ($ classTemplates [$ templateName ]);
260+ $ targetObj = $ isClassLevelTemplate ? $ thisObj : null ;
261+
262+ if (isset ($ templates [$ templateName ]) && ! TemplateManager::isBound ($ effectiveFunction , $ targetObj , $ templateName )) {
263+ TemplateManager::bindTemplate ($ effectiveFunction , $ targetObj , $ templateName , TemplateManager::inferTypeFromValue ($ sampleValue ));
264264 }
265265 }
266266
@@ -332,8 +332,6 @@ private static function validateMagicArguments(
332332 $ aliases = $ magicContract ['aliases ' ];
333333 $ parameters = $ magicContract ['parameters ' ];
334334
335- self ::initializeCallContext ($ function , $ thisObj , $ templates );
336-
337335 $ argValues = array_values ($ args );
338336 $ argKeys = array_keys ($ args );
339337
@@ -428,7 +426,12 @@ private static function resolveClassStringTemplate(
428426 $ templateName = $ innerType ->name ;
429427 $ templateNode = $ templates [$ templateName ];
430428
431- if (! TemplateManager::isBound ($ function , $ thisObj , $ templateName )) {
429+ $ contract = ContractParser::parse ($ function );
430+ $ classTemplates = $ contract ['classTemplates ' ] ?? [];
431+ $ isClassLevelTemplate = isset ($ classTemplates [$ templateName ]);
432+ $ targetObj = $ isClassLevelTemplate ? $ thisObj : null ;
433+
434+ if (! TemplateManager::isBound ($ function , $ targetObj , $ templateName )) {
432435 if (! \is_string ($ val ) || ! ClassNameValidator::isValid ($ val ) || (! class_exists ($ val ) && ! interface_exists ($ val ) && ! trait_exists ($ val ) && ! enum_exists ($ val ))) {
433436 return ErrorFactory::createError ($ function . '(): Argument $ ' . $ paramName . ' must be a valid class-string, ' . TypeFormatter::formatGivenValue ($ val ) . ' given ' );
434437 }
@@ -438,17 +441,17 @@ private static function resolveClassStringTemplate(
438441 $ boundName = $ resolvedBound instanceof IdentifierTypeNode ? $ resolvedBound ->name : (string ) $ resolvedBound ;
439442 $ lowerBound = strtolower ($ boundName );
440443
441- if ($ lowerBound !== 'object ' && $ lowerBound !== 'mixed ' && ! is_a ($ val , $ boundName , allow_string: true )) {
444+ if ($ lowerBound !== 'object ' && $ lowerBound !== 'mixed ' && ! is_a ($ val , $ boundName , true )) {
442445 return ErrorFactory::createError ($ function . '(): Argument $ ' . $ paramName . ' (class-string< ' . $ templateName . '>) must be a class-string of ' . $ boundName . ", ' " . $ val . "' given " );
443446 }
444447 }
445448
446- TemplateManager::bindTemplate ($ function , $ thisObj , $ templateName , new IdentifierTypeNode ($ val ));
449+ TemplateManager::bindTemplate ($ function , $ targetObj , $ templateName , new IdentifierTypeNode ($ val ));
447450 } else {
448- $ expectedTypeNode = TemplateManager::getBoundType ($ function , $ thisObj , $ templateName );
451+ $ expectedTypeNode = TemplateManager::getBoundType ($ function , $ targetObj , $ templateName );
449452 $ targetClass = $ expectedTypeNode instanceof IdentifierTypeNode ? $ expectedTypeNode ->name : (string ) $ expectedTypeNode ;
450453
451- if (! \is_string ($ val ) || ! is_a ($ val , $ targetClass , allow_string: true )) {
454+ if (! \is_string ($ val ) || ! is_a ($ val , $ targetClass , true )) {
452455 $ valStr = TypeFormatter::formatGivenValue ($ val );
453456
454457 return ErrorFactory::createError ($ function . '(): Argument $ ' . $ paramName . ' must be a class-string of ' . $ targetClass . ', ' . $ valStr . ' given ' );
@@ -494,7 +497,12 @@ private static function resolveTemplateParam(
494497 $ templateNode = $ templates [$ templateName ];
495498 $ isVariadic = $ typeNode instanceof ArrayTypeNode;
496499
497- if (! TemplateManager::isBound ($ function , $ thisObj , $ templateName )) {
500+ $ contract = ContractParser::parse ($ function );
501+ $ classTemplates = $ contract ['classTemplates ' ] ?? [];
502+ $ isClassLevelTemplate = isset ($ classTemplates [$ templateName ]);
503+ $ targetObj = $ isClassLevelTemplate ? $ thisObj : null ;
504+
505+ if (! TemplateManager::isBound ($ function , $ targetObj , $ templateName )) {
498506 $ sampleVal = ($ isVariadic && \is_array ($ val )) ? ($ val [0 ] ?? null ) : $ val ;
499507 $ inferredType = TemplateManager::inferTypeFromValue ($ sampleVal );
500508
@@ -506,7 +514,7 @@ private static function resolveTemplateParam(
506514 }
507515 }
508516
509- TemplateManager::bindTemplate ($ function , $ thisObj , $ templateName , $ inferredType );
517+ TemplateManager::bindTemplate ($ function , $ targetObj , $ templateName , $ inferredType );
510518
511519 if ($ isVariadic && \is_array ($ val )) {
512520 foreach ($ val as $ idx => $ item ) {
@@ -517,7 +525,7 @@ private static function resolveTemplateParam(
517525 }
518526 }
519527 } else {
520- $ expectedTypeNode = TemplateManager::getBoundType ($ function , $ thisObj , $ templateName );
528+ $ expectedTypeNode = TemplateManager::getBoundType ($ function , $ targetObj , $ templateName );
521529 if ($ expectedTypeNode === null ) {
522530 return null ;
523531 }
0 commit comments