Skip to content

Commit

Permalink
internal/core/dep: prepare for public APi
Browse files Browse the repository at this point in the history
Issue #2247

Signed-off-by: Marcel van Lohuizen <[email protected]>
Change-Id: Ia53243890ff23f6653e03025138292a494c9b140
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/556459
Reviewed-by: Daniel Martí <[email protected]>
Unity-Result: CUE porcuepine <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
  • Loading branch information
mpvl committed Jul 11, 2023
1 parent d4fd104 commit 0554d4e
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 67 deletions.
2 changes: 1 addition & 1 deletion encoding/openapi/cycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (b *builder) checkCycle(v cue.Value) bool {
r, n := internalvalue.ToInternal(v)
ctx := eval.NewContext(r, n)

err := dep.Visit(ctx, nil, n, func(d dep.Dependency) error {
err := dep.Visit(nil, ctx, n, func(d dep.Dependency) error {
for _, m := range b.ctx.cycleNodes {
if m == d.Node {
var p token.Pos
Expand Down
123 changes: 73 additions & 50 deletions internal/core/dep/dep.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
package dep

import (
"errors"

"cuelang.org/go/cue/errors"
"cuelang.org/go/internal/core/adt"
)

Expand Down Expand Up @@ -82,6 +81,34 @@ import (
// [add more as they come up]
//

type Config struct {
// Dynamic enables evaluting dependencies Vertex Arcs, recursively
Dynamic bool

// Descend enables recursively descending into fields. This option is
// implied by Dynamic.
Descend bool

// Cycles allows a Node to reported more than once. This includes the node
// passed to Visit, which is otherwise never reported. This option can be
// used to disable cycle checking. TODO: this is not yet implemented.
AllowCycles bool

// Rootless enables reporting nodes that do not have a path from the root.
// This includes variables of comprehensions and fields of composite literal
// values that are part of expressions, such as {out: v}.out.
Rootless bool

// TODO:
// ContinueOnError indicates whether to continue finding dependencies
// even when there are errors.
// ContinueOnError bool

// pkg indicates the main package for which the analyzer is configured,
// which is used for reporting purposes.
Pkg *adt.ImportReference
}

// A Dependency is a reference and the node that reference resolves to.
type Dependency struct {
// Node is the referenced node.
Expand All @@ -108,10 +135,6 @@ func (d *Dependency) IsRoot() bool {
return d.top
}

func (d *Dependency) Path() []adt.Feature {
return nil
}

func importRef(r adt.Expr) *adt.ImportReference {
switch x := r.(type) {
case *adt.ImportReference:
Expand All @@ -127,37 +150,6 @@ func importRef(r adt.Expr) *adt.ImportReference {
// VisitFunc is used for reporting dependencies.
type VisitFunc func(Dependency) error

// Visit calls f for all vertices referenced by the conjuncts of n without
// descending into the elements of list or fields of structs. Only references
// that do not refer to the conjuncts of n itself are reported. pkg indicates
// the the package within which n is contained, which is used for reporting
// purposes. It may be nil, indicating the main package.
func Visit(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitFunc) error {
return visit(c, pkg, n, f, false, true)
}

// VisitAll calls f for all vertices referenced by the conjuncts of n including
// those of descendant fields and elements. Only references that do not refer to
// the conjuncts of n itself are reported. pkg indicates the current
// package, which is used for reporting purposes.
func VisitAll(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitFunc) error {
return visit(c, pkg, n, f, true, true)
}

// VisitFields calls f for n and all its descendent arcs that have a conjunct
// that originates from a conjunct in n. Only the conjuncts of n that ended up
// as a conjunct in an actual field are visited and they are visited for each
// field in which the occurs. pkg indicates the current package, which is
// used for reporting purposes.
func VisitFields(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitFunc) error {
m := marked{}

m.markExpr(n)

dynamic(c, pkg, n, f, m, true)
return nil
}

var empty *adt.Vertex

func init() {
Expand All @@ -166,21 +158,50 @@ func init() {
empty.ForceDone()
}

func visit(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitFunc, all, top bool) (err error) {
var zeroConfig = &Config{}

// Visit calls f for the dependencies of n as determined by the given
// configuration.
func Visit(cfg *Config, c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
if cfg == nil {
cfg = zeroConfig
}
if c == nil {
panic("nil context")
}
v := visitor{
ctxt: c,
visit: f,
node: n,
pkg: pkg,
recurse: all,
all: all,
top: top,
fn: f,
pkg: cfg.Pkg,
recurse: cfg.Descend,
all: cfg.Descend,
top: true,
}

if cfg.Dynamic {
v.marked = marked{}

v.marked.markExpr(n)

v.dynamic(n, true)
} else {
v.visit(n, true)
}

return v.err
}

func (v *visitor) visit(n *adt.Vertex, top bool) (err error) {
savedNode := v.node
savedTop := v.top

v.node = n
v.top = top

defer func() {
v.node = savedNode
v.top = savedTop

switch x := recover(); x {
case nil:
case aborted:
Expand All @@ -200,11 +221,11 @@ func visit(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitFun
var aborted = errors.New("aborted")

type visitor struct {
ctxt *adt.OpContext
visit VisitFunc
node *adt.Vertex
err error
pkg *adt.ImportReference
ctxt *adt.OpContext
fn VisitFunc
node *adt.Vertex
err error
pkg *adt.ImportReference

// recurse indicates whether, during static analysis, to process references
// that will be unified into different fields.
Expand All @@ -217,6 +238,8 @@ type visitor struct {
topRef adt.Resolver
pathStack []refEntry
numRefs int // count of reported dependencies

marked marked
}

type refEntry struct {
Expand Down Expand Up @@ -416,7 +439,7 @@ func (c *visitor) reportDependency(env *adt.Environment, ref adt.Resolver, v *ad
pkg: pkg,
top: c.top,
}
if err := c.visit(d); err != nil {
if err := c.fn(d); err != nil {
c.err = err
panic(aborted)
}
Expand Down
21 changes: 12 additions & 9 deletions internal/core/dep/dep_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ func TestVisit(t *testing.T) {
testCases := []struct {
name string
root string
fn visitFunc
cfg *dep.Config
}{{
name: "field",
root: "a.b",
fn: dep.Visit,
cfg: nil,
}, {
name: "all",
root: "a",
fn: dep.VisitAll,
cfg: &dep.Config{Descend: true},
}, {
name: "dynamic",
root: "a",
fn: dep.VisitFields,
cfg: &dep.Config{Dynamic: true},
}}

for _, tc := range testCases {
Expand All @@ -73,21 +73,21 @@ func TestVisit(t *testing.T) {
w := t.Writer(tc.name)

t.Run(tc.name, func(sub *testing.T) {
testVisit(sub, w, ctxt, n, tc.fn)
testVisit(sub, w, ctxt, n, tc.cfg)
})
}
})
}

func testVisit(t *testing.T, w io.Writer, ctxt *adt.OpContext, v *adt.Vertex, fn visitFunc) {
func testVisit(t *testing.T, w io.Writer, ctxt *adt.OpContext, v *adt.Vertex, cfg *dep.Config) {
t.Helper()

tw := tabwriter.NewWriter(w, 0, 4, 1, ' ', 0)
defer tw.Flush()

fmt.Fprintf(tw, "line \vreference\v path of resulting vertex\n")

fn(ctxt, nil, v, func(d dep.Dependency) error {
dep.Visit(cfg, ctxt, v, func(d dep.Dependency) error {
if d.Reference == nil {
t.Fatal("no reference")
}
Expand All @@ -113,7 +113,10 @@ func testVisit(t *testing.T, w io.Writer, ctxt *adt.OpContext, v *adt.Vertex, fn

// DO NOT REMOVE: for Testing purposes.
func TestX(t *testing.T) {
fn := dep.VisitAll
cfg := &dep.Config{
Dynamic: true,
// Recurse: true,
}

in := `
`
Expand Down Expand Up @@ -142,7 +145,7 @@ func TestX(t *testing.T) {
w := &strings.Builder{}
fmt.Fprintln(w)

testVisit(t, w, ctxt, n, fn)
testVisit(t, w, ctxt, n, cfg)

t.Error(w.String())
}
10 changes: 5 additions & 5 deletions internal/core/dep/mixed.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ import (
// evaluated Vertex. A more correct and more performant algorithm would be to
// descend into the conjuncts and evaluate the necessary values, like fields
// and comprehension sources.
func dynamic(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitFunc, m marked, top bool) {
func (v *visitor) dynamic(n *adt.Vertex, top bool) {
found := false
for _, c := range n.Conjuncts {
if m[c.Expr()] {
if v.marked[c.Expr()] {
found = true
break
}
Expand All @@ -40,15 +40,15 @@ func dynamic(c *adt.OpContext, pkg *adt.ImportReference, n *adt.Vertex, f VisitF
return
}

if visit(c, pkg, n, f, false, top) != nil {
if v.visit(n, top) != nil {
return
}

for _, a := range n.Arcs {
if !a.IsDefined(c) || a.Label.IsLet() {
if !a.IsDefined(v.ctxt) || a.Label.IsLet() {
continue
}
dynamic(c, pkg, a, f, m, false)
v.dynamic(a, false)
}
}

Expand Down
6 changes: 5 additions & 1 deletion internal/core/export/self.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ func getParent(d *depData) *depData {
func (p *pivotter) markDeps(v *adt.Vertex, pkg *adt.ImportReference) {
// TODO: sweep all child nodes and mark as no need for recursive checks.

dep.VisitAll(p.x.ctx, pkg, v, func(d dep.Dependency) error {
cfg := &dep.Config{
Descend: true,
Pkg: pkg,
}
dep.Visit(cfg, p.x.ctx, v, func(d dep.Dependency) error {
node := d.Node

switch {
Expand Down
5 changes: 4 additions & 1 deletion tools/flow/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ func (c *Controller) findImpliedTask(d dep.Dependency) *Task {
// - as regular nodes are traversed recursively they are marked with a cycle
// marker to detect cycles, ensuring a finite traversal as well.
func (c *Controller) markTaskDependencies(t *Task, n *adt.Vertex) {
dep.VisitFields(c.opCtx, nil, n, func(d dep.Dependency) error {
cfg := &dep.Config{
Dynamic: true,
}
dep.Visit(cfg, c.opCtx, n, func(d dep.Dependency) error {
depTask := c.findImpliedTask(d)
if depTask != nil {
if depTask != cycleMarker {
Expand Down

0 comments on commit 0554d4e

Please sign in to comment.