From f512c1663d6b026e2d518b92b764de6afe949fa5 Mon Sep 17 00:00:00 2001 From: M Date: Sat, 29 Aug 2026 14:56:55 +0200 Subject: [PATCH] Hold a path's draw at every level it passes through --- render.go | 27 +++++++++++++++++++++++---- template.go | 11 ++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/render.go b/render.go index 8e89665..5a493d1 100644 --- a/render.go +++ b/render.go @@ -189,17 +189,36 @@ func resolve(s *session, arms []arm, t *template, bound *draws) string { n = drawn(s, t.fields[a.key]) bound.variant[a.key] = n } - if len(a.tail) > 0 { - var err error - if n, err = descend(s, n, a.tail); err != nil { - panic(fmt.Sprintf("fakes: %s: %v", a.name, err)) + // Walk the tail, holding the draw at every level the path passes through, so + // two paths sharing a prefix share every choice along it, not just the head. + for i, seg := range a.tail { + if i < len(a.steps) { + held, drew := bound.variant[a.steps[i]] + if !drew { + held = drawn(s, child(n, seg)) + bound.variant[a.steps[i]] = held + } + n = held + continue } + n = child(n, seg) } v := render(s, n) bound.value[a.name] = v return v } +// child is the node one path segment names below an already-drawn node. checkPath +// proved the segment exists and that drawn leaves a template here, so this cannot +// fail. +func child(n node, seg string) node { + t, ok := n.(*template) + if !ok { + panic(fmt.Sprintf("fakes: %q under %T, which carries no fields", seg, n)) + } + return t.fields[seg] +} + // drawn resolves a choice to one variant, so a bound head is a concrete node the // rest of the expansion shares. Nested choices unwrap too: a draw is one value, not // another set to pick from. diff --git a/template.go b/template.go index 2a8d749..74f1bb9 100644 --- a/template.go +++ b/template.go @@ -180,6 +180,10 @@ type arm struct { name string // as written, and the key a bound draw's value is held under key string tail []string + // steps holds the key for each segment the walk passes *through* — the path + // prefixes below the head and above the leaf. The head is bound under key and + // the leaf's value under name, so a one-segment tail needs none of these. + steps []string } // splitArm splits one token alternative into key and tail. A reference keeps its @@ -192,7 +196,12 @@ func splitArm(name string) arm { if !dotted { return arm{name: name, key: name} } - return arm{name: name, key: head, tail: strings.Split(tail, ".")} + segs := strings.Split(tail, ".") + var steps []string + for i := 0; i < len(segs)-1; i++ { // every prefix except the leaf's own + steps = append(steps, head+"."+strings.Join(segs[:i+1], ".")) + } + return arm{name: name, key: head, tail: segs, steps: steps} } // splitArms splits a token body's '|' alternatives.