import (
"fmt"
+ "sync"
"github.com/hashicorp/hcl2/hcl"
"github.com/zclconf/go-cty/cty"
}
func (e *ScopeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
- return e.Traversal.TraverseAbs(ctx)
+ val, diags := e.Traversal.TraverseAbs(ctx)
+ setDiagEvalContext(diags, e, ctx)
+ return val, diags
}
func (e *ScopeTraversalExpr) Range() hcl.Range {
}
func (e *RelativeTraversalExpr) walkChildNodes(w internalWalkFunc) {
- // Scope traversals have no child nodes
+ w(e.Source)
}
func (e *RelativeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
src, diags := e.Source.Value(ctx)
ret, travDiags := e.Traversal.TraverseRel(src)
+ setDiagEvalContext(travDiags, e, ctx)
diags = append(diags, travDiags...)
return ret, diags
}
}
func (e *FunctionCallExpr) walkChildNodes(w internalWalkFunc) {
- for i, arg := range e.Args {
- e.Args[i] = w(arg).(Expression)
+ for _, arg := range e.Args {
+ w(arg)
}
}
if !hasNonNilMap {
return cty.DynamicVal, hcl.Diagnostics{
{
- Severity: hcl.DiagError,
- Summary: "Function calls not allowed",
- Detail: "Functions may not be called here.",
- Subject: e.Range().Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Function calls not allowed",
+ Detail: "Functions may not be called here.",
+ Subject: e.Range().Ptr(),
+ Expression: e,
+ EvalContext: ctx,
},
}
}
return cty.DynamicVal, hcl.Diagnostics{
{
- Severity: hcl.DiagError,
- Summary: "Call to unknown function",
- Detail: fmt.Sprintf("There is no function named %q.%s", e.Name, suggestion),
- Subject: &e.NameRange,
- Context: e.Range().Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Call to unknown function",
+ Detail: fmt.Sprintf("There is no function named %q.%s", e.Name, suggestion),
+ Subject: &e.NameRange,
+ Context: e.Range().Ptr(),
+ Expression: e,
+ EvalContext: ctx,
},
}
}
case expandVal.Type().IsTupleType() || expandVal.Type().IsListType() || expandVal.Type().IsSetType():
if expandVal.IsNull() {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid expanding argument value",
- Detail: "The expanding argument (indicated by ...) must not be null.",
- Context: expandExpr.Range().Ptr(),
- Subject: e.Range().Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Invalid expanding argument value",
+ Detail: "The expanding argument (indicated by ...) must not be null.",
+ Subject: expandExpr.Range().Ptr(),
+ Context: e.Range().Ptr(),
+ Expression: expandExpr,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
args = newArgs
default:
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid expanding argument value",
- Detail: "The expanding argument (indicated by ...) must be of a tuple, list, or set type.",
- Context: expandExpr.Range().Ptr(),
- Subject: e.Range().Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Invalid expanding argument value",
+ Detail: "The expanding argument (indicated by ...) must be of a tuple, list, or set type.",
+ Subject: expandExpr.Range().Ptr(),
+ Context: e.Range().Ptr(),
+ Expression: expandExpr,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
"Function %q expects%s %d argument(s). Missing value for %q.",
e.Name, qual, len(params), missing.Name,
),
- Subject: &e.CloseParenRange,
- Context: e.Range().Ptr(),
+ Subject: &e.CloseParenRange,
+ Context: e.Range().Ptr(),
+ Expression: e,
+ EvalContext: ctx,
},
}
}
"Function %q expects only %d argument(s).",
e.Name, len(params),
),
- Subject: args[len(params)].StartRange().Ptr(),
- Context: e.Range().Ptr(),
+ Subject: args[len(params)].StartRange().Ptr(),
+ Context: e.Range().Ptr(),
+ Expression: e,
+ EvalContext: ctx,
},
}
}
"Invalid value for %q parameter: %s.",
param.Name, err,
),
- Subject: argExpr.StartRange().Ptr(),
- Context: e.Range().Ptr(),
+ Subject: argExpr.StartRange().Ptr(),
+ Context: e.Range().Ptr(),
+ Expression: argExpr,
+ EvalContext: ctx,
})
}
"Invalid value for %q parameter: %s.",
param.Name, err,
),
- Subject: argExpr.StartRange().Ptr(),
- Context: e.Range().Ptr(),
+ Subject: argExpr.StartRange().Ptr(),
+ Context: e.Range().Ptr(),
+ Expression: argExpr,
+ EvalContext: ctx,
})
default:
"Call to function %q failed: %s.",
e.Name, err,
),
- Subject: e.StartRange().Ptr(),
- Context: e.Range().Ptr(),
+ Subject: e.StartRange().Ptr(),
+ Context: e.Range().Ptr(),
+ Expression: e,
+ EvalContext: ctx,
})
}
}
func (e *ConditionalExpr) walkChildNodes(w internalWalkFunc) {
- e.Condition = w(e.Condition).(Expression)
- e.TrueResult = w(e.TrueResult).(Expression)
- e.FalseResult = w(e.FalseResult).(Expression)
+ w(e.Condition)
+ w(e.TrueResult)
+ w(e.FalseResult)
}
func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
// "These expressions are object and object respectively" if the
// object types don't exactly match.
"The true and false result expressions must have consistent types. The given expressions are %s and %s, respectively.",
- trueResult.Type(), falseResult.Type(),
+ trueResult.Type().FriendlyName(), falseResult.Type().FriendlyName(),
),
- Subject: hcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(),
- Context: &e.SrcRange,
+ Subject: hcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(),
+ Context: &e.SrcRange,
+ Expression: e,
+ EvalContext: ctx,
},
}
}
diags = append(diags, condDiags...)
if condResult.IsNull() {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Null condition",
- Detail: "The condition value is null. Conditions must either be true or false.",
- Subject: e.Condition.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Null condition",
+ Detail: "The condition value is null. Conditions must either be true or false.",
+ Subject: e.Condition.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.Condition,
+ EvalContext: ctx,
})
return cty.UnknownVal(resultType), diags
}
condResult, err := convert.Convert(condResult, cty.Bool)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Incorrect condition type",
- Detail: fmt.Sprintf("The condition expression must be of type bool."),
- Subject: e.Condition.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Incorrect condition type",
+ Detail: fmt.Sprintf("The condition expression must be of type bool."),
+ Subject: e.Condition.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.Condition,
+ EvalContext: ctx,
})
return cty.UnknownVal(resultType), diags
}
"The true result value has the wrong type: %s.",
err.Error(),
),
- Subject: e.TrueResult.Range().Ptr(),
- Context: &e.SrcRange,
+ Subject: e.TrueResult.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.TrueResult,
+ EvalContext: ctx,
})
trueResult = cty.UnknownVal(resultType)
}
"The false result value has the wrong type: %s.",
err.Error(),
),
- Subject: e.TrueResult.Range().Ptr(),
- Context: &e.SrcRange,
+ Subject: e.FalseResult.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.FalseResult,
+ EvalContext: ctx,
})
falseResult = cty.UnknownVal(resultType)
}
}
func (e *IndexExpr) walkChildNodes(w internalWalkFunc) {
- e.Collection = w(e.Collection).(Expression)
- e.Key = w(e.Key).(Expression)
+ w(e.Collection)
+ w(e.Key)
}
func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
diags = append(diags, collDiags...)
diags = append(diags, keyDiags...)
- return hcl.Index(coll, key, &e.SrcRange)
+ val, indexDiags := hcl.Index(coll, key, &e.SrcRange)
+ setDiagEvalContext(indexDiags, e, ctx)
+ diags = append(diags, indexDiags...)
+ return val, diags
}
func (e *IndexExpr) Range() hcl.Range {
}
func (e *TupleConsExpr) walkChildNodes(w internalWalkFunc) {
- for i, expr := range e.Exprs {
- e.Exprs[i] = w(expr).(Expression)
+ for _, expr := range e.Exprs {
+ w(expr)
}
}
}
func (e *ObjectConsExpr) walkChildNodes(w internalWalkFunc) {
- for i, item := range e.Items {
- e.Items[i].KeyExpr = w(item.KeyExpr).(Expression)
- e.Items[i].ValueExpr = w(item.ValueExpr).(Expression)
+ for _, item := range e.Items {
+ w(item.KeyExpr)
+ w(item.ValueExpr)
}
}
if key.IsNull() {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Null value as key",
- Detail: "Can't use a null value as a key.",
- Subject: item.ValueExpr.Range().Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Null value as key",
+ Detail: "Can't use a null value as a key.",
+ Subject: item.ValueExpr.Range().Ptr(),
+ Expression: item.KeyExpr,
+ EvalContext: ctx,
})
known = false
continue
key, err = convert.Convert(key, cty.String)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Incorrect key type",
- Detail: fmt.Sprintf("Can't use this value as a key: %s.", err.Error()),
- Subject: item.ValueExpr.Range().Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Incorrect key type",
+ Detail: fmt.Sprintf("Can't use this value as a key: %s.", err.Error()),
+ Subject: item.KeyExpr.Range().Ptr(),
+ Expression: item.KeyExpr,
+ EvalContext: ctx,
})
known = false
continue
// We only treat our wrapped expression as a real expression if we're
// not going to interpret it as a literal.
if e.literalName() == "" {
- e.Wrapped = w(e.Wrapped).(Expression)
+ w(e.Wrapped)
}
}
func (e *ObjectConsKeyExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
+ // Because we accept a naked identifier as a literal key rather than a
+ // reference, it's confusing to accept a traversal containing periods
+ // here since we can't tell if the user intends to create a key with
+ // periods or actually reference something. To avoid confusing downstream
+ // errors we'll just prohibit a naked multi-step traversal here and
+ // require the user to state their intent more clearly.
+ // (This is handled at evaluation time rather than parse time because
+ // an application using static analysis _can_ accept a naked multi-step
+ // traversal here, if desired.)
+ if travExpr, isTraversal := e.Wrapped.(*ScopeTraversalExpr); isTraversal && len(travExpr.Traversal) > 1 {
+ var diags hcl.Diagnostics
+ diags = append(diags, &hcl.Diagnostic{
+ Severity: hcl.DiagError,
+ Summary: "Ambiguous attribute key",
+ Detail: "If this expression is intended to be a reference, wrap it in parentheses. If it's instead intended as a literal name containing periods, wrap it in quotes to create a string literal.",
+ Subject: e.Range().Ptr(),
+ })
+ return cty.DynamicVal, diags
+ }
+
if ln := e.literalName(); ln != "" {
return cty.StringVal(ln), nil
}
if collVal.IsNull() {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Iteration over null value",
- Detail: "A null value cannot be used as the collection in a 'for' expression.",
- Subject: e.CollExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Iteration over null value",
+ Detail: "A null value cannot be used as the collection in a 'for' expression.",
+ Subject: e.CollExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CollExpr,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
"A value of type %s cannot be used as the collection in a 'for' expression.",
collVal.Type().FriendlyName(),
),
- Subject: e.CollExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Subject: e.CollExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CollExpr,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
return cty.DynamicVal, diags
}
- childCtx := ctx.NewChild()
- childCtx.Variables = map[string]cty.Value{}
-
// Before we start we'll do an early check to see if any CondExpr we've
// been given is of the wrong type. This isn't 100% reliable (it may
// be DynamicVal until real values are given) but it should catch some
// straightforward cases and prevent a barrage of repeated errors.
if e.CondExpr != nil {
+ childCtx := ctx.NewChild()
+ childCtx.Variables = map[string]cty.Value{}
if e.KeyVar != "" {
childCtx.Variables[e.KeyVar] = cty.DynamicVal
}
diags = append(diags, condDiags...)
if result.IsNull() {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Condition is null",
- Detail: "The value of the 'if' clause must not be null.",
- Subject: e.CondExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Condition is null",
+ Detail: "The value of the 'if' clause must not be null.",
+ Subject: e.CondExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CondExpr,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
_, err := convert.Convert(result, cty.Bool)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid 'for' condition",
- Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
- Subject: e.CondExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid 'for' condition",
+ Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
+ Subject: e.CondExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CondExpr,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
known := true
for it.Next() {
k, v := it.Element()
+ childCtx := ctx.NewChild()
+ childCtx.Variables = map[string]cty.Value{}
if e.KeyVar != "" {
childCtx.Variables[e.KeyVar] = k
}
if includeRaw.IsNull() {
if known {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Condition is null",
- Detail: "The value of the 'if' clause must not be null.",
- Subject: e.CondExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid 'for' condition",
+ Detail: "The value of the 'if' clause must not be null.",
+ Subject: e.CondExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CondExpr,
+ EvalContext: childCtx,
})
}
known = false
if err != nil {
if known {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid 'for' condition",
- Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
- Subject: e.CondExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid 'for' condition",
+ Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
+ Subject: e.CondExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CondExpr,
+ EvalContext: childCtx,
})
}
known = false
if keyRaw.IsNull() {
if known {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid object key",
- Detail: "Key expression in 'for' expression must not produce a null value.",
- Subject: e.KeyExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid object key",
+ Detail: "Key expression in 'for' expression must not produce a null value.",
+ Subject: e.KeyExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.KeyExpr,
+ EvalContext: childCtx,
})
}
known = false
if err != nil {
if known {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid object key",
- Detail: fmt.Sprintf("The key expression produced an invalid result: %s.", err.Error()),
- Subject: e.KeyExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid object key",
+ Detail: fmt.Sprintf("The key expression produced an invalid result: %s.", err.Error()),
+ Subject: e.KeyExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.KeyExpr,
+ EvalContext: childCtx,
})
}
known = false
Severity: hcl.DiagError,
Summary: "Duplicate object key",
Detail: fmt.Sprintf(
- "Two different items produced the key %q in this for expression. If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key.",
+ "Two different items produced the key %q in this 'for' expression. If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key.",
k,
),
- Subject: e.KeyExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Subject: e.KeyExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.KeyExpr,
+ EvalContext: childCtx,
})
} else {
vals[key.AsString()] = val
known := true
for it.Next() {
k, v := it.Element()
+ childCtx := ctx.NewChild()
+ childCtx.Variables = map[string]cty.Value{}
if e.KeyVar != "" {
childCtx.Variables[e.KeyVar] = k
}
if includeRaw.IsNull() {
if known {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Condition is null",
- Detail: "The value of the 'if' clause must not be null.",
- Subject: e.CondExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid 'for' condition",
+ Detail: "The value of the 'if' clause must not be null.",
+ Subject: e.CondExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CondExpr,
+ EvalContext: childCtx,
})
}
known = false
if err != nil {
if known {
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Invalid 'for' condition",
- Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
- Subject: e.CondExpr.Range().Ptr(),
- Context: &e.SrcRange,
+ Severity: hcl.DiagError,
+ Summary: "Invalid 'for' condition",
+ Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
+ Subject: e.CondExpr.Range().Ptr(),
+ Context: &e.SrcRange,
+ Expression: e.CondExpr,
+ EvalContext: childCtx,
})
}
known = false
}
func (e *ForExpr) walkChildNodes(w internalWalkFunc) {
- e.CollExpr = w(e.CollExpr).(Expression)
+ w(e.CollExpr)
scopeNames := map[string]struct{}{}
if e.KeyVar != "" {
if e.KeyExpr != nil {
w(ChildScope{
LocalNames: scopeNames,
- Expr: &e.KeyExpr,
+ Expr: e.KeyExpr,
})
}
w(ChildScope{
LocalNames: scopeNames,
- Expr: &e.ValExpr,
+ Expr: e.ValExpr,
})
if e.CondExpr != nil {
w(ChildScope{
LocalNames: scopeNames,
- Expr: &e.CondExpr,
+ Expr: e.CondExpr,
})
}
}
return cty.DynamicVal, diags
}
+ sourceTy := sourceVal.Type()
+ if sourceTy == cty.DynamicPseudoType {
+ // If we don't even know the _type_ of our source value yet then
+ // we'll need to defer all processing, since we can't decide our
+ // result type either.
+ return cty.DynamicVal, diags
+ }
+
+ // A "special power" of splat expressions is that they can be applied
+ // both to tuples/lists and to other values, and in the latter case
+ // the value will be treated as an implicit single-item tuple, or as
+ // an empty tuple if the value is null.
+ autoUpgrade := !(sourceTy.IsTupleType() || sourceTy.IsListType() || sourceTy.IsSetType())
+
if sourceVal.IsNull() {
+ if autoUpgrade {
+ return cty.EmptyTupleVal, diags
+ }
diags = append(diags, &hcl.Diagnostic{
- Severity: hcl.DiagError,
- Summary: "Splat of null value",
- Detail: "Splat expressions (with the * symbol) cannot be applied to null values.",
- Subject: e.Source.Range().Ptr(),
- Context: hcl.RangeBetween(e.Source.Range(), e.MarkerRange).Ptr(),
+ Severity: hcl.DiagError,
+ Summary: "Splat of null value",
+ Detail: "Splat expressions (with the * symbol) cannot be applied to null sequences.",
+ Subject: e.Source.Range().Ptr(),
+ Context: hcl.RangeBetween(e.Source.Range(), e.MarkerRange).Ptr(),
+ Expression: e.Source,
+ EvalContext: ctx,
})
return cty.DynamicVal, diags
}
- if !sourceVal.IsKnown() {
- return cty.DynamicVal, diags
+
+ if autoUpgrade {
+ sourceVal = cty.TupleVal([]cty.Value{sourceVal})
+ sourceTy = sourceVal.Type()
}
- // A "special power" of splat expressions is that they can be applied
- // both to tuples/lists and to other values, and in the latter case
- // the value will be treated as an implicit single-value list. We'll
- // deal with that here first.
- if !(sourceVal.Type().IsTupleType() || sourceVal.Type().IsListType()) {
- sourceVal = cty.ListVal([]cty.Value{sourceVal})
+ // We'll compute our result type lazily if we need it. In the normal case
+ // it's inferred automatically from the value we construct.
+ resultTy := func() (cty.Type, hcl.Diagnostics) {
+ chiCtx := ctx.NewChild()
+ var diags hcl.Diagnostics
+ switch {
+ case sourceTy.IsListType() || sourceTy.IsSetType():
+ ety := sourceTy.ElementType()
+ e.Item.setValue(chiCtx, cty.UnknownVal(ety))
+ val, itemDiags := e.Each.Value(chiCtx)
+ diags = append(diags, itemDiags...)
+ e.Item.clearValue(chiCtx) // clean up our temporary value
+ return cty.List(val.Type()), diags
+ case sourceTy.IsTupleType():
+ etys := sourceTy.TupleElementTypes()
+ resultTys := make([]cty.Type, 0, len(etys))
+ for _, ety := range etys {
+ e.Item.setValue(chiCtx, cty.UnknownVal(ety))
+ val, itemDiags := e.Each.Value(chiCtx)
+ diags = append(diags, itemDiags...)
+ e.Item.clearValue(chiCtx) // clean up our temporary value
+ resultTys = append(resultTys, val.Type())
+ }
+ return cty.Tuple(resultTys), diags
+ default:
+ // Should never happen because of our promotion to list above.
+ return cty.DynamicPseudoType, diags
+ }
+ }
+
+ if !sourceVal.IsKnown() {
+ // We can't produce a known result in this case, but we'll still
+ // indicate what the result type would be, allowing any downstream type
+ // checking to proceed.
+ ty, tyDiags := resultTy()
+ diags = append(diags, tyDiags...)
+ return cty.UnknownVal(ty), diags
}
vals := make([]cty.Value, 0, sourceVal.LengthInt())
e.Item.clearValue(ctx) // clean up our temporary value
if !isKnown {
- return cty.DynamicVal, diags
+ // We'll ingore the resultTy diagnostics in this case since they
+ // will just be the same errors we saw while iterating above.
+ ty, _ := resultTy()
+ return cty.UnknownVal(ty), diags
}
- return cty.TupleVal(vals), diags
+ switch {
+ case sourceTy.IsListType() || sourceTy.IsSetType():
+ if len(vals) == 0 {
+ ty, tyDiags := resultTy()
+ diags = append(diags, tyDiags...)
+ return cty.ListValEmpty(ty.ElementType()), diags
+ }
+ return cty.ListVal(vals), diags
+ default:
+ return cty.TupleVal(vals), diags
+ }
}
func (e *SplatExpr) walkChildNodes(w internalWalkFunc) {
- e.Source = w(e.Source).(Expression)
- e.Each = w(e.Each).(Expression)
+ w(e.Source)
+ w(e.Each)
}
func (e *SplatExpr) Range() hcl.Range {
// assigns it a value.
type AnonSymbolExpr struct {
SrcRange hcl.Range
- values map[*hcl.EvalContext]cty.Value
+
+ // values and its associated lock are used to isolate concurrent
+ // evaluations of a symbol from one another. It is the calling application's
+ // responsibility to ensure that the same splat expression is not evalauted
+ // concurrently within the _same_ EvalContext, but it is fine and safe to
+ // do cuncurrent evaluations with distinct EvalContexts.
+ values map[*hcl.EvalContext]cty.Value
+ valuesLock sync.RWMutex
}
func (e *AnonSymbolExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
if ctx == nil {
return cty.DynamicVal, nil
}
+
+ e.valuesLock.RLock()
+ defer e.valuesLock.RUnlock()
+
val, exists := e.values[ctx]
if !exists {
return cty.DynamicVal, nil
// setValue sets a temporary local value for the expression when evaluated
// in the given context, which must be non-nil.
func (e *AnonSymbolExpr) setValue(ctx *hcl.EvalContext, val cty.Value) {
+ e.valuesLock.Lock()
+ defer e.valuesLock.Unlock()
+
if e.values == nil {
e.values = make(map[*hcl.EvalContext]cty.Value)
}
}
func (e *AnonSymbolExpr) clearValue(ctx *hcl.EvalContext) {
+ e.valuesLock.Lock()
+ defer e.valuesLock.Unlock()
+
if e.values == nil {
return
}