Skip to content

Commit c60e115

Browse files
committed
internal/core/dep: first stab at precise dependency analyzer
Change-Id: I0bf5de9a1b2421ddffa4108c6dbffd38dae2cfe0 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7604 Reviewed-by: CUE cueckoo <[email protected]> Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent dbfa73b commit c60e115

File tree

14 files changed

+633
-7
lines changed

14 files changed

+633
-7
lines changed

internal/core/adt/context.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,14 @@ func (c *OpContext) Resolve(env *Environment, r Resolver) (*Vertex, *Bottom) {
340340
return nil, arc.ChildErrors
341341
}
342342

343+
for {
344+
x, ok := arc.Value.(*Vertex)
345+
if !ok {
346+
break
347+
}
348+
arc = x
349+
}
350+
343351
return arc, err
344352
}
345353

internal/core/dep/dep.go

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
// Copyright 2020 CUE Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package dep analyzes dependencies between values.
16+
package dep
17+
18+
import (
19+
"errors"
20+
21+
"cuelang.org/go/internal/core/adt"
22+
)
23+
24+
// A Dependency is a reference and the node that reference resolves to.
25+
type Dependency struct {
26+
// Node is the referenced node.
27+
Node *adt.Vertex
28+
29+
// Reference is the expression that referenced the node.
30+
Reference adt.Resolver
31+
32+
top bool
33+
}
34+
35+
// Import returns the import reference or nil if the reference was within
36+
// the same package as the visited Vertex.
37+
func (d *Dependency) Import() *adt.ImportReference {
38+
x, _ := d.Reference.(adt.Expr)
39+
return importRef(x)
40+
}
41+
42+
// IsRoot reports whether the dependency is referenced by the root of the
43+
// original Vertex passed to any of the Visit* functions, and not one of its
44+
// descendent arcs. This always returns true for Visit().
45+
func (d *Dependency) IsRoot() bool {
46+
return d.top
47+
}
48+
49+
func (d *Dependency) Path() []adt.Feature {
50+
return nil
51+
}
52+
53+
func importRef(r adt.Expr) *adt.ImportReference {
54+
switch x := r.(type) {
55+
case *adt.ImportReference:
56+
return x
57+
case *adt.SelectorExpr:
58+
return importRef(x.X)
59+
case *adt.IndexExpr:
60+
return importRef(x.X)
61+
}
62+
return nil
63+
}
64+
65+
// VisitFunc is used for reporting dependencies.
66+
type VisitFunc func(Dependency) error
67+
68+
// Visit calls f for all vertices referenced by the conjuncts of n without
69+
// descending into the elements of list or fields of structs. Only references
70+
// that do not refer to the conjuncts of n itself are reported.
71+
func Visit(c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
72+
return visit(c, n, f, false, true)
73+
}
74+
75+
// VisitAll calls f for all vertices referenced by the conjuncts of n including
76+
// those of descendant fields and elements. Only references that do not refer to
77+
// the conjuncts of n itself are reported.
78+
func VisitAll(c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
79+
return visit(c, n, f, true, true)
80+
}
81+
82+
var empty *adt.Vertex
83+
84+
func init() {
85+
empty = &adt.Vertex{}
86+
empty.UpdateStatus(adt.Finalized)
87+
}
88+
89+
func visit(c *adt.OpContext, n *adt.Vertex, f VisitFunc, all, top bool) (err error) {
90+
if c == nil {
91+
panic("nil context")
92+
}
93+
v := visitor{
94+
ctxt: c,
95+
visit: f,
96+
node: n,
97+
all: all,
98+
top: top,
99+
}
100+
101+
defer func() {
102+
switch x := recover(); x {
103+
case nil:
104+
case aborted:
105+
err = v.err
106+
default:
107+
panic(x)
108+
}
109+
}()
110+
111+
for _, x := range n.Conjuncts {
112+
v.markExpr(x.Env, x.Expr())
113+
}
114+
115+
return nil
116+
}
117+
118+
var aborted = errors.New("aborted")
119+
120+
type visitor struct {
121+
ctxt *adt.OpContext
122+
visit VisitFunc
123+
node *adt.Vertex
124+
err error
125+
all bool
126+
top bool
127+
}
128+
129+
// TODO: factor out the below logic as either a low-level dependency analyzer or
130+
// some walk functionality.
131+
132+
// markExpr visits all nodes in an expression to mark dependencies.
133+
func (c *visitor) markExpr(env *adt.Environment, expr adt.Expr) {
134+
switch x := expr.(type) {
135+
case nil:
136+
case adt.Resolver:
137+
c.markResolver(env, x)
138+
139+
case *adt.BinaryExpr:
140+
c.markExpr(env, x.X)
141+
c.markExpr(env, x.Y)
142+
143+
case *adt.UnaryExpr:
144+
c.markExpr(env, x.X)
145+
146+
case *adt.Interpolation:
147+
for i := 1; i < len(x.Parts); i += 2 {
148+
c.markExpr(env, x.Parts[i])
149+
}
150+
151+
case *adt.BoundExpr:
152+
c.markExpr(env, x.Expr)
153+
154+
case *adt.CallExpr:
155+
c.markExpr(env, x.Fun)
156+
saved := c.all
157+
c.all = true
158+
for _, a := range x.Args {
159+
c.markExpr(env, a)
160+
}
161+
c.all = saved
162+
163+
case *adt.DisjunctionExpr:
164+
for _, d := range x.Values {
165+
c.markExpr(env, d.Val)
166+
}
167+
168+
case *adt.SliceExpr:
169+
c.markExpr(env, x.X)
170+
c.markExpr(env, x.Lo)
171+
c.markExpr(env, x.Hi)
172+
c.markExpr(env, x.Stride)
173+
174+
case *adt.ListLit:
175+
for _, e := range x.Elems {
176+
switch x := e.(type) {
177+
case adt.Yielder:
178+
c.markYielder(env, x)
179+
180+
case adt.Expr:
181+
c.markSubExpr(env, x)
182+
183+
case *adt.Ellipsis:
184+
if x.Value != nil {
185+
c.markSubExpr(env, x.Value)
186+
}
187+
}
188+
}
189+
190+
case *adt.StructLit:
191+
env := &adt.Environment{Up: env, Vertex: empty}
192+
for _, e := range x.Decls {
193+
c.markDecl(env, e)
194+
}
195+
}
196+
}
197+
198+
// markResolve resolves dependencies.
199+
func (c *visitor) markResolver(env *adt.Environment, r adt.Resolver) {
200+
switch x := r.(type) {
201+
case nil:
202+
case *adt.LetReference:
203+
saved := c.ctxt.PushState(env, nil)
204+
env := c.ctxt.Env(x.UpCount)
205+
c.markExpr(env, x.X)
206+
c.ctxt.PopState(saved)
207+
return
208+
}
209+
210+
if ref, _ := c.ctxt.Resolve(env, r); ref != nil {
211+
if ref != c.node {
212+
d := Dependency{
213+
Node: ref,
214+
Reference: r,
215+
top: c.top,
216+
}
217+
if err := c.visit(d); err != nil {
218+
c.err = err
219+
panic(aborted)
220+
}
221+
}
222+
223+
return
224+
}
225+
226+
// It is possible that a reference cannot be resolved because it is
227+
// incomplete. In this case, we should check whether subexpressions of the
228+
// reference can be resolved to mark those dependencies. For instance,
229+
// prefix paths of selectors and the value or index of an index experssion
230+
// may independently resolve to a valid dependency.
231+
232+
switch x := r.(type) {
233+
case *adt.NodeLink:
234+
panic("unreachable")
235+
236+
case *adt.IndexExpr:
237+
c.markExpr(env, x.X)
238+
c.markExpr(env, x.Index)
239+
240+
case *adt.SelectorExpr:
241+
c.markExpr(env, x.X)
242+
}
243+
}
244+
245+
func (c *visitor) markSubExpr(env *adt.Environment, x adt.Expr) {
246+
if c.all {
247+
saved := c.top
248+
c.top = false
249+
c.markExpr(env, x)
250+
c.top = saved
251+
}
252+
}
253+
254+
func (c *visitor) markDecl(env *adt.Environment, d adt.Decl) {
255+
switch x := d.(type) {
256+
case *adt.Field:
257+
c.markSubExpr(env, x.Value)
258+
259+
case *adt.OptionalField:
260+
// when dynamic, only continue if there is evidence of
261+
// the field in the parallel actual evaluation.
262+
c.markSubExpr(env, x.Value)
263+
264+
case *adt.BulkOptionalField:
265+
c.markExpr(env, x.Filter)
266+
// when dynamic, only continue if there is evidence of
267+
// the field in the parallel actual evaluation.
268+
c.markSubExpr(env, x.Value)
269+
270+
case *adt.DynamicField:
271+
c.markExpr(env, x.Key)
272+
// when dynamic, only continue if there is evidence of
273+
// a matching field in the parallel actual evaluation.
274+
c.markSubExpr(env, x.Value)
275+
276+
case adt.Yielder:
277+
c.markYielder(env, x)
278+
279+
case adt.Expr:
280+
c.markExpr(env, x)
281+
282+
case *adt.Ellipsis:
283+
if x.Value != nil {
284+
c.markSubExpr(env, x.Value)
285+
}
286+
}
287+
}
288+
289+
func (c *visitor) markYielder(env *adt.Environment, y adt.Yielder) {
290+
switch x := y.(type) {
291+
case *adt.ForClause:
292+
c.markExpr(env, x.Src)
293+
env := &adt.Environment{Up: env, Vertex: empty}
294+
c.markYielder(env, x.Dst)
295+
// In dynamic mode, iterate over all actual value and
296+
// evaluate.
297+
298+
case *adt.LetClause:
299+
c.markExpr(env, x.Expr)
300+
env := &adt.Environment{Up: env, Vertex: empty}
301+
c.markYielder(env, x.Dst)
302+
303+
case *adt.IfClause:
304+
c.markExpr(env, x.Condition)
305+
// In dynamic mode, only continue if condition is true.
306+
c.markYielder(env, x.Dst)
307+
308+
case *adt.ValueClause:
309+
c.markExpr(env, x.StructLit)
310+
}
311+
}

0 commit comments

Comments
 (0)