diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/block_labels.go b/vendor/github.com/hashicorp/hcl2/hcldec/block_labels.go new file mode 100644 index 000000000..7e652e9bc --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcldec/block_labels.go @@ -0,0 +1,21 @@ +package hcldec + +import ( + "github.com/hashicorp/hcl2/hcl" +) + +type blockLabel struct { + Value string + Range hcl.Range +} + +func labelsForBlock(block *hcl.Block) []blockLabel { + ret := make([]blockLabel, len(block.Labels)) + for i := range block.Labels { + ret[i] = blockLabel{ + Value: block.Labels[i], + Range: block.LabelRanges[i], + } + } + return ret +} diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/decode.go b/vendor/github.com/hashicorp/hcl2/hcldec/decode.go index b5756359c..6cf93fedd 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/decode.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/decode.go @@ -5,7 +5,7 @@ import ( "github.com/zclconf/go-cty/cty" ) -func decode(body hcl.Body, block *hcl.Block, ctx *hcl.EvalContext, spec Spec, partial bool) (cty.Value, hcl.Body, hcl.Diagnostics) { +func decode(body hcl.Body, blockLabels []blockLabel, ctx *hcl.EvalContext, spec Spec, partial bool) (cty.Value, hcl.Body, hcl.Diagnostics) { schema := ImpliedSchema(spec) var content *hcl.BodyContent @@ -18,15 +18,19 @@ func decode(body hcl.Body, block *hcl.Block, ctx *hcl.EvalContext, spec Spec, pa content, diags = body.Content(schema) } - val, valDiags := spec.decode(content, block, ctx) + val, valDiags := spec.decode(content, blockLabels, ctx) diags = append(diags, valDiags...) return val, leftovers, diags } -func sourceRange(body hcl.Body, block *hcl.Block, spec Spec) hcl.Range { +func impliedType(spec Spec) cty.Type { + return spec.impliedType() +} + +func sourceRange(body hcl.Body, blockLabels []blockLabel, spec Spec) hcl.Range { schema := ImpliedSchema(spec) content, _, _ := body.PartialContent(schema) - return spec.sourceRange(content, block) + return spec.sourceRange(content, blockLabels) } diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/gob.go b/vendor/github.com/hashicorp/hcl2/hcldec/gob.go index 31b205756..e2027cfd2 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/gob.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/gob.go @@ -19,4 +19,5 @@ func init() { gob.Register((*BlockSetSpec)(nil)) gob.Register((*BlockMapSpec)(nil)) gob.Register((*BlockLabelSpec)(nil)) + gob.Register((*DefaultSpec)(nil)) } diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/public.go b/vendor/github.com/hashicorp/hcl2/hcldec/public.go index a5f693a4a..3e58f7b3c 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/public.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/public.go @@ -26,6 +26,12 @@ func PartialDecode(body hcl.Body, spec Spec, ctx *hcl.EvalContext) (cty.Value, h return decode(body, nil, ctx, spec, true) } +// ImpliedType returns the value type that should result from decoding the +// given spec. +func ImpliedType(spec Spec) cty.Type { + return impliedType(spec) +} + // SourceRange interprets the given body using the given specification and // then returns the source range of the value that would be used to // fulfill the spec. diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/schema.go b/vendor/github.com/hashicorp/hcl2/hcldec/schema.go index fff60b120..b57bd9692 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/schema.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/schema.go @@ -14,7 +14,8 @@ func ImpliedSchema(spec Spec) *hcl.BodySchema { // visitSameBodyChildren walks through the spec structure, calling // the given callback for each descendent spec encountered. We are // interested in the specs that reference attributes and blocks. - visit := func(s Spec) { + var visit visitFunc + visit = func(s Spec) { if as, ok := s.(attrSpec); ok { attrs = append(attrs, as.attrSchemata()...) } @@ -22,10 +23,11 @@ func ImpliedSchema(spec Spec) *hcl.BodySchema { if bs, ok := s.(blockSpec); ok { blocks = append(blocks, bs.blockHeaderSchemata()...) } + + s.visitSameBodyChildren(visit) } visit(spec) - spec.visitSameBodyChildren(visit) return &hcl.BodySchema{ Attributes: attrs, diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go index b385642f9..f0e6842be 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go @@ -1,6 +1,7 @@ package hcldec import ( + "bytes" "fmt" "github.com/hashicorp/hcl2/hcl" @@ -19,7 +20,11 @@ type Spec interface { // // "block" is provided only by the nested calls performed by the spec // types that work on block bodies. - decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) + decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) + + // Return the cty.Type that should be returned when decoding a body with + // this spec. + impliedType() cty.Type // Call the given callback once for each of the nested specs that would // get decoded with the same body and block as the receiver. This should @@ -30,7 +35,7 @@ type Spec interface { // spec in the given content, in the context of the given block // (which might be null). If the corresponding item is missing, return // a place where it might be inserted. - sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range + sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range } type visitFunc func(spec Spec) @@ -61,24 +66,32 @@ func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) { } } -func (s ObjectSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s ObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { vals := make(map[string]cty.Value, len(s)) var diags hcl.Diagnostics for k, spec := range s { var kd hcl.Diagnostics - vals[k], kd = spec.decode(content, block, ctx) + vals[k], kd = spec.decode(content, blockLabels, ctx) diags = append(diags, kd...) } return cty.ObjectVal(vals), diags } -func (s ObjectSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { - if block != nil { - return block.DefRange +func (s ObjectSpec) impliedType() cty.Type { + if len(s) == 0 { + return cty.EmptyObject } + attrTypes := make(map[string]cty.Type) + for k, childSpec := range s { + attrTypes[k] = childSpec.impliedType() + } + return cty.Object(attrTypes) +} + +func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { // This is not great, but the best we can do. In practice, it's rather // strange to ask for the source range of an entire top-level body, since // that's already readily available to the caller. @@ -95,24 +108,32 @@ func (s TupleSpec) visitSameBodyChildren(cb visitFunc) { } } -func (s TupleSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s TupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { vals := make([]cty.Value, len(s)) var diags hcl.Diagnostics for i, spec := range s { var ed hcl.Diagnostics - vals[i], ed = spec.decode(content, block, ctx) + vals[i], ed = spec.decode(content, blockLabels, ctx) diags = append(diags, ed...) } return cty.TupleVal(vals), diags } -func (s TupleSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { - if block != nil { - return block.DefRange +func (s TupleSpec) impliedType() cty.Type { + if len(s) == 0 { + return cty.EmptyTuple } + attrTypes := make([]cty.Type, len(s)) + for i, childSpec := range s { + attrTypes[i] = childSpec.impliedType() + } + return cty.Tuple(attrTypes) +} + +func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { // This is not great, but the best we can do. In practice, it's rather // strange to ask for the source range of an entire top-level body, since // that's already readily available to the caller. @@ -152,7 +173,7 @@ func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema { } } -func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +func (s *AttrSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { attr, exists := content.Attributes[s.Name] if !exists { return content.MissingItemRange @@ -161,7 +182,7 @@ func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.R return attr.Expr.Range() } -func (s *AttrSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { attr, exists := content.Attributes[s.Name] if !exists { // We don't need to check required and emit a diagnostic here, because @@ -193,6 +214,10 @@ func (s *AttrSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.E return val, diags } +func (s *AttrSpec) impliedType() cty.Type { + return s.Type +} + // A LiteralSpec is a Spec that produces the given literal value, ignoring // the given body. type LiteralSpec struct { @@ -203,11 +228,15 @@ func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) { // leaf node } -func (s *LiteralSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { return s.Value, nil } -func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +func (s *LiteralSpec) impliedType() cty.Type { + return s.Value.Type() +} + +func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { // No sensible range to return for a literal, so the caller had better // ensure it doesn't cause any diagnostics. return hcl.Range{ @@ -230,11 +259,16 @@ func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { return s.Expr.Variables() } -func (s *ExprSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { return s.Expr.Value(ctx) } -func (s *ExprSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +func (s *ExprSpec) impliedType() cty.Type { + // We can't know the type of our expression until we evaluate it + return cty.DynamicPseudoType +} + +func (s *ExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { return s.Expr.Range() } @@ -258,7 +292,8 @@ func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) { func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { return []hcl.BlockHeaderSchema{ { - Type: s.TypeName, + Type: s.TypeName, + LabelNames: findLabelSpecs(s.Nested), }, } } @@ -282,7 +317,7 @@ func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { return Variables(childBlock.Body, s.Nested) } -func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s *BlockSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { var diags hcl.Diagnostics var childBlock *hcl.Block @@ -318,18 +353,22 @@ func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl. Subject: &content.MissingItemRange, }) } - return cty.NullVal(cty.DynamicPseudoType), diags + return cty.NullVal(s.Nested.impliedType()), diags } if s.Nested == nil { panic("BlockSpec with no Nested Spec") } - val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false) + val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) diags = append(diags, childDiags...) return val, diags } -func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +func (s *BlockSpec) impliedType() cty.Type { + return s.Nested.impliedType() +} + +func (s *BlockSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { var childBlock *hcl.Block for _, candidate := range content.Blocks { if candidate.Type != s.TypeName { @@ -344,7 +383,7 @@ func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl. return content.MissingItemRange } - return sourceRange(childBlock.Body, childBlock, s.Nested) + return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) } // A BlockListSpec is a Spec that produces a cty list of the results of @@ -364,10 +403,8 @@ func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) { func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { return []hcl.BlockHeaderSchema{ { - Type: s.TypeName, - // FIXME: Need to peek into s.Nested to see if it has any - // BlockLabelSpec instances, which will define then how many - // labels we need. + Type: s.TypeName, + LabelNames: findLabelSpecs(s.Nested), }, } } @@ -387,11 +424,11 @@ func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversa return ret } -func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { +func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { var diags hcl.Diagnostics if s.Nested == nil { - panic("BlockSpec with no Nested Spec") + panic("BlockListSpec with no Nested Spec") } var elems []cty.Value @@ -401,24 +438,24 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx * continue } - val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false) + 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, childBlock, s.Nested)) + sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) } if len(elems) < s.MinItems { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: fmt.Sprintf("insufficient %s blocks", s.TypeName), - Detail: fmt.Sprintf("at least %d blocks are required", s.MinItems), + Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName), + Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName), Subject: &content.MissingItemRange, }) } else if s.MaxItems > 0 && len(elems) > s.MaxItems { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: fmt.Sprintf("too many %s blocks", s.TypeName), - Detail: fmt.Sprintf("no more than %d blocks are allowed", s.MaxItems), + Summary: fmt.Sprintf("Too many %s blocks", s.TypeName), + Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName), Subject: &sourceRanges[s.MaxItems], }) } @@ -426,9 +463,7 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx * var ret cty.Value if len(elems) == 0 { - // FIXME: We don't currently have enough info to construct a type for - // an empty list, so we'll just stub it out. - ret = cty.ListValEmpty(cty.DynamicPseudoType) + ret = cty.ListValEmpty(s.Nested.impliedType()) } else { ret = cty.ListVal(elems) } @@ -436,7 +471,11 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx * return ret, diags } -func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +func (s *BlockListSpec) impliedType() cty.Type { + return cty.List(s.Nested.impliedType()) +} + +func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { // We return the source range of the _first_ block of the given type, // since they are not guaranteed to form a contiguous range. @@ -454,7 +493,7 @@ func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) return content.MissingItemRange } - return sourceRange(childBlock.Body, childBlock, s.Nested) + return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) } // A BlockSetSpec is a Spec that produces a cty set of the results of @@ -470,11 +509,83 @@ func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) { // leaf node ("Nested" does not use the same body) } -func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - panic("BlockSetSpec.decode not yet implemented") +// blockSpec implementation +func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { + return []hcl.BlockHeaderSchema{ + { + Type: s.TypeName, + LabelNames: findLabelSpecs(s.Nested), + }, + } } -func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +// specNeedingVariables implementation +func (s *BlockSetSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { + var ret []hcl.Traversal + + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + ret = append(ret, Variables(childBlock.Body, s.Nested)...) + } + + return ret +} + +func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + var diags hcl.Diagnostics + + if s.Nested == nil { + panic("BlockSetSpec with no Nested Spec") + } + + var elems []cty.Value + var sourceRanges []hcl.Range + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + 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)) + } + + if len(elems) < s.MinItems { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName), + Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName), + Subject: &content.MissingItemRange, + }) + } else if s.MaxItems > 0 && len(elems) > s.MaxItems { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Too many %s blocks", s.TypeName), + Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName), + Subject: &sourceRanges[s.MaxItems], + }) + } + + var ret cty.Value + + if len(elems) == 0 { + ret = cty.SetValEmpty(s.Nested.impliedType()) + } else { + ret = cty.SetVal(elems) + } + + return ret, diags +} + +func (s *BlockSetSpec) impliedType() cty.Type { + return cty.Set(s.Nested.impliedType()) +} + +func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { // We return the source range of the _first_ block of the given type, // since they are not guaranteed to form a contiguous range. @@ -492,7 +603,7 @@ func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) h return content.MissingItemRange } - return sourceRange(childBlock.Body, childBlock, s.Nested) + return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) } // A BlockMapSpec is a Spec that produces a cty map of the results of @@ -510,11 +621,108 @@ func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) { // leaf node ("Nested" does not use the same body) } -func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - panic("BlockMapSpec.decode not yet implemented") +// blockSpec implementation +func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { + return []hcl.BlockHeaderSchema{ + { + Type: s.TypeName, + LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...), + }, + } } -func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { +// specNeedingVariables implementation +func (s *BlockMapSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { + var ret []hcl.Traversal + + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + ret = append(ret, Variables(childBlock.Body, s.Nested)...) + } + + return ret +} + +func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + var diags hcl.Diagnostics + + if s.Nested == nil { + panic("BlockSetSpec with no Nested Spec") + } + + elems := map[string]interface{}{} + for _, childBlock := range content.Blocks { + if childBlock.Type != s.TypeName { + continue + } + + childLabels := labelsForBlock(childBlock) + val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false) + targetMap := elems + for _, key := range childBlock.Labels[:len(s.LabelNames)-1] { + if _, exists := targetMap[key]; !exists { + targetMap[key] = make(map[string]interface{}) + } + targetMap = targetMap[key].(map[string]interface{}) + } + + diags = append(diags, childDiags...) + + key := childBlock.Labels[len(s.LabelNames)-1] + if _, exists := targetMap[key]; exists { + labelsBuf := bytes.Buffer{} + for _, label := range childBlock.Labels { + fmt.Fprintf(&labelsBuf, " %q", label) + } + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Duplicate %s block", s.TypeName), + Detail: fmt.Sprintf( + "A block for %s%s was already defined. The %s labels must be unique.", + s.TypeName, labelsBuf.String(), s.TypeName, + ), + Subject: &childBlock.DefRange, + }) + continue + } + + targetMap[key] = val + } + + if len(elems) == 0 { + return cty.MapValEmpty(s.Nested.impliedType()), diags + } + + var ctyMap func(map[string]interface{}, int) cty.Value + ctyMap = func(raw map[string]interface{}, depth int) cty.Value { + vals := make(map[string]cty.Value, len(raw)) + if depth == 1 { + for k, v := range raw { + vals[k] = v.(cty.Value) + } + } else { + for k, v := range raw { + vals[k] = ctyMap(v.(map[string]interface{}), depth-1) + } + } + return cty.MapVal(vals) + } + + return ctyMap(elems, len(s.LabelNames)), diags +} + +func (s *BlockMapSpec) impliedType() cty.Type { + ret := s.Nested.impliedType() + for _ = range s.LabelNames { + ret = cty.Map(ret) + } + return ret +} + +func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { // We return the source range of the _first_ block of the given type, // since they are not guaranteed to form a contiguous range. @@ -532,7 +740,7 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) h return content.MissingItemRange } - return sourceRange(childBlock.Body, childBlock, s.Nested) + return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) } // A BlockLabelSpec is a Spec that returns a cty.String representing the @@ -555,14 +763,97 @@ func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) { // leaf node } -func (s *BlockLabelSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - panic("BlockLabelSpec.decode not yet implemented") -} - -func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { - if block == nil { +func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + if s.Index >= len(blockLabels) { panic("BlockListSpec used in non-block context") } - return block.LabelRanges[s.Index] + return cty.StringVal(blockLabels[s.Index].Value), nil +} + +func (s *BlockLabelSpec) impliedType() cty.Type { + return cty.String // labels are always strings +} + +func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { + if s.Index >= len(blockLabels) { + panic("BlockListSpec used in non-block context") + } + + return blockLabels[s.Index].Range +} + +func findLabelSpecs(spec Spec) []string { + maxIdx := -1 + var names map[int]string + + var visit visitFunc + visit = func(s Spec) { + if ls, ok := s.(*BlockLabelSpec); ok { + if maxIdx < ls.Index { + maxIdx = ls.Index + } + if names == nil { + names = make(map[int]string) + } + names[ls.Index] = ls.Name + } + s.visitSameBodyChildren(visit) + } + + visit(spec) + + if maxIdx < 0 { + return nil // no labels at all + } + + ret := make([]string, maxIdx+1) + for i := range ret { + name := names[i] + if name == "" { + // Should never happen if the spec is conformant, since we require + // consecutive indices starting at zero. + name = fmt.Sprintf("missing%02d", i) + } + ret[i] = name + } + + return ret +} + +// DefaultSpec is a spec that wraps two specs, evaluating the primary first +// and then evaluating the default if the primary returns a null value. +// +// The two specifications must have the same implied result type for correct +// operation. If not, the result is undefined. +type DefaultSpec struct { + Primary Spec + Default Spec +} + +func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) { + cb(s.Primary) + cb(s.Default) +} + +func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { + val, diags := s.Primary.decode(content, blockLabels, ctx) + if val.IsNull() { + var moreDiags hcl.Diagnostics + val, moreDiags = s.Default.decode(content, blockLabels, ctx) + diags = append(diags, moreDiags...) + } + return val, diags +} + +func (s *DefaultSpec) impliedType() cty.Type { + return s.Primary.impliedType() +} + +func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { + // We can't tell from here which of the two specs will ultimately be used + // in our result, so we'll just assume the first. This is usually the right + // choice because the default is often a literal spec that doesn't have a + // reasonable source range to return anyway. + return s.Primary.sourceRange(content, blockLabels) } diff --git a/vendor/vendor.json b/vendor/vendor.json index d62999e02..2bc86cda0 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1556,56 +1556,56 @@ { "checksumSHA1": "6kxMiZSmgazD/CZgmnEeEMJSAOM=", "path": "github.com/hashicorp/hcl2/gohcl", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "TsNlThzf92FMwcnM4Fc0mArHroU=", "path": "github.com/hashicorp/hcl2/hcl", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "+Dv8V2cfl7Vy6rUklhXj5Cli8aU=", "path": "github.com/hashicorp/hcl2/hcl/hclsyntax", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "GAArMzjaoFNPa7HFnhjZmaeBZII=", "path": "github.com/hashicorp/hcl2/hcl/json", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { - "checksumSHA1": "t6Zwqq1Xz+RnDXaa+qBLEPHLWn8=", + "checksumSHA1": "u6YPoPz3GflgHb1dN1YN8nCWAXY=", "path": "github.com/hashicorp/hcl2/hcldec", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "sySYF9Ew71VS/LfrG+s/0jK+1VQ=", "path": "github.com/hashicorp/hcl2/hcled", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "IzmftuG99BqNhbFGhxZaGwtiMtM=", "path": "github.com/hashicorp/hcl2/hclparse", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "4supppf3CMdAEUEDrXP8niXvAR0=", "path": "github.com/hashicorp/hcl2/hcltest", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "p+dun/Fx4beswXTtoEjVnwDJE+Y=", "path": "github.com/hashicorp/hcl2/hclwrite", - "revision": "f44382c4fab57666c486c77a8b89c9ea00be08ba", - "revisionTime": "2017-09-20T23:23:23Z" + "revision": "44bad6dbf5490f5da17ec991e664df3d017b706f", + "revisionTime": "2017-10-03T23:27:34Z" }, { "checksumSHA1": "M09yxoBoCEtG7EcHR8aEWLzMMJc=",