internal/core/adt: refactor node lookup
This change distinguish path for evaluating values (evalState)
versus evaluating nodes (new: unifyNode).
Goals:
1) allow evaluation state to pass to calls. This allwos for more
precise partial evaluation.
2) more control over when to use addNotify.
For instance: for for loops, it is necessary to evaluate to
the AllArcs state, as all arcs need to be known at the
time of iteration.
For looking up values in a struct, however, it is okay to
have partial evaluation of struct arcs when looking up
in mode Partial.
Issue #667
Issue #661
Change-Id: Ib456dffc9d0161e8899c55ffdbe48c7a4b51bc17
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8321
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/core/adt/adt.go b/internal/core/adt/adt.go
index d746190..f67f1cb 100644
--- a/internal/core/adt/adt.go
+++ b/internal/core/adt/adt.go
@@ -114,7 +114,7 @@
// a value.
type Resolver interface {
Node
- resolve(ctx *OpContext) *Vertex
+ resolve(ctx *OpContext, state VertexStatus) *Vertex
}
type YieldFunc func(env *Environment, s *StructLit)
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 4a9ce1d..443fc90 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -398,7 +398,7 @@
func (c *OpContext) Resolve(env *Environment, r Resolver) (*Vertex, *Bottom) {
s := c.PushState(env, r.Source())
- arc := r.resolve(c)
+ arc := r.resolve(c, AllArcs)
// TODO: check for cycle errors?
err := c.PopState(s)
@@ -583,7 +583,7 @@
return v
case Resolver:
- arc := x.resolve(c)
+ arc := x.resolve(c, state)
if c.HasErr() {
return nil
}
@@ -600,6 +600,67 @@
}
}
+// unifyNode returns a possibly partially evaluated node value.
+//
+// TODO: maybe return *Vertex, *Bottom
+func (c *OpContext) unifyNode(v Expr, state VertexStatus) (result Value) {
+ savedSrc := c.src
+ c.src = v.Source()
+ err := c.errs
+ c.errs = nil
+
+ defer func() {
+ c.errs = CombineErrors(c.src, c.errs, err)
+ // TODO: remove this when we handle errors more principally.
+ if b, ok := result.(*Bottom); ok && c.src != nil &&
+ b.Code == CycleError &&
+ b.Err.Position() == token.NoPos &&
+ len(b.Err.InputPositions()) == 0 {
+ bb := *b
+ bb.Err = errors.Wrapf(b.Err, c.src.Pos(), "")
+ result = &bb
+ }
+ c.errs = CombineErrors(c.src, c.errs, result)
+ if c.errs != nil {
+ result = c.errs
+ }
+ c.src = savedSrc
+ }()
+
+ switch x := v.(type) {
+ case Value:
+ return x
+
+ case Evaluator:
+ v := x.evaluate(c)
+ return v
+
+ case Resolver:
+ v := x.resolve(c, state)
+ if c.HasErr() {
+ return nil
+ }
+ if v == nil {
+ return nil
+ }
+
+ if v.BaseValue == nil || v.BaseValue == cycle {
+ // Use node itself to allow for cycle detection.
+ c.Unifier.Unify(c, v, state)
+ }
+
+ if b, _ := v.BaseValue.(*Bottom); b != nil && b.Code != IncompleteError {
+ return b
+ }
+
+ return v
+
+ default:
+ // This can only happen, really, if v == nil, which is not allowed.
+ panic(fmt.Sprintf("unexpected Expr type %T", v))
+ }
+}
+
func (c *OpContext) lookup(x *Vertex, pos token.Pos, l Feature) *Vertex {
if l == InvalidLabel || x == nil {
// TODO: is it possible to have an invalid label here? Maybe through the
@@ -726,12 +787,11 @@
return x.Source().Pos()
}
-func (c *OpContext) node(orig Node, x Expr, scalar bool) *Vertex {
+func (c *OpContext) node(orig Node, x Expr, scalar bool, state VertexStatus) *Vertex {
// TODO: always get the vertex. This allows a whole bunch of trickery
// down the line.
- // This must be partial, because
- // TODO: this should always be "AllArcs"
- v := c.evalState(x, AllArcs)
+
+ v := c.unifyNode(x, AllArcs)
v, ok := c.getDefault(v)
if !ok {
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index ba5aabd..494199e 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -533,8 +533,11 @@
func (x *NodeLink) Kind() Kind {
return x.Node.Kind()
}
-func (x *NodeLink) Source() ast.Node { return x.Node.Source() }
-func (x *NodeLink) resolve(c *OpContext) *Vertex { return x.Node }
+func (x *NodeLink) Source() ast.Node { return x.Node.Source() }
+
+func (x *NodeLink) resolve(c *OpContext, state VertexStatus) *Vertex {
+ return x.Node
+}
// A FieldReference represents a lexical reference to a field.
//
@@ -553,7 +556,7 @@
return x.Src
}
-func (x *FieldReference) resolve(c *OpContext) *Vertex {
+func (x *FieldReference) resolve(c *OpContext, state VertexStatus) *Vertex {
n := c.relNode(x.UpCount)
pos := pos(x)
return c.lookup(n, pos, x.Label)
@@ -616,7 +619,7 @@
return x.Src
}
-func (x *DynamicReference) resolve(ctx *OpContext) *Vertex {
+func (x *DynamicReference) resolve(ctx *OpContext, state VertexStatus) *Vertex {
e := ctx.Env(x.UpCount)
frame := ctx.PushState(e, x.Src)
v := ctx.value(x.Label)
@@ -644,7 +647,7 @@
return x.Src
}
-func (x *ImportReference) resolve(ctx *OpContext) *Vertex {
+func (x *ImportReference) resolve(ctx *OpContext, state VertexStatus) *Vertex {
path := x.ImportPath.StringValue(ctx)
v, _ := ctx.Runtime.LoadImport(path)
return v
@@ -668,7 +671,7 @@
return x.Src
}
-func (x *LetReference) resolve(c *OpContext) *Vertex {
+func (x *LetReference) resolve(c *OpContext, state VertexStatus) *Vertex {
e := c.Env(x.UpCount)
label := e.Vertex.Label
if x.X == nil {
@@ -702,8 +705,8 @@
return x.Src
}
-func (x *SelectorExpr) resolve(c *OpContext) *Vertex {
- n := c.node(x, x.X, x.Sel.IsRegular())
+func (x *SelectorExpr) resolve(c *OpContext, state VertexStatus) *Vertex {
+ n := c.node(x, x.X, x.Sel.IsRegular(), state)
if n == emptyNode {
return n
}
@@ -727,9 +730,9 @@
return x.Src
}
-func (x *IndexExpr) resolve(ctx *OpContext) *Vertex {
+func (x *IndexExpr) resolve(ctx *OpContext, state VertexStatus) *Vertex {
// TODO: support byte index.
- n := ctx.node(x, x.X, true)
+ n := ctx.node(x, x.X, true, state)
i := ctx.value(x.Index)
if n == emptyNode {
return n
@@ -1379,7 +1382,7 @@
}
func (x *ForClause) yield(c *OpContext, f YieldFunc) {
- n := c.node(x, x.Src, true)
+ n := c.node(x, x.Src, true, AllArcs)
for _, a := range n.Arcs {
if !a.Label.IsRegular() {
continue