4ab33cdce0
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.
830 lines
21 KiB
Go
830 lines
21 KiB
Go
package zclsyntax
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
"github.com/zclconf/go-cty/cty/function"
|
|
"github.com/zclconf/go-zcl/zcl"
|
|
)
|
|
|
|
// Expression is the abstract type for nodes that behave as zcl expressions.
|
|
type Expression interface {
|
|
Node
|
|
|
|
// The zcl.Expression methods are duplicated here, rather than simply
|
|
// embedded, because both Node and zcl.Expression have a Range method
|
|
// and so they conflict.
|
|
|
|
Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics)
|
|
Variables() []zcl.Traversal
|
|
StartRange() zcl.Range
|
|
}
|
|
|
|
// Assert that Expression implements zcl.Expression
|
|
var assertExprImplExpr zcl.Expression = Expression(nil)
|
|
|
|
// LiteralValueExpr is an expression that just always returns a given value.
|
|
type LiteralValueExpr struct {
|
|
Val cty.Value
|
|
SrcRange zcl.Range
|
|
}
|
|
|
|
func (e *LiteralValueExpr) walkChildNodes(w internalWalkFunc) {
|
|
// Literal values have no child nodes
|
|
}
|
|
|
|
func (e *LiteralValueExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
return e.Val, nil
|
|
}
|
|
|
|
func (e *LiteralValueExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *LiteralValueExpr) StartRange() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
// ScopeTraversalExpr is an Expression that retrieves a value from the scope
|
|
// using a traversal.
|
|
type ScopeTraversalExpr struct {
|
|
Traversal zcl.Traversal
|
|
SrcRange zcl.Range
|
|
}
|
|
|
|
func (e *ScopeTraversalExpr) walkChildNodes(w internalWalkFunc) {
|
|
// Scope traversals have no child nodes
|
|
}
|
|
|
|
func (e *ScopeTraversalExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
return e.Traversal.TraverseAbs(ctx)
|
|
}
|
|
|
|
func (e *ScopeTraversalExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *ScopeTraversalExpr) StartRange() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
// RelativeTraversalExpr is an Expression that retrieves a value from another
|
|
// value using a _relative_ traversal.
|
|
type RelativeTraversalExpr struct {
|
|
Source Expression
|
|
Traversal zcl.Traversal
|
|
SrcRange zcl.Range
|
|
}
|
|
|
|
func (e *RelativeTraversalExpr) walkChildNodes(w internalWalkFunc) {
|
|
// Scope traversals have no child nodes
|
|
}
|
|
|
|
func (e *RelativeTraversalExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
src, diags := e.Source.Value(ctx)
|
|
ret, travDiags := e.Traversal.TraverseRel(src)
|
|
diags = append(diags, travDiags...)
|
|
return ret, diags
|
|
}
|
|
|
|
func (e *RelativeTraversalExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *RelativeTraversalExpr) StartRange() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
// FunctionCallExpr is an Expression that calls a function from the EvalContext
|
|
// and returns its result.
|
|
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
|
|
}
|
|
|
|
func (e *FunctionCallExpr) walkChildNodes(w internalWalkFunc) {
|
|
for i, arg := range e.Args {
|
|
e.Args[i] = w(arg).(Expression)
|
|
}
|
|
}
|
|
|
|
func (e *FunctionCallExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
var diags zcl.Diagnostics
|
|
|
|
if ctx == nil || ctx.Functions == nil {
|
|
return cty.DynamicVal, zcl.Diagnostics{
|
|
{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Function calls not allowed",
|
|
Detail: "Functions may not be called here.",
|
|
Subject: &e.NameRange,
|
|
Context: e.Range().Ptr(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// FIXME: also need to look in ctx.parent, etc
|
|
f, exists := ctx.Functions[e.Name]
|
|
if !exists {
|
|
avail := make([]string, 0, len(ctx.Functions))
|
|
for name := range ctx.Functions {
|
|
avail = append(avail, name)
|
|
}
|
|
suggestion := nameSuggestion(e.Name, avail)
|
|
if suggestion != "" {
|
|
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
|
}
|
|
|
|
return cty.DynamicVal, zcl.Diagnostics{
|
|
{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Call to unknown function",
|
|
Detail: fmt.Sprintf("There is no function named %q.%s", e.Name, suggestion),
|
|
Subject: &e.NameRange,
|
|
Context: e.Range().Ptr(),
|
|
},
|
|
}
|
|
}
|
|
|
|
params := f.Params()
|
|
varParam := f.VarParam()
|
|
|
|
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"
|
|
}
|
|
return cty.DynamicVal, zcl.Diagnostics{
|
|
{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Not enough function arguments",
|
|
Detail: fmt.Sprintf(
|
|
"Function %q expects%s %d argument(s). Missing value for %q.",
|
|
e.Name, qual, len(params), missing.Name,
|
|
),
|
|
Subject: &e.CloseParenRange,
|
|
Context: e.Range().Ptr(),
|
|
},
|
|
}
|
|
}
|
|
|
|
if varParam == nil && len(args) > len(params) {
|
|
return cty.DynamicVal, zcl.Diagnostics{
|
|
{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Too many function arguments",
|
|
Detail: fmt.Sprintf(
|
|
"Function %q expects only %d argument(s).",
|
|
e.Name, len(params),
|
|
),
|
|
Subject: args[len(params)].StartRange().Ptr(),
|
|
Context: e.Range().Ptr(),
|
|
},
|
|
}
|
|
}
|
|
|
|
argVals := make([]cty.Value, len(args))
|
|
|
|
for i, argExpr := range args {
|
|
var param *function.Parameter
|
|
if i < len(params) {
|
|
param = ¶ms[i]
|
|
} else {
|
|
param = varParam
|
|
}
|
|
|
|
val, argDiags := argExpr.Value(ctx)
|
|
if len(argDiags) > 0 {
|
|
diags = append(diags, argDiags...)
|
|
}
|
|
|
|
// Try to convert our value to the parameter type
|
|
val, err := convert.Convert(val, param.Type)
|
|
if err != nil {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid function argument",
|
|
Detail: fmt.Sprintf(
|
|
"Invalid value for %q parameter: %s.",
|
|
param.Name, err,
|
|
),
|
|
Subject: argExpr.StartRange().Ptr(),
|
|
Context: e.Range().Ptr(),
|
|
})
|
|
}
|
|
|
|
argVals[i] = val
|
|
}
|
|
|
|
if diags.HasErrors() {
|
|
// Don't try to execute the function if we already have errors with
|
|
// the arguments, because the result will probably be a confusing
|
|
// error message.
|
|
return cty.DynamicVal, diags
|
|
}
|
|
|
|
resultVal, err := f.Call(argVals)
|
|
if err != nil {
|
|
switch terr := err.(type) {
|
|
case function.ArgError:
|
|
i := terr.Index
|
|
var param *function.Parameter
|
|
if i < len(params) {
|
|
param = ¶ms[i]
|
|
} else {
|
|
param = varParam
|
|
}
|
|
argExpr := e.Args[i]
|
|
|
|
// TODO: we should also unpick a PathError here and show the
|
|
// path to the deep value where the error was detected.
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid function argument",
|
|
Detail: fmt.Sprintf(
|
|
"Invalid value for %q parameter: %s.",
|
|
param.Name, err,
|
|
),
|
|
Subject: argExpr.StartRange().Ptr(),
|
|
Context: e.Range().Ptr(),
|
|
})
|
|
|
|
default:
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Error in function call",
|
|
Detail: fmt.Sprintf(
|
|
"Call to function %q failed: %s.",
|
|
e.Name, err,
|
|
),
|
|
Subject: e.StartRange().Ptr(),
|
|
Context: e.Range().Ptr(),
|
|
})
|
|
}
|
|
|
|
return cty.DynamicVal, diags
|
|
}
|
|
|
|
return resultVal, diags
|
|
}
|
|
|
|
func (e *FunctionCallExpr) Range() zcl.Range {
|
|
return zcl.RangeBetween(e.NameRange, e.CloseParenRange)
|
|
}
|
|
|
|
func (e *FunctionCallExpr) StartRange() zcl.Range {
|
|
return zcl.RangeBetween(e.NameRange, e.OpenParenRange)
|
|
}
|
|
|
|
type ConditionalExpr struct {
|
|
Condition Expression
|
|
TrueResult Expression
|
|
FalseResult Expression
|
|
|
|
SrcRange zcl.Range
|
|
}
|
|
|
|
func (e *ConditionalExpr) walkChildNodes(w internalWalkFunc) {
|
|
e.Condition = w(e.Condition).(Expression)
|
|
e.TrueResult = w(e.TrueResult).(Expression)
|
|
e.FalseResult = w(e.FalseResult).(Expression)
|
|
}
|
|
|
|
func (e *ConditionalExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
trueResult, trueDiags := e.TrueResult.Value(ctx)
|
|
falseResult, falseDiags := e.FalseResult.Value(ctx)
|
|
var diags zcl.Diagnostics
|
|
|
|
// Try to find a type that both results can be converted to.
|
|
resultType, convs := convert.UnifyUnsafe([]cty.Type{trueResult.Type(), falseResult.Type()})
|
|
if resultType == cty.NilType {
|
|
return cty.DynamicVal, zcl.Diagnostics{
|
|
{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Inconsistent conditional result types",
|
|
Detail: fmt.Sprintf(
|
|
// FIXME: Need a helper function for showing natural-language type diffs,
|
|
// since this will generate some useless messages in some cases, like
|
|
// "These expressions are object and object respectively" if the
|
|
// object types don't exactly match.
|
|
"The true and false result expressions must have consistent types. The given expressions are %s and %s, respectively.",
|
|
trueResult.Type(), falseResult.Type(),
|
|
),
|
|
Subject: zcl.RangeBetween(e.TrueResult.Range(), e.FalseResult.Range()).Ptr(),
|
|
Context: &e.SrcRange,
|
|
},
|
|
}
|
|
}
|
|
|
|
condResult, condDiags := e.Condition.Value(ctx)
|
|
diags = append(diags, condDiags...)
|
|
if condResult.IsNull() {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Null condition",
|
|
Detail: "The condition value is null. Conditions must either be true or false.",
|
|
Subject: e.Condition.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
return cty.UnknownVal(resultType), diags
|
|
}
|
|
if !condResult.IsKnown() {
|
|
return cty.UnknownVal(resultType), diags
|
|
}
|
|
condResult, err := convert.Convert(condResult, cty.Bool)
|
|
if err != nil {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Incorrect condition type",
|
|
Detail: fmt.Sprintf("The condition expression must be of type bool."),
|
|
Subject: e.Condition.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
return cty.UnknownVal(resultType), diags
|
|
}
|
|
|
|
if condResult.True() {
|
|
diags = append(diags, trueDiags...)
|
|
if convs[0] != nil {
|
|
var err error
|
|
trueResult, err = convs[0](trueResult)
|
|
if err != nil {
|
|
// Unsafe conversion failed with the concrete result value
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Inconsistent conditional result types",
|
|
Detail: fmt.Sprintf(
|
|
"The true result value has the wrong type: %s.",
|
|
err.Error(),
|
|
),
|
|
Subject: e.TrueResult.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
trueResult = cty.UnknownVal(resultType)
|
|
}
|
|
}
|
|
return trueResult, diags
|
|
} else {
|
|
diags = append(diags, falseDiags...)
|
|
if convs[1] != nil {
|
|
var err error
|
|
falseResult, err = convs[1](falseResult)
|
|
if err != nil {
|
|
// Unsafe conversion failed with the concrete result value
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Inconsistent conditional result types",
|
|
Detail: fmt.Sprintf(
|
|
"The false result value has the wrong type: %s.",
|
|
err.Error(),
|
|
),
|
|
Subject: e.TrueResult.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
falseResult = cty.UnknownVal(resultType)
|
|
}
|
|
}
|
|
return falseResult, diags
|
|
}
|
|
}
|
|
|
|
func (e *ConditionalExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *ConditionalExpr) StartRange() zcl.Range {
|
|
return e.Condition.StartRange()
|
|
}
|
|
|
|
type IndexExpr struct {
|
|
Collection Expression
|
|
Key Expression
|
|
|
|
SrcRange zcl.Range
|
|
OpenRange zcl.Range
|
|
}
|
|
|
|
func (e *IndexExpr) walkChildNodes(w internalWalkFunc) {
|
|
e.Collection = w(e.Collection).(Expression)
|
|
e.Key = w(e.Key).(Expression)
|
|
}
|
|
|
|
func (e *IndexExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
var diags zcl.Diagnostics
|
|
coll, collDiags := e.Collection.Value(ctx)
|
|
key, keyDiags := e.Key.Value(ctx)
|
|
diags = append(diags, collDiags...)
|
|
diags = append(diags, keyDiags...)
|
|
|
|
return zcl.Index(coll, key, &e.SrcRange)
|
|
}
|
|
|
|
func (e *IndexExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *IndexExpr) StartRange() zcl.Range {
|
|
return e.OpenRange
|
|
}
|
|
|
|
type TupleConsExpr struct {
|
|
Exprs []Expression
|
|
|
|
SrcRange zcl.Range
|
|
OpenRange zcl.Range
|
|
}
|
|
|
|
func (e *TupleConsExpr) walkChildNodes(w internalWalkFunc) {
|
|
for i, expr := range e.Exprs {
|
|
e.Exprs[i] = w(expr).(Expression)
|
|
}
|
|
}
|
|
|
|
func (e *TupleConsExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
var vals []cty.Value
|
|
var diags zcl.Diagnostics
|
|
|
|
vals = make([]cty.Value, len(e.Exprs))
|
|
for i, expr := range e.Exprs {
|
|
val, valDiags := expr.Value(ctx)
|
|
vals[i] = val
|
|
diags = append(diags, valDiags...)
|
|
}
|
|
|
|
return cty.TupleVal(vals), diags
|
|
}
|
|
|
|
func (e *TupleConsExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *TupleConsExpr) StartRange() zcl.Range {
|
|
return e.OpenRange
|
|
}
|
|
|
|
type ObjectConsExpr struct {
|
|
Items []ObjectConsItem
|
|
|
|
SrcRange zcl.Range
|
|
OpenRange zcl.Range
|
|
}
|
|
|
|
type ObjectConsItem struct {
|
|
KeyExpr Expression
|
|
ValueExpr Expression
|
|
}
|
|
|
|
func (e *ObjectConsExpr) walkChildNodes(w internalWalkFunc) {
|
|
for i, item := range e.Items {
|
|
e.Items[i].KeyExpr = w(item.KeyExpr).(Expression)
|
|
e.Items[i].ValueExpr = w(item.ValueExpr).(Expression)
|
|
}
|
|
}
|
|
|
|
func (e *ObjectConsExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
var vals map[string]cty.Value
|
|
var diags zcl.Diagnostics
|
|
|
|
// This will get set to true if we fail to produce any of our keys,
|
|
// either because they are actually unknown or if the evaluation produces
|
|
// errors. In all of these case we must return DynamicPseudoType because
|
|
// we're unable to know the full set of keys our object has, and thus
|
|
// we can't produce a complete value of the intended type.
|
|
//
|
|
// We still evaluate all of the item keys and values to make sure that we
|
|
// get as complete as possible a set of diagnostics.
|
|
known := true
|
|
|
|
vals = make(map[string]cty.Value, len(e.Items))
|
|
for _, item := range e.Items {
|
|
key, keyDiags := item.KeyExpr.Value(ctx)
|
|
diags = append(diags, keyDiags...)
|
|
|
|
val, valDiags := item.ValueExpr.Value(ctx)
|
|
diags = append(diags, valDiags...)
|
|
|
|
if keyDiags.HasErrors() {
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
if key.IsNull() {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Null value as key",
|
|
Detail: "Can't use a null value as a key.",
|
|
Subject: item.ValueExpr.Range().Ptr(),
|
|
})
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
key, err = convert.Convert(key, cty.String)
|
|
if err != nil {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Incorrect key type",
|
|
Detail: fmt.Sprintf("Can't use this value as a key: %s.", err.Error()),
|
|
Subject: item.ValueExpr.Range().Ptr(),
|
|
})
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
if !key.IsKnown() {
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
keyStr := key.AsString()
|
|
|
|
vals[keyStr] = val
|
|
}
|
|
|
|
if !known {
|
|
return cty.DynamicVal, diags
|
|
}
|
|
|
|
return cty.ObjectVal(vals), diags
|
|
}
|
|
|
|
func (e *ObjectConsExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *ObjectConsExpr) StartRange() zcl.Range {
|
|
return e.OpenRange
|
|
}
|
|
|
|
// ForExpr represents iteration constructs:
|
|
//
|
|
// tuple = [for i, v in list: upper(v) if i > 2]
|
|
// object = {for k, v in map: k => upper(v)}
|
|
// object_of_tuples = {for v in list: v.key: v...}
|
|
type ForExpr struct {
|
|
KeyVar string // empty if ignoring the key
|
|
ValVar string
|
|
|
|
CollExpr Expression
|
|
|
|
KeyExpr Expression // nil when producing a tuple
|
|
ValExpr Expression
|
|
CondExpr Expression // null if no "if" clause is present
|
|
|
|
Group bool // set if the ellipsis is used on the value in an object for
|
|
|
|
SrcRange zcl.Range
|
|
OpenRange zcl.Range
|
|
CloseRange zcl.Range
|
|
}
|
|
|
|
func (e *ForExpr) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
|
var diags zcl.Diagnostics
|
|
|
|
collVal, collDiags := e.CollExpr.Value(ctx)
|
|
diags = append(diags, collDiags...)
|
|
|
|
if collVal.IsNull() {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Iteration over null value",
|
|
Detail: "A null value cannot be used as the collection in a 'for' expression.",
|
|
Subject: e.CollExpr.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
return cty.DynamicVal, diags
|
|
}
|
|
if !collVal.IsKnown() {
|
|
return cty.DynamicVal, diags
|
|
}
|
|
if !collVal.CanIterateElements() {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Iteration over non-iterable value",
|
|
Detail: fmt.Sprintf(
|
|
"A value of type %s cannot be used as the collection in a 'for' expression.",
|
|
collVal.Type().FriendlyName(),
|
|
),
|
|
Subject: e.CollExpr.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
return cty.DynamicVal, diags
|
|
}
|
|
|
|
childCtx := ctx.NewChild()
|
|
childCtx.Variables = map[string]cty.Value{}
|
|
|
|
if e.KeyExpr != nil {
|
|
// Producing an object
|
|
vals := map[string]cty.Value{}
|
|
|
|
it := collVal.ElementIterator()
|
|
|
|
known := true
|
|
for it.Next() {
|
|
k, v := it.Element()
|
|
if e.KeyVar != "" {
|
|
childCtx.Variables[e.KeyVar] = k
|
|
}
|
|
childCtx.Variables[e.ValVar] = v
|
|
|
|
if e.CondExpr != nil {
|
|
includeRaw, condDiags := e.CondExpr.Value(childCtx)
|
|
diags = append(diags, condDiags...)
|
|
if includeRaw.IsNull() {
|
|
if known {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Condition is null",
|
|
Detail: "The value of the 'if' clause must not be null.",
|
|
Subject: e.CondExpr.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
}
|
|
known = false
|
|
continue
|
|
}
|
|
if !includeRaw.IsKnown() {
|
|
// We will eventually return DynamicVal, but we'll continue
|
|
// iterating in case there are other diagnostics to gather
|
|
// for later elements.
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
include, err := convert.Convert(includeRaw, cty.Bool)
|
|
if err != nil {
|
|
if known {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid 'for' condition",
|
|
Detail: fmt.Sprintf("The 'if' clause value is invalid: %s.", err.Error()),
|
|
Subject: e.CondExpr.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
}
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
if include.False() {
|
|
// Skip this element
|
|
continue
|
|
}
|
|
}
|
|
|
|
keyRaw, keyDiags := e.KeyExpr.Value(childCtx)
|
|
diags = append(diags, keyDiags...)
|
|
if keyRaw.IsNull() {
|
|
if known {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid object key",
|
|
Detail: "Key expression in 'for' expression must not produce a null value.",
|
|
Subject: e.KeyExpr.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
}
|
|
known = false
|
|
continue
|
|
}
|
|
if !keyRaw.IsKnown() {
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
key, err := convert.Convert(keyRaw, cty.String)
|
|
if err != nil {
|
|
if known {
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
Severity: zcl.DiagError,
|
|
Summary: "Invalid object key",
|
|
Detail: fmt.Sprintf("The key expression produced an invalid result: %s.", err.Error()),
|
|
Subject: e.KeyExpr.Range().Ptr(),
|
|
Context: &e.SrcRange,
|
|
})
|
|
}
|
|
known = false
|
|
continue
|
|
}
|
|
|
|
val, valDiags := e.ValExpr.Value(childCtx)
|
|
diags = append(diags, valDiags...)
|
|
vals[key.AsString()] = val
|
|
}
|
|
|
|
if !known {
|
|
return cty.DynamicVal, diags
|
|
}
|
|
|
|
return cty.ObjectVal(vals), diags
|
|
|
|
} else {
|
|
// Producing a tuple
|
|
vals := []cty.Value{}
|
|
panic("for into a tuple is not yet implemented")
|
|
return cty.TupleVal(vals), diags
|
|
}
|
|
}
|
|
|
|
func (e *ForExpr) walkChildNodes(w internalWalkFunc) {
|
|
e.CollExpr = w(e.CollExpr).(Expression)
|
|
|
|
scopeNames := map[string]struct{}{}
|
|
if e.KeyVar != "" {
|
|
scopeNames[e.KeyVar] = struct{}{}
|
|
}
|
|
if e.ValVar != "" {
|
|
scopeNames[e.ValVar] = struct{}{}
|
|
}
|
|
|
|
if e.KeyExpr != nil {
|
|
w(ChildScope{
|
|
LocalNames: scopeNames,
|
|
Expr: &e.KeyExpr,
|
|
})
|
|
}
|
|
w(ChildScope{
|
|
LocalNames: scopeNames,
|
|
Expr: &e.ValExpr,
|
|
})
|
|
if e.CondExpr != nil {
|
|
w(ChildScope{
|
|
LocalNames: scopeNames,
|
|
Expr: &e.CondExpr,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (e *ForExpr) Range() zcl.Range {
|
|
return e.SrcRange
|
|
}
|
|
|
|
func (e *ForExpr) StartRange() zcl.Range {
|
|
return e.OpenRange
|
|
}
|