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
|
||||
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
|
||||
OpenParenRange zcl.Range
|
||||
CloseParenRange zcl.Range
|
||||
@ -155,8 +159,60 @@ func (e *FunctionCallExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnosti
|
||||
params := f.Params()
|
||||
varParam := f.VarParam()
|
||||
|
||||
if len(e.Args) < len(params) {
|
||||
missing := params[len(e.Args)]
|
||||
args := 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 := ""
|
||||
if varParam != nil {
|
||||
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{
|
||||
{
|
||||
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).",
|
||||
e.Name, len(params),
|
||||
),
|
||||
Subject: e.Args[len(params)].StartRange().Ptr(),
|
||||
Subject: args[len(params)].StartRange().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
|
||||
if i < len(params) {
|
||||
param = ¶ms[i]
|
||||
|
@ -242,6 +242,46 @@ upper(
|
||||
cty.StringVal("FOO"),
|
||||
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,
|
||||
|
@ -779,6 +779,7 @@ func (p *parser) finishParsingFunctionCall(name Token) (Expression, zcl.Diagnost
|
||||
|
||||
var args []Expression
|
||||
var diags zcl.Diagnostics
|
||||
var expandFinal bool
|
||||
var closeTok Token
|
||||
|
||||
// Arbitrary newlines are allowed inside the function call parentheses.
|
||||
@ -810,6 +811,26 @@ 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 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
@ -818,7 +839,7 @@ Token:
|
||||
Subject: &sep.Range,
|
||||
Context: zcl.RangeBetween(name.Range, sep.Range).Ptr(),
|
||||
})
|
||||
p.recover(TokenCParen)
|
||||
closeTok = p.recover(TokenCParen)
|
||||
break Token
|
||||
}
|
||||
|
||||
@ -836,6 +857,8 @@ Token:
|
||||
Name: string(name.Bytes),
|
||||
Args: args,
|
||||
|
||||
ExpandFinal: expandFinal,
|
||||
|
||||
NameRange: name.Range,
|
||||
OpenParenRange: openTok.Range,
|
||||
CloseParenRange: closeTok.Range,
|
||||
|
Loading…
Reference in New Issue
Block a user