Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
cue/ast/astutil: fix resolution bugs
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Marcel van Lohuizen <[email protected]>
  • Loading branch information
mpvl committed May 11, 2021
1 parent cd286a8 commit 858efa9
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 19 deletions.
1 change: 0 additions & 1 deletion cue/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,6 @@ type Ident struct {
// 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
Expand Down
24 changes: 8 additions & 16 deletions cue/ast/astutil/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ type ErrFunc func(pos token.Pos, msg string, args ...interface{})
// 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
Expand Down Expand Up @@ -125,21 +125,11 @@ func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope
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)
}
}
}

Expand Down Expand Up @@ -277,6 +267,8 @@ func (s *scope) Before(n ast.Node) (w visitor) {

case *ast.Comprehension:
s = scopeClauses(s, x.Clauses)
walk(s, x.Value)
return nil

case *ast.Field:
var n ast.Node = x.Label
Expand Down
73 changes: 73 additions & 0 deletions cue/ast/astutil/resolve_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
36 changes: 36 additions & 0 deletions cue/ast/astutil/testdata/resolve/comprehensions.txtar
Original file line number Diff line number Diff line change
@@ -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[]

31 changes: 31 additions & 0 deletions cue/ast/astutil/testdata/resolve/fieldalias.txtar
Original file line number Diff line number Diff line change
@@ -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]

24 changes: 24 additions & 0 deletions cue/ast/astutil/testdata/resolve/labels.txtar
Original file line number Diff line number Diff line change
@@ -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[]

13 changes: 13 additions & 0 deletions cue/ast/astutil/testdata/resolve/let.txtar
Original file line number Diff line number Diff line change
@@ -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]

12 changes: 12 additions & 0 deletions cue/ast/astutil/testdata/resolve/value.txtar
Original file line number Diff line number Diff line change
@@ -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[]

4 changes: 2 additions & 2 deletions cue/testdata/compile/erralias.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Z1=[Z2=string]: Z1+Z2
-- 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
Expand All @@ -24,7 +24,7 @@ for[].a: reference "E" not found:
--- in.cue
{
["foo"]: 3
a: _|_(illegal reference Y)
a: _|_(reference "Y" not found)
"\(〈0;b〉)": 3
b: "foo"
c: {}
Expand Down
38 changes: 38 additions & 0 deletions cue/testdata/compile/files.txtar
Original file line number Diff line number Diff line change
@@ -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){
}
}

0 comments on commit 858efa9

Please sign in to comment.