diff --git a/hcl/hclsyntax/expression_test.go b/hcl/hclsyntax/expression_test.go index 0a9f16d..30379f8 100644 --- a/hcl/hclsyntax/expression_test.go +++ b/hcl/hclsyntax/expression_test.go @@ -507,6 +507,20 @@ upper( }), 0, }, + { + // This one is different than the previous because the extra level of + // object constructor causes the inner for expression to begin parsing + // in newline-sensitive mode, which it must then properly disable in + // order to peek the "for" keyword. + "{\n a = {\n for k, v in {hello: \"world\"}:\nk => v\n }\n}", + nil, + cty.ObjectVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "hello": cty.StringVal("world"), + }), + }), + 0, + }, { `{for k, v in {hello: "world"}: k => v if k == "hello"}`, nil, diff --git a/hcl/hclsyntax/parser.go b/hcl/hclsyntax/parser.go index 4a046f5..5513602 100644 --- a/hcl/hclsyntax/parser.go +++ b/hcl/hclsyntax/parser.go @@ -1242,7 +1242,13 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { panic("parseObjectCons called without peeker pointing to open brace") } - if forKeyword.TokenMatches(p.Peek()) { + // We must temporarily stop looking at newlines here while we check for + // a "for" keyword, since for expressions are _not_ newline-sensitive, + // even though object constructors are. + p.PushIncludeNewlines(false) + isFor := forKeyword.TokenMatches(p.Peek()) + p.PopIncludeNewlines() + if isFor { return p.finishParsingForExpr(open) } @@ -1377,6 +1383,8 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { } func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) { + p.PushIncludeNewlines(false) + defer p.PopIncludeNewlines() introducer := p.Read() if !forKeyword.TokenMatches(introducer) { // Should never happen if callers are behaving