Skip to content

Commit eb9995d

Browse files
fix: allow keywords as block type names and labels
`in { ... }`, `for { ... }`, and `resource in { ... }` failed to parse while the matching attribute form `in = 1` already worked. Keywords are reserved only inside expressions; in a body they are ordinary names. Reuse the existing `_attribute_name` rule for the block type and label positions, and normalize KeywordRule/LiteralValueRule to IdentifierRule in the block transformer so labels stay a single type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 1874eb7 commit eb9995d

7 files changed

Lines changed: 192 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## \[Unreleased\]
99

10-
- Nothing yet.
10+
### Fixed
11+
12+
- Parse keywords as block types and block labels, not only as attribute names. `in { ... }`, `for { ... }`, `for_each { ... }`, and `resource in { ... }` all failed to parse even though the matching attribute form `in = 1` worked. Keywords are reserved only inside expressions; in a body they are ordinary names. ([#355](https://github.com/amplify-education/python-hcl2/pull/355))
1113

1214
## \[8.1.3\] - 2026-08-26
1315

hcl2/hcl2.lark

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ start : body
113113
body : (new_line_or_comment? (attribute | block))* new_line_or_comment?
114114
attribute : _attribute_name EQ expression
115115
_attribute_name : identifier | keyword | literal_value
116-
block : identifier (identifier | string)* new_line_or_comment? LBRACE body RBRACE
116+
block : _attribute_name (_attribute_name | string)* new_line_or_comment? LBRACE body RBRACE
117117

118118
// Whitespace and comments
119119
new_line_or_comment: ( NL_OR_COMMENT )+

hcl2/transformer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ def body(self, meta: Meta, args) -> BodyRule:
130130

131131
@v_args(meta=True)
132132
def block(self, meta: Meta, args) -> BlockRule:
133+
# _attribute_name is flattened, so the block type and bare labels may be
134+
# KeywordRule or LiteralValueRule; normalize so labels are all one type.
135+
args = [
136+
IdentifierRule([NAME(a.token.value)], meta)
137+
if isinstance(a, (KeywordRule, LiteralValueRule))
138+
else a
139+
for a in args
140+
]
133141
return BlockRule(args, meta)
134142

135143
@v_args(meta=True)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
resource "custom_provider_resource" "resource_name" {
2+
name = "resource_name"
3+
4+
if {
5+
value = "block_value1"
6+
}
7+
8+
in {
9+
value = "block_value2"
10+
}
11+
12+
for {
13+
value = "block_value3"
14+
}
15+
16+
for_each {
17+
value = "block_value4"
18+
}
19+
20+
true {
21+
value = "block_value5"
22+
}
23+
}
24+
25+
in "labeled_block" {
26+
value = "top_level_value"
27+
}
28+
29+
resource in {
30+
value = "keyword_label_value"
31+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
resource "custom_provider_resource" "resource_name" {
2+
name = "resource_name"
3+
4+
if {
5+
value = "block_value1"
6+
}
7+
8+
9+
in {
10+
value = "block_value2"
11+
}
12+
13+
14+
for {
15+
value = "block_value3"
16+
}
17+
18+
19+
for_each {
20+
value = "block_value4"
21+
}
22+
23+
24+
true {
25+
value = "block_value5"
26+
}
27+
}
28+
29+
30+
resource in {
31+
value = "keyword_label_value"
32+
}
33+
34+
35+
in "labeled_block" {
36+
value = "top_level_value"
37+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"resource": [
3+
{
4+
"\"custom_provider_resource\"": {
5+
"\"resource_name\"": {
6+
"name": "\"resource_name\"",
7+
"if": [
8+
{
9+
"value": "\"block_value1\"",
10+
"__is_block__": true
11+
}
12+
],
13+
"in": [
14+
{
15+
"value": "\"block_value2\"",
16+
"__is_block__": true
17+
}
18+
],
19+
"for": [
20+
{
21+
"value": "\"block_value3\"",
22+
"__is_block__": true
23+
}
24+
],
25+
"for_each": [
26+
{
27+
"value": "\"block_value4\"",
28+
"__is_block__": true
29+
}
30+
],
31+
"true": [
32+
{
33+
"value": "\"block_value5\"",
34+
"__is_block__": true
35+
}
36+
],
37+
"__is_block__": true
38+
}
39+
}
40+
},
41+
{
42+
"in": {
43+
"value": "\"keyword_label_value\"",
44+
"__is_block__": true
45+
}
46+
}
47+
],
48+
"in": [
49+
{
50+
"\"labeled_block\"": {
51+
"value": "\"top_level_value\"",
52+
"__is_block__": true
53+
}
54+
}
55+
]
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"resource": [
3+
{
4+
"\"custom_provider_resource\"": {
5+
"\"resource_name\"": {
6+
"name": "\"resource_name\"",
7+
"if": [
8+
{
9+
"value": "\"block_value1\"",
10+
"__is_block__": true
11+
}
12+
],
13+
"in": [
14+
{
15+
"value": "\"block_value2\"",
16+
"__is_block__": true
17+
}
18+
],
19+
"for": [
20+
{
21+
"value": "\"block_value3\"",
22+
"__is_block__": true
23+
}
24+
],
25+
"for_each": [
26+
{
27+
"value": "\"block_value4\"",
28+
"__is_block__": true
29+
}
30+
],
31+
"true": [
32+
{
33+
"value": "\"block_value5\"",
34+
"__is_block__": true
35+
}
36+
],
37+
"__is_block__": true
38+
}
39+
}
40+
},
41+
{
42+
"in": {
43+
"value": "\"keyword_label_value\"",
44+
"__is_block__": true
45+
}
46+
}
47+
],
48+
"in": [
49+
{
50+
"\"labeled_block\"": {
51+
"value": "\"top_level_value\"",
52+
"__is_block__": true
53+
}
54+
}
55+
]
56+
}

0 commit comments

Comments
 (0)