1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
|
package pkglint
import "sort"
// Scope remembers which variables are defined and which are used
// in a certain scope, such as a package or a file.
//
// TODO: Decide whether the scope should consider variable assignments
// from the pkgsrc infrastructure. For Package.checkGnuConfigureUseLanguages
// it would be better to ignore them completely.
//
// TODO: Merge this code with Var, which defines essentially the
// same features.
//
// See also substScope, which already analyzes the possible variable values
// based on the conditional code paths.
//
// See also RedundantScope.
type Scope struct {
vs map[string]*scopeVar
names []string
}
type scopeVar struct {
firstDef *MkLine
lastDef *MkLine
value string
used *MkLine
fallback string
usedAtLoadTime bool
indeterminate bool
}
func NewScope() Scope {
return Scope{make(map[string]*scopeVar), nil}
}
func (s *Scope) varnames() []string {
if len(s.names) == 0 && len(s.vs) > 0 {
varnames := make([]string, len(s.vs))
i := 0
for varname := range s.vs {
varnames[i] = varname
i++
}
sort.Strings(varnames)
s.names = varnames
}
return s.names
}
func (s *Scope) create(varname string) *scopeVar {
if v := s.vs[varname]; v != nil {
return v
}
var sv scopeVar
s.vs[varname] = &sv
s.names = nil
return &sv
}
// Define marks the variable and its canonicalized form as defined.
func (s *Scope) Define(varname string, mkline *MkLine) {
s.def(varname, mkline)
varcanon := varnameCanon(varname)
if varcanon != varname {
s.def(varcanon, mkline)
}
}
func (s *Scope) def(name string, mkline *MkLine) {
v := s.create(name)
if v.firstDef == nil {
v.firstDef = mkline
if trace.Tracing {
trace.Step2("Defining %q for the first time in %s", name, mkline.String())
}
} else if trace.Tracing {
trace.Step2("Defining %q in %s", name, mkline.String())
}
v.lastDef = mkline
// In most cases the defining lines are indeed variable assignments.
// Exceptions are comments from documentation sections, which still mark
// the variable as defined so that it doesn't produce the "used but not defined" warning;
// see MkLines.collectDocumentedVariables.
if !mkline.IsVarassign() {
return
}
switch mkline.Op() {
case opAssignAppend:
value := mkline.Value()
if trace.Tracing {
trace.Stepf("Scope.Define.append %s: %s = %q + %q",
mkline.String(), name, v.value, value)
}
v.value += " " + value
case opAssignDefault:
if v.value == "" && !v.indeterminate {
v.value = mkline.Value()
}
case opAssignShell:
v.value = ""
v.indeterminate = true
default:
v.value = mkline.Value()
}
}
func (s *Scope) Fallback(varname string, value string) {
s.create(varname).fallback = value
}
// Use marks the variable and its canonicalized form as used.
func (s *Scope) Use(varname string, mkline *MkLine, time VucTime) {
use := func(name string) {
v := s.create(name)
if v.used == nil {
v.used = mkline
if trace.Tracing {
trace.Step2("Using %q in %s", name, mkline.String())
}
}
if time == VucLoadTime {
v.usedAtLoadTime = true
}
}
use(varname)
use(varnameCanon(varname))
}
// Mentioned returns the first line in which the variable is either:
// - defined,
// - mentioned in a commented variable assignment,
// - mentioned in a documentation comment.
func (s *Scope) Mentioned(varname string) *MkLine {
if v := s.vs[varname]; v != nil {
return v.firstDef
}
return nil
}
// IsDefined tests whether the variable is defined.
// It does NOT test the canonicalized variable name.
//
// Even if IsDefined returns true, FirstDefinition doesn't necessarily return true
// since the latter ignores the default definitions from vardefs.go, keyword dummyVardefMkline.
func (s *Scope) IsDefined(varname string) bool {
mkline := s.Mentioned(varname)
return mkline != nil && mkline.IsVarassign()
}
// IsDefinedSimilar tests whether the variable or its canonicalized form is defined.
func (s *Scope) IsDefinedSimilar(varname string) bool {
if s.IsDefined(varname) {
if trace.Tracing {
trace.Step1("Variable %q is defined", varname)
}
return true
}
varcanon := varnameCanon(varname)
if s.IsDefined(varcanon) {
if trace.Tracing {
trace.Step2("Variable %q (similar to %q) is defined", varcanon, varname)
}
return true
}
return false
}
// IsUsed tests whether the variable is used.
// It does NOT test the canonicalized variable name.
func (s *Scope) IsUsed(varname string) bool {
return s.FirstUse(varname) != nil
}
// IsUsedSimilar tests whether the variable or its canonicalized form is used.
func (s *Scope) IsUsedSimilar(varname string) bool {
return s.FirstUse(varname) != nil || s.FirstUse(varnameCanon(varname)) != nil
}
// IsUsedAtLoadTime returns true if the variable is used at load time
// somewhere.
func (s *Scope) IsUsedAtLoadTime(varname string) bool {
v := s.vs[varname]
return v != nil && v.usedAtLoadTime
}
// FirstDefinition returns the line in which the variable has been defined first.
//
// Having multiple definitions is typical in the branches of "if" statements.
//
// Another typical case involves two files: the included file defines a default
// value, and the including file later overrides that value. Or the other way
// round: the including file sets a value first, and the included file then
// assigns a default value using ?=.
func (s *Scope) FirstDefinition(varname string) *MkLine {
v := s.vs[varname]
if v == nil {
return nil
}
mkline := v.firstDef
if mkline != nil && mkline.IsVarassign() {
lastLine := s.LastDefinition(varname)
if trace.Tracing && lastLine != mkline {
trace.Stepf("%s: FirstDefinition differs from LastDefinition in %s.",
mkline.String(), mkline.RelMkLine(lastLine))
}
return mkline
}
return nil // See NewPackage and G.Pkgsrc.UserDefinedVars
}
// LastDefinition returns the line in which the variable has been defined last.
//
// Having multiple definitions is typical in the branches of "if" statements.
//
// Another typical case involves two files: the included file defines a default
// value, and the including file later overrides that value. Or the other way
// round: the including file sets a value first, and the included file then
// assigns a default value using ?=.
func (s *Scope) LastDefinition(varname string) *MkLine {
v := s.vs[varname]
if v == nil {
return nil
}
mkline := v.lastDef
if mkline != nil && mkline.IsVarassign() {
return mkline
}
return nil // See NewPackage and G.Pkgsrc.UserDefinedVars
}
// Commented returns whether the variable has only been defined in commented
// variable assignments. These are ignored by bmake but used heavily in
// mk/defaults/mk.conf for documentation.
func (s *Scope) Commented(varname string) *MkLine {
v := s.vs[varname]
if v == nil {
return nil
}
mklines := make([]*MkLine, 0, 2)
if v.firstDef != nil {
mklines = append(mklines, v.firstDef)
}
if v.lastDef != nil {
mklines = append(mklines, v.lastDef)
}
for _, mkline := range mklines {
if mkline.IsVarassign() {
return nil
}
}
for _, mkline := range mklines {
if mkline.IsCommentedVarassign() {
return mkline
}
}
return nil
}
func (s *Scope) FirstUse(varname string) *MkLine {
v := s.vs[varname]
if v == nil {
return nil
}
return v.used
}
// LastValue returns the value from the last variable definition.
//
// If an empty string is returned, this can mean either that the
// variable value is indeed the empty string or that the variable
// was not found, or that the variable value cannot be determined
// reliably. To distinguish these cases, call LastValueFound instead.
func (s *Scope) LastValue(varname string) string {
value, _, _ := s.LastValueFound(varname)
return value
}
func (s *Scope) LastValueFound(varname string) (value string, found bool, indeterminate bool) {
v := s.vs[varname]
if v == nil {
return
}
value = v.value
found = v.firstDef != nil && v.firstDef.IsVarassign()
indeterminate = v.indeterminate
if found {
return
}
return v.fallback, v.fallback != "", v.indeterminate
}
func (s *Scope) DefineAll(other *Scope) {
for _, varname := range other.varnames() {
v := other.vs[varname]
if v.firstDef != nil {
s.Define(varname, v.firstDef)
s.Define(varname, v.lastDef)
}
}
}
func (s *Scope) forEach(action func(varname string, data *scopeVar)) {
var keys []string
for key := range s.vs {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
action(key, s.vs[key])
}
}
|