Skip to content

Commit ba43dd4

Browse files
committed
Fix blockParams count for inverse helpers
1 parent 1983c29 commit ba43dd4

2 files changed

Lines changed: 30 additions & 43 deletions

File tree

src/Compiler.php

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -171,18 +171,18 @@ private function BlockStatement(BlockStatement $block): string
171171
if ($this->context->options->knownHelpersOnly) {
172172
$this->throwKnownHelpersOnly($name);
173173
}
174-
// Simple/Literal path: look up the key in context. Complex path: compile the full expression.
175-
$var = $helperName !== null
176-
? $this->compileModeAwareLookup('$in', [$helperName], $helperName)
177-
: $this->compileExpression($block->path);
178-
return $this->compileDynamicBlockHelper($block, $name, $var);
179174
}
180175

176+
// Simple/Literal path: look up the key in context. Complex path: compile the full expression.
181177
$var = $helperName !== null
182178
? $this->compileModeAwareLookup('$in', [$helperName], $helperName)
183179
: $this->compileExpression($block->path);
184-
$escapedName = self::quote($name);
185180

181+
if ($type === SexprType::Helper) {
182+
return $this->compileDynamicBlockHelper($block, $name, $var);
183+
}
184+
185+
$escapedName = self::quote($name);
186186
$inverted = $block->program === null;
187187
$fnProgram = $block->program ?? $block->inverse ?? throw new \LogicException('Inverted section must have an inverse program');
188188
$blockFn = $this->compileProgramWithBlockParams($fnProgram);
@@ -192,10 +192,8 @@ private function BlockStatement(BlockStatement $block): string
192192
if (!$inverted && !$this->context->options->knownHelpersOnly) {
193193
$bp = $block->program->blockParams;
194194
if ($block->hash !== null || $bp) {
195-
$params = $this->compileParams([], $block->hash);
196-
$outerBp = $this->outerBlockParamsExpr();
197195
$helperExpr = self::getRuntimeFunc('resolveHelper', "\$cx, $escapedName, $var, true");
198-
return self::buildHbbchCall($helperExpr, $escapedName, $params, $blockFn, $else, count($bp), $outerBp);
196+
return $this->buildBlockHelperCall($helperExpr, $escapedName, $block, $blockFn, $else);
199197
}
200198
}
201199

@@ -234,23 +232,6 @@ private function blockParamsUseVars(): string
234232
return $this->blockParamValues ? '$blockParams' : '';
235233
}
236234

237-
/**
238-
* Returns the PHP expression for the outer block param stack at the current compile-time scope.
239-
* '$blockParams' when inside a bp-declaring block; '[]' otherwise (top-level for this each/helper).
240-
* When returning '$blockParams', marks the current bpRefStack frame so the enclosing closure
241-
* captures $blockParams via use(), even if it doesn't directly access block param values.
242-
*/
243-
private function outerBlockParamsExpr(): string
244-
{
245-
if (!$this->blockParamValues) {
246-
return '[]';
247-
}
248-
if ($this->bpRefStack) {
249-
$this->bpRefStack[array_key_last($this->bpRefStack)] = true;
250-
}
251-
return '$blockParams';
252-
}
253-
254235
/**
255236
* Compile a block program, pushing/popping its block params around the compilation.
256237
* Returns a PHP closure string: the signature varies based on whether the program declares or
@@ -309,12 +290,9 @@ private function compileBlockHelper(BlockStatement $block, string $name): string
309290
$else = $this->compileProgramOrNull($block->inverse);
310291
}
311292

312-
$outerBp = $this->outerBlockParamsExpr();
313-
$params = $this->compileParams($block->params, $block->hash);
314293
$helperName = self::quote($name);
315-
$bpCount = count($fnProgram->blockParams);
316294

317-
return self::buildHbbchCall("\$cx->helpers[$helperName]", $helperName, $params, $fn, $else, $bpCount, $outerBp);
295+
return $this->buildBlockHelperCall("\$cx->helpers[$helperName]", $helperName, $block, $fn, $else);
318296
}
319297

320298
/**
@@ -363,16 +341,13 @@ private function compileConditionalExpr(Expression $condExpr, bool $negate): str
363341

364342
private function compileDynamicBlockHelper(BlockStatement $block, string $name, string $varPath): string
365343
{
366-
$bp = $block->program->blockParams ?? [];
367-
$params = $this->compileParams($block->params, $block->hash);
368344
$blockFn = $block->program !== null
369345
? $this->compileProgramWithBlockParams($block->program)
370346
: 'null';
371347
$else = $this->compileProgramOrNull($block->inverse);
372-
$outerBp = $this->outerBlockParamsExpr();
373348
$helperName = self::quote($name);
374349
$helperExpr = self::getRuntimeFunc('resolveHelper', "\$cx, $helperName, $varPath, true");
375-
return self::buildHbbchCall($helperExpr, $helperName, $params, $blockFn, $else, count($bp), $outerBp);
350+
return $this->buildBlockHelperCall($helperExpr, $helperName, $block, $blockFn, $else);
376351
}
377352

378353
private function DecoratorBlock(BlockStatement $block): string
@@ -837,14 +812,20 @@ private static function buildKeyAccess(array $parts): string
837812
return $n;
838813
}
839814

840-
/**
841-
* Build an hbbch call with the given helper expression.
842-
* Trailing bpCount/outerBp args are omitted when both are zero/empty.
843-
*/
844-
private static function buildHbbchCall(string $helperExpr, string $escapedName, string $params, string $blockFn, string $else, int $bpCount, string $outerBp): string
815+
private function buildBlockHelperCall(string $helperExpr, string $escapedName, BlockStatement $block, string $fn, string $else): string
845816
{
817+
// Mark the enclosing bp-declaring closure as needing to capture $blockParams via use().
818+
if ($this->blockParamValues && $this->bpRefStack) {
819+
$this->bpRefStack[array_key_last($this->bpRefStack)] = true;
820+
}
821+
$outerBp = $this->blockParamValues ? '$blockParams' : '[]';
822+
$bpCount = count(($block->program ?? $block->inverse)->blockParams ?? []);
823+
$params = $this->compileParams($block->params, $block->hash);
824+
825+
// omit trailing bpCount/outerBp args when both are zero/empty
846826
$trailingArgs = ($bpCount > 0 || $outerBp !== '[]') ? ", $bpCount, $outerBp" : '';
847-
return self::getRuntimeFunc('hbbch', "\$cx, $helperExpr, $escapedName, $params, \$in, $blockFn, $else$trailingArgs");
827+
$args = "\$cx, $helperExpr, $escapedName, $params, \$in, $fn, $else";
828+
return self::getRuntimeFunc('hbbch', $args . $trailingArgs);
848829
}
849830

850831
/**

tests/RegressionTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -745,12 +745,12 @@ public static function helperProvider(): array
745745
'inverted helpers should support block params' => [
746746
'template' => '{{^helper items as |foo bar baz|}}{{foo}}{{bar}}{{baz}}{{/helper}}',
747747
'helpers' => [
748-
'helper' => function (array $items, HelperOptions $options) {
749-
return $options->inverse($options->scope, ['blockParams' => [1, 2, 3]]);
748+
'helper' => function (array $items, HelperOptions $opts) {
749+
return $opts->blockParams . $opts->inverse($opts->scope, ['blockParams' => ['a', 'b', 'c']]);
750750
},
751751
],
752752
'data' => ['items' => []],
753-
'expected' => '123',
753+
'expected' => '3abc',
754754
],
755755
'inverse() called with no args at top level: ../ in else body resolves to current scope' => [
756756
'template' => '{{^myHelper}}{{../parent}}{{/myHelper}}',
@@ -1373,6 +1373,12 @@ public static function builtInProvider(): array
13731373
'expected' => "0: a\n1: b\nHelper!",
13741374
],
13751375

1376+
'outer block param accessible from inner each without block params' => [
1377+
'template' => '{{#each items as |item|}}{{#each ../cols}}{{item}},{{/each}}{{/each}}',
1378+
'data' => ['items' => ['a', 'b'], 'cols' => [1, 2]],
1379+
'expected' => 'a,a,b,b,',
1380+
],
1381+
13761382
'each over array with @key' => [
13771383
'template' => '{{#each foo}}{{@key}}: {{.}},{{/each}}',
13781384
'data' => ['foo' => [1, 'a' => 'b', 5]],

0 commit comments

Comments
 (0)