@@ -172,7 +172,7 @@ private function BlockStatement(BlockStatement $block): string
172172 }
173173
174174 $ escapedKey = self ::quote ($ literalKey );
175- $ var = "\$ in[ $ escapedKey ] ?? " . $ this -> missValue ( $ literalKey );
175+ $ var = $ this -> compileModeAwareLookup ( ' $in ' , [ $ literalKey ], $ literalKey );
176176
177177 if ($ block ->program === null ) {
178178 return $ this ->compileInvertedSection ($ block , $ var , $ escapedKey );
@@ -554,8 +554,7 @@ private function MustacheStatement(MustacheStatement $mustache): string
554554 return self ::getRuntimeFunc ($ fn , self ::getRuntimeFunc ('hv ' , "\$cx, $ escapedKey, \$in " ));
555555 }
556556
557- $ miss = $ this ->missValue ($ literalKey );
558- return self ::getRuntimeFunc ($ fn , "\$in[ $ escapedKey] ?? $ miss " );
557+ return self ::getRuntimeFunc ($ fn , $ this ->compileModeAwareLookup ('$in ' , [$ literalKey ], $ literalKey ));
559558 }
560559
561560 // ── Expressions ─────────────────────────────────────────────────
@@ -611,38 +610,36 @@ private function PathExpression(PathExpression $expression): string
611610 }
612611
613612 $ isLength = end ($ stringParts ) === 'length ' ;
614- $ varParts = $ isLength ? array_slice ($ stringParts , 0 , -1 ) : $ stringParts ;
615- $ miss = $ this ->missValue ($ expression ->original );
616613
617614 // Check block params (depth-0, non-data, non-scoped paths only, not SubExpression-headed)
618615 if (!$ hasSubExprHead && !$ data && $ depth === 0 && !self ::scopedId ($ expression )) {
619- $ bp = $ this ->lookupBlockParam ($ stringParts [ 0 ] );
616+ $ bp = $ this ->lookupBlockParam ($ expression -> head );
620617 if ($ bp !== null ) {
621618 [$ bpDepth , $ bpIndex ] = $ bp ;
622619 $ bpBase = "\$blockParams[ $ bpDepth][ $ bpIndex] " ;
623620 // Mark the current compileProgram() level as having a direct $blockParams reference.
624621 if ($ this ->bpRefStack ) {
625622 $ this ->bpRefStack [array_key_last ($ this ->bpRefStack )] = true ;
626623 }
627- // Skip the block param name ($varParts[0]) since it has been resolved to a $blockParams index.
628- $ parent = $ bpBase . self ::buildKeyAccess (array_slice ($ varParts , 1 ));
629- if ($ isLength ) {
630- return $ this ->buildLookupLength ($ parent );
631- }
632- return "$ parent ?? $ miss " ;
624+
625+ // Skip the block param name since it has been resolved to a $blockParams index.
626+ $ keys = $ isLength ? array_slice ($ expression ->tail , 0 , -1 ) : $ expression ->tail ;
627+ $ lookup = $ this ->compileModeAwareLookup ($ bpBase , $ keys , $ expression ->original );
628+ return $ isLength ? $ this ->buildLookupLength ($ lookup ) : $ lookup ;
633629 }
634630 }
635631
636632 // Handle .length: compile parent path through the normal mode-aware logic, then wrap in
637633 // lookupLength() at runtime. This mirrors HBS.js, where .length is a normal property
638634 // access with no compile-time special casing.
639635 if ($ isLength ) {
636+ $ partsExceptLength = array_slice ($ stringParts , 0 , -1 );
640637 return $ this ->buildLookupLength (
641- $ this ->compileModeAwareLookup ($ base , $ varParts , $ expression ->original , ' null ' ),
638+ $ this ->compileModeAwareLookup ($ base , $ partsExceptLength , $ expression ->original ),
642639 );
643640 }
644641
645- return $ this ->compileModeAwareLookup ($ base , $ stringParts , $ expression ->original , $ miss );
642+ return $ this ->compileModeAwareLookup ($ base , $ stringParts , $ expression ->original );
646643 }
647644
648645 /**
@@ -841,9 +838,9 @@ private function resolvePartialName(PathExpression|StringLiteral|NumberLiteral $
841838 * An optional $extraArg is appended to every call's argument list.
842839 * @param string[] $parts
843840 */
844- private static function buildCallChain (string $ fn , string $ base , array $ parts , string $ extraArg = '' ): string
841+ private static function buildCallChain (string $ fn , string $ base , array $ parts , ? string $ extraArg = null ): string
845842 {
846- $ extra = $ extraArg !== '' ? ", $ extraArg " : '' ;
843+ $ extra = $ extraArg !== null ? ", $ extraArg " : '' ;
847844 $ expr = $ base ;
848845 foreach ($ parts as $ part ) {
849846 $ expr = self ::getRuntimeFunc ($ fn , "$ expr, " . self ::quote ($ part ) . $ extra );
@@ -878,13 +875,6 @@ private static function quote(string $string): string
878875 return "' " . addcslashes ($ string , "' \\" ) . "' " ;
879876 }
880877
881- private function missValue (string $ key ): string
882- {
883- return ($ this ->context ->options ->strict && !$ this ->compilingHelperArgs )
884- ? self ::getRuntimeFunc ('miss ' , self ::quote ($ key ))
885- : 'null ' ;
886- }
887-
888878 private function compileProgramOrNull (?Program $ program ): string
889879 {
890880 if (!$ program ) {
@@ -913,7 +903,7 @@ private function buildLookupLength(string $parent): string
913903 * Compile a mode-aware path access expression for the given base and parts.
914904 * @param string[] $parts
915905 */
916- private function compileModeAwareLookup (string $ base , array $ parts , string $ original, string $ miss ): string
906+ private function compileModeAwareLookup (string $ base , array $ parts , string $ original ): string
917907 {
918908 if (!$ parts ) {
919909 return $ base ;
@@ -927,7 +917,7 @@ private function compileModeAwareLookup(string $base, array $parts, string $orig
927917 if ($ this ->context ->options ->strict ) {
928918 return self ::buildCallChain ('strictLookup ' , $ base , $ parts , self ::quote ($ original ));
929919 }
930- return $ base . self ::buildKeyAccess ($ parts ) . " ?? $ miss " ;
920+ return $ base . self ::buildKeyAccess ($ parts ) . ' ?? null ' ;
931921 }
932922
933923 private function throwKnownHelpersOnly (string $ helperName ): never
0 commit comments