zclsyntax: "expanding" function arguments
This syntax func(arg...) allows the final argument to be a sequence-typed value that then expands to be one argument for each element of the value. This allows applications to define variadic functions where that's user-friendly while still allowing users to pass tuples to those functions in situations where the args are chosen dynamically.
This commit is contained in:
parent
fa06f40141
commit
4ab33cdce0
@ -103,6 +103,10 @@ type FunctionCallExpr struct {
|
|||||||
Name string
|
Name string
|
||||||
Args []Expression
|
Args []Expression
|
||||||
|
|
||||||
|
// If true, the final argument should be a tuple, list or set which will
|
||||||
|
// expand to be one argument per element.
|
||||||
|
ExpandFinal bool
|
||||||
|
|
||||||
NameRange zcl.Range
|
NameRange zcl.Range
|
||||||
OpenParenRange zcl.Range
|
OpenParenRange zcl.Range
|
||||||
CloseParenRange zcl.Range
|
CloseParenRange zcl.Range
|
||||||
@ -155,8 +159,60 @@ func (e *FunctionCallExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnosti
|
|||||||
params := f.Params()
|
params := f.Params()
|
||||||
varParam := f.VarParam()
|
varParam := f.VarParam()
|
||||||
|
|
||||||
if len(e.Args) < len(params) {
|
args := e.Args
|
||||||
missing := params[len(e.Args)]
|
if e.ExpandFinal {
|
||||||
|
if len(args) < 1 {
|
||||||
|
// should never happen if the parser is behaving
|
||||||
|
panic("ExpandFinal set on function call with no arguments")
|
||||||
|
}
|
||||||
|
expandExpr := args[len(args)-1]
|
||||||
|
expandVal, expandDiags := expandExpr.Value(ctx)
|
||||||
|
diags = append(diags, expandDiags...)
|
||||||
|
if expandDiags.HasErrors() {
|
||||||
|
return cty.DynamicVal, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case expandVal.Type().IsTupleType() || expandVal.Type().IsListType() || expandVal.Type().IsSetType():
|
||||||
|
if expandVal.IsNull() {
|
||||||
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Invalid expanding argument value",
|
||||||
|
Detail: "The expanding argument (indicated by ...) must not be null.",
|
||||||
|
Context: expandExpr.Range().Ptr(),
|
||||||
|
Subject: e.Range().Ptr(),
|
||||||
|
})
|
||||||
|
return cty.DynamicVal, diags
|
||||||
|
}
|
||||||
|
if !expandVal.IsKnown() {
|
||||||
|
return cty.DynamicVal, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
newArgs := make([]Expression, 0, (len(args)-1)+expandVal.LengthInt())
|
||||||
|
newArgs = append(newArgs, args[:len(args)-1]...)
|
||||||
|
it := expandVal.ElementIterator()
|
||||||
|
for it.Next() {
|
||||||
|
_, val := it.Element()
|
||||||
|
newArgs = append(newArgs, &LiteralValueExpr{
|
||||||
|
Val: val,
|
||||||
|
SrcRange: expandExpr.Range(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
args = newArgs
|
||||||
|
default:
|
||||||
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Invalid expanding argument value",
|
||||||
|
Detail: "The expanding argument (indicated by ...) must be of a tuple, list, or set type.",
|
||||||
|
Context: expandExpr.Range().Ptr(),
|
||||||
|
Subject: e.Range().Ptr(),
|
||||||
|
})
|
||||||
|
return cty.DynamicVal, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < len(params) {
|
||||||
|
missing := params[len(args)]
|
||||||
qual := ""
|
qual := ""
|
||||||
if varParam != nil {
|
if varParam != nil {
|
||||||
qual = " at least"
|
qual = " at least"
|
||||||
@ -175,7 +231,7 @@ func (e *FunctionCallExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnosti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if varParam == nil && len(e.Args) > len(params) {
|
if varParam == nil && len(args) > len(params) {
|
||||||
return cty.DynamicVal, zcl.Diagnostics{
|
return cty.DynamicVal, zcl.Diagnostics{
|
||||||
{
|
{
|
||||||
Severity: zcl.DiagError,
|
Severity: zcl.DiagError,
|
||||||
@ -184,15 +240,15 @@ func (e *FunctionCallExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnosti
|
|||||||
"Function %q expects only %d argument(s).",
|
"Function %q expects only %d argument(s).",
|
||||||
e.Name, len(params),
|
e.Name, len(params),
|
||||||
),
|
),
|
||||||
Subject: e.Args[len(params)].StartRange().Ptr(),
|
Subject: args[len(params)].StartRange().Ptr(),
|
||||||
Context: e.Range().Ptr(),
|
Context: e.Range().Ptr(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
argVals := make([]cty.Value, len(e.Args))
|
argVals := make([]cty.Value, len(args))
|
||||||
|
|
||||||
for i, argExpr := range e.Args {
|
for i, argExpr := range args {
|
||||||
var param *function.Parameter
|
var param *function.Parameter
|
||||||
if i < len(params) {
|
if i < len(params) {
|
||||||
param = ¶ms[i]
|
param = ¶ms[i]
|
||||||
|
@ -242,6 +242,46 @@ upper(
|
|||||||
cty.StringVal("FOO"),
|
cty.StringVal("FOO"),
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`upper(["foo"]...)`,
|
||||||
|
&zcl.EvalContext{
|
||||||
|
Functions: map[string]function.Function{
|
||||||
|
"upper": stdlib.UpperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.StringVal("FOO"),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`upper("foo", []...)`,
|
||||||
|
&zcl.EvalContext{
|
||||||
|
Functions: map[string]function.Function{
|
||||||
|
"upper": stdlib.UpperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.StringVal("FOO"),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`upper("foo", "bar")`,
|
||||||
|
&zcl.EvalContext{
|
||||||
|
Functions: map[string]function.Function{
|
||||||
|
"upper": stdlib.UpperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.DynamicVal,
|
||||||
|
1, // too many function arguments
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`upper(["foo", "bar"]...)`,
|
||||||
|
&zcl.EvalContext{
|
||||||
|
Functions: map[string]function.Function{
|
||||||
|
"upper": stdlib.UpperFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.DynamicVal,
|
||||||
|
1, // too many function arguments
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`[]`,
|
`[]`,
|
||||||
nil,
|
nil,
|
||||||
|
@ -779,6 +779,7 @@ func (p *parser) finishParsingFunctionCall(name Token) (Expression, zcl.Diagnost
|
|||||||
|
|
||||||
var args []Expression
|
var args []Expression
|
||||||
var diags zcl.Diagnostics
|
var diags zcl.Diagnostics
|
||||||
|
var expandFinal bool
|
||||||
var closeTok Token
|
var closeTok Token
|
||||||
|
|
||||||
// Arbitrary newlines are allowed inside the function call parentheses.
|
// Arbitrary newlines are allowed inside the function call parentheses.
|
||||||
@ -810,6 +811,26 @@ Token:
|
|||||||
break Token
|
break Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sep.Type == TokenEllipsis {
|
||||||
|
expandFinal = true
|
||||||
|
|
||||||
|
if p.Peek().Type != TokenCParen {
|
||||||
|
if !p.recovery {
|
||||||
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Missing closing parenthesis",
|
||||||
|
Detail: "An expanded function argument (with ...) must be immediately followed by closing parentheses.",
|
||||||
|
Subject: &sep.Range,
|
||||||
|
Context: zcl.RangeBetween(name.Range, sep.Range).Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
closeTok = p.recover(TokenCParen)
|
||||||
|
} else {
|
||||||
|
closeTok = p.Read() // eat closing paren
|
||||||
|
}
|
||||||
|
break Token
|
||||||
|
}
|
||||||
|
|
||||||
if sep.Type != TokenComma {
|
if sep.Type != TokenComma {
|
||||||
diags = append(diags, &zcl.Diagnostic{
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
Severity: zcl.DiagError,
|
Severity: zcl.DiagError,
|
||||||
@ -818,7 +839,7 @@ Token:
|
|||||||
Subject: &sep.Range,
|
Subject: &sep.Range,
|
||||||
Context: zcl.RangeBetween(name.Range, sep.Range).Ptr(),
|
Context: zcl.RangeBetween(name.Range, sep.Range).Ptr(),
|
||||||
})
|
})
|
||||||
p.recover(TokenCParen)
|
closeTok = p.recover(TokenCParen)
|
||||||
break Token
|
break Token
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,6 +857,8 @@ Token:
|
|||||||
Name: string(name.Bytes),
|
Name: string(name.Bytes),
|
||||||
Args: args,
|
Args: args,
|
||||||
|
|
||||||
|
ExpandFinal: expandFinal,
|
||||||
|
|
||||||
NameRange: name.Range,
|
NameRange: name.Range,
|
||||||
OpenParenRange: openTok.Range,
|
OpenParenRange: openTok.Range,
|
||||||
CloseParenRange: closeTok.Range,
|
CloseParenRange: closeTok.Range,
|
||||||
|
Loading…
Reference in New Issue
Block a user