diff --git a/cmd/cue/cmd/testdata/script/export_issue3511.txtar b/cmd/cue/cmd/testdata/script/export_issue3511.txtar new file mode 100644 index 00000000000..a4a3f2482a7 --- /dev/null +++ b/cmd/cue/cmd/testdata/script/export_issue3511.txtar @@ -0,0 +1,34 @@ + +env CUE_EXPERIMENT=evalv3=1 +exec cue export +cmp stdout out/stdout + +-- cue.mod/module.cue -- +module: "module.test/foo" +language: version: "v0.9.0" + +-- main.cue -- +package p + +import "module.test/foo/imported@v0" + +items: [imported.List] +-- imported/imported.cue -- +package imported + +Namespace: "default" + +List: [...{namespace: Namespace}] + +List: [{name: "kube-api-server"}] +-- out/stdout -- +{ + "items": [ + [ + { + "name": "kube-api-server", + "namespace": "default" + } + ] + ] +} diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go index d6b867a70b7..65bb2716a45 100644 --- a/internal/core/adt/context.go +++ b/internal/core/adt/context.go @@ -229,6 +229,12 @@ type OpContext struct { // structural cycle errors. vertex *Vertex + // list of vertices that need to be finalized. + // TODO: remove this again once we have a proper way of detecting references + // across optional boundaries in hasAncestorV3. We can probably do this + // with an optional depth counter. + toFinalize []*Vertex + // These fields are used associate scratch fields for computing closedness // of a Vertex. These fields could have been included in StructInfo (like // Tomabechi's unification algorithm), but we opted for an indirection to diff --git a/internal/core/adt/share.go b/internal/core/adt/share.go index 2f9c4816f52..099a72bcc6b 100644 --- a/internal/core/adt/share.go +++ b/internal/core/adt/share.go @@ -58,10 +58,20 @@ func (n *nodeContext) finalizeSharing() { n.sharedID.cc.decDependent(n.ctx, SHARED, n.node.cc()) n.sharedID.cc = nil } - if n.isShared { - v := n.node.BaseValue.(*Vertex) + if !n.isShared { + return + } + switch v := n.node.BaseValue.(type) { + case *Vertex: + if n.sharedID.CycleType == NoCycle { + v.Finalize(n.ctx) + } else if !v.isFinal() { + // TODO: ideally we just handle cycles in optional chains directly, + // rather than relying on this mechanism. This requires us to add + // a mechanism to detect that. + n.ctx.toFinalize = append(n.ctx.toFinalize, v) + } if v.Parent == n.node.Parent { - v.unify(n.ctx, needTasksDone, finalize) if !v.Rooted() && v.MayAttach() { n.isShared = false n.node.Arcs = v.Arcs @@ -71,6 +81,10 @@ func (n *nodeContext) finalizeSharing() { n.node.HasEllipsis = v.HasEllipsis } } + case *Bottom: + // An error trumps sharing. We can leave it as is. + default: + panic("unreachable") } } diff --git a/internal/core/adt/unify.go b/internal/core/adt/unify.go index 69f51339ae0..dcb04f7cfbb 100644 --- a/internal/core/adt/unify.go +++ b/internal/core/adt/unify.go @@ -130,6 +130,19 @@ func (v *Vertex) unify(c *OpContext, needs condition, mode runMode) bool { }() } + if c.evalDepth == 0 { + defer func() { + // This loop processes nodes that need to be evaluated, but should be + // evaluated outside of the stack to avoid structural cycle detection. + // See comment at toFinalize. + a := c.toFinalize + c.toFinalize = c.toFinalize[:0] + for _, x := range a { + x.Finalize(c) + } + }() + } + if mode == ignore { return false }