1// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package runtime
16
17import (
18 "path"
19 "slices"
20 "strconv"
21
22 "cuelang.org/go/cue/ast"
23 "cuelang.org/go/cue/build"
24 "cuelang.org/go/cue/errors"
25)
26
27// TODO(resolve): this is also done in compile, do we need both?
28func (r *Runtime) ResolveFiles(p *build.Instance) (errs errors.Error) {
29 idx := r.index
30
31 // Link top-level declarations. As top-level entries get unified, an entry
32 // may be linked to any top-level entry of any of the files.
33 allFields := map[string]ast.Node{}
34 for _, f := range p.Files {
35 if f.PackageName() == "" {
36 continue
37 }
38 for _, d := range f.Decls {
39 if f, ok := d.(*ast.Field); ok && f.Value != nil {
40 if ident, ok := f.Label.(*ast.Ident); ok {
41 allFields[ident.Name] = f.Value
42 }
43 }
44 }
45 }
46 for _, f := range p.Files {
47 err := resolveFile(idx, f, p, allFields)
48 errs = errors.Append(errs, err)
49 }
50 return errs
51}
52
53func resolveFile(
54 idx *index,
55 f *ast.File,
56 p *build.Instance,
57 allFields map[string]ast.Node,
58) errors.Error {
59 unresolved := map[string][]*ast.Ident{}
60 for _, u := range f.Unresolved {
61 unresolved[u.Name] = append(unresolved[u.Name], u)
62 }
63 fields := map[string]ast.Node{}
64 for _, d := range f.Decls {
65 if f, ok := d.(*ast.Field); ok && f.Value != nil {
66 if ident, ok := f.Label.(*ast.Ident); ok {
67 fields[ident.Name] = d
68 }
69 }
70 }
71 var errs errors.Error
72
73 specs := []*ast.ImportSpec{}
74
75 for spec := range f.ImportSpecs() {
76 id, err := strconv.Unquote(spec.Path.Value)
77 if err != nil {
78 continue // quietly ignore the error
79 }
80 name := path.Base(id)
81 if imp := p.LookupImport(id); imp != nil {
82 name = imp.PkgName
83 } else if _, ok := idx.builtinPaths[id]; !ok {
84 errs = errors.Append(errs,
85 nodeErrorf(spec, "package %q not found", id))
86 continue
87 }
88 if spec.Name != nil {
89 name = spec.Name.Name
90 }
91 if n, ok := fields[name]; ok {
92 errs = errors.Append(errs, nodeErrorf(spec,
93 "%s redeclared as imported package name\n"+
94 "\tprevious declaration at %s", name, n.Pos()))
95 continue
96 }
97 fields[name] = spec
98 used := false
99 for _, u := range unresolved[name] {
100 used = true
101 u.Node = spec
102 }
103 if !used {
104 specs = append(specs, spec)
105 }
106 }
107
108 // Verify each import is used.
109 if len(specs) > 0 {
110 // Find references to imports. This assumes that identifiers in labels
111 // are not resolved or that such errors are caught elsewhere.
112 ast.Walk(f, nil, func(n ast.Node) {
113 if x, ok := n.(*ast.Ident); ok {
114 // As we also visit labels, most nodes will be nil.
115 if x.Node == nil {
116 return
117 }
118 for i, s := range specs {
119 if s == x.Node {
120 specs[i] = nil
121 return
122 }
123 }
124 }
125 })
126
127 // Add errors for unused imports.
128 for _, spec := range specs {
129 if spec == nil {
130 continue
131 }
132 if spec.Name == nil {
133 errs = errors.Append(errs, nodeErrorf(spec,
134 "imported and not used: %s", spec.Path.Value))
135 } else {
136 errs = errors.Append(errs, nodeErrorf(spec,
137 "imported and not used: %s as %s", spec.Path.Value, spec.Name))
138 }
139 }
140 }
141
142 f.Unresolved = slices.DeleteFunc(f.Unresolved, func(u *ast.Ident) bool {
143 if u.Node != nil {
144 return true
145 }
146 if n, ok := allFields[u.Name]; ok {
147 u.Node = n
148 u.Scope = f
149 return true
150 }
151 return false
152 })
153 // TODO: also need to resolve types.
154 // if len(f.Unresolved) > 0 {
155 // n := f.Unresolved[0]
156 // return ctx.mkErr(newBase(n), "unresolved reference %s", n.Name)
157 // }
158 return errs
159}