github.com/hashicorp/hcl/v2@v2.20.0/traversal.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcl 5 6 import ( 7 "fmt" 8 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 // A Traversal is a description of traversing through a value through a 13 // series of operations such as attribute lookup, index lookup, etc. 14 // 15 // It is used to look up values in scopes, for example. 16 // 17 // The traversal operations are implementations of interface Traverser. 18 // This is a closed set of implementations, so the interface cannot be 19 // implemented from outside this package. 20 // 21 // A traversal can be absolute (its first value is a symbol name) or relative 22 // (starts from an existing value). 23 type Traversal []Traverser 24 25 // TraversalJoin appends a relative traversal to an absolute traversal to 26 // produce a new absolute traversal. 27 func TraversalJoin(abs Traversal, rel Traversal) Traversal { 28 if abs.IsRelative() { 29 panic("first argument to TraversalJoin must be absolute") 30 } 31 if !rel.IsRelative() { 32 panic("second argument to TraversalJoin must be relative") 33 } 34 35 ret := make(Traversal, len(abs)+len(rel)) 36 copy(ret, abs) 37 copy(ret[len(abs):], rel) 38 return ret 39 } 40 41 // TraverseRel applies the receiving traversal to the given value, returning 42 // the resulting value. This is supported only for relative traversals, 43 // and will panic if applied to an absolute traversal. 44 func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { 45 if !t.IsRelative() { 46 panic("can't use TraverseRel on an absolute traversal") 47 } 48 49 current := val 50 var diags Diagnostics 51 for _, tr := range t { 52 var newDiags Diagnostics 53 current, newDiags = tr.TraversalStep(current) 54 diags = append(diags, newDiags...) 55 if newDiags.HasErrors() { 56 return cty.DynamicVal, diags 57 } 58 } 59 return current, diags 60 } 61 62 // TraverseAbs applies the receiving traversal to the given eval context, 63 // returning the resulting value. This is supported only for absolute 64 // traversals, and will panic if applied to a relative traversal. 65 func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { 66 if t.IsRelative() { 67 panic("can't use TraverseAbs on a relative traversal") 68 } 69 70 split := t.SimpleSplit() 71 root := split.Abs[0].(TraverseRoot) 72 name := root.Name 73 74 thisCtx := ctx 75 hasNonNil := false 76 for thisCtx != nil { 77 if thisCtx.Variables == nil { 78 thisCtx = thisCtx.parent 79 continue 80 } 81 hasNonNil = true 82 val, exists := thisCtx.Variables[name] 83 if exists { 84 return split.Rel.TraverseRel(val) 85 } 86 thisCtx = thisCtx.parent 87 } 88 89 if !hasNonNil { 90 return cty.DynamicVal, Diagnostics{ 91 { 92 Severity: DiagError, 93 Summary: "Variables not allowed", 94 Detail: "Variables may not be used here.", 95 Subject: &root.SrcRange, 96 }, 97 } 98 } 99 100 suggestions := make([]string, 0, len(ctx.Variables)) 101 thisCtx = ctx 102 for thisCtx != nil { 103 for k := range thisCtx.Variables { 104 suggestions = append(suggestions, k) 105 } 106 thisCtx = thisCtx.parent 107 } 108 suggestion := nameSuggestion(name, suggestions) 109 if suggestion != "" { 110 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 111 } 112 113 return cty.DynamicVal, Diagnostics{ 114 { 115 Severity: DiagError, 116 Summary: "Unknown variable", 117 Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion), 118 Subject: &root.SrcRange, 119 }, 120 } 121 } 122 123 // IsRelative returns true if the receiver is a relative traversal, or false 124 // otherwise. 125 func (t Traversal) IsRelative() bool { 126 if len(t) == 0 { 127 return true 128 } 129 if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot { 130 return false 131 } 132 return true 133 } 134 135 // SimpleSplit returns a TraversalSplit where the name lookup is the absolute 136 // part and the remainder is the relative part. Supported only for 137 // absolute traversals, and will panic if applied to a relative traversal. 138 // 139 // This can be used by applications that have a relatively-simple variable 140 // namespace where only the top-level is directly populated in the scope, with 141 // everything else handled by relative lookups from those initial values. 142 func (t Traversal) SimpleSplit() TraversalSplit { 143 if t.IsRelative() { 144 panic("can't use SimpleSplit on a relative traversal") 145 } 146 return TraversalSplit{ 147 Abs: t[0:1], 148 Rel: t[1:], 149 } 150 } 151 152 // RootName returns the root name for a absolute traversal. Will panic if 153 // called on a relative traversal. 154 func (t Traversal) RootName() string { 155 if t.IsRelative() { 156 panic("can't use RootName on a relative traversal") 157 158 } 159 return t[0].(TraverseRoot).Name 160 } 161 162 // SourceRange returns the source range for the traversal. 163 func (t Traversal) SourceRange() Range { 164 if len(t) == 0 { 165 // Nothing useful to return here, but we'll return something 166 // that's correctly-typed at least. 167 return Range{} 168 } 169 170 return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange()) 171 } 172 173 // TraversalSplit represents a pair of traversals, the first of which is 174 // an absolute traversal and the second of which is relative to the first. 175 // 176 // This is used by calling applications that only populate prefixes of the 177 // traversals in the scope, with Abs representing the part coming from the 178 // scope and Rel representing the remaining steps once that part is 179 // retrieved. 180 type TraversalSplit struct { 181 Abs Traversal 182 Rel Traversal 183 } 184 185 // TraverseAbs traverses from a scope to the value resulting from the 186 // absolute traversal. 187 func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { 188 return t.Abs.TraverseAbs(ctx) 189 } 190 191 // TraverseRel traverses from a given value, assumed to be the result of 192 // TraverseAbs on some scope, to a final result for the entire split traversal. 193 func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { 194 return t.Rel.TraverseRel(val) 195 } 196 197 // Traverse is a convenience function to apply TraverseAbs followed by 198 // TraverseRel. 199 func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) { 200 v1, diags := t.TraverseAbs(ctx) 201 if diags.HasErrors() { 202 return cty.DynamicVal, diags 203 } 204 v2, newDiags := t.TraverseRel(v1) 205 diags = append(diags, newDiags...) 206 return v2, diags 207 } 208 209 // Join concatenates together the Abs and Rel parts to produce a single 210 // absolute traversal. 211 func (t TraversalSplit) Join() Traversal { 212 return TraversalJoin(t.Abs, t.Rel) 213 } 214 215 // RootName returns the root name for the absolute part of the split. 216 func (t TraversalSplit) RootName() string { 217 return t.Abs.RootName() 218 } 219 220 // A Traverser is a step within a Traversal. 221 type Traverser interface { 222 TraversalStep(cty.Value) (cty.Value, Diagnostics) 223 SourceRange() Range 224 isTraverserSigil() isTraverser 225 } 226 227 // Embed this in a struct to declare it as a Traverser 228 type isTraverser struct { 229 } 230 231 func (tr isTraverser) isTraverserSigil() isTraverser { 232 return isTraverser{} 233 } 234 235 // TraverseRoot looks up a root name in a scope. It is used as the first step 236 // of an absolute Traversal, and cannot itself be traversed directly. 237 type TraverseRoot struct { 238 isTraverser 239 Name string 240 SrcRange Range 241 } 242 243 // TraversalStep on a TraverseName immediately panics, because absolute 244 // traversals cannot be directly traversed. 245 func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) { 246 panic("Cannot traverse an absolute traversal") 247 } 248 249 func (tn TraverseRoot) SourceRange() Range { 250 return tn.SrcRange 251 } 252 253 // TraverseAttr looks up an attribute in its initial value. 254 type TraverseAttr struct { 255 isTraverser 256 Name string 257 SrcRange Range 258 } 259 260 func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { 261 return GetAttr(val, tn.Name, &tn.SrcRange) 262 } 263 264 func (tn TraverseAttr) SourceRange() Range { 265 return tn.SrcRange 266 } 267 268 // TraverseIndex applies the index operation to its initial value. 269 type TraverseIndex struct { 270 isTraverser 271 Key cty.Value 272 SrcRange Range 273 } 274 275 func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { 276 return Index(val, tn.Key, &tn.SrcRange) 277 } 278 279 func (tn TraverseIndex) SourceRange() Range { 280 return tn.SrcRange 281 } 282 283 // TraverseSplat applies the splat operation to its initial value. 284 type TraverseSplat struct { 285 isTraverser 286 Each Traversal 287 SrcRange Range 288 } 289 290 func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { 291 panic("TraverseSplat not yet implemented") 292 } 293 294 func (tn TraverseSplat) SourceRange() Range { 295 return tn.SrcRange 296 }