Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ExprSyntaxError #668

Merged
merged 4 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions hclsyntax/expression.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
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
23 changes: 17 additions & 6 deletions hclsyntax/parser.go
Original file line number Diff line number Diff line change
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},
ansgarm marked this conversation as resolved.
Show resolved Hide resolved
Placeholder: cty.DynamicVal,
ansgarm marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
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