Document and benchmark sibling inheritance

This commit is contained in:
2026-09-29 23:13:50 +00:00
parent df9a02539c
commit 2ca1f80094
22 changed files with 440 additions and 56 deletions

View File

@@ -19,8 +19,7 @@ type inheritanceWalker struct {
}
// expandInheritanceTree resolves each destination scope after its enclosing
// relationships. It remains a private, test-only operation until the loading
// lifecycle also carries its complete provenance and inspection evidence.
// relationships. The loading lifecycle uses the origin-aware entry point.
func expandInheritanceTree(input *document.Node, limits projectionLimits) (*inheritanceScope, []Diagnostic) {
return expandInheritanceWithOrigins(input, nil, limits)
}
@@ -34,9 +33,13 @@ func expandInheritanceWithBudgets(input *document.Node, inputOrigins map[string]
return nil, []Diagnostic{{Code: CodeInheritanceInvalid, Severity: SeverityError,
Message: "inheritance requires a mapping document root"}}
}
root := input
root := document.Clone(input)
origins := cloneOriginIndex(inputOrigins)
if inputOrigins == nil {
origins = directInheritanceOrigins(input, Path{}, nil)
}
walker := inheritanceWalker{
root: &root, limits: limits, origins: cloneOriginIndex(inputOrigins),
root: &root, limits: limits, origins: origins,
retained: newRetainedNodeCounter(historyLimit),
}
if problem := walker.visit(&root, Path{}, true); problem != nil {
@@ -75,22 +78,24 @@ func (w *inheritanceWalker) visit(node **document.Node, path Path, unscoped bool
*node = withoutInheritanceDirective(*node)
}
}
resolved, diagnostics := expandSiblingDefinitionsWithOrigins(*node, path, w.limits, w.origins, w.retained)
if len(diagnostics) != 0 {
return &diagnostics[0]
}
*node = resolved.root
deleteOriginSubtree(w.origins, path, original)
for pointer, origin := range resolved.origins {
w.origins[pointer] = origin
}
w.transfers = append(w.transfers, resolved.transfers...)
w.controls = append(w.controls, resolved.controls...)
w.replacements = append(w.replacements, resolved.replacements...)
w.operations = append(w.operations, resolved.operations...)
if !inheritanceLowerBoundWithin(*w.root, w.limits.nodes) {
problem := inheritanceBudgetDiagnostic(path, (*node).Location)
return &problem
if scopeHasInheritanceDefinitions(*node) {
resolved, diagnostics := expandSiblingDefinitionsWithOrigins(*node, path, w.limits, w.origins, w.retained)
if len(diagnostics) != 0 {
return &diagnostics[0]
}
*node = resolved.root
deleteOriginSubtree(w.origins, path, original)
for pointer, origin := range resolved.origins {
w.origins[pointer] = origin
}
w.transfers = append(w.transfers, resolved.transfers...)
w.controls = append(w.controls, resolved.controls...)
w.replacements = append(w.replacements, resolved.replacements...)
w.operations = append(w.operations, resolved.operations...)
if !inheritanceLowerBoundWithin(*w.root, w.limits.nodes) {
problem := inheritanceBudgetDiagnostic(path, (*node).Location)
return &problem
}
}
indices := make([]int, len((*node).Mapping))
for index := range indices {
@@ -116,6 +121,18 @@ func (w *inheritanceWalker) visit(node **document.Node, path Path, unscoped bool
return nil
}
func scopeHasInheritanceDefinitions(scope *document.Node) bool {
for _, entry := range scope.Mapping {
if entry.Value.Kind != document.Mapping {
continue
}
if _, found := inheritanceDirective(entry.Value); found {
return true
}
}
return false
}
// A directive may disappear during later destination resolution, so only
// non-directive nodes form a safe early lower bound on final output size.
func inheritanceLowerBoundWithin(root *document.Node, limit int) bool {