internal/core/eval: track fieldSets for data values
This is, unfortunately, necessary because of
acceptor.isClosed. This tracking can be removed
once we get rid of that value.
Fixes #530
Change-Id: I34fe6d8af0667200595e000c02ca5898a4ab9f8b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7745
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index e5aeba4..6d160d9 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -565,7 +565,11 @@
return a
}
a = appendPath(a, v.Parent)
- return append(a, v.Label)
+ if v.Label != 0 {
+ // A Label may be 0 for programmatically inserted nodes.
+ a = append(a, v.Label)
+ }
+ return a
}
func (v *Vertex) appendListArcs(arcs []*Vertex) (err *Bottom) {
diff --git a/internal/core/convert/go.go b/internal/core/convert/go.go
index 1f2b5b7..aedd6ed 100644
--- a/internal/core/convert/go.go
+++ b/internal/core/convert/go.go
@@ -402,8 +402,7 @@
case reflect.Struct:
obj := &adt.StructLit{Src: src}
- v := &adt.Vertex{}
- v.AddConjunct(adt.MakeRootConjunct(nil, obj))
+ v := &adt.Vertex{Structs: []*adt.StructLit{obj}}
v.SetValue(ctx, adt.Finalized, &adt.StructMarker{})
t := value.Type()
@@ -440,10 +439,7 @@
if sf.Anonymous && name == "" {
arc, ok := sub.(*adt.Vertex)
if ok {
- for _, a := range arc.Arcs {
- obj.Decls = append(obj.Decls, &adt.Field{Label: a.Label, Value: a.Value})
- v.Arcs = append(v.Arcs, a)
- }
+ v.Arcs = append(v.Arcs, arc.Arcs...)
}
continue
}
@@ -455,6 +451,7 @@
arc.Label = f
} else {
arc = &adt.Vertex{Label: f, Value: sub}
+ arc.UpdateStatus(adt.Finalized)
arc.AddConjunct(adt.MakeRootConjunct(nil, sub))
}
v.Arcs = append(v.Arcs, arc)
@@ -463,9 +460,7 @@
return v
case reflect.Map:
- obj := &adt.StructLit{Src: src}
v := &adt.Vertex{Value: &adt.StructMarker{}}
- v.AddConjunct(adt.MakeRootConjunct(nil, obj))
v.SetValue(ctx, adt.Finalized, &adt.StructMarker{})
t := value.Type()
@@ -497,15 +492,12 @@
s := fmt.Sprint(k)
f := ctx.StringLabel(s)
- obj.Decls = append(obj.Decls, &adt.Field{
- Label: f,
- Value: sub,
- })
arc, ok := sub.(*adt.Vertex)
if ok {
arc.Label = f
} else {
arc = &adt.Vertex{Label: f, Value: sub}
+ arc.UpdateStatus(adt.Finalized)
arc.AddConjunct(adt.MakeRootConjunct(nil, sub))
}
v.Arcs = append(v.Arcs, arc)
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index 3fa7a60..db3e806 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -1393,21 +1393,37 @@
return
case *adt.StructMarker:
+ // TODO: this would not be necessary if acceptor.isClose were
+ // not used. See comment at acceptor.
+ opt := fieldSet{env: env, id: id}
+
+ // Keep ordering of Go struct for topological sort.
+ n.node.Structs = append(n.node.Structs, x.Structs...)
for _, a := range x.Arcs {
c := adt.MakeConjunct(nil, a, id)
c = updateCyclic(c, cyclic, nil, nil)
n.insertField(a.Label, c)
+ opt.MarkField(ctx, a.Label)
}
+ closedInfo(n.node).insertFieldSet(id, &opt)
+
default:
n.addValueConjunct(env, v, id)
+ // TODO: this would not be necessary if acceptor.isClose were
+ // not used. See comment at acceptor.
+ opt := fieldSet{env: env, id: id}
+
for _, a := range x.Arcs {
// TODO(errors): report error when this is a regular field.
- n.insertField(a.Label, adt.MakeConjunct(nil, a, id))
- // sub, _ := n.node.GetArc(a.Label)
- // sub.Add(a)
+ c := adt.MakeConjunct(nil, a, id)
+ c = updateCyclic(c, cyclic, nil, nil)
+ n.insertField(a.Label, c)
+ opt.MarkField(ctx, a.Label)
}
+
+ closedInfo(n.node).insertFieldSet(id, &opt)
}
return
@@ -1434,7 +1450,7 @@
case *adt.Top:
n.hasTop = true
- // TODO: Is this correct. Needed for elipsis, but not sure for others.
+ // TODO: Is this correct? Needed for elipsis, but not sure for others.
// n.optionals = append(n.optionals, fieldSet{env: env, id: id, isOpen: true})
if a, _ := n.node.Closed.(*acceptor); a != nil {
// Embedding top indicates that now all possible values are allowed
@@ -1604,7 +1620,7 @@
for _, d := range s.Decls {
switch x := d.(type) {
case *adt.Field:
- opt.MarkField(ctx, x)
+ opt.MarkField(ctx, x.Label)
// handle in next iteration.
case *adt.OptionalField:
diff --git a/internal/core/eval/optionals.go b/internal/core/eval/optionals.go
index 674f7ef..765e341 100644
--- a/internal/core/eval/optionals.go
+++ b/internal/core/eval/optionals.go
@@ -153,9 +153,9 @@
return -1
}
-func (o *fieldSet) MarkField(c *adt.OpContext, x *adt.Field) {
- if o.fieldIndex(x.Label) < 0 {
- o.fields = append(o.fields, field{label: x.Label})
+func (o *fieldSet) MarkField(c *adt.OpContext, f adt.Feature) {
+ if o.fieldIndex(f) < 0 {
+ o.fields = append(o.fields, field{label: f})
}
}
diff --git a/internal/core/eval/vertex_test.go b/internal/core/eval/vertex_test.go
new file mode 100644
index 0000000..04dcb13
--- /dev/null
+++ b/internal/core/eval/vertex_test.go
@@ -0,0 +1,142 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package eval_test
+
+import (
+ "testing"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/internal/core/adt"
+ "cuelang.org/go/internal/core/compile"
+ "cuelang.org/go/internal/core/convert"
+ "cuelang.org/go/internal/core/debug"
+ "cuelang.org/go/internal/core/eval"
+)
+
+// TestVertex tests the use of programmatically generated Vertex values that
+// may have different properties than the one generated.
+func TestVertex(t *testing.T) {
+ r := cue.NewRuntime()
+ ctx := eval.NewContext(r, nil)
+
+ goValue := func(x interface{}) *adt.Vertex {
+ v := convert.GoValueToValue(ctx, x, true)
+ return v.(*adt.Vertex)
+ }
+
+ goType := func(x interface{}) *adt.Vertex {
+ v, err := convert.GoTypeToExpr(ctx, x)
+ if err != nil {
+ t.Fatal(err)
+ }
+ n := &adt.Vertex{}
+ n.AddConjunct(adt.MakeRootConjunct(nil, v))
+ n.Finalize(ctx)
+ return n
+ }
+
+ schema := func(field, config string) *adt.Vertex {
+ v := build(t, ctx, config)
+ f := adt.MakeIdentLabel(r, field, "")
+ return v.Lookup(f)
+ }
+
+ testCases := []struct {
+ name string
+ a, b *adt.Vertex
+ want string
+ verbose bool
+ }{{
+
+ // Issue #530
+ a: schema("Steps", `
+ Steps: [...#Step]
+
+ #Step: Args: _
+ `),
+ b: goValue([]*struct{ Args interface{} }{{
+ Args: map[string]interface{}{
+ "Message": "Hello, world!",
+ },
+ }}),
+ want: `[{Args:{Message:"Hello, world!"}}]`,
+ }, {
+
+ name: "list of values",
+ a: goValue([]int{1, 2, 3}),
+ b: goType([]int{}),
+ want: `[1,2,3]`,
+ }, {
+ name: "list of list of values",
+ a: goValue([][]int{{1, 2, 3}}),
+ b: goType([][]int{}),
+ want: `[[1,2,3]]`,
+ }, {
+ a: schema("Steps", `
+ Steps: [...#Step]
+
+ #Step: Args: _
+ `),
+ b: schema("a", `
+ a: [{Args: { Message: "hello" }}]
+ `),
+ want: `[{Args:{Message:"hello"}}]`,
+ }, {
+
+ // Issue #530
+ a: schema("Steps", `
+ Steps: [...#Step]
+
+ #Step: Args: _
+ `),
+ b: goValue([]*struct{ Args interface{} }{{
+ Args: map[string]interface{}{
+ "Message": "Hello, world!",
+ },
+ }}),
+ want: `[{Args:{Message:"Hello, world!"}}]`,
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+
+ n := &adt.Vertex{}
+ n.AddConjunct(adt.MakeRootConjunct(nil, tc.a))
+ n.AddConjunct(adt.MakeRootConjunct(nil, tc.b))
+ n.Finalize(ctx)
+
+ got := debug.NodeString(r, n, &debug.Config{Compact: !tc.verbose})
+ if got != tc.want {
+ t.Errorf("got:\n%s\nwant:\n%s", got, tc.want)
+ }
+ })
+ }
+}
+
+func build(t *testing.T, ctx *adt.OpContext, schema string) *adt.Vertex {
+ f, err := parser.ParseFile("test", schema)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ v, err := compile.Files(nil, ctx, "test", f)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ v.Finalize(ctx)
+
+ return v
+}
diff --git a/internal/core/export/export_test.go b/internal/core/export/export_test.go
index 52bce8a..57eb3f4 100644
--- a/internal/core/export/export_test.go
+++ b/internal/core/export/export_test.go
@@ -87,7 +87,7 @@
}
return convert.GoValueToValue(ctx, in, false), nil
},
- out: `{Terminals: [{Description: "Desc", Name: "Name"}]}`,
+ out: `{Terminals: [{Name: "Name", Description: "Desc"}]}`,
}, {
in: func(ctx *adt.OpContext) (adt.Expr, error) {
in := &C{