internal/core/eval: centralize type checking

This aims to make error checking more correct, easier
to manage and improve error messages.

Change-Id: I00bfd0742bb90549679eaa63894c30b1092a228d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6702
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/eval/eval.go b/internal/core/eval/eval.go
index a912c4b..896c983 100644
--- a/internal/core/eval/eval.go
+++ b/internal/core/eval/eval.go
@@ -424,10 +424,12 @@
 	for n.maybeSetCache(); n.expandOne(); n.maybeSetCache() {
 	}
 
-	// TODO: preparation for association lists:
-	// We assume that association types may not be created dynamically for now.
-	// So we add lists
-	n.addLists(ctx)
+	if aList := n.addLists(ctx); aList != nil {
+		n.updateNodeType(adt.ListKind, aList)
+	}
+	if n.aStruct != nil {
+		n.updateNodeType(adt.StructKind, n.aStruct)
+	}
 
 	switch err := n.getErr(); {
 	case err != nil:
@@ -473,37 +475,19 @@
 
 		// Either set to Conjunction or error.
 		var v adt.Value = n.node.Value
-		kind := n.kind
+		// TODO: verify and simplify the below code to determine whether
+		// something is a struct.
 		markStruct := false
-		if n.isStruct {
-			if kind != 0 && kind&adt.StructKind == 0 {
-				n.node.Value = n.ctx.NewErrf(
-					"conflicting values struct and %s", n.kind)
-			}
+		if n.aStruct != nil {
 			markStruct = true
 		} else if len(n.node.Structs) > 0 {
-			markStruct = kind&adt.StructKind != 0 && !n.hasTop
+			markStruct = n.kind&adt.StructKind != 0 && !n.hasTop
 		}
 		if v == nil && markStruct {
-			kind = adt.StructKind
 			n.node.Value = &adt.StructMarker{}
 			v = n.node
 		}
 		if v != nil && adt.IsConcrete(v) {
-			if n.scalar != nil {
-				kind = n.scalar.Kind()
-			}
-			if v.Kind()&^kind != 0 {
-				p := token.NoPos
-				if src := v.Source(); src != nil {
-					p = src.Pos()
-				}
-				n.addErr(ctx.NewPosf(p,
-					// TODO(errors): position of all value types.
-					"conflicting types %v and %v",
-					v.Kind(), n.kind,
-				))
-			}
 			if n.lowerBound != nil {
 				if b := ctx.Validate(n.lowerBound, v); b != nil {
 					n.addBottom(b)
@@ -722,6 +706,7 @@
 
 	// Concrete conjuncts
 	kind       adt.Kind
+	kindExpr   adt.Expr        // expr that adjust last value (for error reporting)
 	lowerBound *adt.BoundValue // > or >=
 	upperBound *adt.BoundValue // < or <=
 	checks     []adt.Validator // BuiltinValidator, other bound values.
@@ -739,7 +724,7 @@
 	// - parent node is closing
 	needClose bool
 	openList  bool
-	isStruct  bool
+	aStruct   adt.Expr
 	hasTop    bool
 	newClose  *CloseDef
 	// closeID   uint32 // from parent, or if not exist, new if introducing a def.
@@ -759,6 +744,32 @@
 	isFinal      bool
 }
 
+func (n *nodeContext) updateNodeType(k adt.Kind, v adt.Expr) bool {
+	ctx := n.ctx
+	kind := n.kind & k
+
+	switch {
+	case n.kind == adt.BottomKind,
+		k == adt.BottomKind:
+		return false
+
+	case kind == adt.BottomKind:
+		if n.kindExpr != nil {
+			n.addErr(ctx.Newf(
+				"conflicting values %s and %s (mismatched types %s and %s)",
+				ctx.Str(n.kindExpr), ctx.Str(v), n.kind, k))
+		} else {
+			n.addErr(ctx.Newf(
+				"conflicting value %s (mismatched types %s and %s)",
+				ctx.Str(v), n.kind, k))
+		}
+	}
+
+	n.kind = kind
+	n.kindExpr = v
+	return kind != adt.BottomKind
+}
+
 func (n *nodeContext) done() bool {
 	return len(n.dynamicFields) == 0 &&
 		len(n.ifClauses) == 0 &&
@@ -825,9 +836,7 @@
 		// Src is the combined input.
 		v = &adt.BasicType{K: n.kind}
 
-		// TODO: Change to isStruct?
 		if len(n.node.Structs) > 0 {
-			// n.isStruct = true
 			v = structSentinel
 
 		}
@@ -1130,7 +1139,7 @@
 	if x, ok := v.(*adt.Vertex); ok {
 		needClose := false
 		if isStruct(x) {
-			n.isStruct = true
+			n.aStruct = x
 			// TODO: find better way to mark as struct.
 			// For instance, we may want to add a faux
 			// Structlit for topological sort.
@@ -1167,6 +1176,9 @@
 
 		case *adt.StructMarker:
 			for _, a := range x.Arcs {
+				if a.Label.IsString() {
+					n.aStruct = x
+				}
 				// TODO, insert here as
 				n.insertField(a.Label, adt.MakeConjunct(nil, a))
 				// sub, _ := n.node.GetArc(a.Label)
@@ -1177,7 +1189,7 @@
 			n.addValueConjunct(env, v)
 
 			for _, a := range x.Arcs {
-				// TODO, insert here as
+				// TODO(errors): report error when this is a regular field.
 				n.insertField(a.Label, adt.MakeConjunct(nil, a))
 				// sub, _ := n.node.GetArc(a.Label)
 				// sub.Add(a)
@@ -1193,16 +1205,11 @@
 		return
 	}
 
-	ctx := n.ctx
-	kind := n.kind & v.Kind()
-	if kind == adt.BottomKind {
-		// TODO: how to get other conflicting values?
-		n.addErr(ctx.Newf(
-			"invalid value %s (mismatched types %s and %s)",
-			ctx.Str(v), v.Kind(), n.kind))
+	if !n.updateNodeType(v.Kind(), v) {
 		return
 	}
-	n.kind = kind
+
+	ctx := n.ctx
 
 	switch x := v.(type) {
 	case *adt.Disjunction:
@@ -1219,6 +1226,7 @@
 		n.optionals = append(n.optionals, fieldSet{env: env, isOpen: true})
 
 	case *adt.BasicType:
+		// handled above
 
 	case *adt.BoundValue:
 		switch x.Op {
@@ -1321,6 +1329,7 @@
 	}
 
 	var hasOther, hasBulk adt.Node
+	hasEmbed := false
 
 	opt := fieldSet{pos: s, env: childEnv}
 
@@ -1331,9 +1340,13 @@
 			// handle in next iteration.
 
 		case *adt.OptionalField:
+			if x.Label.IsString() {
+				n.aStruct = s
+			}
 			opt.AddOptional(ctx, x)
 
 		case *adt.DynamicField:
+			n.aStruct = s
 			hasOther = x
 			n.dynamicFields = append(n.dynamicFields, envDynamic{childEnv, x})
 			opt.AddDynamic(ctx, childEnv, x)
@@ -1347,6 +1360,8 @@
 			n.ifClauses = append(n.ifClauses, envYield{childEnv, x})
 
 		case adt.Expr:
+			hasEmbed = true
+
 			// push and opo embedding type.
 			id := n.eval.nextID()
 
@@ -1366,10 +1381,12 @@
 			n.addOr(closeID, current)
 
 		case *adt.BulkOptionalField:
+			n.aStruct = s
 			hasBulk = x
 			opt.AddBulk(ctx, x)
 
 		case *adt.Ellipsis:
+			n.aStruct = s
 			hasBulk = x
 			opt.AddEllipsis(ctx, x)
 
@@ -1382,6 +1399,10 @@
 		n.addErr(ctx.Newf("cannot mix bulk optional fields with dynamic fields, embeddings, or comprehensions within the same struct"))
 	}
 
+	if !hasEmbed {
+		n.aStruct = s
+	}
+
 	// Apply existing fields
 	for _, arc := range n.node.Arcs {
 		// Reuse adt.Acceptor interface.
@@ -1393,6 +1414,9 @@
 	for _, d := range s.Decls {
 		switch x := d.(type) {
 		case *adt.Field:
+			if x.Label.IsString() {
+				n.aStruct = s
+			}
 			n.insertField(x.Label, adt.MakeConjunct(childEnv, x))
 		}
 	}
@@ -1402,10 +1426,6 @@
 	ctx := n.ctx
 	arc, isNew := n.node.GetArc(f)
 
-	if f.IsString() {
-		n.isStruct = true
-	}
-
 	// TODO: disallow adding conjuncts when cache set?
 	arc.AddConjunct(x)
 
@@ -1548,24 +1568,18 @@
 //
 // TODO(embeddedScalars): for embedded scalars, there should be another pass
 // of evaluation expressions after expanding lists.
-func (n *nodeContext) addLists(c *adt.OpContext) {
+func (n *nodeContext) addLists(c *adt.OpContext) (oneOfTheLists adt.Expr) {
 	if len(n.lists) == 0 && len(n.vLists) == 0 {
-		return
+		return nil
 	}
 
-	for _, a := range n.node.Arcs {
-		if t := a.Label.Typ(); t == adt.StringLabel {
-			n.addErr(c.Newf("conflicting types list and struct"))
-		}
-	}
-
-	// fmt.Println(len(n.lists), "ELNE")
-
 	isOpen := true
 	max := 0
 	var maxNode adt.Expr
 
 	for _, l := range n.vLists {
+		oneOfTheLists = l
+
 		elems := l.Elems()
 		isClosed := l.IsClosed(c)
 
@@ -1603,6 +1617,8 @@
 
 outer:
 	for i, l := range n.lists {
+		oneOfTheLists = l.list
+
 		n.updateCyclicStatus(l.env)
 
 		index := int64(0)
@@ -1712,6 +1728,8 @@
 		Src:    ast.NewBinExpr(token.AND, sources...),
 		IsOpen: isOpen,
 	})
+
+	return oneOfTheLists
 }
 
 func (n *nodeContext) invalidListLength(na, nb int, a, b adt.Expr) {