internal/core/adt: improve error message for optional fields

Fixes #455

Change-Id: Ic1d2a14541a9b6507db64502a56891c92a382e32
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7802
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git "a/cue/testdata/fulleval/029_Issue_\04394.txtar" "b/cue/testdata/fulleval/029_Issue_\04394.txtar"
index 231bb0d..ba23b79 100644
--- "a/cue/testdata/fulleval/029_Issue_\04394.txtar"
+++ "b/cue/testdata/fulleval/029_Issue_\04394.txtar"
@@ -97,7 +97,7 @@
   }
   select: (struct){
     opt: (_|_){
-      // [incomplete] select.opt: undefined field opt:
+      // [incomplete] select.opt: cannot reference optional field opt:
       //     ./in.cue:10:15
     }
     txt: (int){ 2 }
@@ -107,7 +107,7 @@
   }
   index: (struct){
     opt: (_|_){
-      // [incomplete] index.opt: undefined field opt:
+      // [incomplete] index.opt: cannot reference optional field opt:
       //     ./in.cue:17:15
     }
     txt: (int){ 2 }
diff --git a/cue/testdata/references/optional.txtar b/cue/testdata/references/optional.txtar
index 2b1f3f3..804b07a 100644
--- a/cue/testdata/references/optional.txtar
+++ b/cue/testdata/references/optional.txtar
@@ -16,7 +16,7 @@
 (struct){
   a: (struct){
     b: (_|_){
-      // [incomplete] a.b: undefined field foo:
+      // [incomplete] a.b: cannot reference optional field foo:
       //     ./in.cue:4:8
     }
   }
diff --git a/cue/testdata/resolve/010_optional_field_resolves_to_incomplete.txtar b/cue/testdata/resolve/010_optional_field_resolves_to_incomplete.txtar
index 50df2cb..d69371d 100644
--- a/cue/testdata/resolve/010_optional_field_resolves_to_incomplete.txtar
+++ b/cue/testdata/resolve/010_optional_field_resolves_to_incomplete.txtar
@@ -29,11 +29,11 @@
 (struct){
   r: (struct){
     b: (_|_){
-      // [incomplete] r.b: undefined field a:
+      // [incomplete] r.b: cannot reference optional field a:
       //     ./in.cue:3:6
     }
     c: (_|_){
-      // [incomplete] r.c: undefined field a:
+      // [incomplete] r.c: cannot reference optional field a:
       //     ./in.cue:4:8
     }
   }
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index c7c9bdf..6545899 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -426,6 +426,10 @@
 	// OptionalTypes returns a bit field with the type of optional constraints
 	// that are represented by this Acceptor.
 	OptionalTypes() OptionalType
+
+	// IsOptional reports whether a field is explicitly defined as optional,
+	// as opposed to whether it is allowed by a pattern constraint.
+	IsOptional(f Feature) bool
 }
 
 // OptionalType is a bit field of the type of optional constraints in use by an
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 1b9e3d6..fc19b80 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -637,7 +637,12 @@
 			c.addErrf(code, pos, "index out of range [%d] with length %d",
 				l.Index(), len(x.Elems()))
 		} else {
-			c.addErrf(code, pos, "undefined field %s", label)
+			if code != 0 && x.Closed != nil && x.Closed.IsOptional(l) {
+				c.addErrf(code, pos,
+					"cannot reference optional field %s", label)
+			} else {
+				c.addErrf(code, pos, "undefined field %s", label)
+			}
 		}
 	}
 	return a
diff --git a/internal/core/eval/closed.go b/internal/core/eval/closed.go
index 8432e79..786ebfa 100644
--- a/internal/core/eval/closed.go
+++ b/internal/core/eval/closed.go
@@ -126,6 +126,14 @@
 	return mask
 }
 
+func (a *acceptor) IsOptional(label adt.Feature) bool {
+	optional := false
+	a.visitAllFieldSets(func(f *fieldSet) {
+		optional = optional || f.IsOptional(label)
+	})
+	return optional
+}
+
 // A disjunction acceptor represents a disjunction of all possible fields. Note
 // that this is never used in evaluation as evaluation stops at incomplete nodes
 // and a disjunction is incomplete. When the node is referenced, the original
diff --git a/internal/core/eval/optionals.go b/internal/core/eval/optionals.go
index 47ffe47..b7ea096 100644
--- a/internal/core/eval/optionals.go
+++ b/internal/core/eval/optionals.go
@@ -66,6 +66,15 @@
 	return mask
 }
 
+func (o *fieldSet) IsOptional(label adt.Feature) bool {
+	for _, f := range o.fields {
+		if f.label == label && len(f.optional) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
 type field struct {
 	label    adt.Feature
 	optional []adt.Node