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) {