summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnsgar Mertens <ansgar@hashicorp.com>2024-03-12 09:36:49 +0100
committerAnsgar Mertens <ansgar@hashicorp.com>2024-03-12 09:36:49 +0100
commit1cbb0d41b1499559bd3dddc37d6873c28f179952 (patch)
treef045fc6d1cb12e38608b067d7b687e01c7b49a40
parent57f8bbf184a628a6dedb36520db1c90bfab7fb06 (diff)
feat: return ExprSyntaxError instead of nil when expression parsing fails for namespaced functions
-rw-r--r--hclsyntax/expression.go23
-rw-r--r--hclsyntax/expression_test.go8
-rwxr-xr-xhclsyntax/expression_vars.go6
-rw-r--r--hclsyntax/expression_vars_gen.go2
-rw-r--r--hclsyntax/parser.go21
-rw-r--r--hclsyntax/parser_test.go106
6 files changed, 154 insertions, 12 deletions
diff --git a/hclsyntax/expression.go b/hclsyntax/expression.go
index c4a353c..63bd24c 100644
--- a/hclsyntax/expression.go
+++ b/hclsyntax/expression.go
@@ -2013,3 +2013,26 @@ func (e *AnonSymbolExpr) Range() hcl.Range {
func (e *AnonSymbolExpr) StartRange() hcl.Range {
return e.SrcRange
}
+
+// ExprSyntaxError is a placeholder for an invalid expression that could not
+// be parsed due to syntax errors.
+type ExprSyntaxError struct {
+ Placeholder cty.Value
+ ParseDiags hcl.Diagnostics
+}
+
+func (e *ExprSyntaxError) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
+ return e.Placeholder, e.ParseDiags
+}
+
+func (e *ExprSyntaxError) walkChildNodes(w internalWalkFunc) {
+ // ExprSyntaxError is a leaf node in the tree
+}
+
+func (e *ExprSyntaxError) Range() hcl.Range {
+ return hcl.Range{}
+}
+
+func (e *ExprSyntaxError) StartRange() hcl.Range {
+ return hcl.Range{}
+}
diff --git a/hclsyntax/expression_test.go b/hclsyntax/expression_test.go
index 4f3f75f..b18214c 100644
--- a/hclsyntax/expression_test.go
+++ b/hclsyntax/expression_test.go
@@ -379,8 +379,8 @@ upper(
"double::::upper": stdlib.UpperFunc,
},
},
- cty.NilVal,
- 1,
+ cty.DynamicVal,
+ 2,
},
{
`missing::("foo")`, // missing name after ::
@@ -389,8 +389,8 @@ upper(
"missing::": stdlib.UpperFunc,
},
},
- cty.NilVal,
- 1,
+ cty.DynamicVal,
+ 2,
},
{
`misbehave()`,
diff --git a/hclsyntax/expression_vars.go b/hclsyntax/expression_vars.go
index ce5a5cb..6c3e472 100755
--- a/hclsyntax/expression_vars.go
+++ b/hclsyntax/expression_vars.go
@@ -3,7 +3,7 @@
package hclsyntax
-// Generated by expression_vars_get.go. DO NOT EDIT.
+// Generated by expression_vars_gen.go. DO NOT EDIT.
// Run 'go generate' on this package to update the set of functions here.
import (
@@ -22,6 +22,10 @@ func (e *ConditionalExpr) Variables() []hcl.Traversal {
return Variables(e)
}
+func (e *ExprSyntaxError) Variables() []hcl.Traversal {
+ return Variables(e)
+}
+
func (e *ForExpr) Variables() []hcl.Traversal {
return Variables(e)
}
diff --git a/hclsyntax/expression_vars_gen.go b/hclsyntax/expression_vars_gen.go
index efbc2d8..d088802 100644
--- a/hclsyntax/expression_vars_gen.go
+++ b/hclsyntax/expression_vars_gen.go
@@ -92,7 +92,7 @@ const outputPreamble = `// Copyright (c) HashiCorp, Inc.
package hclsyntax
-// Generated by expression_vars_get.go. DO NOT EDIT.
+// Generated by expression_vars_gen.go. DO NOT EDIT.
// Run 'go generate' on this package to update the set of functions here.
import (
diff --git a/hclsyntax/parser.go b/hclsyntax/parser.go
index cd9d63d..e9e8406 100644
--- a/hclsyntax/parser.go
+++ b/hclsyntax/parser.go
@@ -1161,15 +1161,19 @@ func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnost
for openTok.Type == TokenDoubleColon {
nextName := p.Read()
if nextName.Type != TokenIdent {
- diags = append(diags, &hcl.Diagnostic{
+ diag := hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing function name",
Detail: "Function scope resolution symbol :: must be followed by a function name in this scope.",
Subject: &nextName.Range,
Context: hcl.RangeBetween(name.Range, nextName.Range).Ptr(),
- })
+ }
+ diags = append(diags, &diag)
p.recoverOver(TokenOParen)
- return nil, diags
+ return &ExprSyntaxError{
+ ParseDiags: hcl.Diagnostics{&diag},
+ Placeholder: cty.DynamicVal,
+ }, diags
}
// Initial versions of HCLv2 didn't support function namespaces, and
@@ -1192,15 +1196,20 @@ func (p *parser) finishParsingFunctionCall(name Token) (Expression, hcl.Diagnost
}
if openTok.Type != TokenOParen {
- diags = append(diags, &hcl.Diagnostic{
+ diag := hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing open parenthesis",
Detail: "Function selector must be followed by an open parenthesis to begin the function call.",
Subject: &openTok.Range,
Context: hcl.RangeBetween(name.Range, openTok.Range).Ptr(),
- })
+ }
+
+ diags = append(diags, &diag)
p.recoverOver(TokenOParen)
- return nil, diags
+ return &ExprSyntaxError{
+ ParseDiags: hcl.Diagnostics{&diag},
+ Placeholder: cty.DynamicVal,
+ }, diags
}
var args []Expression
diff --git a/hclsyntax/parser_test.go b/hclsyntax/parser_test.go
index 6e226a6..2231fdb 100644
--- a/hclsyntax/parser_test.go
+++ b/hclsyntax/parser_test.go
@@ -2559,6 +2559,112 @@ block "valid" {}
},
},
},
+ {
+ "a = partial::namespaced\n",
+ 1,
+ &Body{
+ Attributes: Attributes{
+ "a": {
+ Name: "a",
+ Expr: &ExprSyntaxError{
+ Placeholder: cty.DynamicVal,
+ ParseDiags: hcl.Diagnostics{
+ {
+ Severity: hcl.DiagError,
+ Summary: "Missing open parenthesis",
+ Detail: "Function selector must be followed by an open parenthesis to begin the function call.",
+ Subject: &hcl.Range{
+ Start: hcl.Pos{Line: 1, Column: 24, Byte: 23},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
+ },
+ Context: &hcl.Range{
+ Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
+ },
+ },
+ },
+ },
+ SrcRange: hcl.Range{
+ Filename: "",
+ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
+ },
+ NameRange: hcl.Range{
+ Filename: "",
+ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
+ End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
+ },
+ EqualsRange: hcl.Range{
+ Filename: "",
+ 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: 24},
+ },
+ EndRange: hcl.Range{
+ Start: hcl.Pos{Line: 2, Column: 1, Byte: 24},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 24},
+ },
+ },
+ },
+ {
+ "a = partial::\n",
+ 1,
+ &Body{
+ Attributes: Attributes{
+ "a": {
+ Name: "a",
+ Expr: &ExprSyntaxError{
+ Placeholder: cty.DynamicVal,
+ ParseDiags: hcl.Diagnostics{
+ {
+ Severity: hcl.DiagError,
+ Summary: "Missing function name",
+ Detail: "Function scope resolution symbol :: must be followed by a function name in this scope.",
+ Subject: &hcl.Range{
+ Start: hcl.Pos{Line: 1, Column: 14, Byte: 13},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
+ },
+ Context: &hcl.Range{
+ Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
+ },
+ },
+ },
+ },
+ SrcRange: hcl.Range{
+ Filename: "",
+ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
+ },
+ NameRange: hcl.Range{
+ Filename: "",
+ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
+ End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
+ },
+ EqualsRange: hcl.Range{
+ Filename: "",
+ 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: 14},
+ },
+ EndRange: hcl.Range{
+ Start: hcl.Pos{Line: 2, Column: 1, Byte: 14},
+ End: hcl.Pos{Line: 2, Column: 1, Byte: 14},
+ },
+ },
+ },
}
for _, test := range tests {