cue/ast: split off Ellipsis from ListLit

- allows reuse of Ellipsis for StructLit
  (needed for closed lits)
- allows relaxing positioning of ellipsis in
   the future (may be useful for structs and
   associative lists)
- simplifies code in general

Change-Id: I18caac6677b6178a9fe4c3db609046ff76eb7b10
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2863
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index b19891d..cc79744 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -208,8 +208,9 @@
 			}
 
 		case *ast.ListLit:
-			if x.Type != nil {
-				t.markAlwaysGen(x.Type, false)
+			_, e := internal.ListEllipsis(x)
+			if e != nil && e.Type != nil {
+				t.markAlwaysGen(e.Type, false)
 			}
 
 		case *ast.Field:
diff --git a/cue/ast.go b/cue/ast.go
index eb2403d..8e7f5bc 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -248,22 +248,28 @@
 			object:   v.object,
 			parent:   v,
 		}
+
+		elts, ellipsis := internal.ListEllipsis(n)
+
 		arcs := []arc{}
-		for i, e := range n.Elts {
+		for i, e := range elts {
 			v1.sel = strconv.Itoa(i)
 			arcs = append(arcs, arc{feature: label(i), v: v1.walk(e)})
 		}
 		s := &structLit{baseValue: newExpr(n), arcs: arcs}
 		list := &list{baseValue: newExpr(n), elem: s}
 		list.initLit()
-		if n.Ellipsis != token.NoPos || n.Type != nil {
+		if ellipsis != nil {
 			list.len = newBound(v.ctx(), list.baseValue, opGeq, intKind, list.len)
-			if n.Type != nil {
-				list.typ = v1.walk(n.Type)
+			if ellipsis.Type != nil {
+				list.typ = v1.walk(ellipsis.Type)
 			}
 		}
 		ret = list
 
+	case *ast.Ellipsis:
+		return v.errf(n, "ellipsis (...) only allowed at end of list")
+
 	case *ast.ComprehensionDecl:
 		yielder := &yield{baseValue: newExpr(n.Field.Value)}
 		fc := &fieldComprehension{
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 3bbc5a6..a36c1d1 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -61,6 +61,7 @@
 func (*Interpolation) exprNode() {}
 func (*StructLit) exprNode()     {}
 func (*ListLit) exprNode()       {}
+func (*Ellipsis) exprNode()      {}
 
 // func (*StructComprehension) exprNode() {}
 func (*ListComprehension) exprNode() {}
@@ -392,11 +393,15 @@
 // A ListLit node represents a literal list.
 type ListLit struct {
 	comments
-	Lbrack   token.Pos // position of "["
-	Elts     []Expr    // list of composite elements; or nil
+	Lbrack token.Pos // position of "["
+	Elts   []Expr    // list of composite elements; or nil
+	Rbrack token.Pos // position of "]"
+}
+
+type Ellipsis struct {
+	comments
 	Ellipsis token.Pos // open list if set
 	Type     Expr      // type for the remaining elements
-	Rbrack   token.Pos // position of "]"
 }
 
 // A ListComprehension node represents as list comprehension.
@@ -501,6 +506,7 @@
 }
 
 func (x *ListLit) Pos() token.Pos           { return x.Lbrack }
+func (x *Ellipsis) Pos() token.Pos          { return x.Ellipsis }
 func (x *ListComprehension) Pos() token.Pos { return x.Lbrack }
 func (x *ForClause) Pos() token.Pos         { return x.For }
 func (x *IfClause) Pos() token.Pos          { return x.If }
@@ -527,7 +533,13 @@
 	}
 	return x.Rbrace.Add(1)
 }
-func (x *ListLit) End() token.Pos           { return x.Rbrack.Add(1) }
+func (x *ListLit) End() token.Pos { return x.Rbrack.Add(1) }
+func (x *Ellipsis) End() token.Pos {
+	if x.Type != nil {
+		return x.Type.End()
+	}
+	return x.Ellipsis.Add(3) // len("...")
+}
 func (x *ListComprehension) End() token.Pos { return x.Rbrack }
 func (x *ForClause) End() token.Pos         { return x.Source.End() }
 func (x *IfClause) End() token.Pos          { return x.Condition.End() }
diff --git a/cue/ast/walk.go b/cue/ast/walk.go
index 49e6acd..89e93a6 100644
--- a/cue/ast/walk.go
+++ b/cue/ast/walk.go
@@ -110,6 +110,8 @@
 
 	case *ListLit:
 		walkExprList(v, n.Elts)
+
+	case *Ellipsis:
 		if n.Type != nil {
 			walk(v, n.Type)
 		}
diff --git a/cue/export.go b/cue/export.go
index bb9969c..3e804a3 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -563,7 +563,7 @@
 			open = true
 		}
 		if !ok || ln > len(x.elem.arcs) {
-			list.Type = p.expr(x.typ)
+			list.Elts = append(list.Elts, &ast.Ellipsis{Type: p.expr(x.typ)})
 			if !open && !isTop(x.typ) {
 				expr = &ast.BinaryExpr{
 					X: &ast.BinaryExpr{
diff --git a/cue/format/node.go b/cue/format/node.go
index 2ab6395..32076ad 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -457,19 +457,17 @@
 	case *ast.ListLit:
 		f.print(x.Lbrack, token.LBRACK, indent)
 		f.walkExprList(x.Elts, 1)
-		if x.Ellipsis != token.NoPos || x.Type != nil {
-			f.print(x.Ellipsis, token.ELLIPSIS)
-			if x.Type != nil && !isTop(x.Type) {
-				f.expr(x.Type)
-			}
-		} else {
-			f.print(trailcomma, noblank)
-			f.current.pos += 2
-			f.visitComments(f.current.pos)
-		}
+		f.print(trailcomma, noblank)
+		f.visitComments(f.current.pos)
 		f.matchUnindent()
 		f.print(noblank, x.Rbrack, token.RBRACK)
 
+	case *ast.Ellipsis:
+		f.print(x.Ellipsis, token.ELLIPSIS)
+		if x.Type != nil && !isTop(x.Type) {
+			f.expr(x.Type)
+		}
+
 	case *ast.ListComprehension:
 		f.print(x.Lbrack, token.LBRACK, blank, indent)
 		f.expr(x.Expr)
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index be7b001..65aa717 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -98,6 +98,8 @@
 	e: 'aa \(aaa) aa'
 	e: "aa \(aaa)"
 
+	e: [1, 2,
+	]
 	e: [1, 2]
 	e: [1, 2, 3, 4,
 		5, 6, 7, 8]
@@ -107,7 +109,7 @@
 	e: [...]
 	e: [
 		...]
-	e: [...
+	e: [...,
 	]
 	e: [1, 2, ...]
 	e: [1, 2,
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index f8b61d3..1d656ea 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -98,6 +98,8 @@
     e: 'aa \(aaa) aa'
     e: "aa \(aaa)"
 
+    e: [1, 2
+    ]
     e: [1, 2]
        e: [1, 2, 3, 4,
     5, 6, 7, 8]
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 7b1f78b..c331dc5 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -960,13 +960,14 @@
 		}
 	}
 
-	ellipsis := token.NoPos
-	typ := ast.Expr(nil)
 	if p.tok == token.ELLIPSIS {
-		ellipsis = p.pos
+		ellipsis := &ast.Ellipsis{
+			Ellipsis: p.pos,
+		}
+		elts = append(elts, ellipsis)
 		p.next()
 		if p.tok != token.COMMA && p.tok != token.RBRACK {
-			typ = p.parseRHS()
+			ellipsis.Type = p.parseRHS()
 		}
 		if p.atComma("list literal", token.RBRACK) {
 			p.next()
@@ -975,11 +976,9 @@
 
 	rbrack := p.expectClosing(token.RBRACK, "list literal")
 	return &ast.ListLit{
-		Lbrack:   lbrack,
-		Elts:     elts,
-		Ellipsis: ellipsis,
-		Type:     typ,
-		Rbrack:   rbrack}
+		Lbrack: lbrack,
+		Elts:   elts,
+		Rbrack: rbrack}
 }
 
 func (p *parser) parseListElements() (list []ast.Expr) {
diff --git a/cue/parser/print.go b/cue/parser/print.go
index 40a85cf..2a91d64 100644
--- a/cue/parser/print.go
+++ b/cue/parser/print.go
@@ -97,18 +97,16 @@
 	case *ast.ListLit:
 		out := "["
 		out += debugStr(v.Elts)
-		if v.Ellipsis != token.NoPos || v.Type != nil {
-			if out != "[" {
-				out += ", "
-			}
-			out += "..."
-			if v.Type != nil {
-				out += debugStr(v.Type)
-			}
-		}
 		out += "]"
 		return out
 
+	case *ast.Ellipsis:
+		out := "..."
+		if v.Type != nil {
+			out += debugStr(v.Type)
+		}
+		return out
+
 	case *ast.ListComprehension:
 		out := "["
 		out += debugStr(v.Expr)
diff --git a/cue/parser/walk.go b/cue/parser/walk.go
index 82f88c6..eebe315 100644
--- a/cue/parser/walk.go
+++ b/cue/parser/walk.go
@@ -105,6 +105,8 @@
 
 	case *ast.ListLit:
 		walkExprList(v, n.Elts)
+
+	case *ast.Ellipsis:
 		if n.Type != nil {
 			walk(v, n.Type)
 		}
diff --git a/encoding/protobuf/parse.go b/encoding/protobuf/parse.go
index 9647df4..4df5977 100644
--- a/encoding/protobuf/parse.go
+++ b/encoding/protobuf/parse.go
@@ -509,9 +509,8 @@
 
 		if x.Repeated {
 			f.Value = &ast.ListLit{
-				Lbrack:   p.toCUEPos(x.Position),
-				Ellipsis: token.NoSpace.Pos(),
-				Type:     f.Value,
+				Lbrack: p.toCUEPos(x.Position),
+				Elts:   []ast.Expr{&ast.Ellipsis{Type: f.Value}},
 			}
 		}
 
diff --git a/internal/internal.go b/internal/internal.go
index 36cacc8..fa417ab 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -19,7 +19,10 @@
 
 // TODO: refactor packages as to make this package unnecessary.
 
-import "github.com/cockroachdb/apd/v2"
+import (
+	"cuelang.org/go/cue/ast"
+	"github.com/cockroachdb/apd/v2"
+)
 
 // A Decimal is an arbitrary-precision binary-coded decimal number.
 //
@@ -64,3 +67,18 @@
 // if it does not, and returns a forked runtime that will discard additional
 // keys.
 var CheckAndForkRuntime func(runtime, value interface{}) interface{}
+
+// ListEllipsis reports the list type and remaining elements of a list. If we
+// ever relax the usage of ellipsis, this function will likely change. Using
+// this function will ensure keeping correct behavior or causing a compiler
+// failure.
+func ListEllipsis(n *ast.ListLit) (elts []ast.Expr, e *ast.Ellipsis) {
+	elts = n.Elts
+	if n := len(elts); n > 0 {
+		var ok bool
+		if e, ok = elts[n-1].(*ast.Ellipsis); ok {
+			elts = elts[:n-1]
+		}
+	}
+	return elts, e
+}