diff --git a/command/format/diff.go b/command/format/diff.go index 00179da882..c2f4e7804e 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -299,12 +299,8 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config // For the sake of handling nested blocks, we'll treat a null list // the same as an empty list since the config language doesn't // distinguish these anyway. - if old.IsNull() { - old = cty.ListValEmpty(old.Type().ElementType()) - } - if new.IsNull() { - new = cty.ListValEmpty(new.Type().ElementType()) - } + old = ctyNullBlockListAsEmpty(old) + new = ctyNullBlockListAsEmpty(new) oldItems := ctyCollectionValues(old) newItems := ctyCollectionValues(new) @@ -358,12 +354,8 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config // For the sake of handling nested blocks, we'll treat a null set // the same as an empty set since the config language doesn't // distinguish these anyway. - if old.IsNull() { - old = cty.SetValEmpty(old.Type().ElementType()) - } - if new.IsNull() { - new = cty.SetValEmpty(new.Type().ElementType()) - } + old = ctyNullBlockSetAsEmpty(old) + new = ctyNullBlockSetAsEmpty(new) oldItems := ctyCollectionValues(old) newItems := ctyCollectionValues(new) @@ -1101,3 +1093,46 @@ func ctyEnsurePathCapacity(path cty.Path, minExtra int) cty.Path { copy(newPath, path) return newPath } + +// ctyNullBlockListAsEmpty either returns the given value verbatim if it is non-nil +// or returns an empty value of a suitable type to serve as a placeholder for it. +// +// In particular, this function handles the special situation where a "list" is +// actually represented as a tuple type where nested blocks contain +// dynamically-typed values. +func ctyNullBlockListAsEmpty(in cty.Value) cty.Value { + if !in.IsNull() { + return in + } + if ty := in.Type(); ty.IsListType() { + return cty.ListValEmpty(ty.ElementType()) + } + return cty.EmptyTupleVal // must need a tuple, then +} + +// ctyNullBlockMapAsEmpty either returns the given value verbatim if it is non-nil +// or returns an empty value of a suitable type to serve as a placeholder for it. +// +// In particular, this function handles the special situation where a "map" is +// actually represented as an object type where nested blocks contain +// dynamically-typed values. +func ctyNullBlockMapAsEmpty(in cty.Value) cty.Value { + if !in.IsNull() { + return in + } + if ty := in.Type(); ty.IsMapType() { + return cty.MapValEmpty(ty.ElementType()) + } + return cty.EmptyObjectVal // must need an object, then +} + +// ctyNullBlockSetAsEmpty either returns the given value verbatim if it is non-nil +// or returns an empty value of a suitable type to serve as a placeholder for it. +func ctyNullBlockSetAsEmpty(in cty.Value) cty.Value { + if !in.IsNull() { + return in + } + // Dynamically-typed attributes are not supported inside blocks backed by + // sets, so our result here is always a set. + return cty.SetValEmpty(in.Type().ElementType()) +}