hclsyntax: Allow parens to force mapping key to be expression
Our error message for the ambiguous situation recommends doing this, but the parser didn't actually previously allow it. Now we'll accept the form that the error message recommends. As before, we also accept a template with an interpolation sequence as a disambiguation, but the error message doesn't mention that because it's no longer idiomatic to use an inline string template containing just a single interpolation sequence.
This commit is contained in:
parent
ee38c67330
commit
22ba006718
@ -804,7 +804,8 @@ func (e *ObjectConsExpr) ExprMap() []hcl.KeyValuePair {
|
||||
// which deals with the special case that a naked identifier in that position
|
||||
// must be interpreted as a literal string rather than evaluated directly.
|
||||
type ObjectConsKeyExpr struct {
|
||||
Wrapped Expression
|
||||
Wrapped Expression
|
||||
ForceNonLiteral bool
|
||||
}
|
||||
|
||||
func (e *ObjectConsKeyExpr) literalName() string {
|
||||
@ -834,19 +835,21 @@ func (e *ObjectConsKeyExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnost
|
||||
// (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 !e.ForceNonLiteral {
|
||||
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 ln := e.literalName(); ln != "" {
|
||||
return cty.StringVal(ln), nil
|
||||
}
|
||||
}
|
||||
return e.Wrapped.Value(ctx)
|
||||
}
|
||||
@ -861,6 +864,12 @@ func (e *ObjectConsKeyExpr) StartRange() hcl.Range {
|
||||
|
||||
// Implementation for hcl.AbsTraversalForExpr.
|
||||
func (e *ObjectConsKeyExpr) AsTraversal() hcl.Traversal {
|
||||
// If we're forcing a non-literal then we can never be interpreted
|
||||
// as a traversal.
|
||||
if e.ForceNonLiteral {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We can produce a traversal only if our wrappee can.
|
||||
st, diags := hcl.AbsTraversalForExpr(e.Wrapped)
|
||||
if diags.HasErrors() {
|
||||
|
@ -455,6 +455,46 @@ upper(
|
||||
cty.EmptyObjectVal, // (due to parser recovery behavior)
|
||||
1, // Missing key/value separator; Expected an equals sign ("=") to mark the beginning of the attribute value. If you intended to given an attribute name containing periods or spaces, write the name in quotes to create a string literal.
|
||||
},
|
||||
{
|
||||
`{var.greeting = "world"}`,
|
||||
&hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(map[string]cty.Value{
|
||||
"greeting": cty.StringVal("hello"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.DynamicVal,
|
||||
1, // Ambiguous attribute key
|
||||
},
|
||||
{
|
||||
`{(var.greeting) = "world"}`,
|
||||
&hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(map[string]cty.Value{
|
||||
"greeting": cty.StringVal("hello"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"hello": cty.StringVal("world"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`{"${var.greeting}" = "world"}`,
|
||||
&hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(map[string]cty.Value{
|
||||
"greeting": cty.StringVal("hello"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"hello": cty.StringVal("world"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`{"hello" = "world", "goodbye" = "cruel world"}`,
|
||||
nil,
|
||||
|
@ -1291,6 +1291,13 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) {
|
||||
break
|
||||
}
|
||||
|
||||
// Wrapping parens are not explicitly represented in the AST, but
|
||||
// we want to use them here to disambiguate intepreting a mapping
|
||||
// key as a full expression rather than just a name, and so
|
||||
// we'll remember this was present and use it to force the
|
||||
// behavior of our final ObjectConsKeyExpr.
|
||||
forceNonLiteral := (p.Peek().Type == TokenOParen)
|
||||
|
||||
var key Expression
|
||||
var keyDiags hcl.Diagnostics
|
||||
key, keyDiags = p.ParseExpression()
|
||||
@ -1307,7 +1314,10 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) {
|
||||
// We wrap up the key expression in a special wrapper that deals
|
||||
// with our special case that naked identifiers as object keys
|
||||
// are interpreted as literal strings.
|
||||
key = &ObjectConsKeyExpr{Wrapped: key}
|
||||
key = &ObjectConsKeyExpr{
|
||||
Wrapped: key,
|
||||
ForceNonLiteral: forceNonLiteral,
|
||||
}
|
||||
|
||||
next = p.Peek()
|
||||
if next.Type != TokenEqual && next.Type != TokenColon {
|
||||
|
Loading…
Reference in New Issue
Block a user