cue/ast/astutil: catch cycle for label expressions

If such labels need to would be applied to
the field referenced to.

In some cases this should actually be legal,
according to the spec, but we will make this
a compilation error for now until we fix the
evaluator.

Closes #251

Change-Id: I167a87cb1866fc2991abe29a05c53356cf23805b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4720
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast/astutil/resolve.go b/cue/ast/astutil/resolve.go
index 6f923fb..af8cbd3 100644
--- a/cue/ast/astutil/resolve.go
+++ b/cue/ast/astutil/resolve.go
@@ -45,10 +45,11 @@
 // scope.
 //
 type scope struct {
-	file  *ast.File
-	outer *scope
-	node  ast.Node
-	index map[string]ast.Node
+	file    *ast.File
+	outer   *scope
+	node    ast.Node
+	index   map[string]ast.Node
+	inField bool
 
 	errFn func(p token.Pos, msg string, args ...interface{})
 }
@@ -208,7 +209,7 @@
 			if len(label.Elts) != 1 {
 				break
 			}
-			s := newScope(s.file, s, x, nil)
+			s = newScope(s.file, s, x, nil)
 			if alias != nil {
 				if name, _, _ := ast.LabelName(alias.Ident); name != "" {
 					s.insert(name, x)
@@ -231,22 +232,33 @@
 					s.insert(name, a.Expr)
 				}
 			}
+
+			ast.Walk(expr, nil, func(n ast.Node) {
+				if x, ok := n.(*ast.Ident); ok {
+					for s := s; s != nil && !s.inField; s = s.outer {
+						if _, ok := s.index[x.Name]; ok {
+							s.errFn(n.Pos(),
+								"reference %q in label expression refers to field against which it would be matched", x.Name)
+						}
+					}
+				}
+			})
 			walk(s, expr)
-			walk(s, x.Value)
-			return nil
 
 		case *ast.TemplateLabel:
-			s := newScope(s.file, s, x, nil)
+			s = newScope(s.file, s, x, nil)
 			name, err := ast.ParseIdent(label.Ident)
 			if err == nil {
 				s.insert(name, x.Label) // Field used for entire lambda.
 			}
-			walk(s, x.Value)
-			return nil
 		}
+
 		if x.Value != nil {
+			s.inField = true
 			walk(s, x.Value)
+			s.inField = false
 		}
+
 		return nil
 
 	case *ast.Alias:
diff --git a/cue/ast_test.go b/cue/ast_test.go
index 87f93dd..240105f 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -230,14 +230,28 @@
 		// optional fields with key filters
 		in: `
 			JobID: =~"foo"
-			[JobID]: { name: string }
+			a: [JobID]: { name: string }
 
 			[<"s"]: { other: string }
 			`,
 		out: `<0>{` +
-			`[<0>.JobID]: <1>(_: string)-><2>{name: string}, ` +
-			`[<"s"]: <3>(_: string)-><4>{other: string}, ` +
-			`JobID: =~"foo"}`,
+			`[<"s"]: <1>(_: string)-><2>{other: string}, ` +
+			`JobID: =~"foo", a: <3>{` +
+			`[<0>.JobID]: <4>(_: string)-><5>{name: string}, ` +
+			`}}`,
+	}, {
+		// Issue #251
+		// TODO: the is one of the cases where it is relatively easy to catch
+		// a structural cycle. We should be able, however, to break the cycle
+		// with a post-validation constraint. Clean this up with the evaluator
+		// update.
+		in: `
+		{
+			[x]: 3
+		}
+		x:   "x"
+		`,
+		out: "reference \"x\" in label expression refers to field against which it would be matched:\n    test:3:5\n<0>{}",
 	}, {
 		// illegal alias usage
 		in: `