Fence a calc operand against every route that renders it
checkBoundLevelsHeld already performed the right walk; it iterated the wrong set. Walking t.held covers both kinds of hold — the level a dotted token reads and the sibling a calc reads — so the guarantee the README states now holds for every spelling of the operand, not just the bare one. The path message is unchanged. A calc-only head gets its own, naming the sibling spelling that reads the held draw. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -38,21 +38,24 @@ func linkRefs(root map[string]node) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkBoundLevelsHeld rejects every route to a bound level except the paths that
|
// checkBoundLevelsHeld rejects every route to a held name except the ones that read
|
||||||
// read it. A path holds one draw of the level; anything else that renders it draws
|
// its draw. An expansion holds one draw of that name; anything else that renders it
|
||||||
// again, and the two disagree. checkNoOverlap settles the spellings within one
|
// draws again, and the two disagree. checkNoOverlap settles the spellings within one
|
||||||
// format (a token, a calc operand); this settles the rest — a reference, whether it
|
// format (a token, a calc operand); this settles the rest — a reference, whether it
|
||||||
// sits in that format or in anything the format renders, however deep.
|
// sits in that format or in anything the format renders, however deep.
|
||||||
//
|
//
|
||||||
|
// It walks t.held, so it covers both kinds of hold: the level a dotted token reads,
|
||||||
|
// and the sibling a {calc()} reads.
|
||||||
|
//
|
||||||
// It runs after checkNoCycles, whose guarantee is what lets the walk terminate.
|
// It runs after checkNoCycles, whose guarantee is what lets the walk terminate.
|
||||||
func checkBoundLevelsHeld(root map[string]node) error {
|
func checkBoundLevelsHeld(root map[string]node) error {
|
||||||
return walkNodes(root, func(path string, n node) error {
|
return walkNodes(root, func(path string, n node) error {
|
||||||
t, ok := n.(*template)
|
t, ok := n.(*template)
|
||||||
if !ok || len(t.bound) == 0 {
|
if !ok || len(t.held) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
heads := make([]string, 0, len(t.bound))
|
heads := make([]string, 0, len(t.held))
|
||||||
for head := range t.bound {
|
for head := range t.held {
|
||||||
heads = append(heads, head)
|
heads = append(heads, head)
|
||||||
}
|
}
|
||||||
sort.Strings(heads) // so which overlap is reported does not vary
|
sort.Strings(heads) // so which overlap is reported does not vary
|
||||||
@@ -64,10 +67,13 @@ func checkBoundLevelsHeld(root map[string]node) error {
|
|||||||
seen := map[node]bool{}
|
seen := map[node]bool{}
|
||||||
for _, e := range renderEdges(t) {
|
for _, e := range renderEdges(t) {
|
||||||
if splitArm(e.label).key == head {
|
if splitArm(e.label).key == head {
|
||||||
continue // a path token reading this level, which is the one route allowed
|
continue // a token or operand reading this draw, the routes allowed
|
||||||
}
|
}
|
||||||
if renders(e.to, held, seen) {
|
if renders(e.to, held, seen) {
|
||||||
return fmt.Errorf("%s: {%s} renders %q, which {%s} reads a path into; name the fields you want instead", path, e.label, head, t.bound[head])
|
if reader, isPath := t.bound[head]; isPath {
|
||||||
|
return fmt.Errorf("%s: {%s} renders %q, which {%s} reads a path into; name the fields you want instead", path, e.label, head, reader)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: {%s} renders %q, which a {calc()} also reads; spell it {%s} so both read one draw", path, e.label, head, head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user