Skip to content

Commit

Permalink
feat: return ExprSyntaxError instead of nil when expression parsing f…
Browse files Browse the repository at this point in the history
…ails for namespaced functions
  • Loading branch information
ansgarm committed Mar 12, 2024
1 parent 57f8bbf commit 1cbb0d4
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 12 deletions.
23 changes: 23 additions & 0 deletions hclsyntax/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
8 changes: 4 additions & 4 deletions hclsyntax/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,8 @@ upper(
"double::::upper": stdlib.UpperFunc,
},
},
cty.NilVal,
1,
cty.DynamicVal,
2,
},
{
`missing::("foo")`, // missing name after ::
Expand All @@ -389,8 +389,8 @@ upper(
"missing::": stdlib.UpperFunc,
},
},
cty.NilVal,
1,
cty.DynamicVal,
2,
},
{
`misbehave()`,
Expand Down
6 changes: 5 additions & 1 deletion hclsyntax/expression_vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion hclsyntax/expression_vars_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
21 changes: 15 additions & 6 deletions hclsyntax/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
106 changes: 106 additions & 0 deletions hclsyntax/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 1cbb0d4

Please sign in to comment.