@@ -79,7 +79,7 @@ public function compile(Program $program, Context $context): string
7979 public function composePHPRender (string $ code ): string
8080 {
8181 $ partials = implode (", \n" , $ this ->context ->partialCode );
82- $ closure = self ::templateClosure ($ code , $ partials , "\n \$in = & \$cx->data[' root'] ; " );
82+ $ closure = self ::templateClosure ($ code , $ partials , "\n \$in = & \$cx->data-> root; " );
8383 return "use " . Runtime::class . " as LR; \nreturn $ closure; " ;
8484 }
8585
@@ -590,7 +590,9 @@ private function PathExpression(PathExpression $expression): string
590590 // sub-expression as the base and use the string tail as the remaining key accesses.
591591 $ hasSubExprHead = $ expression ->head instanceof SubExpression;
592592 if ($ hasSubExprHead ) {
593- $ base = '( ' . $ this ->SubExpression ($ expression ->head ) . ') ' ;
593+ // Normalize the sub-expression result so PHP array returns become ArrayContext and
594+ // can be accessed via ->{'key'} property syntax.
595+ $ base = 'LR::normalizeContext( ' . $ this ->SubExpression ($ expression ->head ) . ') ' ;
594596 $ stringParts = $ expression ->tail ;
595597 } else {
596598 $ base = $ this ->buildBasePath ($ data , $ depth );
@@ -818,7 +820,7 @@ private function getSimpleHelperName(PathExpression|Literal $path): ?string
818820 private function buildBasePath (bool $ data , int $ depth ): string
819821 {
820822 if ($ data ) {
821- return '$cx->data ' . str_repeat (" [' _parent'] " , $ depth );
823+ return '$cx->data ' . str_repeat (' -> _parent ' , $ depth );
822824 }
823825 return $ depth > 0 ? "\$cx->depths[count( \$cx->depths)- $ depth] " : '$in ' ;
824826 }
@@ -848,15 +850,15 @@ private static function buildCallChain(string $fn, string $base, array $parts, ?
848850 }
849851
850852 /**
851- * Build a chained array -access string for the given path parts.
852- * e.g. ['foo', 'bar'] → "[ 'foo'][ 'bar'] "
853+ * Build a chained object-property -access string for the given path parts.
854+ * e.g. ['foo', 'bar'] → "->{ 'foo'}->{ 'bar'} "
853855 * @param string[] $parts
854856 */
855857 private static function buildKeyAccess (array $ parts ): string
856858 {
857859 $ n = '' ;
858860 foreach ($ parts as $ part ) {
859- $ n .= '[ ' . self ::quote ($ part ) . '] ' ;
861+ $ n .= '->{ ' . self ::quote ($ part ) . '} ' ;
860862 }
861863 return $ n ;
862864 }
@@ -908,10 +910,11 @@ private function compileModeAwareLookup(string $base, array $parts, string $orig
908910 return $ base ;
909911 }
910912 if ($ this ->context ->options ->assumeObjects || ($ this ->context ->options ->strict && $ this ->compilingHelperArgs )) {
911- // Use nullCheck chain for assumeObjects and helper arguments in strict mode.
912- // This mirrors HBS.js: both paths use bare nameLookup, so only a null intermediate throws
913- // (JS TypeError), while a missing key on a valid object returns null silently (JS undefined).
914- return self ::buildCallChain ('nullCheck ' , $ base , $ parts );
913+ // Bare access without ??: a null intermediate triggers a PHP warning that the null-property
914+ // handler converts to an ErrorException (PHP 8), or a native TypeError (PHP 9).
915+ // This mirrors HBS.js assumeObjects: missing keys on valid objects return null silently
916+ // (stdClass property access), while null intermediates throw.
917+ return $ base . self ::buildKeyAccess ($ parts );
915918 }
916919 if ($ this ->context ->options ->strict ) {
917920 return self ::buildCallChain ('strictLookup ' , $ base , $ parts , self ::quote ($ original ));
0 commit comments