From 93562f805f4038bfab54bb3ae518c87aeb116253 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Sat, 28 Jul 2018 13:14:36 -0700 Subject: [PATCH] hcl: Annotate diagnostics with expression EvalContext When we're evaluating expressions, we may end up evaluating the same source-level expression a number of times in different contexts, such as in a 'for' expression, where each one may produce a different set of diagnostic messages. Now we'll attach the EvalContext to each expression diagnostic so that a diagnostic renderer can potentially show additional information to help distinguish the different iterations in rendered diagnostics. --- ext/dynblock/expand_spec.go | 45 ++--- hcl/diagnostic.go | 31 +++- hcl/hclsyntax/diagnostics.go | 22 +++ hcl/hclsyntax/expression.go | 249 +++++++++++++++------------ hcl/hclsyntax/expression_ops.go | 51 +++--- hcl/hclsyntax/expression_template.go | 16 +- hcl/json/structure.go | 27 +-- 7 files changed, 270 insertions(+), 171 deletions(-) create mode 100644 hcl/hclsyntax/diagnostics.go diff --git a/ext/dynblock/expand_spec.go b/ext/dynblock/expand_spec.go index fa91c9f..a0734f6 100644 --- a/ext/dynblock/expand_spec.go +++ b/ext/dynblock/expand_spec.go @@ -43,19 +43,21 @@ func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Bloc if !eachVal.CanIterateElements() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic for_each value", - Detail: fmt.Sprintf("Cannot use a value of type %s in for_each. An iterable collection is required.", eachVal.Type()), - Subject: eachAttr.Expr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic for_each value", + Detail: fmt.Sprintf("Cannot use a value of type %s in for_each. An iterable collection is required.", eachVal.Type()), + Subject: eachAttr.Expr.Range().Ptr(), + EvalContext: b.forEachCtx, }) return nil, diags } if eachVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic for_each value", - Detail: "Cannot use a null value in for_each.", - Subject: eachAttr.Expr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic for_each value", + Detail: "Cannot use a null value in for_each.", + Subject: eachAttr.Expr.Range().Ptr(), + EvalContext: b.forEachCtx, }) return nil, diags } @@ -159,28 +161,31 @@ func (s *expandSpec) newBlock(i *iteration, ctx *hcl.EvalContext) (*hcl.Block, h labelVal, convErr = convert.Convert(labelVal, cty.String) if convErr != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic block label", - Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr), - Subject: labelExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic block label", + Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr), + Subject: labelExpr.Range().Ptr(), + EvalContext: lCtx, }) return nil, diags } if labelVal.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic block label", - Detail: "Cannot use a null value as a dynamic block label.", - Subject: labelExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic block label", + Detail: "Cannot use a null value as a dynamic block label.", + Subject: labelExpr.Range().Ptr(), + EvalContext: lCtx, }) return nil, diags } if !labelVal.IsKnown() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid dynamic block label", - Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.", - Subject: labelExpr.Range().Ptr(), + Severity: hcl.DiagError, + Summary: "Invalid dynamic block label", + Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.", + Subject: labelExpr.Range().Ptr(), + EvalContext: lCtx, }) return nil, diags } diff --git a/hcl/diagnostic.go b/hcl/diagnostic.go index f1e0056..cea2843 100644 --- a/hcl/diagnostic.go +++ b/hcl/diagnostic.go @@ -26,14 +26,43 @@ const ( type Diagnostic struct { Severity DiagnosticSeverity - // Summary and detail contain the English-language description of the + // Summary and Detail contain the English-language description of the // problem. Summary is a terse description of the general problem and // detail is a more elaborate, often-multi-sentence description of // the probem and what might be done to solve it. Summary string Detail string + + // Subject and Context are both source ranges relating to the diagnostic. + // + // Subject is a tight range referring to exactly the construct that + // is problematic, while Context is an optional broader range (which should + // fully contain Subject) that ought to be shown around Subject when + // generating isolated source-code snippets in diagnostic messages. + // If Context is nil, the Subject is also the Context. + // + // Some diagnostics have no source ranges at all. If Context is set then + // Subject should always also be set. Subject *Range Context *Range + + // For diagnostics that occur when evaluating an expression, EvalContext + // may point to the EvalContext that was active when evaluating that + // expression, which may allow for the inclusion of additional useful + // information when rendering a diagnostic message to the user. + // + // It is not always possible to select a single EvalContext for a + // diagnostic, and so in some cases this field may be nil even when an + // expression causes a problem. Therefore it is not valid to use the + // nil-ness of this field to definitively decide whether a diagnostic + // relates to an expression. + // + // EvalContexts form a tree, so the given EvalContext may refer to a parent + // which in turn refers to another parent, etc. For a full picture of all + // of the active variables and functions the caller must walk up this + // chain, preferring definitions that are "closer" to the expression in + // case of colliding names. + EvalContext *EvalContext } // Diagnostics is a list of Diagnostic instances. diff --git a/hcl/hclsyntax/diagnostics.go b/hcl/hclsyntax/diagnostics.go new file mode 100644 index 0000000..53d4ec0 --- /dev/null +++ b/hcl/hclsyntax/diagnostics.go @@ -0,0 +1,22 @@ +package hclsyntax + +import ( + "github.com/hashicorp/hcl2/hcl" +) + +// setDiagEvalContext is an internal helper that will impose a particular +// EvalContext on a set of diagnostics in-place, for any diagnostic that +// does not already have an EvalContext set. +// +// We generally expect diagnostics to be immutable, but this is safe to use +// on any Diagnostics where none of the contained Diagnostic objects have yet +// been seen by a caller. Its purpose is to apply additional context to a +// set of diagnostics produced by a "deeper" component as the stack unwinds +// during expression evaluation. +func setDiagEvalContext(diags hcl.Diagnostics, ctx *hcl.EvalContext) { + for _, diag := range diags { + if diag.EvalContext == nil { + diag.EvalContext = ctx + } + } +} diff --git a/hcl/hclsyntax/expression.go b/hcl/hclsyntax/expression.go index e54806e..7984103 100644 --- a/hcl/hclsyntax/expression.go +++ b/hcl/hclsyntax/expression.go @@ -105,7 +105,9 @@ func (e *ScopeTraversalExpr) walkChildNodes(w internalWalkFunc) { } func (e *ScopeTraversalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { - return e.Traversal.TraverseAbs(ctx) + val, diags := e.Traversal.TraverseAbs(ctx) + setDiagEvalContext(diags, ctx) + return val, diags } func (e *ScopeTraversalExpr) Range() hcl.Range { @@ -136,6 +138,7 @@ func (e *RelativeTraversalExpr) walkChildNodes(w internalWalkFunc) { 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, ctx) diags = append(diags, travDiags...) return ret, diags } @@ -207,10 +210,11 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti 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(), + EvalContext: ctx, }, } } @@ -226,11 +230,12 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti 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(), + EvalContext: ctx, }, } } @@ -255,11 +260,12 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti 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.", + Context: expandExpr.Range().Ptr(), + Subject: e.Range().Ptr(), + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -280,11 +286,12 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti 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.", + Context: expandExpr.Range().Ptr(), + Subject: e.Range().Ptr(), + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -304,8 +311,9 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "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(), + EvalContext: ctx, }, } } @@ -319,8 +327,9 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "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(), + EvalContext: ctx, }, } } @@ -350,8 +359,9 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "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(), + EvalContext: ctx, }) } @@ -387,8 +397,9 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "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(), + EvalContext: ctx, }) default: @@ -399,8 +410,9 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "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(), + EvalContext: ctx, }) } @@ -467,8 +479,9 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic "The true and false result expressions must have consistent types. The given expressions are %s and %s, respectively.", trueResult.Type(), falseResult.Type(), ), - 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, + EvalContext: ctx, }, } } @@ -477,11 +490,12 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic 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, + EvalContext: ctx, }) return cty.UnknownVal(resultType), diags } @@ -491,11 +505,12 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic 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, + EvalContext: ctx, }) return cty.UnknownVal(resultType), diags } @@ -514,8 +529,9 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic "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, + EvalContext: ctx, }) trueResult = cty.UnknownVal(resultType) } @@ -535,8 +551,9 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic "The false 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, + EvalContext: ctx, }) falseResult = cty.UnknownVal(resultType) } @@ -676,10 +693,11 @@ func (e *ObjectConsExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics 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(), + EvalContext: ctx, }) known = false continue @@ -689,10 +707,11 @@ func (e *ObjectConsExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics 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.ValueExpr.Range().Ptr(), + EvalContext: ctx, }) known = false continue @@ -819,11 +838,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -838,8 +858,9 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { "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, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -864,22 +885,24 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + 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, + EvalContext: ctx, }) return cty.DynamicVal, diags } @@ -914,11 +937,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } known = false @@ -928,11 +952,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } known = false @@ -954,11 +979,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } known = false @@ -973,11 +999,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } known = false @@ -997,11 +1024,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } else { vals[key.AsString()] = val @@ -1042,11 +1070,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } known = false @@ -1064,11 +1093,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { 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, + EvalContext: ctx, }) } known = false @@ -1154,11 +1184,12 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if sourceVal.IsNull() { 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 values.", + Subject: e.Source.Range().Ptr(), + Context: hcl.RangeBetween(e.Source.Range(), e.MarkerRange).Ptr(), + EvalContext: ctx, }) return cty.DynamicVal, diags } diff --git a/hcl/hclsyntax/expression_ops.go b/hcl/hclsyntax/expression_ops.go index 9a5da04..4cf0ccd 100644 --- a/hcl/hclsyntax/expression_ops.go +++ b/hcl/hclsyntax/expression_ops.go @@ -149,21 +149,23 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid operand", - Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err), - Subject: e.LHS.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid operand", + Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err), + Subject: e.LHS.Range().Ptr(), + Context: &e.SrcRange, + EvalContext: ctx, }) } rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid operand", - Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err), - Subject: e.RHS.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid operand", + Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err), + Subject: e.RHS.Range().Ptr(), + Context: &e.SrcRange, + EvalContext: ctx, }) } @@ -178,10 +180,11 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) if err != nil { diags = append(diags, &hcl.Diagnostic{ // FIXME: This diagnostic is useless. - Severity: hcl.DiagError, - Summary: "Operation failed", - Detail: fmt.Sprintf("Error during operation: %s.", err), - Subject: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Operation failed", + Detail: fmt.Sprintf("Error during operation: %s.", err), + Subject: &e.SrcRange, + EvalContext: ctx, }) return cty.UnknownVal(e.Op.Type), diags } @@ -219,11 +222,12 @@ func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { val, err := convert.Convert(givenVal, param.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid operand", - Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err), - Subject: e.Val.Range().Ptr(), - Context: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Invalid operand", + Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err), + Subject: e.Val.Range().Ptr(), + Context: &e.SrcRange, + EvalContext: ctx, }) } @@ -238,10 +242,11 @@ func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { if err != nil { diags = append(diags, &hcl.Diagnostic{ // FIXME: This diagnostic is useless. - Severity: hcl.DiagError, - Summary: "Operation failed", - Detail: fmt.Sprintf("Error during operation: %s.", err), - Subject: &e.SrcRange, + Severity: hcl.DiagError, + Summary: "Operation failed", + Detail: fmt.Sprintf("Error during operation: %s.", err), + Subject: &e.SrcRange, + EvalContext: ctx, }) return cty.UnknownVal(e.Op.Type), diags } diff --git a/hcl/hclsyntax/expression_template.go b/hcl/hclsyntax/expression_template.go index a1c4727..10a3c73 100644 --- a/hcl/hclsyntax/expression_template.go +++ b/hcl/hclsyntax/expression_template.go @@ -37,8 +37,9 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) Detail: fmt.Sprintf( "The expression result is null. Cannot include a null value in a string template.", ), - Subject: part.Range().Ptr(), - Context: &e.SrcRange, + Subject: part.Range().Ptr(), + Context: &e.SrcRange, + EvalContext: ctx, }) continue } @@ -61,8 +62,9 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) "Cannot include the given value in a string template: %s.", err.Error(), ), - Subject: part.Range().Ptr(), - Context: &e.SrcRange, + Subject: part.Range().Ptr(), + Context: &e.SrcRange, + EvalContext: ctx, }) continue } @@ -127,7 +129,8 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti Detail: fmt.Sprintf( "An iteration result is null. Cannot include a null value in a string template.", ), - Subject: e.Range().Ptr(), + Subject: e.Range().Ptr(), + EvalContext: ctx, }) continue } @@ -143,7 +146,8 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti "Cannot include one of the interpolation results into the string template: %s.", err.Error(), ), - Subject: e.Range().Ptr(), + Subject: e.Range().Ptr(), + EvalContext: ctx, }) continue } diff --git a/hcl/json/structure.go b/hcl/json/structure.go index 9e0c92a..0f2b201 100644 --- a/hcl/json/structure.go +++ b/hcl/json/structure.go @@ -440,19 +440,21 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { name, err = convert.Convert(name, cty.String) if err != nil { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid object key expression", - Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err), - Subject: &jsonAttr.NameRange, + Severity: hcl.DiagError, + Summary: "Invalid object key expression", + Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err), + Subject: &jsonAttr.NameRange, + EvalContext: ctx, }) continue } if name.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid object key expression", - Detail: "Cannot use null value as an object key.", - Subject: &jsonAttr.NameRange, + Severity: hcl.DiagError, + Summary: "Invalid object key expression", + Detail: "Cannot use null value as an object key.", + Subject: &jsonAttr.NameRange, + EvalContext: ctx, }) continue } @@ -471,10 +473,11 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { nameStr := name.AsString() if _, defined := attrs[nameStr]; defined { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Duplicate object attribute", - Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]), - Subject: &jsonAttr.NameRange, + Severity: hcl.DiagError, + Summary: "Duplicate object attribute", + Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]), + Subject: &jsonAttr.NameRange, + EvalContext: ctx, }) continue }