Skip to content

Commit

Permalink
hcldec.UnknownBody
Browse files Browse the repository at this point in the history
Allow unknown block bodies during decoding
  • Loading branch information
jbardin committed Apr 15, 2021
1 parent 805c25b commit 8cb01b6
Showing 1 changed file with 125 additions and 90 deletions.
215 changes: 125 additions & 90 deletions hcldec/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ type specNeedingVariables interface {
variablesNeeded(content *hcl.BodyContent) []hcl.Traversal
}

// UnknownBody can be optionally implemented by an hcl.Body instance which may
// be entirely unknown.
type UnknownBody interface {
Unknown() bool
}

func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) {
for _, c := range s {
cb(c)
Expand Down Expand Up @@ -462,6 +468,14 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe
continue
}

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
// must be unknown
return cty.UnknownVal(s.impliedType()), diags
}
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)
elems = append(elems, val)
Expand All @@ -484,53 +498,49 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe
})
}

var ret cty.Value

if len(elems) == 0 {
ret = cty.ListValEmpty(s.Nested.impliedType())
} else {
// Since our target is a list, all of the decoded elements must have the
// same type or cty.ListVal will panic below. Different types can arise
// if there is an attribute spec of type cty.DynamicPseudoType in the
// nested spec; all given values must be convertable to a single type
// in order for the result to be considered valid.
etys := make([]cty.Type, len(elems))
for i, v := range elems {
etys[i] = v.Type()
}
ety, convs := convert.UnifyUnsafe(etys)
if ety == cty.NilType {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: "Corresponding attributes in all blocks of this type must be the same.",
Subject: &sourceRanges[0],
})
return cty.DynamicVal, diags
}
for i, v := range elems {
if convs[i] != nil {
newV, err := convs[i](v)
if err != nil {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
Subject: &sourceRanges[i],
})
// Bail early here so we won't panic below in cty.ListVal
return cty.DynamicVal, diags
}
elems[i] = newV
return cty.ListValEmpty(s.Nested.impliedType()), diags
}

// Since our target is a list, all of the decoded elements must have the
// same type or cty.ListVal will panic below. Different types can arise
// if there is an attribute spec of type cty.DynamicPseudoType in the
// nested spec; all given values must be convertable to a single type
// in order for the result to be considered valid.
etys := make([]cty.Type, len(elems))
for i, v := range elems {
etys[i] = v.Type()
}
ety, convs := convert.UnifyUnsafe(etys)
if ety == cty.NilType {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: "Corresponding attributes in all blocks of this type must be the same.",
Subject: &sourceRanges[0],
})
return cty.DynamicVal, diags
}
for i, v := range elems {
if convs[i] != nil {
newV, err := convs[i](v)
if err != nil {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
Subject: &sourceRanges[i],
})
// Bail early here so we won't panic below in cty.ListVal
return cty.DynamicVal, diags
}
elems[i] = newV
}

ret = cty.ListVal(elems)
}

return ret, diags
return cty.ListVal(elems), diags
}

func (s *BlockListSpec) impliedType() cty.Type {
Expand Down Expand Up @@ -619,6 +629,14 @@ func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
continue
}

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
// must be unknown
return cty.UnknownVal(s.impliedType()), diags
}
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)
elems = append(elems, val)
Expand All @@ -641,15 +659,11 @@ func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
})
}

var ret cty.Value

if len(elems) == 0 {
ret = cty.EmptyTupleVal
} else {
ret = cty.TupleVal(elems)
return cty.EmptyTupleVal, diags
}

return ret, diags
return cty.TupleVal(elems), diags
}

func (s *BlockTupleSpec) impliedType() cty.Type {
Expand Down Expand Up @@ -731,11 +745,20 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel

var elems []cty.Value
var sourceRanges []hcl.Range

for _, childBlock := range content.Blocks {
if childBlock.Type != s.TypeName {
continue
}

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
// must be unknown
return cty.UnknownVal(s.impliedType()), diags
}
}

val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
diags = append(diags, childDiags...)
elems = append(elems, val)
Expand All @@ -758,53 +781,49 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
})
}

var ret cty.Value

if len(elems) == 0 {
ret = cty.SetValEmpty(s.Nested.impliedType())
} else {
// Since our target is a set, all of the decoded elements must have the
// same type or cty.SetVal will panic below. Different types can arise
// if there is an attribute spec of type cty.DynamicPseudoType in the
// nested spec; all given values must be convertable to a single type
// in order for the result to be considered valid.
etys := make([]cty.Type, len(elems))
for i, v := range elems {
etys[i] = v.Type()
}
ety, convs := convert.UnifyUnsafe(etys)
if ety == cty.NilType {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: "Corresponding attributes in all blocks of this type must be the same.",
Subject: &sourceRanges[0],
})
return cty.DynamicVal, diags
}
for i, v := range elems {
if convs[i] != nil {
newV, err := convs[i](v)
if err != nil {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
Subject: &sourceRanges[i],
})
// Bail early here so we won't panic below in cty.ListVal
return cty.DynamicVal, diags
}
elems[i] = newV
return cty.SetValEmpty(s.Nested.impliedType()), diags
}

// Since our target is a set, all of the decoded elements must have the
// same type or cty.SetVal will panic below. Different types can arise
// if there is an attribute spec of type cty.DynamicPseudoType in the
// nested spec; all given values must be convertable to a single type
// in order for the result to be considered valid.
etys := make([]cty.Type, len(elems))
for i, v := range elems {
etys[i] = v.Type()
}
ety, convs := convert.UnifyUnsafe(etys)
if ety == cty.NilType {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: "Corresponding attributes in all blocks of this type must be the same.",
Subject: &sourceRanges[0],
})
return cty.DynamicVal, diags
}
for i, v := range elems {
if convs[i] != nil {
newV, err := convs[i](v)
if err != nil {
// FIXME: This is a pretty terrible error message.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
Subject: &sourceRanges[i],
})
// Bail early here so we won't panic below in cty.ListVal
return cty.DynamicVal, diags
}
elems[i] = newV
}

ret = cty.SetVal(elems)
}

return ret, diags
return cty.SetVal(elems), diags
}

func (s *BlockSetSpec) impliedType() cty.Type {
Expand Down Expand Up @@ -893,6 +912,14 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
continue
}

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
// must be unknown
return cty.UnknownVal(s.impliedType()), diags
}
}

childLabels := labelsForBlock(childBlock)
val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
targetMap := elems
Expand Down Expand Up @@ -1039,6 +1066,14 @@ func (s *BlockObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLa
continue
}

if u, ok := childBlock.Body.(UnknownBody); ok {
if u.Unknown() {
// If any block Body is unknown, then the entire block value
// must be unknown
return cty.UnknownVal(s.impliedType()), diags
}
}

childLabels := labelsForBlock(childBlock)
val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
targetMap := elems
Expand Down

0 comments on commit 8cb01b6

Please sign in to comment.