internal/core/adt: keep nodeContext in Vertex
This allows reusing the nodeContext in different phases
and better control over prevendting duplicate work.
Change-Id: I54223595ea69320c96c7e3d3fbd68d96d138cfb1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8106
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/basicrewrite/018_self-reference_cycles.txtar b/cue/testdata/basicrewrite/018_self-reference_cycles.txtar
index 8bb44c9..e65c857 100644
--- a/cue/testdata/basicrewrite/018_self-reference_cycles.txtar
+++ b/cue/testdata/basicrewrite/018_self-reference_cycles.txtar
@@ -27,11 +27,11 @@
(struct){
a: (_|_){
// [cycle] cycle error:
- // ./in.cue:2:4
+ // ./in.cue:1:4
}
b: (_|_){
// [cycle] cycle error:
- // ./in.cue:2:4
+ // ./in.cue:1:4
}
c: (#list){
0: (_){ _ }
diff --git a/cue/testdata/cycle/021_delayed_constraint_failure.txtar b/cue/testdata/cycle/021_delayed_constraint_failure.txtar
index 8e769c7..72b64a5 100644
--- a/cue/testdata/cycle/021_delayed_constraint_failure.txtar
+++ b/cue/testdata/cycle/021_delayed_constraint_failure.txtar
@@ -26,7 +26,7 @@
}
-- out/eval --
Errors:
-b: conflicting values 200 and 210:
+b: conflicting values 210 and 200:
./in.cue:2:4
./in.cue:3:4
x: conflicting values 101 and 100:
@@ -38,7 +38,7 @@
// [eval]
a: (int){ 100 }
b: (_|_){
- // [eval] b: conflicting values 200 and 210:
+ // [eval] b: conflicting values 210 and 200:
// ./in.cue:2:4
// ./in.cue:3:4
}
diff --git a/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar b/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar
index 875d6e0..e764e84 100644
--- a/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar
+++ b/cue/testdata/cycle/049_self-reference_cycles_conflicts_with_strings.txtar
@@ -42,10 +42,6 @@
// ./in.cue:2:5
// ./in.cue:5:7
}
- y: (_|_){
- // [eval] a.x: conflicting values "hey!?" and "hey":
- // ./in.cue:2:5
- // ./in.cue:5:7
- }
+ y: (string){ "hey!" }
}
}
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index 4a19c49..798e68c 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -409,6 +409,7 @@
c1.a.c.c: structural cycle
d1.a.b.c.d.t: structural cycle
d1.r: structural cycle
+d2.r.c.d.t: structural cycle
e1.a.c: structural cycle
e1.b.c: structural cycle
e2.a.c: structural cycle
@@ -468,10 +469,6 @@
p5.#T.a.0.link: structural cycle
p6.#U.#T.a.0.link: structural cycle
z1.z.g.h: structural cycle
-b4.x.y.0.0: structural cycle:
- ./in.cue:53:8
-d2.r.c.d.t: structural cycle:
- ./in.cue:254:8
0: structural cycle:
./in.cue:264:19
@@ -561,9 +558,10 @@
b4: (_|_){
// [structural cycle]
b: (_|_){
- // [structural cycle] b4.x.y.0.0: structural cycle:
- // ./in.cue:53:8
- 0: (int){ 1 }
+ // [structural cycle]
+ 0: (_|_){
+ // [structural cycle] b4.x.y.0: structural cycle
+ }
}
x: (_|_){
// [structural cycle]
@@ -586,9 +584,7 @@
x: (struct){
y: (struct){
a: (#list){
- 0: ((int|list)){ |((#list){
- 0: (int){ int }
- }, (int){ int }) }
+ 0: (int){ int }
}
}
}
@@ -1053,8 +1049,14 @@
d2: (_|_){
// [structural cycle]
x: (_|_){
- // [structural cycle] d2.r.c.d.t: structural cycle:
- // ./in.cue:254:8
+ // [structural cycle]
+ d: (_|_){
+ // [structural cycle]
+ h: (int){ int }
+ t: (_|_){
+ // [structural cycle]
+ }
+ }
}
r: (_|_){
// [structural cycle]
@@ -1064,8 +1066,7 @@
// [structural cycle]
h: (int){ int }
t: (_|_){
- // [structural cycle] d2.r.c.d.t: structural cycle:
- // ./in.cue:254:8
+ // [structural cycle] d2.r.c.d.t: structural cycle
c: (_|_){// {
// d: {
// h: int
@@ -1088,6 +1089,16 @@
h: (int){ int }
t: (_|_){
// [structural cycle]
+ c: (_|_){
+ // [structural cycle]
+ d: (_|_){
+ // [structural cycle]
+ h: (int){ int }
+ t: (_|_){
+ // [structural cycle]
+ }
+ }
+ }
}
}
}
diff --git a/cue/testdata/eval/incomplete.txtar b/cue/testdata/eval/incomplete.txtar
index b72808d..2fb31ea 100644
--- a/cue/testdata/eval/incomplete.txtar
+++ b/cue/testdata/eval/incomplete.txtar
@@ -48,21 +48,19 @@
E: (struct){
a: (_|_){
// [cycle] cycle error:
- // ./in.cue:12:6
- // cycle error:
- // ./in.cue:13:6
+ // ./in.cue:11:6
}
b: (_|_){
// [cycle] cycle error:
- // ./in.cue:12:6
+ // ./in.cue:11:6
// cycle error:
- // ./in.cue:13:6
+ // ./in.cue:12:6
}
c: (_|_){
// [cycle] cycle error:
- // ./in.cue:12:6
+ // ./in.cue:11:6
// cycle error:
- // ./in.cue:13:6
+ // ./in.cue:12:6
}
}
permanentlyIncompleteOperands: (_|_){
diff --git a/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar b/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
index 22fbe13..377d3e3 100644
--- a/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
+++ b/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
@@ -49,11 +49,11 @@
n1: (struct){
min: (_|_){
// [cycle] cycle error:
- // ./in.cue:3:23
+ // ./in.cue:3:12
}
max: (_|_){
// [cycle] cycle error:
- // ./in.cue:3:23
+ // ./in.cue:3:12
}
}
n2: (_|_){
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index 13b4472..1915932 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -137,7 +137,7 @@
}
env, src := c.e, c.src
c.e, c.src = e, x.Source()
- v = c.eval(x)
+ v = c.evalState(x, Partial)
c.e, c.src = env, src
e.cache[x] = v
}
@@ -161,7 +161,13 @@
// Label is the feature leading to this vertex.
Label Feature
- // TODO: move the following status fields to a separate struct.
+ // State:
+ // eval: nil, BaseValue: nil -- unevaluated
+ // eval: *, BaseValue: nil -- evaluating
+ // eval: *, BaseValue: * -- finalized
+ //
+ state *nodeContext
+ // TODO: move the following status fields to nodeContext.
// status indicates the evaluation progress of this vertex.
status VertexStatus
@@ -245,6 +251,10 @@
// nodeContext to allow reusing the computations done so far.
Partial
+ // AllArcs is request only. It must be past Partial, but
+ // before recursively resolving arcs.
+ AllArcs
+
// EvaluatingArcs indicates that the arcs of the Vertex are currently being
// evaluated. If this is encountered it indicates a structural cycle.
// Value does not have to be nil
@@ -274,7 +284,6 @@
// Value returns the Value of v without definitions if it is a scalar
// or itself otherwise.
func (v *Vertex) Value() Value {
- // TODO: rename to Value.
switch x := v.BaseValue.(type) {
case nil:
return nil
@@ -425,6 +434,15 @@
if !ok {
return v
}
+ // b, _ := x.BaseValue.(*Bottom)
+ if n := x.state; n != nil && x.BaseValue == cycle {
+ if n.errs != nil && !n.errs.IsIncomplete() {
+ return n.errs
+ }
+ if n.scalar != nil {
+ return n.scalar
+ }
+ }
return x.Value()
}
@@ -575,13 +593,17 @@
// This is likely a bug in the evaluator and should not happen.
return &Bottom{Err: errors.Newf(token.NoPos, "cannot add conjunct")}
}
+ v.addConjunct(c)
+ return nil
+}
+
+func (v *Vertex) addConjunct(c Conjunct) {
for _, x := range v.Conjuncts {
if x == c {
- return nil
+ return
}
}
v.Conjuncts = append(v.Conjuncts, c)
- return nil
}
func (v *Vertex) AddStruct(s *StructLit, env *Environment, ci CloseInfo) *StructInfo {
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 57d6446..37e62ec 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -504,7 +504,7 @@
func (c *OpContext) Evaluate(env *Environment, x Expr) (result Value, complete bool) {
s := c.PushState(env, x.Source())
- val := c.eval(x)
+ val := c.evalState(x, Partial)
complete = true
@@ -541,11 +541,6 @@
return v
}
-func (c *OpContext) eval(x Expr) (result Value) {
- v := c.evalState(x, Partial)
- return v
-}
-
func (c *OpContext) evalState(v Expr, state VertexStatus) (result Value) {
savedSrc := c.src
c.src = v.Source()
@@ -583,14 +578,11 @@
if c.HasErr() {
return nil
}
- if isIncomplete(arc) {
- if arc != nil {
- return arc.Value() // *Bottom
- }
+ if arc == nil {
return nil
}
- v := c.Unifier.Evaluate(c, arc)
+ v := c.Unifier.evaluate(c, arc, state)
return v
default:
@@ -728,7 +720,9 @@
func (c *OpContext) node(orig Node, x Expr, scalar bool) *Vertex {
// TODO: always get the vertex. This allows a whole bunch of trickery
// down the line.
- v := c.evalState(x, EvaluatingArcs)
+ // This must be partial, because
+ // TODO: this should always be "AllArcs"
+ v := c.evalState(x, AllArcs)
v, ok := c.getDefault(v)
if !ok {
diff --git a/internal/core/adt/disjunct.go b/internal/core/adt/disjunct.go
index 4772396..b47c32c 100644
--- a/internal/core/adt/disjunct.go
+++ b/internal/core/adt/disjunct.go
@@ -133,11 +133,16 @@
m defaultMode,
recursive bool) {
- n.eval.stats.DisjunctCount++
+ n.ctx.stats.DisjunctCount++
for n.expandOne() {
}
+ errNode := n
+ if parent != nil {
+ errNode = parent
+ }
+
// save node to snapShot in nodeContex
// save nodeContext.
@@ -165,25 +170,20 @@
err = x.ChildErrors
}
if err != nil {
- n.disjunctErrs = append(n.disjunctErrs, err)
+ errNode.disjunctErrs = append(errNode.disjunctErrs, err)
}
if recursive || len(n.disjunctions) > 0 {
- n.eval.freeNodeContext(n)
+ n.ctx.Unifier.freeNodeContext(n)
}
return
}
// TODO: clean up this mess:
- n.touched = true // move result setting here
result := *n.node // XXX: n.result = snapshotVertex(n.node)?
if result.BaseValue == nil {
result.BaseValue = n.getValidators()
}
- n.nodeShared.setResult(n.node)
- if n.node.BaseValue == nil {
- n.node.BaseValue = result.BaseValue
- }
if state < Finalized {
*n = m
}
@@ -191,6 +191,9 @@
n.disjuncts = append(n.disjuncts, n)
case len(n.disjunctions) > 0:
+ // Process full disjuncts to ensure that erroneous disjuncts are
+ // eliminated.
+ state = Finalized
n.disjuncts = append(n.disjuncts, n)
@@ -226,7 +229,7 @@
if i > 0 {
for _, d := range a {
- n.eval.freeNodeContext(d)
+ n.ctx.freeNodeContext(d)
}
}
@@ -251,12 +254,15 @@
// TODO: if only one value is left, set to maybeDefault.
switch p := parent; {
case p != nil:
+ p.disjunctErrs = append(p.disjunctErrs, n.disjunctErrs...)
+ n.disjunctErrs = n.disjunctErrs[:0]
+
k := 0
outer:
for _, d := range n.disjuncts {
for _, v := range p.disjuncts {
if Equal(n.ctx, &v.result, &d.result) {
- n.eval.freeNodeContext(n)
+ n.ctx.Unifier.freeNodeContext(n)
continue outer
}
}
@@ -270,11 +276,11 @@
n.disjuncts = n.disjuncts[:0]
case n.done():
- n.nodeShared.isDone = true
+ n.isDone = true
}
}
-func (n *nodeShared) makeError() {
+func (n *nodeContext) makeError() {
code := IncompleteError
if len(n.disjunctErrs) > 0 {
@@ -401,7 +407,7 @@
//
// TODO(perf): the set of errors is now computed during evaluation. Eventually,
// this could be done lazily.
-func (n *nodeShared) disjunctError() (errs errors.Error) {
+func (n *nodeContext) disjunctError() (errs errors.Error) {
ctx := n.ctx
disjuncts := selectErrors(n.disjunctErrs)
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index 960bb00..fc62de6 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -51,9 +51,10 @@
DisjunctCount int
UnifyCount int
- Freed int
- Reused int
- Allocs int
+ Freed int
+ Retained int
+ Reused int
+ Allocs int
}
var stats = template.Must(template.New("stats").Parse(`{{"" -}}
@@ -102,8 +103,7 @@
stats Stats
- freeListNode *nodeContext
- freeListShared *nodeShared
+ freeListNode *nodeContext
}
func (e *Unifier) Eval(v *Vertex) errors.Error {
@@ -129,36 +129,30 @@
//
// TODO: return *Vertex
func (e *Unifier) Evaluate(c *OpContext, v *Vertex) Value {
- var result Vertex
+ return e.evaluate(c, v, Partial)
+}
- if b, _ := v.BaseValue.(*Bottom); b != nil {
- return b
+func (e *Unifier) evaluate(c *OpContext, v *Vertex, state VertexStatus) Value {
+ if v.BaseValue == nil || v.BaseValue == cycle {
+ // Use node itself to allow for cycle detection.
+ e.Unify(c, v, state)
}
- if v.BaseValue == nil {
- save := *v
- // Use node itself to allow for cycle detection.
- s := e.evalVertex(c, v, Partial)
- defer e.freeSharedNode(s)
-
- result = *v
-
- if s.result_.BaseValue != nil { // There is a complete result.
- *v = s.result_
- // result = *v
- } else if b, ok := v.BaseValue.(*Bottom); ok {
- *v = save
- return b
- } else {
- *v = save
+ if n := v.state; n != nil {
+ if n.errs != nil && !n.errs.IsIncomplete() {
+ return n.errs
}
+ // TODO: consider enabling this
+ // if n.scalar != nil {
+ // return n.scalar
+ // }
+ }
- switch {
- case !s.touched:
-
- case len(s.disjuncts) == 1 || s.numDefaults == 1:
- // TODO: this seems unnecessary as long as we have a better way
- // to handle incomplete, and perhaps referenced. nodes.
+ switch x := v.BaseValue.(type) {
+ case *Bottom:
+ return x
+ case *Disjunction:
+ if x.NumDefaults == 1 {
if c.IsTentative() && isStruct(v) {
// TODO(perf): do something more efficient perhaps? This discards
// the computed arcs so far. Instead, we could have a separate
@@ -172,67 +166,25 @@
Conjuncts: v.Conjuncts,
}
w.UpdateStatus(v.Status())
- *v = save
return w
}
+ }
+ return x
- default:
- d := s.createDisjunct()
- last := len(d.Values) - 1
- clone := *(d.Values[last])
- d.Values[last] = &clone
-
- v.UpdateStatus(Finalized)
- v.BaseValue = d
- v.Arcs = nil
- v.Structs = nil // TODO: maybe not do this.
- // The conjuncts will have too much information. Better have no
- // information than incorrect information.
- for _, d := range d.Values {
- d.Conjuncts = nil
- for _, a := range d.Arcs {
- for _, x := range a.Conjuncts {
- // All the environments for embedded structs need to be
- // dereferenced.
- for env := x.Env; env != nil && env.Vertex == v; env = env.Up {
- env.Vertex = d
- }
- }
- }
+ case nil:
+ if v.state != nil {
+ switch x := v.state.getValidators().(type) {
+ case Value:
+ return x
+ default:
+ w := *v
+ w.BaseValue = x
+ return &w
}
-
- return d
}
-
- err, _ := result.BaseValue.(*Bottom)
- // BEFORE RESTORING, copy the value to return one
- // with the temporary arcs.
- if !s.done() && (err == nil || err.IsIncomplete()) {
- // Clear values afterwards
- *v = save
- }
- if s.hasResult() {
- if b, _ := v.BaseValue.(*Bottom); b != nil {
- *v = save
- return b
- }
- // TODO: Only use result when not a cycle.
- w := s.result()
- v = &w
- }
- // TODO: Store if concrete and fully resolved.
+ panic("nil value")
}
- // TODO: Use this and ensure that each use of Evaluate handles
- // struct numbers correctly. E.g. by using a function that
- // gets the concrete value.
- //
- if v.BaseValue == nil {
- if result.BaseValue == nil {
- panic("nil basevalue")
- }
- return &result
- }
return v
}
@@ -243,157 +195,174 @@
// defer c.PopVertex(c.PushVertex(v))
if state <= v.Status() {
- return
+ if v.Status() != Partial && state != Partial {
+ return
+ }
}
- if x := v.BaseValue; x != nil {
- // if state == Partial || x == cycle {
- // return
- // }
+ n := v.state
+
+ switch v.Status() {
+ case Evaluating:
return
- }
- n := e.evalVertex(c, v, state)
- defer e.freeSharedNode(n)
+ case EvaluatingArcs:
+ return
- switch {
- case len(n.disjuncts) == 1:
- *v = n.disjuncts[0].result
+ case 0:
+ // from state 0
+ n = e.newNodeContext(c, v)
- case len(n.disjuncts) > 0:
- d := n.createDisjunct()
- v.BaseValue = d
- // The conjuncts will have too much information. Better have no
- // information than incorrect information.
- for _, d := range d.Values {
- // We clear the conjuncts for now. As these disjuncts are for API
- // use only, we will fill them out when necessary (using Defaults).
- d.Conjuncts = nil
+ v.state = n
- // TODO: use a more principled form of dereferencing. For instance,
- // disjuncts could already be assumed to be the given Vertex, and
- // the the main vertex could be dereferenced during evaluation.
- for _, a := range d.Arcs {
- for _, x := range a.Conjuncts {
- // All the environments for embedded structs need to be
- // dereferenced.
- for env := x.Env; env != nil && env.Vertex == v; env = env.Up {
- env.Vertex = d
+ if v.Label.IsDef() {
+ v.Closed = true
+ }
+
+ ignore := false
+ if v.Parent != nil {
+ if v.Parent.Closed {
+ v.Closed = true
+ }
+ }
+
+ if !v.Label.IsInt() && v.Parent != nil && !ignore && state == Finalized {
+ // Visit arcs recursively to validate and compute error.
+ if _, err := verifyArc2(c, v.Label, v, v.Closed); err != nil {
+ // Record error in child node to allow recording multiple
+ // conflicts at the appropriate place, to allow valid fields to
+ // be represented normally and, most importantly, to avoid
+ // recursive processing of a disallowed field.
+ v.SetValue(c, Finalized, err)
+ return
+ }
+ }
+
+ defer c.PopArc(c.PushArc(v))
+
+ e.stats.UnifyCount++
+
+ // Clear any remaining error.
+ if err := c.Err(); err != nil {
+ panic("uncaught error")
+ }
+
+ // Set the cache to a cycle error to ensure a cyclic reference will result
+ // in an error if applicable. A cyclic error may be ignored for
+ // non-expression references. The cycle error may also be removed as soon
+ // as there is evidence what a correct value must be, but before all
+ // validation has taken place.
+ v.BaseValue = cycle
+
+ v.UpdateStatus(Evaluating)
+
+ // If the result is a struct, it needs to be closed if:
+ // 1) this node introduces a definition
+ // 2) this node is a child of a node that introduces a definition,
+ // recursively.
+ // 3) this node embeds a closed struct.
+
+ for _, x := range v.Conjuncts {
+ // TODO: needed for reentrancy. Investigate usefulness for cycle
+ // detection.
+ n.addExprConjunct(x)
+ }
+
+ fallthrough
+
+ case Partial:
+ defer c.PopArc(c.PushArc(v))
+
+ // Use maybeSetCache for cycle breaking
+ for n.maybeSetCache(); n.expandOne(); n.maybeSetCache() {
+ }
+
+ if !n.done() {
+ if len(n.disjunctions) > 0 && v.BaseValue == cycle {
+ // We disallow entering computations of disjunctions with
+ // incomplete data.
+ if state == Finalized {
+ b := c.NewErrf("incomplete cause disjunction")
+ b.Code = IncompleteError
+ n.errs = CombineErrors(nil, n.errs, b)
+ v.SetValue(n.ctx, Finalized, b)
+ } else {
+ n.node.UpdateStatus(Partial)
+ }
+ return
+ }
+ }
+
+ if !n.done() && state <= Partial {
+ n.node.UpdateStatus(Partial)
+ return
+ }
+
+ if s := v.Status(); state <= s {
+ // We have found a partial result. There may still be errors
+ // down the line which may result from further evaluating this
+ // field, but that will be caught when evaluating this field
+ // for real.
+
+ // This also covers the case where a recursive evaluation triggered
+ // this field to become finalized in the mean time. In that case
+ // we can avoid running another expandDisjuncts.
+ return
+ }
+
+ n.expandDisjuncts(state, nil, maybeDefault, false)
+
+ switch len(n.disjuncts) {
+ case 0:
+ case 1:
+ *v = n.disjuncts[0].result
+
+ default:
+ d := n.createDisjunct()
+ v.BaseValue = d
+ // The conjuncts will have too much information. Better have no
+ // information than incorrect information.
+ for _, d := range d.Values {
+ // We clear the conjuncts for now. As these disjuncts are for API
+ // use only, we will fill them out when necessary (using Defaults).
+ d.Conjuncts = nil
+
+ // TODO: use a more principled form of dereferencing. For instance,
+ // disjuncts could already be assumed to be the given Vertex, and
+ // the the main vertex could be dereferenced during evaluation.
+ for _, a := range d.Arcs {
+ for _, x := range a.Conjuncts {
+ // All the environments for embedded structs need to be
+ // dereferenced.
+ for env := x.Env; env != nil && env.Vertex == v; env = env.Up {
+ env.Vertex = d
+ }
}
}
}
+ v.Arcs = nil
+ // v.Structs = nil // TODO: should we keep or discard the Structs?
+ // TODO: how to represent closedness information? Do we need it?
}
- v.Arcs = nil
- // v.Structs = nil // TODO: should we keep or discard the Structs?
- // TODO: how to represent closedness information? Do we need it?
- default:
- if r := n.result(); r.BaseValue != nil {
- *v = r
+ if state != Finalized {
+ return
}
- }
- // Else set it to something.
-
- if v.BaseValue == nil {
- panic("error")
- }
-
- // Check whether result is done.
-}
-
-// evalVertex computes the vertex results. The state indicates the minimum
-// status to which this vertex should be evaluated. It should be either
-// Finalized or Partial.
-func (e *Unifier) evalVertex(c *OpContext, v *Vertex, state VertexStatus) *nodeShared {
- shared := e.newSharedNode(c, v)
-
- if v.Label.IsDef() {
- v.Closed = true
- }
-
- ignore := false
- if v.Parent != nil {
- if v.Parent.Closed {
- v.Closed = true
+ if v.BaseValue == nil {
+ v.BaseValue = n.getValidators()
}
+
+ // Free memory here?
+ v.UpdateStatus(Finalized)
+
+ case AllArcs:
+ defer c.PopArc(c.PushArc(v))
+
+ n.completeArcs(state)
+
+ case Finalized:
}
-
- if !v.Label.IsInt() && v.Parent != nil && !ignore && state == Finalized {
- // Visit arcs recursively to validate and compute error.
- if _, err := verifyArc2(c, v.Label, v, v.Closed); err != nil {
- // Record error in child node to allow recording multiple
- // conflicts at the appropriate place, to allow valid fields to
- // be represented normally and, most importantly, to avoid
- // recursive processing of a disallowed field.
- v.SetValue(c, Finalized, err)
- return shared
- }
- }
-
- defer c.PopArc(c.PushArc(v))
-
- e.stats.UnifyCount++
-
- // Clear any remaining error.
- if err := c.Err(); err != nil {
- panic("uncaught error")
- }
-
- // Set the cache to a cycle error to ensure a cyclic reference will result
- // in an error if applicable. A cyclic error may be ignored for
- // non-expression references. The cycle error may also be removed as soon
- // as there is evidence what a correct value must be, but before all
- // validation has taken place.
- v.BaseValue = cycle
-
- v.UpdateStatus(Evaluating)
-
- // If the result is a struct, it needs to be closed if:
- // 1) this node introduces a definition
- // 2) this node is a child of a node that introduces a definition,
- // recursively.
- // 3) this node embeds a closed struct.
- n := e.newNodeContext(shared)
-
- for _, x := range v.Conjuncts {
- // TODO: needed for reentrancy. Investigate usefulness for cycle
- // detection.
- n.addExprConjunct(x)
- }
-
- // Use maybeSetCache for cycle breaking
- for n.maybeSetCache(); n.expandOne(); n.maybeSetCache() {
- }
- if v.Status() > Evaluating && state <= Partial {
- // We have found a partial result. There may still be errors
- // down the line which may result from further evaluating this
- // field, but that will be caught when evaluating this field
- // for real.
- shared.setResult(v)
- e.freeNodeContext(n)
- return shared
- }
-
- if !n.done() && len(n.disjunctions) > 0 && isEvaluating(v) {
- // We disallow entering computations of disjunctions with
- // incomplete data.
- b := c.NewErrf("incomplete cause disjunction")
- b.Code = IncompleteError
- v.SetValue(n.ctx, Finalized, b)
- shared.setResult(v)
- e.freeNodeContext(n)
- return shared
- }
-
- n.expandDisjuncts(state, nil, maybeDefault, false)
- // TODO: reorganize nodeShared and nodeContext
- n.nodeShared.disjuncts = append(n.nodeShared.disjuncts, n.disjuncts...)
- if v.BaseValue == nil {
- v.BaseValue = n.getValidators()
- }
- // e.freeNodeContext(n) freed in freeSharedNode
- return shared
}
func isStruct(v *Vertex) bool {
@@ -426,7 +395,7 @@
n.errs = nil
default:
- if isEvaluating(n.node) {
+ if n.node.BaseValue == cycle {
if !n.done() { // && !ctx.IsTentative() {
// collect incomplete errors.
var err *Bottom // n.incomplete
@@ -453,7 +422,8 @@
}
// We are no longer evaluating.
- n.node.UpdateStatus(Partial)
+ // n.node.UpdateStatus(Partial)
+ n.node.UpdateStatus(Evaluating)
// Either set to Conjunction or error.
// TODO: verify and simplify the below code to determine whether
@@ -497,21 +467,20 @@
n.addBottom(b)
}
}
- for _, v := range n.checks {
- // TODO(errors): make Validate return bottom and generate
- // optimized conflict message. Also track and inject IDs
- // to determine origin location.s
- if b := ctx.Validate(v, n.node); b != nil {
- n.addBottom(b)
+ // MOVE BELOW
+ if v := n.node.Value(); v != nil && IsConcrete(v) {
+ for _, v := range n.checks {
+ // TODO(errors): make Validate return bottom and generate
+ // optimized conflict message. Also track and inject IDs
+ // to determine origin location.s
+ if b := ctx.Validate(v, n.node); b != nil {
+ n.addBottom(b)
+ }
}
}
-
- } else if !ctx.IsTentative() {
+ } else if state == Finalized {
n.node.BaseValue = n.getValidators()
}
- // else if v == nil {
- // n.node.Value = incompleteSentinel
- // }
if v == nil {
break
@@ -551,6 +520,14 @@
}
func (n *nodeContext) completeArcs(state VertexStatus) {
+
+ if state <= AllArcs {
+ n.node.UpdateStatus(AllArcs)
+ return
+ }
+
+ n.node.UpdateStatus(EvaluatingArcs)
+
ctx := n.ctx
if cyclic := n.hasCycle && !n.hasNonCycle; cyclic {
@@ -567,17 +544,19 @@
for _, a := range n.node.Arcs {
// Call UpdateStatus here to be absolutely sure the status is set
// correctly and that we are not regressing.
- if state == Finalized {
- n.node.UpdateStatus(EvaluatingArcs)
+ n.node.UpdateStatus(EvaluatingArcs)
+ n.ctx.Unifier.Unify(ctx, a, state)
+ // Don't set the state to Finalized if the child arcs are not done.
+ if state == Finalized && a.status < Finalized {
+ state = AllArcs
}
- n.eval.Unify(ctx, a, state)
if err, _ := a.BaseValue.(*Bottom); err != nil {
n.node.AddChildError(err)
}
}
}
- n.node.UpdateStatus(Finalized)
+ n.node.UpdateStatus(state)
}
// TODO: this is now a sentinel. Use a user-facing error that traces where
@@ -587,79 +566,18 @@
Code: CycleError,
}
-func isEvaluating(v *Vertex) bool {
- isCycle := v.Status() == Evaluating
- if isCycle != (v.BaseValue == cycle) {
- panic(fmt.Sprintf("cycle data of sync %d vs %#v", v.Status(), v.BaseValue))
- }
- return isCycle
-}
-
-// TODO(perf): merge this type with nodeContext once we're certain we can
-// remove the distinction (after other optimizations).
-type nodeShared struct {
- nextFree *nodeShared
-
- eval *Unifier
- ctx *OpContext
- node *Vertex
-
- // Disjunction handling
- touched bool
- disjuncts []*nodeContext
- disjunctErrs []*Bottom
- numDefaults int
-
- result_ Vertex
- isDone bool
-}
-
-func (e *Unifier) newSharedNode(ctx *OpContext, node *Vertex) *nodeShared {
- if n := e.freeListShared; n != nil {
- e.stats.Reused++
- e.freeListShared = n.nextFree
-
- *n = nodeShared{
- eval: e,
- ctx: ctx,
- node: node,
-
- disjuncts: n.disjuncts[:0],
- disjunctErrs: n.disjunctErrs[:0],
- }
-
- return n
- }
- e.stats.Allocs++
-
- return &nodeShared{
- ctx: ctx,
- eval: e,
- node: node,
- }
-}
-
-func (e *Unifier) freeSharedNode(n *nodeShared) {
- e.stats.Freed++
- n.nextFree = e.freeListShared
- e.freeListShared = n
-
- for _, d := range n.disjuncts {
- e.freeNodeContext(d)
- }
-}
-
-func (n *nodeShared) createDisjunct() *Disjunction {
+func (n *nodeContext) createDisjunct() *Disjunction {
a := make([]*Vertex, len(n.disjuncts))
p := 0
for i, x := range n.disjuncts {
- v := x.result
+ v := new(Vertex)
+ *v = x.result
if x.defaultMode == isDefault {
a[i] = a[p]
- a[p] = &v
+ a[p] = v
p++
} else {
- a[i] = &v
+ a[i] = v
}
}
return &Disjunction{
@@ -668,22 +586,6 @@
}
}
-func (n *nodeShared) result() Vertex {
- return n.result_
-}
-
-func (n *nodeShared) setResult(v *Vertex) {
- n.result_ = *v
-}
-
-func (n *nodeShared) hasResult() bool {
- return len(n.disjuncts) > 0
-}
-
-func (n *nodeShared) done() bool {
- return n.isDone
-}
-
type arcKey struct {
arc *Vertex
id CloseInfo
@@ -696,7 +598,8 @@
type nodeContext struct {
nextFree *nodeContext
- *nodeShared
+ ctx *OpContext
+ node *Vertex
// TODO: (this is CL is first step)
// filter *Vertex a subset of composite with concrete fields for
@@ -742,16 +645,21 @@
hasTop bool
hasCycle bool // has conjunct with structural cycle
hasNonCycle bool // has conjunct without structural cycle
+ isDone bool
// Disjunction handling
disjunctions []envDisjunct
defaultMode defaultMode
disjuncts []*nodeContext
buffer []*nodeContext
+ disjunctErrs []*Bottom
}
func (n *nodeContext) clone() *nodeContext {
- d := n.eval.newNodeContext(n.nodeShared)
+ d := n.ctx.Unifier.newNodeContext(n.ctx, n.node)
+
+ d.ctx = n.ctx
+ d.node = n.node
d.scalar = n.scalar
d.scalarID = n.scalarID
@@ -782,14 +690,15 @@
return d
}
-func (e *Unifier) newNodeContext(shared *nodeShared) *nodeContext {
+func (e *Unifier) newNodeContext(ctx *OpContext, node *Vertex) *nodeContext {
if n := e.freeListNode; n != nil {
e.stats.Reused++
e.freeListNode = n.nextFree
*n = nodeContext{
+ ctx: ctx,
+ node: node,
kind: TopKind,
- nodeShared: shared,
arcMap: n.arcMap[:0],
checks: n.checks[:0],
dynamicFields: n.dynamicFields[:0],
@@ -799,6 +708,7 @@
vLists: n.vLists[:0],
exprs: n.exprs[:0],
disjunctions: n.disjunctions[:0],
+ disjunctErrs: n.disjunctErrs[:0],
disjuncts: n.disjuncts[:0],
buffer: n.buffer[:0],
}
@@ -808,15 +718,28 @@
e.stats.Allocs++
return &nodeContext{
- kind: TopKind,
- nodeShared: shared,
+ ctx: ctx,
+ node: node,
+ kind: TopKind,
}
}
+func (v *Vertex) getNodeContext(c *OpContext) *nodeContext {
+ if v.state != nil {
+ if v.status == Finalized {
+ panic("dangling node")
+ }
+ return v.state
+ }
+ v.state = c.Unifier.newNodeContext(c, v)
+ return v.state
+}
+
func (e *Unifier) freeNodeContext(n *nodeContext) {
- e.stats.Freed++
- n.nextFree = e.freeListNode
- e.freeListNode = n
+ // TODO: re-enable memory management.
+ // e.stats.Freed++
+ // n.nextFree = e.freeListNode
+ // e.freeListNode = n
}
// TODO(perf): return a dedicated ConflictError that can track original
@@ -953,16 +876,18 @@
return v
}
+// TODO: this function can probably go as this is now handled in the nodeContext.
func (n *nodeContext) maybeSetCache() {
- if n.node.Status() > Evaluating { // n.node.BaseValue != nil
+ if n.node.Status() > Partial { // n.node.BaseValue != nil
return
}
if n.scalar != nil {
n.node.SetValue(n.ctx, Partial, n.scalar)
}
- if n.errs != nil {
- n.node.SetValue(n.ctx, Partial, n.errs)
- }
+ // NOTE: this is now handled by associating the nodeContext
+ // if n.errs != nil {
+ // n.node.SetValue(n.ctx, Partial, n.errs)
+ // }
}
type envExpr struct {
@@ -1641,16 +1566,41 @@
ctx := n.ctx
arc, isNew := n.node.GetArc(f)
- // TODO: disallow adding conjuncts when cache set?
- arc.AddConjunct(x)
+ arc.addConjunct(x)
- if isNew {
+ switch {
+ case isNew:
for _, s := range n.node.Structs {
if s.Disable {
continue
}
s.MatchAndInsert(ctx, arc)
}
+
+ case arc.state != nil:
+ s := arc.state
+ switch {
+ case arc.Status() <= AllArcs:
+ // This may happen when a struct has multiple comprehensions, where
+ // the insertion of one of which depends on the outcome of another.
+
+ // TODO: to something more principled by allowing values to
+ // monotonically increase.
+ arc.status = Partial
+ arc.BaseValue = nil
+ s.disjuncts = s.disjuncts[:0]
+ s.disjunctErrs = s.disjunctErrs[:0]
+
+ fallthrough
+
+ default:
+ arc.state.addExprConjunct(x)
+ }
+
+ case arc.Status() == 0:
+ default:
+ // TODO: handle adding to finalized conjunct
+ panic("unhandled")
}
return arc
}
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 7bbe8dc..4563e88 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -1071,7 +1071,7 @@
if result == nil {
return nil
}
- return c.eval(result)
+ return c.evalState(result, Partial)
}
// A Builtin is a value representing a native function call.
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index ccbfd0c..238db33 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -39,10 +39,6 @@
e *adt.Unifier
}
-func (e *Unifier) Evaluate(ctx *adt.OpContext, v *adt.Vertex) adt.Value {
- return e.e.Evaluate(ctx, v)
-}
-
func (e *Unifier) Unify(ctx *adt.OpContext, v *adt.Vertex, state adt.VertexStatus) {
e.e.Unify(ctx, v, state)
}
diff --git a/internal/core/validate/validate_test.go b/internal/core/validate/validate_test.go
index 0ca51fa..f23f6fe 100644
--- a/internal/core/validate/validate_test.go
+++ b/internal/core/validate/validate_test.go
@@ -121,7 +121,7 @@
y: x + 1
x: y - 1
`,
- out: "cycle\ncycle error:\n test:3:6",
+ out: "cycle\ncycle error:\n test:2:6",
}, {
desc: "disallow cycle",
cfg: &Config{DisallowCycles: true},
@@ -129,7 +129,7 @@
a: b - 100
b: a + 100
c: [c[1], c[0]] `,
- out: "cycle\ncycle error:\n test:3:6",
+ out: "cycle\ncycle error:\n test:2:6",
}, {
desc: "treat cycles as incomplete when not disallowing",
cfg: &Config{},