Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/unreleased/layout-drawn-query.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Layout validation reuses shared definition expansions instead of materializing every inherited occurrence, avoiding exponential tree walks while preserving typed features, placement diagnostics and rendered diagrams.
1 change: 1 addition & 0 deletions changes/unreleased/metadata-identity-positions.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Computing the identities of metadata body features reuses their owning membership positions, avoiding repeated scans of metadata-heavy scopes without changing their identities.
23 changes: 23 additions & 0 deletions internal/check/passes/diagram/layout_shared_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package diagram_test

import (
"fmt"
"strings"
"testing"
)

func TestLayoutWithDeeplySharedDefinitions(t *testing.T) {
var src strings.Builder
src.WriteString("package Shared { private import Views::*; private import DiagramLayout::*; part def D0 { part leaf; }\n")
for level := 1; level <= 12; level++ {
fmt.Fprintf(&src, "part def D%d {\n", level)
for branch := range 8 {
fmt.Fprintf(&src, "part p%d : D%d;\n", branch, level-1)
}
src.WriteString("}\n")
}
src.WriteString("part root : D12; view v { expose root; render asTreeDiagram; metadata Layout about root { x = 0; y = 0; } } }")
if diags := w8dDiags(t, src.String()); len(diags) != 0 {
t.Fatalf("unexpected diagnostics: %v", diags)
}
}
2 changes: 1 addition & 1 deletion internal/ir/view/place.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func (d *Drawn) noteMember(owner, member *symbols.Symbol) {
// not render is the error Render gives.
func (r *Renderer) DrawnIn(view *symbols.Symbol) (*Drawn, error) {
drawn := newDrawn()
if _, err := r.render(view, drawn); err != nil {
if _, err := r.render(view, drawn, true); err != nil {
return nil, err
}
return drawn, nil
Expand Down
62 changes: 51 additions & 11 deletions internal/ir/view/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,17 @@ func (r *Renderer) treeDepth() int {
// typing — are drawn as edges once every node is (treeEdges).
func (r *Renderer) renderTree(view *symbols.Symbol, exposed []*symbols.Symbol, out *Rendering) {
ids := &nodeIDs{}
query := r.treeQuery(view, out, exposed)
out.sites = map[*Node]treeSite{}
descendants := r.exposedDescendants(view, exposed, func(sym *symbols.Symbol) []*symbols.Symbol {
members := func(sym *symbols.Symbol) []*symbols.Symbol {
return append(r.walkMembers(view, sym), r.annotatingMembers(sym)...)
})
}
var descendants map[symbols.ElementKey]bool
if query != nil {
descendants = query.exposedDescendants(exposed, true)
} else {
descendants = r.exposedDescendants(view, exposed, members)
}
for _, elem := range exposed {
if descendants[symbols.KeyOf(elem)] {
continue
Expand All @@ -47,7 +54,7 @@ func (r *Renderer) renderTree(view *symbols.Symbol, exposed []*symbols.Symbol, o
}
continue
}
walk := treeWalk{view: view, ids: ids, path: expansion{}, out: out}
walk := treeWalk{view: view, ids: ids, path: expansion{}, out: out, query: query}
out.Roots = append(out.Roots, r.treeNode(walk, elem, nil, 0, true))
}
out.Roots = append(out.Roots, r.nestedViewNodes(view, ids, map[*symbols.Symbol]bool{view: true}, out)...)
Expand Down Expand Up @@ -151,12 +158,18 @@ func (r *Renderer) nestedViewNodes(view *symbols.Symbol, ids *nodeIDs, rendered
r.dress(view, sub, node, out)
exposed, err := r.model.ExposedElements(sub)
if err == nil {
descendants := r.exposedDescendants(sub, exposed, func(sym *symbols.Symbol) []*symbols.Symbol { return r.walkMembers(sub, sym) })
query := r.treeQuery(sub, out, exposed)
var descendants map[symbols.ElementKey]bool
if query != nil {
descendants = query.exposedDescendants(exposed, false)
} else {
descendants = r.exposedDescendants(sub, exposed, func(sym *symbols.Symbol) []*symbols.Symbol { return r.walkMembers(sub, sym) })
}
for _, elem := range exposed {
if descendants[symbols.KeyOf(elem)] {
continue
}
walk := treeWalk{view: sub, ids: ids, path: expansion{}, out: out}
walk := treeWalk{view: sub, ids: ids, path: expansion{}, out: out, query: query}
node.Children = append(node.Children, r.treeNode(walk, elem, nil, 0, true))
}
}
Expand All @@ -172,18 +185,29 @@ func (r *Renderer) nestedViewNodes(view *symbols.Symbol, ids *nodeIDs, rendered
// treeWalk is what one walk down a tree shares between its nodes: the view
// (nil outside any view), the node ids, the expansion path and the rendering.
type treeWalk struct {
view *symbols.Symbol
ids *nodeIDs
path expansion
out *Rendering
view *symbols.Symbol
ids *nodeIDs
path expansion
out *Rendering
query *treeQuery
}

// treeNode renders one element and what it declares. qualified names the node by
// qualified name, which is what an exposed element is reported by; a member
// nested in it is named as it was declared.
func (r *Renderer) treeNode(walk treeWalk, sym, owner *symbols.Symbol, depth int, qualified bool) *Node {
var key treeQueryKey
if walk.query != nil {
key = walk.query.key(sym, owner, walk.path, qualified)
if previous := walk.query.node(key, depth); previous != nil {
return previous
}
}
view, out := walk.view, walk.out
node := r.treeNodeShallow(walk, sym, owner, qualified)
if walk.query != nil {
defer walk.query.remember(key, depth, node)
}
if walk.path[sym] {
return node
}
Expand All @@ -195,9 +219,20 @@ func (r *Renderer) treeNode(walk treeWalk, sym, owner *symbols.Symbol, depth int
// A parameter is a pin of its owner's node, drawn at every depth. At the depth
// bound, or collapsed, an element keeps its ports and pins — they count at
// their owner's depth — and nothing else nested is drawn.
bound, cut := r.walkBound(view), 0
bound, cut := 0, 0
if walk.query != nil {
bound = walk.query.bound
} else {
bound = r.walkBound(view)
}
folded := depth >= bound || collapsed(node)
for _, member := range r.walkMembers(view, sym) {
var members []*symbols.Symbol
if walk.query != nil {
members = walk.query.members(sym)
} else {
members = r.walkMembers(view, sym)
}
for _, member := range members {
if recursive != nil && member.Owner() != sym {
continue
}
Expand Down Expand Up @@ -246,6 +281,11 @@ func (r *Renderer) annotationChildren(ids *nodeIDs, view, sym *symbols.Symbol, o
// the path being expanded above it is noted as already shown.
func (r *Renderer) treeNodeShallow(walk treeWalk, sym, owner *symbols.Symbol, qualified bool) *Node {
view, out := walk.view, walk.out
if walk.query != nil {
node := &Node{ID: walk.ids.take(), Origin: symbolOrigin(sym), Geometry: walk.query.geometryOf(sym, owner, out)}
out.noteTreeSite(node, view, sym)
return node
}
name := r.notationName(sym)
if !qualified {
name = localName(sym)
Expand Down
27 changes: 26 additions & 1 deletion internal/ir/view/tree_edges.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ func (r *Renderer) treeEdges(out *Rendering) map[symbols.ElementKey]*Node {
// its owner draws it as inherited: a relationship arrow joins declarations,
// not one usage's copy to another's.
var index func(node, parent *Node)
indexed := map[*Node]bool{}
index = func(node, parent *Node) {
if parent != nil {
if parent != nil && (!out.query || !indexed[node]) {
g.parent[node.ID] = parent.ID
}
parentSite, inElement := out.sites[parent]
Expand All @@ -72,6 +73,12 @@ func (r *Renderer) treeEdges(out *Rendering) map[symbols.ElementKey]*Node {
g.nodes[key] = node
}
}
if out.query && indexed[node] {
return
}
if out.query {
indexed[node] = true
}
for _, child := range node.Children {
index(child, node)
}
Expand All @@ -81,7 +88,14 @@ func (r *Renderer) treeEdges(out *Rendering) map[symbols.ElementKey]*Node {
}
g.associationLines()
var walk func(node *Node)
walked := map[*Node]bool{}
walk = func(node *Node) {
if out.query && walked[node] {
return
}
if out.query {
walked[node] = true
}
if site, ok := out.sites[node]; ok && g.nodes[symbols.KeyOf(site.sym)] == node {
g.edgesOf(node, site)
}
Expand Down Expand Up @@ -155,14 +169,25 @@ func declaredUnder(parent, sym *symbols.Symbol) bool {
// the name up.
func (g *treeGraph) associationLines() {
var prune func(nodes []*Node) []*Node
visited, removed := map[*Node]bool{}, map[*Node]bool{}
prune = func(nodes []*Node) []*Node {
kept := nodes[:0]
for _, node := range nodes {
if g.out.query && visited[node] {
if !removed[node] {
kept = append(kept, node)
}
continue
}
if g.out.query {
visited[node] = true
}
site := g.out.sites[node]
if line, ok := g.associationLine(node, site); ok && nary(len(line.ends)) {
g.drawAssociationDot(node, site, line)
} else if ok {
g.drawAssociation(node, site, line)
removed[node] = true
continue
}
node.Children = prune(node.Children)
Expand Down
Loading
Loading