Skip to content

Commit

Permalink
Merge pull request #668 from hashicorp/add-expr-syntax-error
Browse files Browse the repository at this point in the history
add `ExprSyntaxError`
  • Loading branch information
ansgarm committed Mar 13, 2024
2 parents 57f8bbf + cc3af98 commit 5160967
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 12 deletions.
24 changes: 24 additions & 0 deletions hclsyntax/expression.go
Expand Up @@ -2013,3 +2013,27 @@ 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
SrcRange hcl.Range
}

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 e.SrcRange
}

func (e *ExprSyntaxError) StartRange() hcl.Range {
return e.SrcRange
}
22 changes: 22 additions & 0 deletions hclsyntax/expression_template_test.go
Expand Up @@ -438,6 +438,28 @@ trim`,

}

func TestTemplateExprGracefulValue(t *testing.T) {
// we don't care about diags since we know it's invalid config
expr, _ := ParseTemplate([]byte(`prefix${provider::}`), "", hcl.Pos{Line: 1, Column: 1, Byte: 0})

got, _ := expr.Value(nil) // this should not panic

if !got.RawEquals(cty.UnknownVal(cty.String).RefineNotNull()) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, cty.UnknownVal(cty.String).RefineNotNull())
}
}

func TestTemplateExprWrappedGracefulValue(t *testing.T) {
// we don't care about diags since we know it's invalid config
expr, _ := ParseTemplate([]byte(`${provider::}`), "", hcl.Pos{Line: 1, Column: 1, Byte: 0})

got, _ := expr.Value(nil) // this should not panic

if !got.RawEquals(cty.DynamicVal) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, cty.DynamicVal)
}
}

func TestTemplateExprIsStringLiteral(t *testing.T) {
tests := map[string]bool{
// A simple string value is a string literal
Expand Down
8 changes: 4 additions & 4 deletions hclsyntax/expression_test.go
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
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
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
23 changes: 17 additions & 6 deletions hclsyntax/parser.go
Expand Up @@ -1161,15 +1161,20 @@ 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,
SrcRange: hcl.RangeBetween(name.Range, nextName.Range),
}, diags
}

// Initial versions of HCLv2 didn't support function namespaces, and
Expand All @@ -1192,15 +1197,21 @@ 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,
SrcRange: hcl.RangeBetween(name.Range, openTok.Range),
}, diags
}

var args []Expression
Expand Down
114 changes: 114 additions & 0 deletions hclsyntax/parser_test.go
Expand Up @@ -2559,6 +2559,120 @@ 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{
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{
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 5160967

Please sign in to comment.