diff --git a/cue/testdata/cycle/021_delayed_constraint_failure.txtar b/cue/testdata/cycle/021_delayed_constraint_failure.txtar index 72b64a5ba..5469ec64f 100644 --- a/cue/testdata/cycle/021_delayed_constraint_failure.txtar +++ b/cue/testdata/cycle/021_delayed_constraint_failure.txtar @@ -36,7 +36,11 @@ x: conflicting values 101 and 100: Result: (_|_){ // [eval] - a: (int){ 100 } + a: (_|_){ + // [eval] b: conflicting values 210 and 200: + // ./in.cue:2:4 + // ./in.cue:3:4 + } b: (_|_){ // [eval] b: conflicting values 210 and 200: // ./in.cue:2: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 e764e84c8..875d6e004 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,6 +42,10 @@ Result: // ./in.cue:2:5 // ./in.cue:5:7 } - y: (string){ "hey!" } + y: (_|_){ + // [eval] a.x: conflicting values "hey!?" and "hey": + // ./in.cue:2:5 + // ./in.cue:5:7 + } } } diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go index 5bc75d096..16a45bc7b 100644 --- a/internal/core/adt/eval.go +++ b/internal/core/adt/eval.go @@ -167,10 +167,9 @@ func (e *Unifier) evaluate(c *OpContext, v *Vertex, state VertexStatus) Value { Conjuncts: v.Conjuncts, } w.UpdateStatus(v.Status()) - return w + v = w } } - return x case nil: if v.state != nil { @@ -186,6 +185,10 @@ func (e *Unifier) evaluate(c *OpContext, v *Vertex, state VertexStatus) Value { panic("nil value") } + if v.status < Finalized && v.state != nil { + v.state.addNotify(c.vertex) + } + return v } @@ -274,6 +277,8 @@ func (e *Unifier) Unify(c *OpContext, v *Vertex, state VertexStatus) { for n.maybeSetCache(); n.expandOne(); n.maybeSetCache() { } + n.doNotify() + if !n.done() { if len(n.disjunctions) > 0 && v.BaseValue == cycle { // We disallow entering computations of disjunctions with @@ -372,6 +377,23 @@ func (e *Unifier) Unify(c *OpContext, v *Vertex, state VertexStatus) { } } +func (n *nodeContext) doNotify() { + if n.errs != nil && len(n.notify) > 0 { + for _, v := range n.notify { + if v.state == nil { + if b, ok := v.BaseValue.(*Bottom); ok { + v.BaseValue = CombineErrors(nil, b, n.errs) + } else { + v.BaseValue = n.errs + } + } else { + v.state.addBottom(n.errs) + } + } + n.notify = n.notify[:0] + } +} + func isStruct(v *Vertex) bool { _, ok := v.BaseValue.(*StructMarker) return ok @@ -647,6 +669,10 @@ type nodeContext struct { checks []Validator // BuiltinValidator, other bound values. errs *Bottom + // notify is used to communicate errors in cyclic dependencies. + // TODO: also use this to communicate increasingly more concrete values. + notify []*Vertex + // Struct information dynamicFields []envDynamic ifClauses []envYield @@ -671,6 +697,12 @@ type nodeContext struct { disjunctErrs []*Bottom } +func (n *nodeContext) addNotify(v *Vertex) { + if v != nil { + n.notify = append(n.notify, v) + } +} + func (n *nodeContext) clone() *nodeContext { d := n.ctx.Unifier.newNodeContext(n.ctx, n.node) @@ -696,6 +728,7 @@ func (n *nodeContext) clone() *nodeContext { d.hasNonCycle = n.hasNonCycle // d.arcMap = append(d.arcMap, n.arcMap...) // XXX add? + d.notify = append(d.notify, n.notify...) d.checks = append(d.checks, n.checks...) d.dynamicFields = append(d.dynamicFields, n.dynamicFields...) d.ifClauses = append(d.ifClauses, n.ifClauses...)