Fix block inheritance only worked for blocks defined at the root level #1

This commit is contained in:
semihalev 2025-07-26 02:34:17 +03:00
commit 8dcf0b934e
2 changed files with 76 additions and 18 deletions

View file

@ -8,8 +8,8 @@ Environment:
| Engine | Time (µs/op) | Memory Usage (KB/op) |
|-------------|--------------|----------------------|
| Twig | 0.40 | 0.12 |
| Go Template | 12.69 | 1.33 |
| Twig | 0.22 | 0.12 |
| Go Template | 9.14 | 1.47 |
Twig is 0.03x faster than Go's template engine.
Twig uses 0.09x less memory than Go's template engine.
Twig is 0.02x faster than Go's template engine.
Twig uses 0.08x less memory than Go's template engine.

86
node.go
View file

@ -720,13 +720,14 @@ func (n *ExtendsNode) Render(w io.Writer, ctx *RenderContext) error {
// Extract blocks from the parent template and store them as parent blocks
// for any blocks defined in the child but not yet in the parent chain
if rootNode, ok := parentTemplate.nodes.(*RootNode); ok {
for _, child := range rootNode.Children() {
if block, ok := child.(*BlockNode); ok {
// If we don't already have a parent for this block,
// use the parent template's block definition
if _, exists := parentCtx.parentBlocks[block.name]; !exists {
parentCtx.parentBlocks[block.name] = block.body
}
// Use recursive collection to find all blocks in parent template
parentBlocks := make(map[string][]Node)
collectBlocks(rootNode, parentBlocks)
// Store parent blocks that we don't already have
for blockName, blockBody := range parentBlocks {
if _, exists := parentCtx.parentBlocks[blockName]; !exists {
parentCtx.parentBlocks[blockName] = blockBody
}
}
}
@ -1458,6 +1459,55 @@ func (n *ApplyNode) Render(w io.Writer, ctx *RenderContext) error {
return err
}
// collectBlocks recursively collects all block definitions from a node tree
func collectBlocks(node Node, blocks map[string][]Node) {
switch n := node.(type) {
case *BlockNode:
// Found a block - register it
blocks[n.name] = n.body
case *RootNode:
// Recursively collect from all children
for _, child := range n.children {
collectBlocks(child, blocks)
}
case *IfNode:
// Collect blocks from all branches
for _, body := range n.bodies {
for _, child := range body {
collectBlocks(child, blocks)
}
}
// Also check else branch
for _, child := range n.elseBranch {
collectBlocks(child, blocks)
}
case *ForNode:
// Collect blocks from for body
for _, child := range n.body {
collectBlocks(child, blocks)
}
// Also check else branch
for _, child := range n.elseBranch {
collectBlocks(child, blocks)
}
case *SetNode:
// SetNode doesn't have body, skip
case *ApplyNode:
// Collect blocks from apply body
for _, child := range n.body {
collectBlocks(child, blocks)
}
case *MacroNode:
// Collect blocks from macro body
for _, child := range n.body {
collectBlocks(child, blocks)
}
case *VerbatimNode:
// VerbatimNode contains raw content, no blocks to collect
// Add other node types that can contain children as needed
}
}
// Implement Node interface for RootNode
func (n *RootNode) Render(w io.Writer, ctx *RenderContext) error {
// First pass: collect blocks and check for extends
@ -1472,14 +1522,22 @@ func (n *RootNode) Render(w io.Writer, ctx *RenderContext) error {
// First register all blocks in this template before processing extends
// Needed to ensure all blocks are available for parent() calls
// Use recursive collection to find blocks at all levels
templateBlocks := make(map[string][]Node)
collectBlocks(n, templateBlocks)
// Register collected blocks
for blockName, blockBody := range templateBlocks {
// Only register blocks that haven't been defined by a child template
if !hasChildBlocks || ctx.blocks[blockName] == nil {
// Register the block
ctx.blocks[blockName] = blockBody
}
}
// Check for extends node at top level
for _, child := range n.children {
if block, ok := child.(*BlockNode); ok {
// Only register blocks that haven't been defined by a child template
if !hasChildBlocks || ctx.blocks[block.name] == nil {
// Register the block
ctx.blocks[block.name] = block.body
}
} else if ext, ok := child.(*ExtendsNode); ok {
if ext, ok := child.(*ExtendsNode); ok {
// If this is an extends node, record it for later
extendsNode = ext
}