internal/core/export: fix decl export printing and extraction

Fixes #894

A few related fixes:

- Extract from all evaluated structs, not just unevaluated
conjuncts, which results in dropping merged values.
(Note that unlike with fields attributes, the the declaration
attributes are value-bound, and thus need to be included.)

- Decl attrs were not always printed in export. This has now
been fixed.

- Changed the (internal) API to accept a Vertex, instead
of a list of conjuncts, which is a better abstraction.

Change-Id: Id598fefa88aff88aefd126fb80f1637b3f50c7fb
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9382
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/types.go b/cue/types.go
index 2038a70..30efd1e 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -2111,7 +2111,7 @@
 		return nonExistAttr(key)
 	}
 	// look up the attributes
-	for _, a := range export.ExtractFieldAttrs(v.v.Conjuncts) {
+	for _, a := range export.ExtractFieldAttrs(v.v) {
 		k, _ := a.Split()
 		if key != k {
 			continue
@@ -2149,13 +2149,13 @@
 	attrs := []Attribute{}
 
 	if mask&FieldAttr != 0 {
-		for _, a := range export.ExtractFieldAttrs(v.v.Conjuncts) {
+		for _, a := range export.ExtractFieldAttrs(v.v) {
 			attrs = append(attrs, newAttr(internal.FieldAttr, a))
 		}
 	}
 
 	if mask&DeclAttr != 0 {
-		for _, a := range export.ExtractDeclAttrs(v.v.Conjuncts) {
+		for _, a := range export.ExtractDeclAttrs(v.v) {
 			attrs = append(attrs, newAttr(internal.DeclAttr, a))
 		}
 	}
diff --git a/internal/core/export/expr.go b/internal/core/export/expr.go
index 4af83ac..cdd5b28 100644
--- a/internal/core/export/expr.go
+++ b/internal/core/export/expr.go
@@ -137,16 +137,31 @@
 	if len(e.fields) == 0 && !e.hasEllipsis {
 		switch len(e.embed) + len(e.conjuncts) {
 		case 0:
+			if len(e.attrs) > 0 {
+				break
+			}
 			if len(e.structs) > 0 {
 				return s
 			}
 			return ast.NewIdent("_")
 		case 1:
+			var x ast.Expr
 			if len(e.conjuncts) == 1 {
-				return e.conjuncts[0]
+				x = e.conjuncts[0]
+			} else {
+				x = e.embed[0]
 			}
-			return e.embed[0]
+			if len(e.attrs) == 0 {
+				return x
+			}
+			if st, ok := x.(*ast.StructLit); ok {
+				s.Elts = append(s.Elts, st.Elts...)
+				return s
+			}
 		case 2:
+			if len(e.attrs) > 0 {
+				break
+			}
 			// Simplify.
 			e.conjuncts = append(e.conjuncts, e.embed...)
 			return ast.NewBinExpr(token.AND, e.conjuncts...)
@@ -195,7 +210,9 @@
 			ast.SetComments(d, docs)
 		}
 		if x.cfg.ShowAttributes {
-			d.Attrs = ExtractFieldAttrs(a)
+			for _, c := range a {
+				d.Attrs = extractFieldAttrs(d.Attrs, c)
+			}
 		}
 		s.Elts = append(s.Elts, d)
 	}
diff --git a/internal/core/export/extract.go b/internal/core/export/extract.go
index 4090fd9..792563c 100644
--- a/internal/core/export/extract.go
+++ b/internal/core/export/extract.go
@@ -137,12 +137,15 @@
 	return false
 }
 
-func ExtractFieldAttrs(a []adt.Conjunct) (attrs []*ast.Attribute) {
-	for _, x := range a {
-		f, ok := x.Source().(*ast.Field)
-		if !ok {
-			continue
-		}
+func ExtractFieldAttrs(v *adt.Vertex) (attrs []*ast.Attribute) {
+	for _, x := range v.Conjuncts {
+		attrs = extractFieldAttrs(attrs, x)
+	}
+	return attrs
+}
+
+func extractFieldAttrs(attrs []*ast.Attribute, c adt.Conjunct) []*ast.Attribute {
+	if f, ok := c.Source().(*ast.Field); ok {
 		for _, a := range f.Attrs {
 			if !containsAttr(attrs, a) {
 				attrs = append(attrs, a)
@@ -152,9 +155,11 @@
 	return attrs
 }
 
-func ExtractDeclAttrs(a []adt.Conjunct) (attrs []*ast.Attribute) {
-	for _, c := range a {
-		attrs = extractDeclAttrs(attrs, c.Expr().Source())
+func ExtractDeclAttrs(v *adt.Vertex) (attrs []*ast.Attribute) {
+	for _, st := range v.Structs {
+		if src := st.StructLit; src != nil {
+			attrs = extractDeclAttrs(attrs, src.Src)
+		}
 	}
 	return attrs
 }
diff --git a/internal/core/export/testdata/adt.txtar b/internal/core/export/testdata/adt.txtar
index 9adb227..3c65168 100644
--- a/internal/core/export/testdata/adt.txtar
+++ b/internal/core/export/testdata/adt.txtar
@@ -120,6 +120,7 @@
 -- out/definition --
 import mystrings "strings"
 
+@foo(bar)
 p1: {
 	[X=string]: {
 		name: X
diff --git a/internal/core/export/testdata/attrs.txtar b/internal/core/export/testdata/attrs.txtar
index 665335a..e51eb7f 100644
--- a/internal/core/export/testdata/attrs.txtar
+++ b/internal/core/export/testdata/attrs.txtar
@@ -18,6 +18,33 @@
 
 a: {} @field(1) @field(3)
 
+doNotPropagate: {
+	#A: {  } @attr1()
+	a: #A
+
+	// Do not accumulated field attributes in embedding.
+	#B: {  } @attr1()
+	b: { #B }
+}
+
+embedScalarField: {
+	a: { 2 } @attr1()
+	a: { _ } @attr2()
+}
+
+embedScalarDecl: {
+	b0: { 2, @attr1() }
+	b1: b0
+	b2: { 2, b0, @attr2() }
+}
+
+dontMergeForDef: {
+	a: { @decl1() }
+	b: a & { x: 1, @decl2() }
+	c: a & { @decl2() }
+	d: { a, @decl2() }
+}
+
 -- b.cue --
 @package("b")
 
@@ -42,21 +69,143 @@
 	@decl(3)
 	@decl(5)
 } @field(2) @field(1) @field(4) @field(3) @field(5)
+doNotPropagate: {
+	#A: {} @attr1()
+	a: #A
+
+	// Do not accumulated field attributes in embedding.
+	#B: {} @attr1()
+	b: #B
+}
+embedScalarField: {
+	a: 2 @attr1() @attr2()
+}
+embedScalarDecl: {
+	b0: {
+		@attr1()
+		2
+	}
+	b1: b0
+	b2: {
+		@attr2()
+		b0
+		2
+	}
+}
+dontMergeForDef: {
+	a: {
+		@decl1()
+	}
+	b: a & {
+		@decl2()
+		x: 1
+	}
+	c: a & {
+		@decl2()
+	}
+	d: {
+		@decl2()
+		a
+	}
+}
 -- out/doc --
 []
 [a]
+[doNotPropagate]
+[doNotPropagate #A]
+[doNotPropagate a]
+[doNotPropagate #B]
+- Do not accumulated field attributes in embedding.
+
+[doNotPropagate b]
+[embedScalarField]
+[embedScalarField a]
+[embedScalarDecl]
+[embedScalarDecl b0]
+[embedScalarDecl b1]
+[embedScalarDecl b2]
+[dontMergeForDef]
+[dontMergeForDef a]
+[dontMergeForDef b]
+[dontMergeForDef b x]
+[dontMergeForDef c]
+[dontMergeForDef d]
 -- out/value --
 == Simplified
 {
 	a: {}
+	doNotPropagate: {
+		a: {}
+		b: {}
+	}
+	embedScalarField: {
+		a: 2
+	}
+	embedScalarDecl: {
+		b0: 2
+		b1: 2
+		b2: 2
+	}
+	dontMergeForDef: {
+		a: {}
+		b: {
+			x: 1
+		}
+		c: {}
+		d: {}
+	}
 }
 == Raw
 {
 	a: {}
+	doNotPropagate: {
+		#A: {}
+		a: {}
+
+		// Do not accumulated field attributes in embedding.
+		#B: {}
+		b: {}
+	}
+	embedScalarField: {
+		a: 2
+	}
+	embedScalarDecl: {
+		b0: 2
+		b1: 2
+		b2: 2
+	}
+	dontMergeForDef: {
+		a: {}
+		b: {
+			x: 1
+		}
+		c: {}
+		d: {}
+	}
 }
 == Final
 {
 	a: {}
+	doNotPropagate: {
+		a: {}
+		b: {}
+	}
+	embedScalarField: {
+		a: 2
+	}
+	embedScalarDecl: {
+		b0: 2
+		b1: 2
+		b2: 2
+	}
+	dontMergeForDef: {
+		a: {}
+		b: {
+			x: 1
+		}
+		c: {}
+		d: {}
+	}
 }
 == All
 {
@@ -69,6 +218,43 @@
 		@decl(3)
 		@decl(5)
 	} @field(2) @field(1) @field(4) @field(3) @field(5)
+	doNotPropagate: {
+		#A: {} @attr1()
+		a: {}
+
+		// Do not accumulated field attributes in embedding.
+		#B: {} @attr1()
+		b: {}
+	}
+	embedScalarField: {
+		a: 2 @attr1() @attr2()
+	}
+	embedScalarDecl: {
+		b0: {
+			2, @attr1()
+		}
+		b1: {
+			2, @attr1()
+		}
+		b2: {
+			2, @attr2(), @attr1()
+		}
+	}
+	dontMergeForDef: {
+		a: {
+			@decl1()
+		}
+		b: {
+			@decl1(), @decl2()
+			x: 1
+		}
+		c: {
+			@decl1(), @decl2()
+		}
+		d: {
+			@decl2(), @decl1()
+		}
+	}
 }
 == Eval
 {
@@ -81,4 +267,39 @@
 		@decl(3)
 		@decl(5)
 	} @field(2) @field(1) @field(4) @field(3) @field(5)
+	doNotPropagate: {
+		#A: {} @attr1()
+		a: {}
+		#B: {} @attr1()
+		b: {}
+	}
+	embedScalarField: {
+		a: 2 @attr1() @attr2()
+	}
+	embedScalarDecl: {
+		b0: {
+			2, @attr1()
+		}
+		b1: {
+			2, @attr1()
+		}
+		b2: {
+			2, @attr2(), @attr1()
+		}
+	}
+	dontMergeForDef: {
+		a: {
+			@decl1()
+		}
+		b: {
+			@decl1(), @decl2()
+			x: 1
+		}
+		c: {
+			@decl1(), @decl2()
+		}
+		d: {
+			@decl2(), @decl1()
+		}
+	}
 }
diff --git a/internal/core/export/value.go b/internal/core/export/value.go
index 1508581..91aeacd 100644
--- a/internal/core/export/value.go
+++ b/internal/core/export/value.go
@@ -42,15 +42,19 @@
 // value with a reference in graph mode.
 
 func (e *exporter) vertex(n *adt.Vertex) (result ast.Expr) {
+	var attrs []*ast.Attribute
+	if e.cfg.ShowAttributes {
+		attrs = ExtractDeclAttrs(n)
+	}
 	switch x := n.BaseValue.(type) {
 	case nil:
 		// bare
 	case *adt.StructMarker:
-		result = e.structComposite(n)
+		result = e.structComposite(n, attrs)
 
 	case *adt.ListMarker:
-		if e.showArcs(n) {
-			result = e.structComposite(n)
+		if e.showArcs(n) || attrs != nil {
+			result = e.structComposite(n, attrs)
 		} else {
 			result = e.listComposite(n)
 		}
@@ -59,10 +63,10 @@
 		switch {
 		case e.cfg.ShowErrors && x.ChildError:
 			// TODO(perf): use precompiled arc statistics
-			if len(n.Arcs) > 0 && n.Arcs[0].Label.IsInt() && !e.showArcs(n) {
+			if len(n.Arcs) > 0 && n.Arcs[0].Label.IsInt() && !e.showArcs(n) && attrs == nil {
 				result = e.listComposite(n)
 			} else {
-				result = e.structComposite(n)
+				result = e.structComposite(n, attrs)
 			}
 
 		case !x.IsIncomplete() || len(n.Conjuncts) == 0:
@@ -70,8 +74,8 @@
 		}
 
 	case adt.Value:
-		if e.showArcs(n) {
-			result = e.structComposite(n)
+		if e.showArcs(n) || attrs != nil {
+			result = e.structComposite(n, attrs)
 		} else {
 			result = e.value(x, n.Conjuncts...)
 		}
@@ -337,7 +341,7 @@
 	return false
 }
 
-func (e *exporter) structComposite(v *adt.Vertex) ast.Expr {
+func (e *exporter) structComposite(v *adt.Vertex, attrs []*ast.Attribute) ast.Expr {
 	s, saved := e.pushFrame(v.Conjuncts)
 	e.top().upCount++
 	defer func() {
@@ -374,10 +378,8 @@
 		e.addEmbed(e.value(x))
 	}
 
-	if e.cfg.ShowAttributes {
-		for _, a := range ExtractDeclAttrs(v.Conjuncts) {
-			s.Elts = append(s.Elts, a)
-		}
+	for _, a := range attrs {
+		s.Elts = append(s.Elts, a)
 	}
 
 	p := e.cfg
@@ -434,7 +436,7 @@
 		}
 
 		if p.ShowAttributes {
-			f.Attrs = ExtractFieldAttrs(arc.Conjuncts)
+			f.Attrs = ExtractFieldAttrs(arc)
 		}
 
 		if p.ShowDocs {
diff --git a/internal/encoding/yaml/encode.go b/internal/encoding/yaml/encode.go
index d9742e7..8fd0a61 100644
--- a/internal/encoding/yaml/encode.go
+++ b/internal/encoding/yaml/encode.go
@@ -183,6 +183,9 @@
 			docForNext.WriteString("\n\n")
 			continue
 
+		case *ast.Attribute:
+			continue
+
 		case *ast.Field:
 			if x.Token == token.ISA {
 				return nil, errors.Newf(x.TokenPos, "yaml: definition not allowed")