From c3f2300e8099f11226223189c74d2bdf8783db51 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 18 Nov 2021 14:35:49 -0500 Subject: [PATCH] splat expression on unknown should be unknown When a splat expression is used to upgrade an unknown value to a list with a single value, the result must be entirely unknown. For example, if `ukstr` is an unknown string value, then `unkstr.*` could either result in a list of 1 string value, or an empty list, depending on whether the result is a string or null. --- hclsyntax/expression.go | 15 ++++++++++++++- hclsyntax/expression_test.go | 14 +++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/hclsyntax/expression.go b/hclsyntax/expression.go index df615281..72a2e6d9 100644 --- a/hclsyntax/expression.go +++ b/hclsyntax/expression.go @@ -1432,9 +1432,22 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { return cty.DynamicVal, diags } + upgradedUnknown := false if autoUpgrade { + // If we're upgrading an unknown value to a tuple/list, the result + // cannot be known. Otherwise a tuple containing an unknown value will + // upgrade to a different number of elements depending on whether + // sourceVal becomes null or not. + // We record this condition here so we can process any remaining + // expression after the * to derive the correct type. For example, it + // is valid to use a splat on a single object to retrieve a list of a + // single attribute, which means the final expression type still needs + // to be determined. + upgradedUnknown = !sourceVal.IsKnown() + sourceVal = cty.TupleVal([]cty.Value{sourceVal}) sourceTy = sourceVal.Type() + } // We'll compute our result type lazily if we need it. In the normal case @@ -1499,7 +1512,7 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { } e.Item.clearValue(ctx) // clean up our temporary value - if !isKnown { + if !isKnown || upgradedUnknown { // We'll ingore the resultTy diagnostics in this case since they // will just be the same errors we saw while iterating above. ty, _ := resultTy() diff --git a/hclsyntax/expression_test.go b/hclsyntax/expression_test.go index 10e924ed..c1f4c6fe 100644 --- a/hclsyntax/expression_test.go +++ b/hclsyntax/expression_test.go @@ -1135,6 +1135,16 @@ upper( }), 0, }, + { + `unkstr[*]`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "unkstr": cty.UnknownVal(cty.String), + }, + }, + cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), + 0, + }, { `unkstr.*.name`, &hcl.EvalContext{ @@ -1164,9 +1174,7 @@ upper( })), }, }, - cty.TupleVal([]cty.Value{ - cty.UnknownVal(cty.String), - }), + cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), 0, }, {