cue: improve dependency analysis for References
This improves on the current behavior, although it
doesn't fix it completely.
Note that References is deprecated and that one
should use Expr+Reference instead to craft the type
of analysis one wants.
The current References implementation is broken
and the API does not allow easy identification of
cross-package references.
Change-Id: I9ed5ba14f04024ea376003e1f27910afcb13fbc8
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3401
Reviewed-by: roger peppe <rogpeppe@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/cmd_test.go b/cmd/cue/cmd/cmd_test.go
index d69e005..7eec347 100644
--- a/cmd/cue/cmd/cmd_test.go
+++ b/cmd/cue/cmd/cmd_test.go
@@ -32,6 +32,7 @@
"baddisplay",
"errcode",
"http",
+ "print",
}
defer func() {
stdout = os.Stdout
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_print.out b/cmd/cue/cmd/testdata/tasks/cmd_print.out
new file mode 100644
index 0000000..5572191
--- /dev/null
+++ b/cmd/cue/cmd/testdata/tasks/cmd_print.out
@@ -0,0 +1,3 @@
+t.1.
+.t.2.
+
diff --git a/cmd/cue/cmd/testdata/tasks/task_tool.cue b/cmd/cue/cmd/testdata/tasks/task_tool.cue
index 228222b..05383ec 100644
--- a/cmd/cue/cmd/testdata/tasks/task_tool.cue
+++ b/cmd/cue/cmd/testdata/tasks/task_tool.cue
@@ -50,3 +50,24 @@
text: task.http.response.body
}
}
+
+command print: {
+ task: {
+ t1: exec.Run & {
+ cmd: ["sh", "-c", "sleep 1; echo t1"]
+ stdout: string
+ }
+ t2: exec.Run & {
+ cmd: ["sh", "-c", "sleep 1; echo t2"]
+ stdout: string
+ }
+ t3: cli.Print & {
+ text: (f & {arg: t1.stdout + t2.stdout}).result
+ }
+ }
+}
+
+f :: {
+ arg: string
+ result: strings.Join(strings.Split(arg, ""), ".")
+}
\ No newline at end of file
diff --git a/cue/types.go b/cue/types.go
index 3494377..8012301 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1288,9 +1288,9 @@
_, _ = io.WriteString(state, ctx.str(v.path.cache))
}
-// Reference returns path from the root of the instance referred to by this
-// value such that inst.Lookup(path) resolves to the same value, or no path if
-// this value is not a reference,
+// Reference returns the instance and path referred to by this value such that
+// inst.Lookup(path) resolves to the same value, or no path if this value is not
+// a reference,
func (v Value) Reference() (inst *Instance, path []string) {
// TODO: don't include references to hidden fields.
if v.path == nil {
@@ -1308,6 +1308,9 @@
switch x := sel.x.(type) {
case *selectorExpr:
imp, a = mkPath(c, up.parent, x, d+1)
+ if imp == nil {
+ return nil, nil
+ }
case *nodeRef:
// the parent must exist.
for ; up != nil && up.cache != x.node.(value); up = up.parent {
@@ -1319,7 +1322,7 @@
}
imp = c.getImportFromNode(x.node)
default:
- panic("should not happend")
+ return nil, nil
}
return imp, append(a, c.labelStr(sel.feature))
}
@@ -1342,6 +1345,8 @@
//
// Deprecated: can be implemented in terms of Reference and Expr.
func (v Value) References() [][]string {
+ // TODO: the pathFinder algorithm is quite broken. Using Reference and Expr
+ // will cast a much more accurate net on referenced values.
ctx := v.ctx()
pf := pathFinder{up: v.path}
raw := v.path.v
@@ -1354,7 +1359,7 @@
type pathFinder struct {
paths [][]string
- stack []string
+ stack []label
up *valueData
}
@@ -1362,26 +1367,40 @@
switch x := v.(type) {
case *selectorExpr:
i := len(p.stack)
- p.stack = append(p.stack, ctx.labelStr(x.feature))
+ p.stack = append(p.stack, x.feature)
rewrite(ctx, x.x, p.find)
p.stack = p.stack[:i]
return v, false
+
case *nodeRef:
i := len(p.stack)
up := p.up
for ; up != nil && up.cache != x.node.(value); up = up.parent {
}
for ; up != nil && up.feature > 0; up = up.parent {
- p.stack = append(p.stack, ctx.labelStr(up.feature))
+ p.stack = append(p.stack, up.feature)
}
path := make([]string, len(p.stack))
for i, v := range p.stack {
- path[len(path)-1-i] = v
+ path[len(path)-1-i] = ctx.labelStr(v)
}
p.paths = append(p.paths, path)
p.stack = p.stack[:i]
return v, false
- case *structLit: // handled in sub fields
+
+ case *structLit:
+ // If the stack is empty, we do not descend, as we are not evaluating
+ // sub fields.
+ if len(p.stack) == 0 {
+ return v, false
+ }
+
+ stack := p.stack
+ p.stack = nil
+ for _, a := range x.arcs {
+ rewrite(ctx, a.v, p.find)
+ }
+ p.stack = stack
return v, false
}
return v, true
@@ -1649,8 +1668,7 @@
}
// Expr reports the operation of the underlying expression and the values it
-// operates on. The returned values are appended to the given slice, which may
-// be nil.
+// operates on.
//
// For unary expressions, it returns the single value of the expression.
//
@@ -1660,20 +1678,15 @@
// sequence.
//
// For selector and index expressions it returns the subject and then the index.
-// For selectors, the index is the string value of the identifier. For slice
-// expressions, it returns the subject, low value, and high value, in that
-// order.
+// For selectors, the index is the string value of the identifier.
//
// For interpolations it returns a sequence of values to be concatenated, some
// of which will be literal strings and some unevaluated expressions.
//
// A builtin call expression returns the value of the builtin followed by the
// args of the call.
-//
-// If v is not an expression, Partial append v itself.
-// TODO: return v if this is complete? Yes for now
-// TODO: add values if a == nil?
func (v Value) Expr() (Op, []Value) {
+ // TODO: return v if this is complete? Yes for now
if v.path == nil {
return NoOp, nil
}
diff --git a/cue/types_test.go b/cue/types_test.go
index a733022..281a747 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -2000,6 +2000,12 @@
a: { c: 3 }
b: { c: int, d: 4 }
r: (a & b).c
+ c: {args: s1 + s2}.args
+ s1: string
+ s2: string
+ d: ({arg: b}).arg.c
+ e: f.arg.c
+ f: {arg: b}
`
testCases := []struct {
config string
@@ -2011,6 +2017,9 @@
{config1, "c.f", "a"},
{config2, "r", "a.c b.c"},
+ {config2, "c", "s1 s2"},
+ // {config2, "d", "b.c"}, // TODO: make this work as well.
+ {config2, "e", "f.arg.c"}, // TODO: should also report b.c.
}
for _, tc := range testCases {
t.Run(tc.in, func(t *testing.T) {