6 "github.com/zclconf/go-cty/cty"
9 // A Traversal is a description of traversing through a value through a
10 // series of operations such as attribute lookup, index lookup, etc.
12 // It is used to look up values in scopes, for example.
14 // The traversal operations are implementations of interface Traverser.
15 // This is a closed set of implementations, so the interface cannot be
16 // implemented from outside this package.
18 // A traversal can be absolute (its first value is a symbol name) or relative
19 // (starts from an existing value).
20 type Traversal []Traverser
22 // TraversalJoin appends a relative traversal to an absolute traversal to
23 // produce a new absolute traversal.
24 func TraversalJoin(abs Traversal, rel Traversal) Traversal {
26 panic("first argument to TraversalJoin must be absolute")
28 if !rel.IsRelative() {
29 panic("second argument to TraversalJoin must be relative")
32 ret := make(Traversal, len(abs)+len(rel))
34 copy(ret[len(abs):], rel)
38 // TraverseRel applies the receiving traversal to the given value, returning
39 // the resulting value. This is supported only for relative traversals,
40 // and will panic if applied to an absolute traversal.
41 func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) {
43 panic("can't use TraverseRel on an absolute traversal")
48 for _, tr := range t {
49 var newDiags Diagnostics
50 current, newDiags = tr.TraversalStep(current)
51 diags = append(diags, newDiags...)
52 if newDiags.HasErrors() {
53 return cty.DynamicVal, diags
59 // TraverseAbs applies the receiving traversal to the given eval context,
60 // returning the resulting value. This is supported only for absolute
61 // traversals, and will panic if applied to a relative traversal.
62 func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) {
64 panic("can't use TraverseAbs on a relative traversal")
67 split := t.SimpleSplit()
68 root := split.Abs[0].(TraverseRoot)
74 if thisCtx.Variables == nil {
75 thisCtx = thisCtx.parent
79 val, exists := thisCtx.Variables[name]
81 return split.Rel.TraverseRel(val)
83 thisCtx = thisCtx.parent
87 return cty.DynamicVal, Diagnostics{
90 Summary: "Variables not allowed",
91 Detail: "Variables may not be used here.",
92 Subject: &root.SrcRange,
97 suggestions := make([]string, 0, len(ctx.Variables))
100 for k := range thisCtx.Variables {
101 suggestions = append(suggestions, k)
103 thisCtx = thisCtx.parent
105 suggestion := nameSuggestion(name, suggestions)
106 if suggestion != "" {
107 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
110 return cty.DynamicVal, Diagnostics{
113 Summary: "Unknown variable",
114 Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion),
115 Subject: &root.SrcRange,
120 // IsRelative returns true if the receiver is a relative traversal, or false
122 func (t Traversal) IsRelative() bool {
126 if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot {
132 // SimpleSplit returns a TraversalSplit where the name lookup is the absolute
133 // part and the remainder is the relative part. Supported only for
134 // absolute traversals, and will panic if applied to a relative traversal.
136 // This can be used by applications that have a relatively-simple variable
137 // namespace where only the top-level is directly populated in the scope, with
138 // everything else handled by relative lookups from those initial values.
139 func (t Traversal) SimpleSplit() TraversalSplit {
141 panic("can't use SimpleSplit on a relative traversal")
143 return TraversalSplit{
149 // RootName returns the root name for a absolute traversal. Will panic if
150 // called on a relative traversal.
151 func (t Traversal) RootName() string {
153 panic("can't use RootName on a relative traversal")
156 return t[0].(TraverseRoot).Name
159 // SourceRange returns the source range for the traversal.
160 func (t Traversal) SourceRange() Range {
162 // Nothing useful to return here, but we'll return something
163 // that's correctly-typed at least.
167 return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange())
170 // TraversalSplit represents a pair of traversals, the first of which is
171 // an absolute traversal and the second of which is relative to the first.
173 // This is used by calling applications that only populate prefixes of the
174 // traversals in the scope, with Abs representing the part coming from the
175 // scope and Rel representing the remaining steps once that part is
177 type TraversalSplit struct {
182 // TraverseAbs traverses from a scope to the value resulting from the
183 // absolute traversal.
184 func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) {
185 return t.Abs.TraverseAbs(ctx)
188 // TraverseRel traverses from a given value, assumed to be the result of
189 // TraverseAbs on some scope, to a final result for the entire split traversal.
190 func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) {
191 return t.Rel.TraverseRel(val)
194 // Traverse is a convenience function to apply TraverseAbs followed by
196 func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) {
197 v1, diags := t.TraverseAbs(ctx)
198 if diags.HasErrors() {
199 return cty.DynamicVal, diags
201 v2, newDiags := t.TraverseRel(v1)
202 diags = append(diags, newDiags...)
206 // Join concatenates together the Abs and Rel parts to produce a single
207 // absolute traversal.
208 func (t TraversalSplit) Join() Traversal {
209 return TraversalJoin(t.Abs, t.Rel)
212 // RootName returns the root name for the absolute part of the split.
213 func (t TraversalSplit) RootName() string {
214 return t.Abs.RootName()
217 // A Traverser is a step within a Traversal.
218 type Traverser interface {
219 TraversalStep(cty.Value) (cty.Value, Diagnostics)
221 isTraverserSigil() isTraverser
224 // Embed this in a struct to declare it as a Traverser
225 type isTraverser struct {
228 func (tr isTraverser) isTraverserSigil() isTraverser {
232 // TraverseRoot looks up a root name in a scope. It is used as the first step
233 // of an absolute Traversal, and cannot itself be traversed directly.
234 type TraverseRoot struct {
240 // TraversalStep on a TraverseName immediately panics, because absolute
241 // traversals cannot be directly traversed.
242 func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) {
243 panic("Cannot traverse an absolute traversal")
246 func (tn TraverseRoot) SourceRange() Range {
250 // TraverseAttr looks up an attribute in its initial value.
251 type TraverseAttr struct {
257 func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
259 return cty.DynamicVal, Diagnostics{
262 Summary: "Attempt to get attribute from null value",
263 Detail: "This value is null, so it does not have any attributes.",
264 Subject: &tn.SrcRange,
271 case ty.IsObjectType():
272 if !ty.HasAttribute(tn.Name) {
273 return cty.DynamicVal, Diagnostics{
276 Summary: "Unsupported attribute",
277 Detail: fmt.Sprintf("This object does not have an attribute named %q.", tn.Name),
278 Subject: &tn.SrcRange,
284 return cty.UnknownVal(ty.AttributeType(tn.Name)), nil
287 return val.GetAttr(tn.Name), nil
290 return cty.UnknownVal(ty.ElementType()), nil
293 idx := cty.StringVal(tn.Name)
294 if val.HasIndex(idx).False() {
295 return cty.DynamicVal, Diagnostics{
298 Summary: "Missing map element",
299 Detail: fmt.Sprintf("This map does not have an element with the key %q.", tn.Name),
300 Subject: &tn.SrcRange,
305 return val.Index(idx), nil
306 case ty == cty.DynamicPseudoType:
307 return cty.DynamicVal, nil
309 return cty.DynamicVal, Diagnostics{
312 Summary: "Unsupported attribute",
313 Detail: "This value does not have any attributes.",
314 Subject: &tn.SrcRange,
320 func (tn TraverseAttr) SourceRange() Range {
324 // TraverseIndex applies the index operation to its initial value.
325 type TraverseIndex struct {
331 func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
332 return Index(val, tn.Key, &tn.SrcRange)
335 func (tn TraverseIndex) SourceRange() Range {
339 // TraverseSplat applies the splat operation to its initial value.
340 type TraverseSplat struct {
346 func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
347 panic("TraverseSplat not yet implemented")
350 func (tn TraverseSplat) SourceRange() Range {