diff --git a/hclsyntax/expression_template.go b/hclsyntax/expression_template.go index ff9a6e5..0b7e07a 100644 --- a/hclsyntax/expression_template.go +++ b/hclsyntax/expression_template.go @@ -152,6 +152,8 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti return cty.UnknownVal(cty.String), diags } + tuple, marks := tuple.Unmark() + allMarks := []cty.ValueMarks{marks} buf := &bytes.Buffer{} it := tuple.ElementIterator() for it.Next() { @@ -171,7 +173,7 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti continue } if val.Type() == cty.DynamicPseudoType { - return cty.UnknownVal(cty.String), diags + return cty.UnknownVal(cty.String).WithMarks(marks), diags } strVal, err := convert.Convert(val, cty.String) if err != nil { @@ -189,13 +191,17 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti continue } if !val.IsKnown() { - return cty.UnknownVal(cty.String), diags + return cty.UnknownVal(cty.String).WithMarks(marks), diags } + strVal, strValMarks := strVal.Unmark() + if len(strValMarks) > 0 { + allMarks = append(allMarks, strValMarks) + } buf.WriteString(strVal.AsString()) } - return cty.StringVal(buf.String()), diags + return cty.StringVal(buf.String()).WithMarks(allMarks...), diags } func (e *TemplateJoinExpr) Range() hcl.Range { diff --git a/hclsyntax/expression_template_test.go b/hclsyntax/expression_template_test.go index c92157b..ef830cb 100644 --- a/hclsyntax/expression_template_test.go +++ b/hclsyntax/expression_template_test.go @@ -316,6 +316,48 @@ trim`, cty.StringVal(`Authenticate with "my voice is my passport"`).WithMarks(cty.NewValueMarks("sensitive")), 0, }, + { // can loop over marked collections + `%{ for s in secrets }${s}%{ endfor }`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "secrets": cty.ListVal([]cty.Value{ + cty.StringVal("foo"), + cty.StringVal("bar"), + cty.StringVal("baz"), + }).Mark("sensitive"), + }, + }, + cty.StringVal("foobarbaz").Mark("sensitive"), + 0, + }, + { // marks on individual elements propagate to the result + `%{ for s in secrets }${s}%{ endfor }`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "secrets": cty.ListVal([]cty.Value{ + cty.StringVal("foo"), + cty.StringVal("bar").Mark("sensitive"), + cty.StringVal("baz"), + }), + }, + }, + cty.StringVal("foobarbaz").Mark("sensitive"), + 0, + }, + { // lots of marks! + `%{ for s in secrets }${s}%{ endfor }`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "secrets": cty.ListVal([]cty.Value{ + cty.StringVal("foo").Mark("x"), + cty.StringVal("bar").Mark("y"), + cty.StringVal("baz").Mark("z"), + }).Mark("x"), // second instance of x + }, + }, + cty.StringVal("foobarbaz").WithMarks(cty.NewValueMarks("x", "y", "z")), + 0, + }, } for _, test := range tests {