Skip to content

Commit 3e74aa6

Browse files
committed
Fix blockParams count for inverse helpers
1 parent 3860588 commit 3e74aa6

2 files changed

Lines changed: 24 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

@@ -238,23 +236,6 @@ private function blockParamsUseVars(): string
238236
return $this->blockParamValues ? '$blockParams' : '';
239237
}
240238

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

316-
$outerBp = $this->outerBlockParamsExpr();
317-
$params = $this->compileParams($block->params, $block->hash);
318297
$helperName = self::quote($name);
319-
$bpCount = count($fnProgram->blockParams);
320298

321-
return self::buildHbbchCall("\$cx->helpers[$helperName]", $helperName, $params, $fn, $else, $bpCount, $outerBp);
299+
return $this->buildBlockHelperCall("\$cx->helpers[$helperName]", $helperName, $block, $fn, $else);
322300
}
323301

324302
/**
@@ -367,16 +345,13 @@ private function compileConditionalExpr(Expression $condExpr, bool $negate): str
367345

368346
private function compileDynamicBlockHelper(BlockStatement $block, string $name, string $varPath): string
369347
{
370-
$bp = $block->program->blockParams ?? [];
371-
$params = $this->compileParams($block->params, $block->hash);
372348
$blockFn = $block->program !== null
373349
? $this->compileProgramWithBlockParams($block->program)
374350
: 'null';
375351
$else = $this->compileProgramOrNull($block->inverse);
376-
$outerBp = $this->outerBlockParamsExpr();
377352
$helperName = self::quote($name);
378353
$helperExpr = self::getRuntimeFunc('resolveHelper', "\$cx, $helperName, $varPath, true");
379-
return self::buildHbbchCall($helperExpr, $helperName, $params, $blockFn, $else, count($bp), $outerBp);
354+
return $this->buildBlockHelperCall($helperExpr, $helperName, $block, $blockFn, $else);
380355
}
381356

382357
private function DecoratorBlock(BlockStatement $block): string
@@ -841,14 +816,20 @@ private static function buildKeyAccess(array $parts): string
841816
return $n;
842817
}
843818

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

854835
/**

tests/RegressionTest.php

Lines changed: 3 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}}',

0 commit comments

Comments
 (0)