cue: change Reference to return Instance as well

Change-Id: I33c3479d3f580cfe67cc01fa998573f50ddb4bce
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2371
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/build.go b/cue/build.go
index 7d775c3..ae23393 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -49,6 +49,7 @@
 		return nil, err
 	}
 	inst := idx.loadInstance(p)
+	inst.ImportPath = p.ImportPath
 	if inst.Err != nil {
 		return nil, inst.Err
 	}
@@ -121,7 +122,8 @@
 	labelMap map[string]label
 	labels   []string
 
-	loaded map[*build.Instance]*Instance
+	loaded  map[*build.Instance]*Instance
+	imports map[value]*Instance // key is always a *structLit
 
 	offset label
 	parent *index
@@ -143,6 +145,7 @@
 	i := &index{
 		labelMap: map[string]label{"": 0},
 		labels:   []string{""},
+		imports:  map[value]*Instance{},
 	}
 	return i
 }
@@ -153,6 +156,7 @@
 	i := &index{
 		labelMap: map[string]label{},
 		loaded:   map[*build.Instance]*Instance{},
+		imports:  map[value]*Instance{},
 		offset:   label(len(parent.labels)) + parent.offset,
 		parent:   parent,
 	}
@@ -228,6 +232,8 @@
 			inst.insertFile(f)
 		}
 	}
+	inst.ImportPath = p.ImportPath
+
 	inst.complete = true
 	return inst
 }
diff --git a/cue/builtin.go b/cue/builtin.go
index 8a251cc..95282ef 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -251,6 +251,13 @@
 		e := mustCompileBuiltins(ctx, b, k)
 		builtins[k] = e
 		builtins["-/"+path.Base(k)] = e
+
+		sharedIndex.addInst(&Instance{
+			ImportPath: k,
+			Name:       path.Base(k),
+			rootStruct: e,
+			rootValue:  e,
+		})
 	}
 }
 
diff --git a/cue/instance.go b/cue/instance.go
index d27db43..3a4eeec 100644
--- a/cue/instance.go
+++ b/cue/instance.go
@@ -48,15 +48,31 @@
 	complete bool // for cycle detection
 }
 
+func (x *index) addInst(p *Instance) *Instance {
+	if p.rootStruct == nil {
+		panic("struct must not be nil")
+	}
+	p.index = x
+	x.imports[p.rootStruct] = p
+	return p
+}
+
+func (x *index) getImportFromNode(v value) *Instance {
+	imp := x.imports[v]
+	if imp == nil && x.parent != nil {
+		return x.parent.getImportFromNode(v)
+	}
+	return imp
+}
+
 // NewInstance creates a new instance. Use Insert to populate the instance.
 func (x *index) NewInstance(p *build.Instance) *Instance {
 	st := &structLit{baseValue: baseValue{nil}}
-	i := &Instance{
-		index:      x,
+	i := x.addInst(&Instance{
 		rootStruct: st,
 		rootValue:  st,
 		inst:       p,
-	}
+	})
 	if p != nil {
 		i.ImportPath = p.ImportPath
 		i.Dir = p.Dir
@@ -220,12 +236,11 @@
 		return nil
 	}
 
-	p := &Instance{
+	p := ctx.index.addInst(&Instance{
 		rootStruct: st,
 		rootValue:  merged,
-		index:      ctx.index,
 		complete:   true,
-	}
+	})
 	return p
 }
 
@@ -305,18 +320,18 @@
 	case *bottom:
 		err = inst.Value().toErr(x)
 	}
-	inst = &Instance{
+	inst = inst.index.addInst(&Instance{
 		rootStruct: st,
 		rootValue:  st,
-		index:      inst.index,
 		inst:       nil,
 
+		// TODO: somehow indicate this is not an original
 		ImportPath: inst.ImportPath,
 		Name:       inst.Name,
 		Incomplete: inst.Incomplete,
 		Err:        err,
 
 		complete: err != nil,
-	}
+	})
 	return inst, err
 }
diff --git a/cue/types.go b/cue/types.go
index 1b7615e..ee68d3b 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1163,41 +1163,53 @@
 	io.WriteString(state, debugStr(ctx, v.path.cache))
 }
 
-// Reference returns path from the root of the file referred to by this value
-// or no path if this value is not a reference.
-func (v Value) Reference() []string {
+// 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,
+func (v Value) Reference() (inst *Instance, path []string) {
 	// TODO: don't include references to hidden fields.
 	if v.path == nil {
-		return nil
+		return nil, nil
 	}
 	sel, ok := v.path.v.(*selectorExpr)
 	if !ok {
-		return nil
+		return nil, nil
 	}
-	return mkPath(v.ctx(), v.path, sel, 0)
+	imp, a := mkPath(v.ctx(), v.path, sel, 0)
+	return imp, a
 }
 
-func mkPath(c *context, up *valueData, sel *selectorExpr, d int) (a []string) {
+func mkPath(c *context, up *valueData, sel *selectorExpr, d int) (imp *Instance, a []string) {
 	switch x := sel.x.(type) {
 	case *selectorExpr:
-		a = mkPath(c, up.parent, x, d+1)
+		imp, a = mkPath(c, up.parent, x, d+1)
 	case *nodeRef:
 		// the parent must exist.
 		for ; up != nil && up.cache != x.node.(value); up = up.parent {
 		}
-		a = mkFromRoot(c, up, d+1)
+		var v value
+		v, a = mkFromRoot(c, up, d+2)
+		if v == nil {
+			v = x.node
+		}
+		imp = c.getImportFromNode(x.node)
 	default:
 		panic("should not happend")
 	}
-	return append(a, c.labelStr(sel.feature))
+	return imp, append(a, c.labelStr(sel.feature))
 }
 
-func mkFromRoot(c *context, up *valueData, d int) []string {
-	if up == nil || up.parent == nil {
-		return make([]string, 0, d)
+func mkFromRoot(c *context, up *valueData, d int) (root value, a []string) {
+	if up == nil {
+		return nil, make([]string, 0, d)
 	}
-	a := mkFromRoot(c, up.parent, d+1)
-	return append(a, c.labelStr(up.feature))
+	root, a = mkFromRoot(c, up.parent, d+1)
+	if up.parent != nil {
+		a = append(a, c.labelStr(up.feature))
+	} else {
+		root = up.v
+	}
+	return root, a
 }
 
 // References reports all references used to evaluate this value. It does not
diff --git a/cue/types_test.go b/cue/types_test.go
index 8e819f5..c715819 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1813,9 +1813,17 @@
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {
 			v := getInstance(t, tc.input).Lookup("v")
-			if got := strings.Join(v.Reference(), " "); got != tc.want {
+			inst, a := v.Reference()
+			if got := strings.Join(a, " "); got != tc.want {
 				t.Errorf("\n got %v;\nwant %v", got, tc.want)
 			}
+
+			if tc.want != "" {
+				v := inst.Lookup(a...)
+				if x, _ := v.Int64(); x != 1 {
+					t.Errorf("path resolved to %s; want 1", v)
+				}
+			}
 		})
 	}
 }
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 3ba94c0..ff4c6a5 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -92,8 +92,8 @@
 }
 
 // shouldExpand reports is the given identifier is not exported.
-func (c *buildContext) shouldExpand(name string) bool {
-	return c.expandRefs || c.isInternal(name)
+func (c *buildContext) shouldExpand(p *cue.Instance, ref []string) bool {
+	return c.expandRefs
 }
 
 func (b *builder) failf(v cue.Value, format string, args ...interface{}) {
@@ -127,16 +127,17 @@
 	count := 0
 	disallowDefault := false
 	var values cue.Value
-	if b.ctx.shouldExpand(strings.Join(v.Reference(), ".")) {
+	p, a := v.Reference()
+	if b.ctx.shouldExpand(p, a) {
 		values = v
 		count = 1
 	} else {
 		for _, v := range appendSplit(nil, cue.AndOp, v) {
 			// This may be a reference to an enum. So we need to check references before
 			// dissecting them.
-			switch r := v.Reference(); {
-			case r != nil:
-				b.addRef(r)
+			switch p, r := v.Reference(); {
+			case len(r) > 0:
+				b.addRef(v, p, r)
 				disallowDefault = true
 			default:
 				count++
@@ -727,9 +728,7 @@
 	b.add(c.finish())
 }
 
-func (b *builder) addRef(ref []string) {
-	// TODO: validate path.
-	// TODO: map CUE types to OAPI types.
+func (b *builder) addRef(v cue.Value, inst *cue.Instance, ref []string) {
 	b.addConjunct(func(b *builder) {
 		a := append([]string{"#", b.ctx.refPrefix}, ref...)
 		b.set("$ref", path.Join(a...))