diff --git a/ext/dynblock/expand_body.go b/ext/dynblock/expand_body.go index 65a9eab2..0b68a7ae 100644 --- a/ext/dynblock/expand_body.go +++ b/ext/dynblock/expand_body.go @@ -201,28 +201,14 @@ func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, } } } else { - // If our top-level iteration value isn't known then we're forced - // to compromise since HCL doesn't have any concept of an - // "unknown block". In this case then, we'll produce a single - // dynamic block with the iterator values set to DynamicVal, - // which at least makes the potential for a block visible - // in our result, even though it's not represented in a fully-accurate - // way. + // If our top-level iteration value isn't known then we + // substitute an unknownBody, which will cause the entire block + // to evaluate to an unknown value. i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal) block, blockDiags := spec.newBlock(i, b.forEachCtx) diags = append(diags, blockDiags...) if block != nil { - block.Body = b.expandChild(block.Body, i) - - // We additionally force all of the leaf attribute values - // in the result to be unknown so the calling application - // can, if necessary, use that as a heuristic to detect - // when a single nested block might be standing in for - // multiple blocks yet to be expanded. This retains the - // structure of the generated body but forces all of its - // leaf attribute values to be unknown. - block.Body = unknownBody{block.Body} - + block.Body = unknownBody{b.expandChild(block.Body, i)} blocks = append(blocks, block) } } diff --git a/ext/dynblock/expand_body_test.go b/ext/dynblock/expand_body_test.go index 9d574053..a6544526 100644 --- a/ext/dynblock/expand_body_test.go +++ b/ext/dynblock/expand_body_test.go @@ -1,6 +1,7 @@ package dynblock import ( + "strings" "testing" "github.com/hashicorp/hcl/v2" @@ -441,6 +442,28 @@ func TestExpandUnknownBodies(t *testing.T) { }, }), }, + { + Type: "dynamic", + Labels: []string{"invalid_list"}, + LabelRanges: []hcl.Range{hcl.Range{}}, + Body: hcltest.MockBody(&hcl.BodyContent{ + Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ + "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), + }), + Blocks: hcl.Blocks{ + { + Type: "content", + Body: hcltest.MockBody(&hcl.BodyContent{ + Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ + "val": hcltest.MockExprTraversalSrc("each.value"), + // unexpected attributes should still produce an error + "invalid": hcltest.MockExprLiteral(cty.StringVal("static")), + }), + }), + }, + }, + }), + }, }, } @@ -574,4 +597,27 @@ func TestExpandUnknownBodies(t *testing.T) { } }) + t.Run("DecodeInvalidList", func(t *testing.T) { + decSpec := &hcldec.BlockListSpec{ + TypeName: "invalid_list", + Nested: &hcldec.ObjectSpec{ + "val": &hcldec.AttrSpec{ + Name: "val", + Type: cty.String, + }, + }, + } + + _, _, diags := hcldec.PartialDecode(dynBody, decSpec, nil) + if len(diags) != 1 { + t.Error("expected 1 extraneous argument") + } + + want := `Mock body has extraneous argument "invalid"` + + if !strings.Contains(diags.Error(), want) { + t.Errorf("unexpected diagnostics: %v", diags) + } + }) + } diff --git a/hcldec/spec.go b/hcldec/spec.go index afc06b67..42cb070d 100644 --- a/hcldec/spec.go +++ b/hcldec/spec.go @@ -468,6 +468,9 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe continue } + val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) + diags = append(diags, childDiags...) + if u, ok := childBlock.Body.(UnknownBody); ok { if u.Unknown() { // If any block Body is unknown, then the entire block value @@ -476,8 +479,6 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe } } - val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) - diags = append(diags, childDiags...) elems = append(elems, val) sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) } @@ -629,6 +630,9 @@ func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLab continue } + val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) + diags = append(diags, childDiags...) + if u, ok := childBlock.Body.(UnknownBody); ok { if u.Unknown() { // If any block Body is unknown, then the entire block value @@ -637,8 +641,6 @@ func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLab } } - val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) - diags = append(diags, childDiags...) elems = append(elems, val) sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) } @@ -751,6 +753,9 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel continue } + val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) + diags = append(diags, childDiags...) + if u, ok := childBlock.Body.(UnknownBody); ok { if u.Unknown() { // If any block Body is unknown, then the entire block value @@ -759,8 +764,6 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel } } - val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) - diags = append(diags, childDiags...) elems = append(elems, val) sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) }