Skip to content

Allow empty partial-block#606

Open
TheConstructor wants to merge 2 commits into
Handlebars-Net:masterfrom
TheConstructor:feature/empty-partial-block
Open

Allow empty partial-block#606
TheConstructor wants to merge 2 commits into
Handlebars-Net:masterfrom
TheConstructor:feature/empty-partial-block

Conversation

@TheConstructor

Copy link
Copy Markdown

I authored a partial-template with optional block-includes and happened to write {{#>mail}}{{/mail}} for the test-mail, as I did not need to adjust the optionals. This led to an exception similar to the one below:

System.InvalidOperationException
Sequence contains no elements
   at System.Linq.ThrowHelper.ThrowNoElementsException()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at HandlebarsDotNet.Compiler.PartialBlockAccumulatorContext.GetAccumulatedBlock() in ..\Handlebars.Net\source\Handlebars\Compiler\Lexer\Converter\BlockAccumulators\PartialBlockAccumulatorContext.cs:line 27
   at HandlebarsDotNet.Compiler.BlockAccumulator.AccumulateBlock(Expression parentItem, IEnumerator`1 enumerator, BlockAccumulatorContext context) in ..\Handlebars.Net\source\Handlebars\Compiler\Lexer\Converter\BlockAccumulator.cs:line 56
   at HandlebarsDotNet.Compiler.BlockAccumulator.ConvertTokens(IEnumerable`1 sequence)+MoveNext() in ..\Handlebars.Net\source\Handlebars\Compiler\Lexer\Converter\BlockAccumulator.cs:line 32
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at HandlebarsDotNet.Compiler.BlockAccumulator.Accumulate(IEnumerable`1 tokens, ICompiledHandlebarsConfiguration configuration) in ..\Handlebars.Net\source\Handlebars\Compiler\Lexer\Converter\BlockAccumulator.cs:line 13
   at HandlebarsDotNet.Compiler.ExpressionBuilder.ConvertTokensToExpressions(IEnumerable`1 tokens, ICompiledHandlebarsConfiguration configuration) in ..\Handlebars.Net\source\Handlebars\Compiler\ExpressionBuilder.cs:line 25
   at HandlebarsDotNet.Compiler.HandlebarsCompiler.Compile(ExtendedStringReader source, CompilationContext compilationContext) in ..\Handlebars.Net\source\Handlebars\Compiler\HandlebarsCompiler.cs:line 25
   at HandlebarsDotNet.HandlebarsEnvironment.Compile(TextReader template) in ..\Handlebars.Net\source\Handlebars\HandlebarsEnvironment.cs:line 145
   at HandlebarsDotNet.HandlebarsEnvironment.Compile(String template) in ..\Handlebars.Net\source\Handlebars\HandlebarsEnvironment.cs:line 188
   at HandlebarsDotNet.Handlebars.Compile(String template) in ..\Handlebars.Net\source\Handlebars\Handlebars.cs:line 63
   at HandlebarsDotNet.Test.InlinePartialTests.BasicBlockInlinePartial() in ..

Being new to Handlebars.Net it took me a while to understand what really was wrong. I no know, that I can change the template to {{>mail}}, but the exception isn't really helpful and I don't know why it shouldn't just render. So this pull-request allows block-includes with empty blocks.

On a side-note {{#>mail}}{{/mail}} renders, if no mail-partial is present, while {{>mail}} throws an exception, if no MissingPartialTemplateHandler is registered. While not necessary for my use-case it may even be nice to have a syntax, that allows for optional partials out-of-the-box.

@rexm rexm enabled auto-merge June 20, 2026 00:43
auto-merge was automatically disabled June 20, 2026 13:11

Head branch was pushed to by a user without write access

@TheConstructor TheConstructor force-pushed the feature/empty-partial-block branch from e89ad99 to 63c6683 Compare June 20, 2026 13:11
@TheConstructor

Copy link
Copy Markdown
Author

@rexm I rebased on the current master, which disabled the auto-merge you set

@rexm rexm enabled auto-merge June 20, 2026 15:26
@rexm

rexm commented Jun 20, 2026

Copy link
Copy Markdown
Member

CI failure diagnosis

The Issue519_PartialBlockUsableAsBlockAndInIf test fails because of the Expression.Empty() change in PartialBlockAccumulatorContext.GetAccumulatedBlock().

The 0-body case previously returned null, which the partial renderer interpreted as "no fallback — render the outer @partial-block as-is." Changing it to Expression.Empty() gives the renderer a defined (but empty) fallback expression. When {{#> @partial-block }}{{/@partial-block}} appears inside a partial, the empty body is now treated as a new @partial-block definition that overrides the content passed in from the outer call ("Block content"), so the third assertion fails:

Not found: "Block:Block content"
String:    "Conditional: Block content\nPlain: Block content\n..."

Fix: keep null for the 0-body case — this avoids the original InvalidOperationException: Sequence contains no elements crash (which came from .First() on an empty list) while preserving the "no fallback" rendering semantics:

var fallback = _body.Count switch
{
    0 => (Expression)null,   // ← null, not Expression.Empty()
    1 => _body[0],
    _ => Expression.Block(_body)
};

@TheConstructor

Copy link
Copy Markdown
Author

The Issue519_PartialBlockUsableAsBlockAndInIf test fails because of the Expression.Empty() change in PartialBlockAccumulatorContext.GetAccumulatedBlock().

Fix: keep null for the 0-body case — this avoids the original InvalidOperationException: Sequence contains no elements crash (which came from .First() on an empty list) while preserving the "no fallback" rendering semantics:

var fallback = _body.Count switch
{
    0 => (Expression)null,   // ← null, not Expression.Empty()
    1 => _body[0],
    _ => Expression.Block(_body)
};

While it is true, that the test of #519 fails, this is not the fix. If you replaced {{#> @partial-block }}{{/@partial-block}} in said test with
e.g. {{#> @partial-block }}any fallback{{/@partial-block}} it will contain any fallback instead of Block content. I couldn't yet find a way to only override the PartialBlockContent, if we are not in {{#> @partial-block }}, but I will continue tomorrow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants