hclsyntax: Allow Terraform-style legacy index form
Terraform allowed indexing like foo.0.bar to work around HIL limitations, and so we'll permit that as a pragmatic way to accept existing Terraform configurations. However, we can't support this fully because our parser thinks that chained number indexes, like foo.0.0.bar, are single numbers. Since that usage in Terraform is very rare (there are very few lists of lists) we will mark that situation as an error with a helpful message suggesting to use the modern index syntax instead. This also turned up a similar bug in the existing legacy index handling we were doing for splat expressions, which is now handled in the same way.
This commit is contained in:
parent
061412b83a
commit
074b73b8b5
@ -762,6 +762,15 @@ upper(
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`[[[{name:"foo"}]], [[{name:"bar"}], [{name:"baz"}]]].*.0.0.name`,
|
||||
nil,
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.DynamicVal,
|
||||
cty.DynamicVal,
|
||||
}),
|
||||
1, // can't chain legacy index syntax together, like .0.0 (because 0.0 parses as a single number)
|
||||
},
|
||||
{
|
||||
// For an "attribute-only" splat, an index operator applies to
|
||||
// the splat result as a whole, rather than being incorporated
|
||||
@ -787,6 +796,24 @@ upper(
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`["hello"].0`,
|
||||
nil,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`[["hello"]].0.0`,
|
||||
nil,
|
||||
cty.DynamicVal,
|
||||
1, // can't chain legacy index syntax together (because 0.0 parses as 0)
|
||||
},
|
||||
{
|
||||
`[{greeting = "hello"}].0.greeting`,
|
||||
nil,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`[][0]`,
|
||||
nil,
|
||||
|
@ -477,6 +477,53 @@ Traversal:
|
||||
|
||||
ret = makeRelativeTraversal(ret, step, rng)
|
||||
|
||||
case TokenNumberLit:
|
||||
// This is a weird form we inherited from HIL, allowing numbers
|
||||
// to be used as attributes as a weird way of writing [n].
|
||||
// This was never actually a first-class thing in HIL, but
|
||||
// HIL tolerated sequences like .0. in its variable names and
|
||||
// calling applications like Terraform exploited that to
|
||||
// introduce indexing syntax where none existed.
|
||||
numTok := p.Read() // eat token
|
||||
attrTok = numTok
|
||||
|
||||
// This syntax is ambiguous if multiple indices are used in
|
||||
// succession, like foo.0.1.baz: that actually parses as
|
||||
// a fractional number 0.1. Since we're only supporting this
|
||||
// syntax for compatibility with legacy Terraform
|
||||
// configurations, and Terraform does not tend to have lists
|
||||
// of lists, we'll choose to reject that here with a helpful
|
||||
// error message, rather than failing later because the index
|
||||
// isn't a whole number.
|
||||
if dotIdx := bytes.IndexByte(numTok.Bytes, '.'); dotIdx >= 0 {
|
||||
first := numTok.Bytes[:dotIdx]
|
||||
second := numTok.Bytes[dotIdx+1:]
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid legacy index syntax",
|
||||
Detail: fmt.Sprintf("When using the legacy index syntax, chaining two indexes together is not permitted. Use the proper index syntax instead, like [%s][%s].", first, second),
|
||||
Subject: &attrTok.Range,
|
||||
})
|
||||
rng := hcl.RangeBetween(dot.Range, numTok.Range)
|
||||
step := hcl.TraverseIndex{
|
||||
Key: cty.DynamicVal,
|
||||
SrcRange: rng,
|
||||
}
|
||||
ret = makeRelativeTraversal(ret, step, rng)
|
||||
break
|
||||
}
|
||||
|
||||
numVal, numDiags := p.numberLitValue(numTok)
|
||||
diags = append(diags, numDiags...)
|
||||
|
||||
rng := hcl.RangeBetween(dot.Range, numTok.Range)
|
||||
step := hcl.TraverseIndex{
|
||||
Key: numVal,
|
||||
SrcRange: rng,
|
||||
}
|
||||
|
||||
ret = makeRelativeTraversal(ret, step, rng)
|
||||
|
||||
case TokenStar:
|
||||
// "Attribute-only" splat expression.
|
||||
// (This is a kinda weird construct inherited from HIL, which
|
||||
@ -497,6 +544,27 @@ Traversal:
|
||||
// into a list, for expressions like:
|
||||
// foo.bar.*.baz.0.foo
|
||||
numTok := p.Read()
|
||||
|
||||
// Weird special case if the user writes something
|
||||
// like foo.bar.*.baz.0.0.foo, where 0.0 parses
|
||||
// as a number.
|
||||
if dotIdx := bytes.IndexByte(numTok.Bytes, '.'); dotIdx >= 0 {
|
||||
first := numTok.Bytes[:dotIdx]
|
||||
second := numTok.Bytes[dotIdx+1:]
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid legacy index syntax",
|
||||
Detail: fmt.Sprintf("When using the legacy index syntax, chaining two indexes together is not permitted. Use the proper index syntax with a full splat expression [*] instead, like [%s][%s].", first, second),
|
||||
Subject: &attrTok.Range,
|
||||
})
|
||||
trav = append(trav, hcl.TraverseIndex{
|
||||
Key: cty.DynamicVal,
|
||||
SrcRange: hcl.RangeBetween(dot.Range, numTok.Range),
|
||||
})
|
||||
lastRange = numTok.Range
|
||||
continue
|
||||
}
|
||||
|
||||
numVal, numDiags := p.numberLitValue(numTok)
|
||||
diags = append(diags, numDiags...)
|
||||
trav = append(trav, hcl.TraverseIndex{
|
||||
|
@ -1263,6 +1263,72 @@ block "valid" {}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = foo.0.1.baz\n",
|
||||
1, // Chaining legacy index syntax is not supported
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"a": {
|
||||
Name: "a",
|
||||
Expr: &ScopeTraversalExpr{
|
||||
Traversal: hcl.Traversal{
|
||||
hcl.TraverseRoot{
|
||||
Name: "foo",
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 8, Byte: 7},
|
||||
},
|
||||
},
|
||||
hcl.TraverseIndex{
|
||||
Key: cty.DynamicVal,
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
},
|
||||
hcl.TraverseAttr{
|
||||
Name: "baz",
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 8, Byte: 7},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 16, Byte: 15},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 16, Byte: 15},
|
||||
},
|
||||
NameRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
EqualsRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocks: Blocks{},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 16},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 16},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = \"${var.public_subnets[count.index]}\"\n",
|
||||
0,
|
||||
|
Loading…
Reference in New Issue
Block a user