1package hclsyntax 2 3import ( 4 "bytes" 5 "fmt" 6 7 "github.com/hashicorp/hcl2/hcl" 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/convert" 10) 11 12type TemplateExpr struct { 13 Parts []Expression 14 15 SrcRange hcl.Range 16} 17 18func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) { 19 for _, part := range e.Parts { 20 w(part) 21 } 22} 23 24func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 25 buf := &bytes.Buffer{} 26 var diags hcl.Diagnostics 27 isKnown := true 28 29 for _, part := range e.Parts { 30 partVal, partDiags := part.Value(ctx) 31 diags = append(diags, partDiags...) 32 33 if partVal.IsNull() { 34 diags = append(diags, &hcl.Diagnostic{ 35 Severity: hcl.DiagError, 36 Summary: "Invalid template interpolation value", 37 Detail: fmt.Sprintf( 38 "The expression result is null. Cannot include a null value in a string template.", 39 ), 40 Subject: part.Range().Ptr(), 41 Context: &e.SrcRange, 42 Expression: part, 43 EvalContext: ctx, 44 }) 45 continue 46 } 47 48 if !partVal.IsKnown() { 49 // If any part is unknown then the result as a whole must be 50 // unknown too. We'll keep on processing the rest of the parts 51 // anyway, because we want to still emit any diagnostics resulting 52 // from evaluating those. 53 isKnown = false 54 continue 55 } 56 57 strVal, err := convert.Convert(partVal, cty.String) 58 if err != nil { 59 diags = append(diags, &hcl.Diagnostic{ 60 Severity: hcl.DiagError, 61 Summary: "Invalid template interpolation value", 62 Detail: fmt.Sprintf( 63 "Cannot include the given value in a string template: %s.", 64 err.Error(), 65 ), 66 Subject: part.Range().Ptr(), 67 Context: &e.SrcRange, 68 Expression: part, 69 EvalContext: ctx, 70 }) 71 continue 72 } 73 74 buf.WriteString(strVal.AsString()) 75 } 76 77 if !isKnown { 78 return cty.UnknownVal(cty.String), diags 79 } 80 81 return cty.StringVal(buf.String()), diags 82} 83 84func (e *TemplateExpr) Range() hcl.Range { 85 return e.SrcRange 86} 87 88func (e *TemplateExpr) StartRange() hcl.Range { 89 return e.Parts[0].StartRange() 90} 91 92// IsStringLiteral returns true if and only if the template consists only of 93// single string literal, as would be created for a simple quoted string like 94// "foo". 95// 96// If this function returns true, then calling Value on the same expression 97// with a nil EvalContext will return the literal value. 98// 99// Note that "${"foo"}", "${1}", etc aren't considered literal values for the 100// purposes of this method, because the intent of this method is to identify 101// situations where the user seems to be explicitly intending literal string 102// interpretation, not situations that result in literals as a technicality 103// of the template expression unwrapping behavior. 104func (e *TemplateExpr) IsStringLiteral() bool { 105 if len(e.Parts) != 1 { 106 return false 107 } 108 _, ok := e.Parts[0].(*LiteralValueExpr) 109 return ok 110} 111 112// TemplateJoinExpr is used to convert tuples of strings produced by template 113// constructs (i.e. for loops) into flat strings, by converting the values 114// tos strings and joining them. This AST node is not used directly; it's 115// produced as part of the AST of a "for" loop in a template. 116type TemplateJoinExpr struct { 117 Tuple Expression 118} 119 120func (e *TemplateJoinExpr) walkChildNodes(w internalWalkFunc) { 121 w(e.Tuple) 122} 123 124func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 125 tuple, diags := e.Tuple.Value(ctx) 126 127 if tuple.IsNull() { 128 // This indicates a bug in the code that constructed the AST. 129 panic("TemplateJoinExpr got null tuple") 130 } 131 if tuple.Type() == cty.DynamicPseudoType { 132 return cty.UnknownVal(cty.String), diags 133 } 134 if !tuple.Type().IsTupleType() { 135 // This indicates a bug in the code that constructed the AST. 136 panic("TemplateJoinExpr got non-tuple tuple") 137 } 138 if !tuple.IsKnown() { 139 return cty.UnknownVal(cty.String), diags 140 } 141 142 buf := &bytes.Buffer{} 143 it := tuple.ElementIterator() 144 for it.Next() { 145 _, val := it.Element() 146 147 if val.IsNull() { 148 diags = append(diags, &hcl.Diagnostic{ 149 Severity: hcl.DiagError, 150 Summary: "Invalid template interpolation value", 151 Detail: fmt.Sprintf( 152 "An iteration result is null. Cannot include a null value in a string template.", 153 ), 154 Subject: e.Range().Ptr(), 155 Expression: e, 156 EvalContext: ctx, 157 }) 158 continue 159 } 160 if val.Type() == cty.DynamicPseudoType { 161 return cty.UnknownVal(cty.String), diags 162 } 163 strVal, err := convert.Convert(val, cty.String) 164 if err != nil { 165 diags = append(diags, &hcl.Diagnostic{ 166 Severity: hcl.DiagError, 167 Summary: "Invalid template interpolation value", 168 Detail: fmt.Sprintf( 169 "Cannot include one of the interpolation results into the string template: %s.", 170 err.Error(), 171 ), 172 Subject: e.Range().Ptr(), 173 Expression: e, 174 EvalContext: ctx, 175 }) 176 continue 177 } 178 if !val.IsKnown() { 179 return cty.UnknownVal(cty.String), diags 180 } 181 182 buf.WriteString(strVal.AsString()) 183 } 184 185 return cty.StringVal(buf.String()), diags 186} 187 188func (e *TemplateJoinExpr) Range() hcl.Range { 189 return e.Tuple.Range() 190} 191 192func (e *TemplateJoinExpr) StartRange() hcl.Range { 193 return e.Tuple.StartRange() 194} 195 196// TemplateWrapExpr is used instead of a TemplateExpr when a template 197// consists _only_ of a single interpolation sequence. In that case, the 198// template's result is the single interpolation's result, verbatim with 199// no type conversions. 200type TemplateWrapExpr struct { 201 Wrapped Expression 202 203 SrcRange hcl.Range 204} 205 206func (e *TemplateWrapExpr) walkChildNodes(w internalWalkFunc) { 207 w(e.Wrapped) 208} 209 210func (e *TemplateWrapExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 211 return e.Wrapped.Value(ctx) 212} 213 214func (e *TemplateWrapExpr) Range() hcl.Range { 215 return e.SrcRange 216} 217 218func (e *TemplateWrapExpr) StartRange() hcl.Range { 219 return e.SrcRange 220} 221