@@ -78,10 +78,9 @@ public function compile(Program $program, Context $context): string
7878 */
7979 public function composePHPRender (string $ code ): string
8080 {
81- $ runtime = Runtime::class;
8281 $ partials = implode (", \n" , $ this ->context ->partialCode );
8382 $ closure = self ::templateClosure ($ code , $ partials , "\n \$in = & \$cx->data['root']; " );
84- return "use { $ runtime } as LR; \nreturn $ closure; " ;
83+ return "use " . Runtime::class . " as LR; \nreturn $ closure; " ;
8584 }
8685
8786 /**
@@ -274,7 +273,7 @@ private function compileProgramWithBlockParams(Program $program): string
274273 if ($ bp ) {
275274 array_shift ($ this ->blockParamValues );
276275 }
277- return self ::blockClosure ($ body , (bool ) $ program -> blockParams , $ this ->lastCompileProgramHadDirectBpRef );
276+ return self ::blockClosure ($ body , (bool ) $ bp , $ this ->lastCompileProgramHadDirectBpRef );
278277 }
279278
280279 private function compileBlockHelper (BlockStatement $ block , string $ name ): string
@@ -422,42 +421,33 @@ private function PartialStatement(PartialStatement $statement): string
422421 // appendContent opcode) and invoke the partial with an empty indent so its lines are
423422 // not additionally indented.
424423 if ($ this ->context ->options ->preventIndent && $ statement ->indent !== '' ) {
425- return "$ indent. " . self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, 0, '' " );
424+ return "$ indent. " . self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, '' " );
426425 }
427426
428- return self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, 0, $ indent " );
427+ return self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, $ indent " );
429428 }
430429
431430 private function PartialBlockStatement (PartialBlockStatement $ statement ): string
432431 {
433- $ this ->context ->partialBlockId ++;
434- $ pid = $ this ->context ->partialBlockId ;
435-
436432 // Hoist inline partial registrations so they run before the partial is called.
437433 // Without this, inline partials defined in the block would only be registered when
438434 // {{> @partial-block}} is invoked, too late for partials that call them directly.
439- $ hoistedParts = [];
435+ $ parts = [];
440436 foreach ($ statement ->program ->body as $ stmt ) {
441437 if ($ stmt instanceof BlockStatement && $ stmt ->type === 'DecoratorBlock ' ) {
442- $ hoistedParts [] = $ this ->accept ($ stmt );
438+ $ parts [] = $ this ->accept ($ stmt );
443439 }
444440 }
445441
446442 $ name = $ statement ->name ;
447443 $ body = $ this ->compileProgram ($ statement ->program );
444+ $ partialName = null ;
445+ $ found = false ;
448446
449447 if ($ name instanceof PathExpression || $ name instanceof StringLiteral || $ name instanceof NumberLiteral) {
450448 $ partialName = $ this ->resolvePartialName ($ name );
451449 $ p = self ::quote ($ partialName );
452- } else {
453- $ p = $ this ->compileExpression ($ name );
454- $ partialName = null ;
455- }
456-
457- $ found = false ;
458-
459- if ($ partialName !== null ) {
460- $ found = isset ($ this ->context ->usedPartial [$ partialName ]);
450+ $ found = ($ this ->context ->usedPartial [$ partialName ] ?? '' ) !== '' ;
461451
462452 if (!$ found && !str_starts_with ($ partialName , '@partial-block ' )) {
463453 $ cnt = $ this ->resolvePartial ($ partialName );
@@ -468,26 +458,23 @@ private function PartialBlockStatement(PartialBlockStatement $statement): string
468458 }
469459 }
470460
471- if (!$ found ) {
472- // Mark as known so LR::p() can resolve it at runtime.
473- $ this ->context ->usedPartial [$ partialName ] = '' ;
474- // Don't add to partialCode — register via LR::in() at runtime so $blockParams
475- // is captured from the enclosing scope when block params are in use.
476- }
461+ // Mark as known for runtime resolution; not added to partialCode so $blockParams scope is preserved.
462+ $ this ->context ->usedPartial [$ partialName ] ??= '' ;
463+ } else {
464+ $ p = $ this ->compileExpression ($ name );
477465 }
478466
479467 $ vars = $ this ->compilePartialParams ($ statement ->params , $ statement ->hash );
480468
481469 // Capture $blockParams if we're inside a block-param scope so the partial block body can access them.
482470 $ useVars = $ this ->blockParamsUseVars ();
483471 $ bodyClosure = self ::templateClosure ($ body , useVars: $ useVars );
484- $ fallbackParts = ($ partialName !== null && !$ found )
485- ? [self ::getRuntimeFunc ('inFallback ' , "\$cx, " . self ::quote ($ partialName ) . ', ' . $ bodyClosure )]
486- : [];
487- $ parts = [...$ hoistedParts , ...$ fallbackParts ,
488- self ::getRuntimeFunc ('in ' , "\$cx, '@partial-block $ pid', " . $ bodyClosure ),
489- self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, $ pid, '' " ),
490- ];
472+
473+ if ($ partialName !== null && !$ found ) {
474+ // Register the block body as a fallback partial only if no runtime partial with this name exists yet.
475+ $ parts [] = "(isset( \$cx->partials[ $ p]) ? '' : " . self ::getRuntimeFunc ('in ' , "\$cx, $ p, $ bodyClosure " ) . ') ' ;
476+ }
477+ $ parts [] = self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, '', $ bodyClosure " );
491478 return implode ('. ' , $ parts );
492479 }
493480
@@ -617,7 +604,7 @@ private function PathExpression(PathExpression $expression): string
617604
618605 // @partial-block as variable: truthy when an active partial block exists
619606 if ($ data && $ depth === 0 && count ($ stringParts ) === 1 && $ stringParts [0 ] === 'partial-block ' ) {
620- return "isset( \$cx->partials['@partial-block' . \$ cx->partialId]) ? true : null " ;
607+ return "\$cx->partialBlock !== null ? true : null " ;
621608 }
622609
623610 // Check block params (depth-0, non-data, non-scoped paths only, not SubExpression-headed)
@@ -788,13 +775,8 @@ private function compilePartialTemplate(string $name, string $template): void
788775 return ;
789776 }
790777
791- $ tmpContext = clone $ this ->context ;
792- $ tmpContext ->inlinePartial = [];
793- $ tmpContext ->partialBlock = [];
794-
795778 $ program = $ this ->parser ->parse ($ template );
796- $ code = (new Compiler ($ this ->parser ))->compile ($ program , $ tmpContext );
797- $ this ->context ->merge ($ tmpContext );
779+ $ code = (new Compiler ($ this ->parser ))->compile ($ program , $ this ->context );
798780
799781 $ this ->context ->partialCode [$ name ] = self ::quote ($ name ) . ' => ' . self ::templateClosure ($ code );
800782 }
@@ -897,13 +879,10 @@ private function compileElseClause(BlockStatement $block): string
897879 */
898880 private function buildBasePath (bool $ data , int $ depth ): string
899881 {
900- $ base = $ data ? '$cx->frame ' : '$in ' ;
901- if ($ depth > 0 ) {
902- $ base = $ data
903- ? $ base . str_repeat ("['_parent'] " , $ depth )
904- : "\$cx->depths[count( \$cx->depths)- $ depth] " ;
882+ if ($ data ) {
883+ return '$cx->frame ' . str_repeat ("['_parent'] " , $ depth );
905884 }
906- return $ base ;
885+ return $ depth > 0 ? "\$ cx->depths[count( \$ cx->depths)- $ depth ] " : ' $in ' ;
907886 }
908887
909888 /**
@@ -963,14 +942,13 @@ private static function blockClosure(string $body, bool $declaresBp = false, boo
963942 $ preamble = '$sc=count($cx->depths); ' ;
964943 $ body = str_replace ('$cx->depths[count($cx->depths)- ' , '$cx->depths[$sc- ' , $ body );
965944 }
966- if ($ declaresBp ) {
967- return "function( \$cx, \$in, array \$blockParams = []) { {$ preamble }return $ body;} " ;
968- }
969- if ($ inheritsBp ) {
970- // Inherits block params from the enclosing closure's $blockParams variable.
971- return "function( \$cx, \$in) use ( \$blockParams) { {$ preamble }return $ body;} " ;
972- }
973- return "function( \$cx, \$in) { {$ preamble }return $ body;} " ;
945+ // Inherits block params from the enclosing closure's $blockParams variable when $inheritsBp.
946+ $ sig = match (true ) {
947+ $ declaresBp => "function( \$cx, \$in, array \$blockParams = []) " ,
948+ $ inheritsBp => "function( \$cx, \$in) use ( \$blockParams) " ,
949+ default => "function( \$cx, \$in) " ,
950+ };
951+ return "$ sig { {$ preamble }return $ body;} " ;
974952 }
975953
976954 private static function quote (string $ string ): string
0 commit comments