cue/ast/astutil: fix resolution bugs

This fixes several bugs and documentation bugs in
identifier resolution.

1. Resolution in comprehensions would resolve identifiers
to themselves.

2. Label aliases now no longer bind to references outside
the scope of the field. The compiler would catch this invalid
bind and report an error, but it is better not to bind in the
first place.

3. Remove some more mentions of Template labels.

4. Documentation for comprehensions was incorrect
(Scope and Node were reversed).

5. Aliases X in `X=[string]: foo` should only be visible
in foo.

Fixes #946

Change-Id: I3d070e5c67fce55811c1f3fff1e13c38b8d9eeb7
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9741
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index e564bc4..f2bce9c 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -403,7 +403,6 @@
 	// This LHS path element may be an identifier. Possible forms:
 	//  foo:    a normal identifier
 	//  "foo":  JSON compatible
-	//  <foo>:  a template shorthand
 	Name string
 
 	Scope Node // scope in which node was found or nil if referring directly
diff --git a/cue/ast/astutil/resolve.go b/cue/ast/astutil/resolve.go
index 2db1570..28b112d 100644
--- a/cue/ast/astutil/resolve.go
+++ b/cue/ast/astutil/resolve.go
@@ -64,9 +64,9 @@
 //   X in [X=x]: y        Field          Expr (x)
 //   X in X=[x]: y        Field          Field
 //
-// for k, v in            Field          ForClause
+// for k, v in            ForClause      Ident
+// let x = y              LetClause      Ident
 //
-// Template               Field          Template
 // Fields inside lambda
 //    Label               Field          Expr
 //    Value               Field          Field
@@ -125,21 +125,11 @@
 			if a, ok := x.Label.(*ast.Alias); ok {
 				// TODO(legacy): use name := a.Ident.Name once quoted
 				// identifiers are no longer supported.
-				if name, _, _ := ast.LabelName(a.Ident); name != "" {
-					s.insert(name, x, a)
-				}
 				label, _ = a.Expr.(ast.Label)
-			}
-
-			switch y := label.(type) {
-			// TODO: support *ast.ParenExpr?
-			case *ast.ListLit:
-				// In this case, it really should be scoped like a template.
-				if len(y.Elts) != 1 {
-					break
-				}
-				if a, ok := y.Elts[0].(*ast.Alias); ok {
-					s.insert(a.Ident.Name, x, a)
+				if name, _, _ := ast.LabelName(a.Ident); name != "" {
+					if _, ok := label.(*ast.ListLit); !ok {
+						s.insert(name, x, a)
+					}
 				}
 			}
 
@@ -277,6 +267,8 @@
 
 	case *ast.Comprehension:
 		s = scopeClauses(s, x.Clauses)
+		walk(s, x.Value)
+		return nil
 
 	case *ast.Field:
 		var n ast.Node = x.Label
diff --git a/cue/ast/astutil/resolve_test.go b/cue/ast/astutil/resolve_test.go
new file mode 100644
index 0000000..b280982
--- /dev/null
+++ b/cue/ast/astutil/resolve_test.go
@@ -0,0 +1,73 @@
+// Copyright 2021 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package astutil_test
+
+import (
+	"fmt"
+	"path/filepath"
+	"testing"
+	"text/tabwriter"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/internal/astinternal"
+	"cuelang.org/go/internal/cuetest"
+	"cuelang.org/go/internal/cuetxtar"
+)
+
+func TestResolve(t *testing.T) {
+	test := cuetxtar.TxTarTest{
+		Root:   "./testdata/resolve",
+		Name:   "resolve",
+		Update: cuetest.UpdateGoldenFiles,
+	}
+
+	test.Run(t, func(t *cuetxtar.Test) {
+		a := t.ValidInstances()
+
+		for _, f := range a[0].Files {
+			if filepath.Ext(f.Filename) != ".cue" {
+				continue
+			}
+
+			identMap := map[ast.Node]int{}
+			ast.Walk(f, func(n ast.Node) bool {
+				switch n.(type) {
+				case *ast.File, *ast.StructLit, *ast.Field, *ast.ImportSpec,
+					*ast.Ident, *ast.ForClause, *ast.LetClause, *ast.Alias:
+					identMap[n] = len(identMap) + 1
+				}
+				return true
+			}, nil)
+
+			// Resolve was already called.
+
+			base := filepath.Base(f.Filename)
+			b := t.Writer(base[:len(base)-len(".cue")])
+			w := tabwriter.NewWriter(b, 0, 4, 1, ' ', 0)
+			ast.Walk(f, func(n ast.Node) bool {
+				if x, ok := n.(*ast.Ident); ok {
+					fmt.Fprintf(w, "%d[%s]:\tScope: %d[%T]\tNode: %d[%s]\n",
+						identMap[x], astinternal.DebugStr(x),
+						identMap[x.Scope], x.Scope,
+						identMap[x.Node], astinternal.DebugStr(x.Node))
+				}
+				return true
+			}, nil)
+			w.Flush()
+
+			fmt.Fprintln(b)
+		}
+	})
+}
diff --git a/cue/ast/astutil/testdata/resolve/comprehensions.txtar b/cue/ast/astutil/testdata/resolve/comprehensions.txtar
new file mode 100644
index 0000000..b4b89a5
--- /dev/null
+++ b/cue/ast/astutil/testdata/resolve/comprehensions.txtar
@@ -0,0 +1,36 @@
+-- in.cue --
+src: [{foo: 3}]
+
+for k, v in src
+let y = v.foo
+if y > 3 {
+    x: [k, v, y]
+}
+
+-- out/resolve/in --
+3[src]:  Scope: 0[<nil>]           Node: 0[]
+6[foo]:  Scope: 0[<nil>]           Node: 0[]
+8[k]:    Scope: 0[<nil>]           Node: 0[]
+9[v]:    Scope: 0[<nil>]           Node: 0[]
+10[src]: Scope: 1[*ast.File]       Node: 0[[{foo: 3}]]
+12[y]:   Scope: 0[<nil>]           Node: 0[]
+13[v]:   Scope: 7[*ast.ForClause]  Node: 9[v]
+14[foo]: Scope: 0[<nil>]           Node: 0[]
+15[y]:   Scope: 11[*ast.LetClause] Node: 12[y]
+18[x]:   Scope: 0[<nil>]           Node: 0[]
+19[k]:   Scope: 7[*ast.ForClause]  Node: 8[k]
+20[v]:   Scope: 7[*ast.ForClause]  Node: 9[v]
+21[y]:   Scope: 11[*ast.LetClause] Node: 12[y]
+
+-- issue946.cue --
+x: {for a in a {}}
+y: {for aa in a {}}
+
+-- out/resolve/issue946 --
+3[x]:   Scope: 0[<nil>] Node: 0[]
+6[a]:   Scope: 0[<nil>] Node: 0[]
+7[a]:   Scope: 0[<nil>] Node: 0[]
+10[y]:  Scope: 0[<nil>] Node: 0[]
+13[aa]: Scope: 0[<nil>] Node: 0[]
+14[a]:  Scope: 0[<nil>] Node: 0[]
+
diff --git a/cue/ast/astutil/testdata/resolve/fieldalias.txtar b/cue/ast/astutil/testdata/resolve/fieldalias.txtar
new file mode 100644
index 0000000..cbd7c1c
--- /dev/null
+++ b/cue/ast/astutil/testdata/resolve/fieldalias.txtar
@@ -0,0 +1,31 @@
+-- in.cue --
+X=a: int
+b: a
+c: X
+
+-- out/resolve/in --
+4[X]:   Scope: 0[<nil>]     Node: 0[]
+5[a]:   Scope: 0[<nil>]     Node: 0[]
+6[int]: Scope: 0[<nil>]     Node: 0[]
+8[b]:   Scope: 0[<nil>]     Node: 0[]
+9[a]:   Scope: 1[*ast.File] Node: 6[int]
+11[c]:  Scope: 0[<nil>]     Node: 0[]
+12[X]:  Scope: 1[*ast.File] Node: 2[X=a: int]
+
+-- dynamic.cue --
+X=("foo"):  int
+Y="\(X)":   string
+a: X
+b: Y
+
+-- out/resolve/dynamic --
+4[X]:       Scope: 0[<nil>]     Node: 0[]
+5[int]:     Scope: 0[<nil>]     Node: 0[]
+8[Y]:       Scope: 0[<nil>]     Node: 0[]
+9[X]:       Scope: 1[*ast.File] Node: 2[X=("foo"): int]
+10[string]: Scope: 0[<nil>]     Node: 0[]
+12[a]:      Scope: 0[<nil>]     Node: 0[]
+13[X]:      Scope: 1[*ast.File] Node: 2[X=("foo"): int]
+15[b]:      Scope: 0[<nil>]     Node: 0[]
+16[Y]:      Scope: 1[*ast.File] Node: 6[Y="\(X)": string]
+
diff --git a/cue/ast/astutil/testdata/resolve/labels.txtar b/cue/ast/astutil/testdata/resolve/labels.txtar
new file mode 100644
index 0000000..b0fe12c
--- /dev/null
+++ b/cue/ast/astutil/testdata/resolve/labels.txtar
@@ -0,0 +1,24 @@
+-- in.cue --
+[X=string]: name: X
+a: X
+
+Z=[string]: { x: string, y: Z.x }
+c: Z
+
+-- out/resolve/in --
+4[X]:       Scope: 0[<nil>]       Node: 0[]
+5[string]:  Scope: 0[<nil>]       Node: 0[]
+8[name]:    Scope: 0[<nil>]       Node: 0[]
+9[X]:       Scope: 2[*ast.Field]  Node: 5[string]
+11[a]:      Scope: 0[<nil>]       Node: 0[]
+12[X]:      Scope: 0[<nil>]       Node: 0[]
+15[Z]:      Scope: 0[<nil>]       Node: 0[]
+16[string]: Scope: 0[<nil>]       Node: 0[]
+19[x]:      Scope: 0[<nil>]       Node: 0[]
+20[string]: Scope: 0[<nil>]       Node: 0[]
+22[y]:      Scope: 0[<nil>]       Node: 0[]
+23[Z]:      Scope: 13[*ast.Field] Node: 13[Z=[string]: {x: string, y: Z.x}]
+24[x]:      Scope: 0[<nil>]       Node: 0[]
+26[c]:      Scope: 0[<nil>]       Node: 0[]
+27[Z]:      Scope: 0[<nil>]       Node: 0[]
+
diff --git a/cue/ast/astutil/testdata/resolve/let.txtar b/cue/ast/astutil/testdata/resolve/let.txtar
new file mode 100644
index 0000000..b3a5f66
--- /dev/null
+++ b/cue/ast/astutil/testdata/resolve/let.txtar
@@ -0,0 +1,13 @@
+-- let.cue --
+b: X
+let X = int
+a: X
+
+-- out/resolve/let --
+3[b]:   Scope: 0[<nil>]     Node: 0[]
+4[X]:   Scope: 1[*ast.File] Node: 5[let X=int]
+6[X]:   Scope: 0[<nil>]     Node: 0[]
+7[int]: Scope: 0[<nil>]     Node: 0[]
+9[a]:   Scope: 0[<nil>]     Node: 0[]
+10[X]:  Scope: 1[*ast.File] Node: 5[let X=int]
+
diff --git a/cue/ast/astutil/testdata/resolve/value.txtar b/cue/ast/astutil/testdata/resolve/value.txtar
new file mode 100644
index 0000000..3ba1621
--- /dev/null
+++ b/cue/ast/astutil/testdata/resolve/value.txtar
@@ -0,0 +1,12 @@
+-- in.cue --
+b: X={
+    c: X.a
+}
+
+-- out/resolve/in --
+3[b]:  Scope: 0[<nil>]      Node: 0[]
+5[X]:  Scope: 0[<nil>]      Node: 0[]
+8[c]:  Scope: 0[<nil>]      Node: 0[]
+9[X]:  Scope: 2[*ast.Field] Node: 4[X={c: X.a}]
+10[a]: Scope: 0[<nil>]      Node: 0[]
+
diff --git a/cue/testdata/compile/erralias.txtar b/cue/testdata/compile/erralias.txtar
index c30c1a9..07c3c0a 100644
--- a/cue/testdata/compile/erralias.txtar
+++ b/cue/testdata/compile/erralias.txtar
@@ -15,7 +15,7 @@
 -- out/compile --
 unreferenced alias or let clause X:
     ./in.cue:1:1
-a: illegal reference Y:
+a: reference "Y" not found:
     ./in.cue:4:4
 definitions not supported for interpolations:
     ./in.cue:6:1
@@ -24,7 +24,7 @@
 --- in.cue
 {
   ["foo"]: 3
-  a: _|_(illegal reference Y)
+  a: _|_(reference "Y" not found)
   "\(〈0;b〉)": 3
   b: "foo"
   c: {}
diff --git a/cue/testdata/compile/files.txtar b/cue/testdata/compile/files.txtar
new file mode 100644
index 0000000..44414a3
--- /dev/null
+++ b/cue/testdata/compile/files.txtar
@@ -0,0 +1,38 @@
+// Issue #946
+-- in.cue --
+package repro
+
+a: []
+
+-- out.cue --
+package repro
+
+x: {for a in a {}}
+y: {{{for a in a {}}}}
+-- out/compile --
+--- in.cue
+{
+  a: []
+}
+--- out.cue
+{
+  x: {
+    for _, a in 〈1;a〉 {}
+  }
+  y: {
+    {
+      {
+        for _, a in 〈3;a〉 {}
+      }
+    }
+  }
+}
+-- out/eval --
+(struct){
+  a: (#list){
+  }
+  x: (struct){
+  }
+  y: (struct){
+  }
+}