forked from hashicorp/hcl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ops.go
288 lines (262 loc) · 8.19 KB
/
ops.go
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
package hcl
import (
"fmt"
"math/big"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// Index is a helper function that performs the same operation as the index
// operator in the HCL expression language. That is, the result is the
// same as it would be for collection[key] in a configuration expression.
//
// This is exported so that applications can perform indexing in a manner
// consistent with how the language does it, including handling of null and
// unknown values, etc.
//
// Diagnostics are produced if the given combination of values is not valid.
// Therefore a pointer to a source range must be provided to use in diagnostics,
// though nil can be provided if the calling application is going to
// ignore the subject of the returned diagnostics anyway.
func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) {
if collection.IsNull() {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Attempt to index null value",
Detail: "This value is null, so it does not have any indices.",
Subject: srcRange,
},
}
}
if key.IsNull() {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: "Can't use a null value as an indexing key.",
Subject: srcRange,
},
}
}
ty := collection.Type()
kty := key.Type()
if kty == cty.DynamicPseudoType || ty == cty.DynamicPseudoType {
return cty.DynamicVal, nil
}
switch {
case ty.IsListType() || ty.IsTupleType() || ty.IsMapType():
var wantType cty.Type
switch {
case ty.IsListType() || ty.IsTupleType():
wantType = cty.Number
case ty.IsMapType():
wantType = cty.String
default:
// should never happen
panic("don't know what key type we want")
}
key, keyErr := convert.Convert(key, wantType)
if keyErr != nil {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: fmt.Sprintf(
"The given key does not identify an element in this collection value: %s.",
keyErr.Error(),
),
Subject: srcRange,
},
}
}
has := collection.HasIndex(key)
if !has.IsKnown() {
if ty.IsTupleType() {
return cty.DynamicVal, nil
} else {
return cty.UnknownVal(ty.ElementType()), nil
}
}
if has.False() {
// We have a more specialized error message for the situation of
// using a fractional number to index into a sequence, because
// that will tend to happen if the user is trying to use division
// to calculate an index and not realizing that HCL does float
// division rather than integer division.
if (ty.IsListType() || ty.IsTupleType()) && key.Type().Equals(cty.Number) {
if key.IsKnown() && !key.IsNull() {
bf := key.AsBigFloat()
if _, acc := bf.Int(nil); acc != big.Exact {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: fmt.Sprintf("The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index (%g) has a fractional part.", bf),
Subject: srcRange,
},
}
}
}
}
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: "The given key does not identify an element in this collection value.",
Subject: srcRange,
},
}
}
return collection.Index(key), nil
case ty.IsObjectType():
key, keyErr := convert.Convert(key, cty.String)
if keyErr != nil {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: fmt.Sprintf(
"The given key does not identify an element in this collection value: %s.",
keyErr.Error(),
),
Subject: srcRange,
},
}
}
if !collection.IsKnown() {
return cty.DynamicVal, nil
}
if !key.IsKnown() {
return cty.DynamicVal, nil
}
attrName := key.AsString()
if !ty.HasAttribute(attrName) {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: "The given key does not identify an element in this collection value.",
Subject: srcRange,
},
}
}
return collection.GetAttr(attrName), nil
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: "This value does not have any indices.",
Subject: srcRange,
},
}
}
}
// GetAttr is a helper function that performs the same operation as the
// attribute access in the HCL expression language. That is, the result is the
// same as it would be for obj.attr in a configuration expression.
//
// This is exported so that applications can access attributes in a manner
// consistent with how the language does it, including handling of null and
// unknown values, etc.
//
// Diagnostics are produced if the given combination of values is not valid.
// Therefore a pointer to a source range must be provided to use in diagnostics,
// though nil can be provided if the calling application is going to
// ignore the subject of the returned diagnostics anyway.
func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagnostics) {
if obj.IsNull() {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Attempt to get attribute from null value",
Detail: "This value is null, so it does not have any attributes.",
Subject: srcRange,
},
}
}
ty := obj.Type()
switch {
case ty.IsObjectType():
if !ty.HasAttribute(attrName) {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Unsupported attribute",
Detail: fmt.Sprintf("This object does not have an attribute named %q.", attrName),
Subject: srcRange,
},
}
}
if !obj.IsKnown() {
return cty.UnknownVal(ty.AttributeType(attrName)), nil
}
return obj.GetAttr(attrName), nil
case ty.IsMapType():
if !obj.IsKnown() {
return cty.UnknownVal(ty.ElementType()), nil
}
idx := cty.StringVal(attrName)
if obj.HasIndex(idx).False() {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Missing map element",
Detail: fmt.Sprintf("This map does not have an element with the key %q.", attrName),
Subject: srcRange,
},
}
}
return obj.Index(idx), nil
case ty == cty.DynamicPseudoType:
return cty.DynamicVal, nil
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Unsupported attribute",
Detail: "This value does not have any attributes.",
Subject: srcRange,
},
}
}
}
// ApplyPath is a helper function that applies a cty.Path to a value using the
// indexing and attribute access operations from HCL.
//
// This is similar to calling the path's own Apply method, but ApplyPath uses
// the more relaxed typing rules that apply to these operations in HCL, rather
// than cty's relatively-strict rules. ApplyPath is implemented in terms of
// Index and GetAttr, and so it has the same behavior for individual steps
// but will stop and return any errors returned by intermediate steps.
//
// Diagnostics are produced if the given path cannot be applied to the given
// value. Therefore a pointer to a source range must be provided to use in
// diagnostics, though nil can be provided if the calling application is going
// to ignore the subject of the returned diagnostics anyway.
func ApplyPath(val cty.Value, path cty.Path, srcRange *Range) (cty.Value, Diagnostics) {
var diags Diagnostics
for _, step := range path {
var stepDiags Diagnostics
switch ts := step.(type) {
case cty.IndexStep:
val, stepDiags = Index(val, ts.Key, srcRange)
case cty.GetAttrStep:
val, stepDiags = GetAttr(val, ts.Name, srcRange)
default:
// Should never happen because the above are all of the step types.
diags = diags.Append(&Diagnostic{
Severity: DiagError,
Summary: "Invalid path step",
Detail: fmt.Sprintf("Go type %T is not a valid path step. This is a bug in this program.", step),
Subject: srcRange,
})
return cty.DynamicVal, diags
}
diags = append(diags, stepDiags...)
if stepDiags.HasErrors() {
return cty.DynamicVal, diags
}
}
return val, diags
}