@@ -217,19 +217,15 @@ private function isKnownHelper(string $helperName): bool
217217 */
218218 private function classifySexpr (?string $ simpleName , array $ params , ?Hash $ hash ): SexprType
219219 {
220- if ($ simpleName !== null && $ this ->lookupBlockParam ($ simpleName ) !== null ) {
221- return SexprType::Simple;
222- }
223- if ($ simpleName !== null && $ this ->isKnownHelper ($ simpleName )) {
224- return SexprType::Helper;
225- }
226- if ($ params || $ hash !== null ) {
227- return SexprType::Helper;
228- }
229220 if ($ simpleName !== null ) {
230- return SexprType::Ambiguous;
221+ if ($ this ->lookupBlockParam ($ simpleName ) !== null ) {
222+ return SexprType::Simple;
223+ } elseif ($ params || $ hash || $ this ->isKnownHelper ($ simpleName )) {
224+ return SexprType::Helper;
225+ }
226+ return $ this ->context ->options ->knownHelpersOnly ? SexprType::Simple : SexprType::Ambiguous;
231227 }
232- return SexprType::Simple;
228+ return ( $ params || $ hash ) ? SexprType::Helper : SexprType::Simple;
233229 }
234230
235231 /** Returns '$blockParams' when inside a block-param scope (for use() capture), '' otherwise. */
@@ -485,16 +481,16 @@ private function PartialBlockStatement(PartialBlockStatement $statement): string
485481
486482 if ($ partialName !== null && !$ found ) {
487483 // Register the block body as a fallback partial only if no runtime partial with this name exists yet.
488- $ parts [] = "(isset( \$cx->inlinePartials[ $ p]) || isset( \$cx->partials[ $ p]) ? '' : " . self ::getRuntimeFunc ('in ' , "\$cx, $ p, $ bodyClosure " ) . ') ' ;
484+ $ parts [] = "(isset( \$cx->inlinePartials[ $ p]) || isset( \$cx->partials[ $ p]) ? '' : "
485+ . self ::getRuntimeFunc ('in ' , "\$cx, $ p, $ bodyClosure " ) . ') ' ;
489486 }
490487 $ parts [] = self ::getRuntimeFunc ('p ' , "\$cx, $ p, $ vars, '', $ bodyClosure " );
491488 return implode ('. ' , $ parts );
492489 }
493490
494491 private function MustacheStatement (MustacheStatement $ mustache ): string
495492 {
496- $ raw = !$ mustache ->escaped || $ this ->context ->options ->noEscape ;
497- $ fn = $ raw ? 'raw ' : 'encq ' ;
493+ $ fn = (!$ mustache ->escaped || $ this ->context ->options ->noEscape ) ? 'raw ' : 'encq ' ;
498494 $ path = $ mustache ->path ;
499495
500496 // SubExpression path: {{(path args)}} — always a direct helper call result
@@ -512,27 +508,38 @@ private function MustacheStatement(MustacheStatement $mustache): string
512508 return self ::getRuntimeFunc ($ fn , $ call );
513509 }
514510
515- if ($ helperName !== null && $ type === SexprType::Ambiguous && !$ this ->context ->options ->strict ) {
511+ if ($ type === SexprType::Ambiguous) {
512+ assert ($ helperName !== null );
516513 $ escapedKey = self ::quote ($ helperName );
517514 $ isData = $ path instanceof PathExpression && $ path ->data ;
518- if ($ this ->context ->options ->knownHelpersOnly ) {
519- $ lookup = $ isData ? "\$cx->data[ $ escapedKey] ?? null " : self ::getRuntimeFunc ('cv ' , "\$in, $ escapedKey " );
520- return self ::getRuntimeFunc ($ fn , $ lookup );
521- }
522515 $ scope = $ isData ? '$cx->data ' : '$in ' ;
523- $ hvArgs = "\$cx, $ escapedKey, $ scope " . ($ this ->context ->options ->assumeObjects ? ', true ' : '' );
516+ $ hvArgs = "\$cx, $ escapedKey, $ scope " ;
517+ if ($ this ->context ->options ->assumeObjects ) {
518+ $ hvArgs .= ', true ' ;
519+ } elseif ($ this ->context ->options ->strict ) {
520+ $ hvArgs .= ', false, true ' ;
521+ }
524522 return self ::getRuntimeFunc ($ fn , self ::getRuntimeFunc ('hv ' , $ hvArgs ));
525523 }
526524
527- // Simple: direct path lookup
525+ // Simple: direct path lookup with lambda resolution.
526+ // For knownHelpersOnly bare identifiers (single-segment, non-data): use cv() to pass the
527+ // current context to any Closure, mirroring JS fn.call(context) where context is `this`.
528+ // For all other simple paths (multi-segment, scoped, depth, data): use dv() with zero args,
529+ // matching HBS.js container.lambda which also passes no positional arguments.
528530 if ($ path instanceof PathExpression) {
529- return self ::getRuntimeFunc ($ fn , self ::getRuntimeFunc ('dv ' , $ this ->PathExpression ($ path )));
531+ if ($ helperName !== null && !$ path ->data && $ this ->context ->options ->knownHelpersOnly ) {
532+ $ cvArgs = '$in, ' . self ::quote ($ helperName ) . ($ this ->context ->options ->strict ? ', true ' : '' );
533+ return self ::getRuntimeFunc ($ fn , self ::getRuntimeFunc ('cv ' , $ cvArgs ));
534+ }
535+ $ expression = $ this ->PathExpression ($ path );
536+ } else {
537+ // Literal in simple position: same lambda resolution as PathExpression above.
538+ $ literalKey = $ this ->getLiteralKeyName ($ path );
539+ $ expression = $ this ->compileModeAwareLookup ('$in ' , [$ literalKey ], $ literalKey );
530540 }
531541
532- // Literal in simple/fallthrough position (strict, assumeObjects, or knownHelpersOnly):
533- // compile as a direct context lookup using the normalized key name.
534- $ literalKey = $ this ->getLiteralKeyName ($ path );
535- return self ::getRuntimeFunc ($ fn , $ this ->compileModeAwareLookup ('$in ' , [$ literalKey ], $ literalKey ));
542+ return self ::getRuntimeFunc ($ fn , self ::getRuntimeFunc ('dv ' , $ expression ));
536543 }
537544
538545 // ── Expressions ─────────────────────────────────────────────────
@@ -547,26 +554,24 @@ private function SubExpression(SubExpression $expression): string
547554 return $ this ->compileHelperCall ($ helperName , $ path , $ expression ->params , $ expression ->hash );
548555 }
549556
550- private function PathExpression (PathExpression $ expression ): string
557+ private function PathExpression (PathExpression $ path ): string
551558 {
552- $ data = $ expression ->data ;
553- $ depth = $ expression ->depth ;
554- $ parts = $ expression ->parts ;
559+ $ data = $ path ->data ;
560+ $ depth = $ path ->depth ;
555561
556562 // When the path head is a SubExpression (e.g. (helper).foo.bar), compile the
557563 // sub-expression as the base and use the string tail as the remaining key accesses.
558- $ hasSubExprHead = $ expression ->head instanceof SubExpression;
564+ $ hasSubExprHead = $ path ->head instanceof SubExpression;
559565 if ($ hasSubExprHead ) {
560- $ base = '( ' . $ this ->SubExpression ($ expression ->head ) . ') ' ;
561- $ stringParts = $ expression ->tail ;
566+ $ base = '( ' . $ this ->SubExpression ($ path ->head ) . ') ' ;
567+ $ stringParts = $ path ->tail ;
562568 } else {
563569 $ base = $ this ->buildBasePath ($ data , $ depth );
564- /** @var string[] $parts */
565- $ stringParts = $ parts ;
570+ /** @var string[] $stringParts */
571+ $ stringParts = $ path -> parts ;
566572 }
567573
568- // `this` with no parts or empty parts
569- if (($ expression ->this_ && !$ parts ) || !$ stringParts ) {
574+ if (!$ stringParts ) {
570575 return $ base ;
571576 }
572577
@@ -578,8 +583,8 @@ private function PathExpression(PathExpression $expression): string
578583 $ isLength = end ($ stringParts ) === 'length ' ;
579584
580585 // Check block params (depth-0, non-data, non-scoped paths only, not SubExpression-headed)
581- if (!$ hasSubExprHead && !$ data && $ depth === 0 && !self ::scopedId ($ expression )) {
582- $ bp = $ this ->lookupBlockParam ($ expression ->head );
586+ if (!$ hasSubExprHead && !$ data && $ depth === 0 && !self ::scopedId ($ path )) {
587+ $ bp = $ this ->lookupBlockParam ($ path ->head );
583588 if ($ bp !== null ) {
584589 [$ bpDepth , $ bpIndex ] = $ bp ;
585590 $ bpBase = "\$blockParams[ $ bpDepth][ $ bpIndex] " ;
@@ -589,8 +594,8 @@ private function PathExpression(PathExpression $expression): string
589594 }
590595
591596 // Skip the block param name since it has been resolved to a $blockParams index.
592- $ keys = $ isLength ? array_slice ($ expression ->tail , 0 , -1 ) : $ expression ->tail ;
593- $ lookup = $ this ->compileModeAwareLookup ($ bpBase , $ keys , $ expression ->original );
597+ $ keys = $ isLength ? array_slice ($ path ->tail , 0 , -1 ) : $ path ->tail ;
598+ $ lookup = $ this ->compileModeAwareLookup ($ bpBase , $ keys , $ path ->original );
594599 return $ isLength ? $ this ->buildLookupLength ($ lookup ) : $ lookup ;
595600 }
596601 }
@@ -601,11 +606,11 @@ private function PathExpression(PathExpression $expression): string
601606 if ($ isLength ) {
602607 $ partsExceptLength = array_slice ($ stringParts , 0 , -1 );
603608 return $ this ->buildLookupLength (
604- $ this ->compileModeAwareLookup ($ base , $ partsExceptLength , $ expression ->original ),
609+ $ this ->compileModeAwareLookup ($ base , $ partsExceptLength , $ path ->original ),
605610 );
606611 }
607612
608- return $ this ->compileModeAwareLookup ($ base , $ stringParts , $ expression ->original );
613+ return $ this ->compileModeAwareLookup ($ base , $ stringParts , $ path ->original );
609614 }
610615
611616 /**
0 commit comments