Skip to content

Commit 1cbb0d4

Browse files
committed
feat: return ExprSyntaxError instead of nil when expression parsing fails for namespaced functions
1 parent 57f8bbf commit 1cbb0d4

File tree

6 files changed

+154
-12
lines changed

6 files changed

+154
-12
lines changed

hclsyntax/expression.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,3 +2013,26 @@ func (e *AnonSymbolExpr) Range() hcl.Range {
20132013
func (e *AnonSymbolExpr) StartRange() hcl.Range {
20142014
return e.SrcRange
20152015
}
2016+
2017+
// ExprSyntaxError is a placeholder for an invalid expression that could not
2018+
// be parsed due to syntax errors.
2019+
type ExprSyntaxError struct {
2020+
Placeholder cty.Value
2021+
ParseDiags hcl.Diagnostics
2022+
}
2023+
2024+
func (e *ExprSyntaxError) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
2025+
return e.Placeholder, e.ParseDiags
2026+
}
2027+
2028+
func (e *ExprSyntaxError) walkChildNodes(w internalWalkFunc) {
2029+
// ExprSyntaxError is a leaf node in the tree
2030+
}
2031+
2032+
func (e *ExprSyntaxError) Range() hcl.Range {
2033+
return hcl.Range{}
2034+
}
2035+
2036+
func (e *ExprSyntaxError) StartRange() hcl.Range {
2037+
return hcl.Range{}
2038+
}

hclsyntax/expression_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,8 @@ upper(
379379
"double::::upper": stdlib.UpperFunc,
380380
},
381381
},
382-
cty.NilVal,
383-
1,
382+
cty.DynamicVal,
383+
2,
384384
},
385385
{
386386
`missing::("foo")`, // missing name after ::
@@ -389,8 +389,8 @@ upper(
389389
"missing::": stdlib.UpperFunc,
390390
},
391391
},
392-
cty.NilVal,
393-
1,
392+
cty.DynamicVal,
393+
2,
394394
},
395395
{
396396
`misbehave()`,

hclsyntax/expression_vars.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
package hclsyntax
55

6-
// Generated by expression_vars_get.go. DO NOT EDIT.
6+
// Generated by expression_vars_gen.go. DO NOT EDIT.
77
// Run 'go generate' on this package to update the set of functions here.
88

99
import (
@@ -22,6 +22,10 @@ func (e *ConditionalExpr) Variables() []hcl.Traversal {
2222
return Variables(e)
2323
}
2424

25+
func (e *ExprSyntaxError) Variables() []hcl.Traversal {
26+
return Variables(e)
27+
}
28+
2529
func (e *ForExpr) Variables() []hcl.Traversal {
2630
return Variables(e)
2731
}

hclsyntax/expression_vars_gen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const outputPreamble = `// Copyright (c) HashiCorp, Inc.
9292
9393
package hclsyntax
9494
95-
// Generated by expression_vars_get.go. DO NOT EDIT.
95+
// Generated by expression_vars_gen.go. DO NOT EDIT.
9696
// Run 'go generate' on this package to update the set of functions here.
9797
9898
import (

hclsyntax/parser.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,15 +1161,19 @@ func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnost
11611161
for openTok.Type == TokenDoubleColon {
11621162
nextName := p.Read()
11631163
if nextName.Type != TokenIdent {
1164-
diags = append(diags, &hcl.Diagnostic{
1164+
diag := hcl.Diagnostic{
11651165
Severity: hcl.DiagError,
11661166
Summary: "Missing function name",
11671167
Detail: "Function scope resolution symbol :: must be followed by a function name in this scope.",
11681168
Subject: &nextName.Range,
11691169
Context: hcl.RangeBetween(name.Range, nextName.Range).Ptr(),
1170-
})
1170+
}
1171+
diags = append(diags, &diag)
11711172
p.recoverOver(TokenOParen)
1172-
return nil, diags
1173+
return &ExprSyntaxError{
1174+
ParseDiags: hcl.Diagnostics{&diag},
1175+
Placeholder: cty.DynamicVal,
1176+
}, diags
11731177
}
11741178

11751179
// Initial versions of HCLv2 didn't support function namespaces, and
@@ -1192,15 +1196,20 @@ func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnost
11921196
}
11931197

11941198
if openTok.Type != TokenOParen {
1195-
diags = append(diags, &hcl.Diagnostic{
1199+
diag := hcl.Diagnostic{
11961200
Severity: hcl.DiagError,
11971201
Summary: "Missing open parenthesis",
11981202
Detail: "Function selector must be followed by an open parenthesis to begin the function call.",
11991203
Subject: &openTok.Range,
12001204
Context: hcl.RangeBetween(name.Range, openTok.Range).Ptr(),
1201-
})
1205+
}
1206+
1207+
diags = append(diags, &diag)
12021208
p.recoverOver(TokenOParen)
1203-
return nil, diags
1209+
return &ExprSyntaxError{
1210+
ParseDiags: hcl.Diagnostics{&diag},
1211+
Placeholder: cty.DynamicVal,
1212+
}, diags
12041213
}
12051214

12061215
var args []Expression

hclsyntax/parser_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2559,6 +2559,112 @@ block "valid" {}
25592559
},
25602560
},
25612561
},
2562+
{
2563+
"a = partial::namespaced\n",
2564+
1,
2565+
&Body{
2566+
Attributes: Attributes{
2567+
"a": {
2568+
Name: "a",
2569+
Expr: &ExprSyntaxError{
2570+
Placeholder: cty.DynamicVal,
2571+
ParseDiags: hcl.Diagnostics{
2572+
{
2573+
Severity: hcl.DiagError,
2574+
Summary: "Missing open parenthesis",
2575+
Detail: "Function selector must be followed by an open parenthesis to begin the function call.",
2576+
Subject: &hcl.Range{
2577+
Start: hcl.Pos{Line: 1, Column: 24, Byte: 23},
2578+
End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
2579+
},
2580+
Context: &hcl.Range{
2581+
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
2582+
End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
2583+
},
2584+
},
2585+
},
2586+
},
2587+
SrcRange: hcl.Range{
2588+
Filename: "",
2589+
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
2590+
End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
2591+
},
2592+
NameRange: hcl.Range{
2593+
Filename: "",
2594+
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
2595+
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
2596+
},
2597+
EqualsRange: hcl.Range{
2598+
Filename: "",
2599+
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
2600+
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
2601+
},
2602+
},
2603+
},
2604+
Blocks: Blocks{},
2605+
SrcRange: hcl.Range{
2606+
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
2607+
End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
2608+
},
2609+
EndRange: hcl.Range{
2610+
Start: hcl.Pos{Line: 2, Column: 1, Byte: 24},
2611+
End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
2612+
},
2613+
},
2614+
},
2615+
{
2616+
"a = partial::\n",
2617+
1,
2618+
&Body{
2619+
Attributes: Attributes{
2620+
"a": {
2621+
Name: "a",
2622+
Expr: &ExprSyntaxError{
2623+
Placeholder: cty.DynamicVal,
2624+
ParseDiags: hcl.Diagnostics{
2625+
{
2626+
Severity: hcl.DiagError,
2627+
Summary: "Missing function name",
2628+
Detail: "Function scope resolution symbol :: must be followed by a function name in this scope.",
2629+
Subject: &hcl.Range{
2630+
Start: hcl.Pos{Line: 1, Column: 14, Byte: 13},
2631+
End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
2632+
},
2633+
Context: &hcl.Range{
2634+
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
2635+
End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
2636+
},
2637+
},
2638+
},
2639+
},
2640+
SrcRange: hcl.Range{
2641+
Filename: "",
2642+
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
2643+
End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
2644+
},
2645+
NameRange: hcl.Range{
2646+
Filename: "",
2647+
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
2648+
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
2649+
},
2650+
EqualsRange: hcl.Range{
2651+
Filename: "",
2652+
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
2653+
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
2654+
},
2655+
},
2656+
},
2657+
Blocks: Blocks{},
2658+
SrcRange: hcl.Range{
2659+
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
2660+
End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
2661+
},
2662+
EndRange: hcl.Range{
2663+
Start: hcl.Pos{Line: 2, Column: 1, Byte: 14},
2664+
End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
2665+
},
2666+
},
2667+
},
25622668
}
25632669

25642670
for _, test := range tests {

0 commit comments

Comments
 (0)