Skip to content

Commit ddc1fe6

Browse files
committed
Fix calling partials named true, false, null, or undefined
{{> true}}, {{> false}}, {{> null}}, and {{> undefined}} fell through to compileExpression, which produced PHP literal values instead of string names. Boolean literals caused a type error; null/undefined silently rendered nothing.
1 parent 3ded108 commit ddc1fe6

2 files changed

Lines changed: 43 additions & 27 deletions

File tree

src/Compiler.php

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,10 @@ private function PartialStatement(PartialStatement $statement): string
410410
if ($name instanceof SubExpression) {
411411
$p = $this->SubExpression($name);
412412
$this->context->usedDynPartial++;
413-
} elseif ($name instanceof PathExpression || $name instanceof StringLiteral || $name instanceof NumberLiteral) {
413+
} else {
414414
$partialName = $this->resolvePartialName($name);
415415
$p = self::quote($partialName);
416416
$this->resolveAndCompilePartial($partialName);
417-
} else {
418-
$p = $this->compileExpression($name);
419417
}
420418

421419
$vars = $this->compilePartialParams($statement->params, $statement->hash);
@@ -452,36 +450,29 @@ private function PartialBlockStatement(PartialBlockStatement $statement): string
452450
$name = $statement->name;
453451
$depsBefore = count($this->programDepStack[array_key_last($this->programDepStack)]);
454452
$body = $this->compileProgram($statement->program);
455-
$partialName = null;
456-
$found = false;
457453

458-
if ($name instanceof PathExpression || $name instanceof StringLiteral || $name instanceof NumberLiteral) {
459-
$partialName = $this->resolvePartialName($name);
460-
$p = self::quote($partialName);
461-
$found = ($this->context->usedPartial[$partialName] ?? '') !== '';
462-
463-
if (!$found && !str_starts_with($partialName, '@partial-block')) {
464-
$cnt = $this->resolvePartial($partialName);
465-
if ($cnt !== null) {
466-
$this->context->usedPartial[$partialName] = $cnt;
467-
$this->compilePartialTemplate($partialName, $cnt);
468-
$found = true;
469-
}
470-
}
454+
$partialName = $this->resolvePartialName($name);
455+
$p = self::quote($partialName);
456+
$found = ($this->context->usedPartial[$partialName] ?? '') !== '';
471457

472-
// Mark as known for runtime resolution; not added to partialCode so $blockParams scope is preserved.
473-
$this->context->usedPartial[$partialName] ??= '';
474-
} else {
475-
$p = $this->compileExpression($name);
458+
if (!$found && !str_starts_with($partialName, '@partial-block')) {
459+
$cnt = $this->resolvePartial($partialName);
460+
if ($cnt !== null) {
461+
$this->context->usedPartial[$partialName] = $cnt;
462+
$this->compilePartialTemplate($partialName, $cnt);
463+
$found = true;
464+
}
476465
}
477466

467+
// Mark as known for runtime resolution; not added to partialCode so $blockParams scope is preserved.
468+
$this->context->usedPartial[$partialName] ??= '';
478469
$vars = $this->compilePartialParams($statement->params, $statement->hash);
479470

480471
// Capture $blockParams and any hoisted program vars so the partial block body can access them.
481472
$useVars = $this->buildInlineUseClause($depsBefore);
482473
$bodyClosure = self::templateClosure($body, useVars: $useVars);
483474

484-
if ($partialName !== null && !$found) {
475+
if (!$found) {
485476
// Register the block body as a fallback partial only if no runtime partial with this name exists yet.
486477
$parts[] = "(isset(\$cx->inlinePartials[$p]) || isset(\$cx->partials[$p]) ? '' : "
487478
. self::getRuntimeFunc('setInlinePartial', "\$cx, $p, $bodyClosure") . ')';
@@ -798,7 +789,7 @@ private function buildBasePath(bool $data, int $depth): string
798789
/**
799790
* Resolve the name of a non-SubExpression partial reference.
800791
*/
801-
private function resolvePartialName(PathExpression|StringLiteral|NumberLiteral $name): string
792+
private function resolvePartialName(PathExpression|Literal $name): string
802793
{
803794
return $name instanceof PathExpression ? $name->original : $this->getLiteralKeyName($name);
804795
}

tests/RegressionTest.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace DevTheorem\Handlebars\Test;
44

5+
use Closure;
56
use DevTheorem\Handlebars\Handlebars;
67
use DevTheorem\Handlebars\HelperOptions;
78
use DevTheorem\Handlebars\Options;
@@ -12,7 +13,7 @@
1213
/**
1314
* @phpstan-type RegIssue array{
1415
* template: string, expected: string, data?: mixed, options?: Options,
15-
* helpers?: array<string, \Closure>, partials?: array<string, string>,
16+
* helpers?: array<Closure>, partials?: array<string>,
1617
* vars?: array<mixed>,
1718
* }
1819
*/
@@ -44,8 +45,8 @@ public function testLog(): void
4445
}
4546

4647
/**
47-
* @param array<string, \Closure> $helpers
48-
* @param array<string, string> $partials
48+
* @param array<Closure> $helpers
49+
* @param array<string> $partials
4950
* @param array<mixed> $vars
5051
*/
5152
#[DataProvider("helperProvider")]
@@ -893,6 +894,18 @@ public static function helperProvider(): array
893894
'data' => ['t' => 'val', 'foo' => ['1st', '2nd']],
894895
'expected' => ' 1st bar, 0 1st val; 2nd bar, 1 2nd val; ',
895896
],
897+
898+
'helpers can have literal names' => [
899+
'template' => '{{42 1}} {{true 2}} {{false 3}} {{null 4}} {{undefined 5}}',
900+
'helpers' => [
901+
'42' => fn($arg) => "number $arg,",
902+
'true' => fn($arg) => "true $arg,",
903+
'false' => fn($arg) => "false $arg,",
904+
'null' => fn($arg) => "null $arg,",
905+
'undefined' => fn($arg) => "undefined $arg",
906+
],
907+
'expected' => 'number 1, true 2, false 3, null 4, undefined 5',
908+
],
896909
];
897910
}
898911

@@ -1154,6 +1167,18 @@ public static function partialProvider(): array
11541167
'data' => ['str' => 'hello'],
11551168
'expected' => 'val',
11561169
],
1170+
1171+
'partials can have literal names' => [
1172+
'template' => '{{> 42}} {{> true}} {{> false}} {{> null}} {{> undefined}}',
1173+
'partials' => [
1174+
'42' => 'p42',
1175+
'true' => 'pTrue',
1176+
'false' => 'pFalse',
1177+
'null' => 'pNull',
1178+
'undefined' => 'pUndefined',
1179+
],
1180+
'expected' => 'p42 pTrue pFalse pNull pUndefined',
1181+
],
11571182
];
11581183
}
11591184

0 commit comments

Comments
 (0)