2017-09-11 23:40:37 +00:00
package hclsyntax
2017-05-23 15:05:44 +00:00
import (
2017-05-25 15:14:43 +00:00
"fmt"
2018-05-23 23:38:39 +00:00
"sync"
2017-05-25 15:14:43 +00:00
2019-09-09 23:08:19 +00:00
"github.com/hashicorp/hcl/v2"
2019-12-13 16:52:25 +00:00
"github.com/hashicorp/hcl/v2/ext/customdecode"
2017-05-28 00:35:44 +00:00
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
2017-05-23 15:05:44 +00:00
)
2018-01-27 18:42:01 +00:00
// Expression is the abstract type for nodes that behave as HCL expressions.
2017-05-23 15:05:44 +00:00
type Expression interface {
Node
2017-09-11 23:40:37 +00:00
// The hcl.Expression methods are duplicated here, rather than simply
// embedded, because both Node and hcl.Expression have a Range method
2017-05-23 15:05:44 +00:00
// and so they conflict.
2017-09-11 23:40:37 +00:00
Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics )
Variables ( ) [ ] hcl . Traversal
StartRange ( ) hcl . Range
2017-05-23 15:05:44 +00:00
}
2017-09-11 23:40:37 +00:00
// Assert that Expression implements hcl.Expression
var assertExprImplExpr hcl . Expression = Expression ( nil )
2017-05-23 15:05:44 +00:00
2020-12-02 19:39:03 +00:00
// ParenthesesExpr represents an expression written in grouping
// parentheses.
//
// The parser takes care of the precedence effect of the parentheses, so the
// only purpose of this separate expression node is to capture the source range
// of the parentheses themselves, rather than the source range of the
// expression within. All of the other expression operations just pass through
// to the underlying expression.
type ParenthesesExpr struct {
Expression
SrcRange hcl . Range
}
var _ hcl . Expression = ( * ParenthesesExpr ) ( nil )
func ( e * ParenthesesExpr ) Range ( ) hcl . Range {
return e . SrcRange
}
func ( e * ParenthesesExpr ) walkChildNodes ( w internalWalkFunc ) {
// We override the walkChildNodes from the embedded Expression to
// ensure that both the parentheses _and_ the content are visible
// in a walk.
w ( e . Expression )
}
2017-05-23 15:05:44 +00:00
// LiteralValueExpr is an expression that just always returns a given value.
type LiteralValueExpr struct {
Val cty . Value
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
2017-05-23 15:05:44 +00:00
}
func ( e * LiteralValueExpr ) walkChildNodes ( w internalWalkFunc ) {
// Literal values have no child nodes
}
2017-09-11 23:40:37 +00:00
func ( e * LiteralValueExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-05-23 15:05:44 +00:00
return e . Val , nil
}
2017-09-11 23:40:37 +00:00
func ( e * LiteralValueExpr ) Range ( ) hcl . Range {
2017-05-23 15:05:44 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * LiteralValueExpr ) StartRange ( ) hcl . Range {
2017-05-24 15:05:52 +00:00
return e . SrcRange
}
2018-02-26 16:38:35 +00:00
// Implementation for hcl.AbsTraversalForExpr.
func ( e * LiteralValueExpr ) AsTraversal ( ) hcl . Traversal {
// This one's a little weird: the contract for AsTraversal is to interpret
// an expression as if it were traversal syntax, and traversal syntax
// doesn't have the special keywords "null", "true", and "false" so these
// are expected to be treated like variables in that case.
// Since our parser already turned them into LiteralValueExpr by the time
// we get here, we need to undo this and infer the name that would've
// originally led to our value.
// We don't do anything for any other values, since they don't overlap
// with traversal roots.
if e . Val . IsNull ( ) {
// In practice the parser only generates null values of the dynamic
// pseudo-type for literals, so we can safely assume that any null
// was orignally the keyword "null".
return hcl . Traversal {
hcl . TraverseRoot {
Name : "null" ,
SrcRange : e . SrcRange ,
} ,
}
}
switch e . Val {
case cty . True :
return hcl . Traversal {
hcl . TraverseRoot {
Name : "true" ,
SrcRange : e . SrcRange ,
} ,
}
case cty . False :
return hcl . Traversal {
hcl . TraverseRoot {
Name : "false" ,
SrcRange : e . SrcRange ,
} ,
}
default :
// No traversal is possible for any other value.
return nil
}
}
2017-05-24 15:05:52 +00:00
// ScopeTraversalExpr is an Expression that retrieves a value from the scope
// using a traversal.
type ScopeTraversalExpr struct {
2017-09-11 23:40:37 +00:00
Traversal hcl . Traversal
SrcRange hcl . Range
2017-05-24 15:05:52 +00:00
}
func ( e * ScopeTraversalExpr ) walkChildNodes ( w internalWalkFunc ) {
// Scope traversals have no child nodes
}
2017-09-11 23:40:37 +00:00
func ( e * ScopeTraversalExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2018-07-28 20:14:36 +00:00
val , diags := e . Traversal . TraverseAbs ( ctx )
2018-07-28 20:36:55 +00:00
setDiagEvalContext ( diags , e , ctx )
2018-07-28 20:14:36 +00:00
return val , diags
2017-05-24 15:05:52 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * ScopeTraversalExpr ) Range ( ) hcl . Range {
2017-05-24 15:05:52 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * ScopeTraversalExpr ) StartRange ( ) hcl . Range {
2017-05-24 15:05:52 +00:00
return e . SrcRange
2017-05-23 15:05:44 +00:00
}
2017-05-24 15:51:34 +00:00
2018-01-13 06:58:55 +00:00
// Implementation for hcl.AbsTraversalForExpr.
func ( e * ScopeTraversalExpr ) AsTraversal ( ) hcl . Traversal {
return e . Traversal
}
2017-06-05 01:40:15 +00:00
// RelativeTraversalExpr is an Expression that retrieves a value from another
// value using a _relative_ traversal.
type RelativeTraversalExpr struct {
Source Expression
2017-09-11 23:40:37 +00:00
Traversal hcl . Traversal
SrcRange hcl . Range
2017-06-05 01:40:15 +00:00
}
func ( e * RelativeTraversalExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
w ( e . Source )
2017-06-05 01:40:15 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * RelativeTraversalExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-06-05 14:41:02 +00:00
src , diags := e . Source . Value ( ctx )
ret , travDiags := e . Traversal . TraverseRel ( src )
2018-07-28 20:36:55 +00:00
setDiagEvalContext ( travDiags , e , ctx )
2017-06-05 14:41:02 +00:00
diags = append ( diags , travDiags ... )
return ret , diags
2017-06-05 01:40:15 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * RelativeTraversalExpr ) Range ( ) hcl . Range {
2017-06-05 01:40:15 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * RelativeTraversalExpr ) StartRange ( ) hcl . Range {
2017-06-05 01:40:15 +00:00
return e . SrcRange
}
2018-02-26 16:38:35 +00:00
// Implementation for hcl.AbsTraversalForExpr.
func ( e * RelativeTraversalExpr ) AsTraversal ( ) hcl . Traversal {
// We can produce a traversal only if our source can.
st , diags := hcl . AbsTraversalForExpr ( e . Source )
if diags . HasErrors ( ) {
return nil
}
ret := make ( hcl . Traversal , len ( st ) + len ( e . Traversal ) )
copy ( ret , st )
copy ( ret [ len ( st ) : ] , e . Traversal )
return ret
}
2017-05-24 15:51:34 +00:00
// FunctionCallExpr is an Expression that calls a function from the EvalContext
// and returns its result.
type FunctionCallExpr struct {
Name string
Args [ ] Expression
2017-06-15 15:18:00 +00:00
// If true, the final argument should be a tuple, list or set which will
// expand to be one argument per element.
ExpandFinal bool
2017-09-11 23:40:37 +00:00
NameRange hcl . Range
OpenParenRange hcl . Range
CloseParenRange hcl . Range
2017-05-24 15:51:34 +00:00
}
func ( e * FunctionCallExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
for _ , arg := range e . Args {
w ( arg )
2017-05-24 15:51:34 +00:00
}
}
2017-09-11 23:40:37 +00:00
func ( e * FunctionCallExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
var diags hcl . Diagnostics
2017-05-25 15:14:43 +00:00
2017-06-16 14:28:29 +00:00
var f function . Function
exists := false
hasNonNilMap := false
thisCtx := ctx
for thisCtx != nil {
if thisCtx . Functions == nil {
thisCtx = thisCtx . Parent ( )
continue
2017-06-05 14:31:09 +00:00
}
2017-06-16 14:28:29 +00:00
hasNonNilMap = true
f , exists = thisCtx . Functions [ e . Name ]
if exists {
break
}
thisCtx = thisCtx . Parent ( )
2017-06-05 14:31:09 +00:00
}
2017-05-25 15:14:43 +00:00
if ! exists {
2017-06-16 14:28:29 +00:00
if ! hasNonNilMap {
2017-09-11 23:40:37 +00:00
return cty . DynamicVal , hcl . Diagnostics {
2017-06-16 14:28:29 +00:00
{
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Function calls not allowed" ,
Detail : "Functions may not be called here." ,
Subject : e . Range ( ) . Ptr ( ) ,
2018-07-28 20:36:55 +00:00
Expression : e ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-16 14:28:29 +00:00
} ,
}
}
2017-05-25 15:14:43 +00:00
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 )
}
2017-09-11 23:40:37 +00:00
return cty . DynamicVal , hcl . Diagnostics {
2017-05-25 15:14:43 +00:00
{
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ( ) ,
2018-07-28 20:36:55 +00:00
Expression : e ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-25 15:14:43 +00:00
} ,
}
}
params := f . Params ( )
varParam := f . VarParam ( )
2017-06-15 15:18:00 +00:00
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 {
2020-06-03 13:33:38 +00:00
case expandVal . Type ( ) . Equals ( cty . DynamicPseudoType ) :
2020-06-03 13:50:50 +00:00
if expandVal . IsNull ( ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid expanding argument value" ,
Detail : "The expanding argument (indicated by ...) must not be null." ,
Subject : expandExpr . Range ( ) . Ptr ( ) ,
Context : e . Range ( ) . Ptr ( ) ,
Expression : expandExpr ,
EvalContext : ctx ,
} )
return cty . DynamicVal , diags
}
2020-06-03 13:33:38 +00:00
return cty . DynamicVal , diags
2017-06-15 15:18:00 +00:00
case expandVal . Type ( ) . IsTupleType ( ) || expandVal . Type ( ) . IsListType ( ) || expandVal . Type ( ) . IsSetType ( ) :
if expandVal . IsNull ( ) {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Invalid expanding argument value" ,
Detail : "The expanding argument (indicated by ...) must not be null." ,
2018-07-28 20:36:55 +00:00
Subject : expandExpr . Range ( ) . Ptr ( ) ,
Context : e . Range ( ) . Ptr ( ) ,
Expression : expandExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-15 15:18:00 +00:00
} )
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 :
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Invalid expanding argument value" ,
Detail : "The expanding argument (indicated by ...) must be of a tuple, list, or set type." ,
2018-07-28 20:36:55 +00:00
Subject : expandExpr . Range ( ) . Ptr ( ) ,
Context : e . Range ( ) . Ptr ( ) ,
Expression : expandExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-15 15:18:00 +00:00
} )
return cty . DynamicVal , diags
}
}
if len ( args ) < len ( params ) {
missing := params [ len ( args ) ]
2017-05-25 15:14:43 +00:00
qual := ""
if varParam != nil {
qual = " at least"
}
2017-09-11 23:40:37 +00:00
return cty . DynamicVal , hcl . Diagnostics {
2017-05-25 15:14:43 +00:00
{
2017-09-11 23:40:37 +00:00
Severity : hcl . DiagError ,
2017-05-25 15:14:43 +00:00
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 ,
) ,
2018-07-28 20:14:36 +00:00
Subject : & e . CloseParenRange ,
Context : e . Range ( ) . Ptr ( ) ,
2018-07-28 20:36:55 +00:00
Expression : e ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-25 15:14:43 +00:00
} ,
}
}
2017-06-15 15:18:00 +00:00
if varParam == nil && len ( args ) > len ( params ) {
2017-09-11 23:40:37 +00:00
return cty . DynamicVal , hcl . Diagnostics {
2017-05-25 15:14:43 +00:00
{
2017-09-11 23:40:37 +00:00
Severity : hcl . DiagError ,
2017-05-25 15:14:43 +00:00
Summary : "Too many function arguments" ,
Detail : fmt . Sprintf (
"Function %q expects only %d argument(s)." ,
e . Name , len ( params ) ,
) ,
2018-07-28 20:14:36 +00:00
Subject : args [ len ( params ) ] . StartRange ( ) . Ptr ( ) ,
Context : e . Range ( ) . Ptr ( ) ,
2018-07-28 20:36:55 +00:00
Expression : e ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-25 15:14:43 +00:00
} ,
}
}
2017-06-15 15:18:00 +00:00
argVals := make ( [ ] cty . Value , len ( args ) )
2017-05-25 15:14:43 +00:00
2017-06-15 15:18:00 +00:00
for i , argExpr := range args {
2017-05-25 15:14:43 +00:00
var param * function . Parameter
if i < len ( params ) {
param = & params [ i ]
} else {
param = varParam
}
2019-12-13 16:52:25 +00:00
var val cty . Value
if decodeFn := customdecode . CustomExpressionDecoderForType ( param . Type ) ; decodeFn != nil {
var argDiags hcl . Diagnostics
val , argDiags = decodeFn ( argExpr , ctx )
2017-05-25 15:14:43 +00:00
diags = append ( diags , argDiags ... )
2019-12-13 16:52:25 +00:00
if val == cty . NilVal {
val = cty . UnknownVal ( param . Type )
}
} else {
var argDiags hcl . Diagnostics
val , argDiags = argExpr . Value ( ctx )
if len ( argDiags ) > 0 {
diags = append ( diags , argDiags ... )
}
2017-05-25 15:14:43 +00:00
2019-12-13 16:52:25 +00:00
// Try to convert our value to the parameter type
var err error
val , err = convert . Convert ( val , param . Type )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . 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 ( ) ,
Expression : argExpr ,
EvalContext : ctx ,
} )
}
2017-05-25 15:14:43 +00:00
}
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 = & params [ i ]
} else {
param = varParam
}
2020-06-03 13:13:36 +00:00
// this can happen if an argument is (incorrectly) null.
if i > len ( e . Args ) - 1 {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid function argument" ,
Detail : fmt . Sprintf (
"Invalid value for %q parameter: %s." ,
param . Name , err ,
) ,
Subject : args [ len ( params ) ] . StartRange ( ) . Ptr ( ) ,
Context : e . Range ( ) . Ptr ( ) ,
Expression : e ,
EvalContext : ctx ,
} )
} else {
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 , & hcl . Diagnostic {
Severity : hcl . 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 ( ) ,
Expression : argExpr ,
EvalContext : ctx ,
} )
}
2017-05-25 15:14:43 +00:00
default :
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
2017-05-25 15:14:43 +00:00
Summary : "Error in function call" ,
Detail : fmt . Sprintf (
"Call to function %q failed: %s." ,
e . Name , err ,
) ,
2018-07-28 20:14:36 +00:00
Subject : e . StartRange ( ) . Ptr ( ) ,
Context : e . Range ( ) . Ptr ( ) ,
2018-07-28 20:36:55 +00:00
Expression : e ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-25 15:14:43 +00:00
} )
}
return cty . DynamicVal , diags
}
return resultVal , diags
2017-05-24 15:51:34 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * FunctionCallExpr ) Range ( ) hcl . Range {
return hcl . RangeBetween ( e . NameRange , e . CloseParenRange )
2017-05-24 15:51:34 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * FunctionCallExpr ) StartRange ( ) hcl . Range {
return hcl . RangeBetween ( e . NameRange , e . OpenParenRange )
2017-05-24 15:51:34 +00:00
}
2017-05-31 15:37:17 +00:00
2018-03-04 22:04:54 +00:00
// Implementation for hcl.ExprCall.
func ( e * FunctionCallExpr ) ExprCall ( ) * hcl . StaticCall {
ret := & hcl . StaticCall {
Name : e . Name ,
NameRange : e . NameRange ,
Arguments : make ( [ ] hcl . Expression , len ( e . Args ) ) ,
ArgsRange : hcl . RangeBetween ( e . OpenParenRange , e . CloseParenRange ) ,
}
// Need to convert our own Expression objects into hcl.Expression.
for i , arg := range e . Args {
ret . Arguments [ i ] = arg
}
return ret
}
2017-05-31 15:37:17 +00:00
type ConditionalExpr struct {
Condition Expression
TrueResult Expression
FalseResult Expression
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
2017-05-31 15:37:17 +00:00
}
func ( e * ConditionalExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
w ( e . Condition )
w ( e . TrueResult )
w ( e . FalseResult )
2017-05-31 15:37:17 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * ConditionalExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-05-31 15:37:17 +00:00
trueResult , trueDiags := e . TrueResult . Value ( ctx )
falseResult , falseDiags := e . FalseResult . Value ( ctx )
2017-09-11 23:40:37 +00:00
var diags hcl . Diagnostics
2017-05-31 15:37:17 +00:00
2019-07-19 00:26:11 +00:00
resultType := cty . DynamicPseudoType
2019-07-02 18:56:34 +00:00
convs := make ( [ ] convert . Conversion , 2 )
switch {
// If either case is a dynamic null value (which would result from a
// literal null in the config), we know that it can convert to the expected
// type of the opposite case, and we don't need to speculatively reduce the
// final result type to DynamicPseudoType.
2019-07-19 00:26:11 +00:00
// If we know that either Type is a DynamicPseudoType, we can be certain
// that the other value can convert since it's a pass-through, and we don't
// need to unify the types. If the final evaluation results in the dynamic
// value being returned, there's no conversion we can do, so we return the
// value directly.
2019-07-02 18:56:34 +00:00
case trueResult . RawEquals ( cty . NullVal ( cty . DynamicPseudoType ) ) :
resultType = falseResult . Type ( )
convs [ 0 ] = convert . GetConversionUnsafe ( cty . DynamicPseudoType , resultType )
case falseResult . RawEquals ( cty . NullVal ( cty . DynamicPseudoType ) ) :
resultType = trueResult . Type ( )
convs [ 1 ] = convert . GetConversionUnsafe ( cty . DynamicPseudoType , resultType )
2019-07-19 00:26:11 +00:00
case trueResult . Type ( ) == cty . DynamicPseudoType , falseResult . Type ( ) == cty . DynamicPseudoType :
// the final resultType type is still unknown
// we don't need to get the conversion, because both are a noop.
2019-07-02 18:56:34 +00:00
default :
// Try to find a type that both results can be converted to.
resultType , convs = convert . UnifyUnsafe ( [ ] cty . Type { trueResult . Type ( ) , falseResult . Type ( ) } )
}
2017-05-31 15:37:17 +00:00
if resultType == cty . NilType {
2017-09-11 23:40:37 +00:00
return cty . DynamicVal , hcl . Diagnostics {
2017-05-31 15:37:17 +00:00
{
2017-09-11 23:40:37 +00:00
Severity : hcl . DiagError ,
2017-05-31 15:37:17 +00:00
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." ,
2018-07-28 22:29:34 +00:00
trueResult . Type ( ) . FriendlyName ( ) , falseResult . Type ( ) . FriendlyName ( ) ,
2017-05-31 15:37:17 +00:00
) ,
2018-07-28 20:14:36 +00:00
Subject : hcl . RangeBetween ( e . TrueResult . Range ( ) , e . FalseResult . Range ( ) ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 20:36:55 +00:00
Expression : e ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-31 15:37:17 +00:00
} ,
}
}
condResult , condDiags := e . Condition . Value ( ctx )
diags = append ( diags , condDiags ... )
if condResult . IsNull ( ) {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 20:36:55 +00:00
Expression : e . Condition ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-31 15:37:17 +00:00
} )
return cty . UnknownVal ( resultType ) , diags
}
if ! condResult . IsKnown ( ) {
return cty . UnknownVal ( resultType ) , diags
}
2017-06-01 02:30:17 +00:00
condResult , err := convert . Convert ( condResult , cty . Bool )
if err != nil {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Incorrect condition type" ,
Detail : fmt . Sprintf ( "The condition expression must be of type bool." ) ,
Subject : e . Condition . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 20:36:55 +00:00
Expression : e . Condition ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-01 02:30:17 +00:00
} )
return cty . UnknownVal ( resultType ) , diags
}
2017-05-31 15:37:17 +00:00
2020-12-03 15:51:18 +00:00
// Unmark result before testing for truthiness
condResult , _ = condResult . UnmarkDeep ( )
2017-05-31 15:37:17 +00:00
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
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
2017-05-31 15:37:17 +00:00
Summary : "Inconsistent conditional result types" ,
Detail : fmt . Sprintf (
"The true result value has the wrong type: %s." ,
err . Error ( ) ,
) ,
2018-07-28 20:14:36 +00:00
Subject : e . TrueResult . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 20:36:55 +00:00
Expression : e . TrueResult ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-31 15:37:17 +00:00
} )
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
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
2017-05-31 15:37:17 +00:00
Summary : "Inconsistent conditional result types" ,
Detail : fmt . Sprintf (
"The false result value has the wrong type: %s." ,
err . Error ( ) ,
) ,
2018-07-28 20:36:55 +00:00
Subject : e . FalseResult . Range ( ) . Ptr ( ) ,
2018-07-28 20:14:36 +00:00
Context : & e . SrcRange ,
2018-07-28 20:36:55 +00:00
Expression : e . FalseResult ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-05-31 15:37:17 +00:00
} )
falseResult = cty . UnknownVal ( resultType )
}
}
return falseResult , diags
}
}
2017-09-11 23:40:37 +00:00
func ( e * ConditionalExpr ) Range ( ) hcl . Range {
2017-05-31 15:37:17 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * ConditionalExpr ) StartRange ( ) hcl . Range {
2017-05-31 15:37:17 +00:00
return e . Condition . StartRange ( )
}
2017-06-04 21:22:51 +00:00
2017-06-05 14:09:04 +00:00
type IndexExpr struct {
Collection Expression
Key Expression
2019-12-06 01:03:57 +00:00
SrcRange hcl . Range
OpenRange hcl . Range
BracketRange hcl . Range
2017-06-05 14:09:04 +00:00
}
func ( e * IndexExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
w ( e . Collection )
w ( e . Key )
2017-06-05 14:09:04 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * IndexExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
var diags hcl . Diagnostics
2017-06-05 14:41:02 +00:00
coll , collDiags := e . Collection . Value ( ctx )
key , keyDiags := e . Key . Value ( ctx )
diags = append ( diags , collDiags ... )
diags = append ( diags , keyDiags ... )
2019-12-06 01:03:57 +00:00
val , indexDiags := hcl . Index ( coll , key , & e . BracketRange )
2018-12-12 23:51:40 +00:00
setDiagEvalContext ( indexDiags , e , ctx )
diags = append ( diags , indexDiags ... )
2018-07-28 22:44:44 +00:00
return val , diags
2017-06-05 14:09:04 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * IndexExpr ) Range ( ) hcl . Range {
2017-06-05 14:09:04 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * IndexExpr ) StartRange ( ) hcl . Range {
2017-06-05 14:09:04 +00:00
return e . OpenRange
}
2017-06-04 21:22:51 +00:00
type TupleConsExpr struct {
Exprs [ ] Expression
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
OpenRange hcl . Range
2017-06-04 21:22:51 +00:00
}
func ( e * TupleConsExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
for _ , expr := range e . Exprs {
w ( expr )
2017-06-04 21:22:51 +00:00
}
}
2017-09-11 23:40:37 +00:00
func ( e * TupleConsExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-06-04 21:22:51 +00:00
var vals [ ] cty . Value
2017-09-11 23:40:37 +00:00
var diags hcl . Diagnostics
2017-06-04 21:22:51 +00:00
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
}
2017-09-11 23:40:37 +00:00
func ( e * TupleConsExpr ) Range ( ) hcl . Range {
2017-06-04 21:22:51 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * TupleConsExpr ) StartRange ( ) hcl . Range {
2017-06-04 21:22:51 +00:00
return e . OpenRange
}
2017-06-04 23:14:02 +00:00
2018-01-13 07:30:41 +00:00
// Implementation for hcl.ExprList
func ( e * TupleConsExpr ) ExprList ( ) [ ] hcl . Expression {
ret := make ( [ ] hcl . Expression , len ( e . Exprs ) )
for i , expr := range e . Exprs {
ret [ i ] = expr
}
return ret
}
2017-06-04 23:14:02 +00:00
type ObjectConsExpr struct {
Items [ ] ObjectConsItem
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
OpenRange hcl . Range
2017-06-04 23:14:02 +00:00
}
type ObjectConsItem struct {
KeyExpr Expression
ValueExpr Expression
}
func ( e * ObjectConsExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
for _ , item := range e . Items {
w ( item . KeyExpr )
w ( item . ValueExpr )
2017-06-04 23:14:02 +00:00
}
}
2017-09-11 23:40:37 +00:00
func ( e * ObjectConsExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-06-04 23:14:02 +00:00
var vals map [ string ] cty . Value
2017-09-11 23:40:37 +00:00
var diags hcl . Diagnostics
2017-06-04 23:14:02 +00:00
// 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 ( ) {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Null value as key" ,
Detail : "Can't use a null value as a key." ,
Subject : item . ValueExpr . Range ( ) . Ptr ( ) ,
2018-07-28 20:36:55 +00:00
Expression : item . KeyExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-04 23:14:02 +00:00
} )
known = false
continue
}
var err error
key , err = convert . Convert ( key , cty . String )
if err != nil {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Incorrect key type" ,
Detail : fmt . Sprintf ( "Can't use this value as a key: %s." , err . Error ( ) ) ,
2018-12-11 23:33:14 +00:00
Subject : item . KeyExpr . Range ( ) . Ptr ( ) ,
Expression : item . KeyExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-04 23:14:02 +00:00
} )
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
}
2017-09-11 23:40:37 +00:00
func ( e * ObjectConsExpr ) Range ( ) hcl . Range {
2017-06-04 23:14:02 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * ObjectConsExpr ) StartRange ( ) hcl . Range {
2017-06-04 23:14:02 +00:00
return e . OpenRange
}
2017-06-13 15:42:36 +00:00
2018-02-23 16:41:58 +00:00
// Implementation for hcl.ExprMap
func ( e * ObjectConsExpr ) ExprMap ( ) [ ] hcl . KeyValuePair {
ret := make ( [ ] hcl . KeyValuePair , len ( e . Items ) )
for i , item := range e . Items {
ret [ i ] = hcl . KeyValuePair {
Key : item . KeyExpr ,
Value : item . ValueExpr ,
}
}
return ret
}
2018-02-26 16:38:35 +00:00
// ObjectConsKeyExpr is a special wrapper used only for ObjectConsExpr keys,
// which deals with the special case that a naked identifier in that position
// must be interpreted as a literal string rather than evaluated directly.
type ObjectConsKeyExpr struct {
2019-09-11 22:31:32 +00:00
Wrapped Expression
ForceNonLiteral bool
2018-02-26 16:38:35 +00:00
}
func ( e * ObjectConsKeyExpr ) literalName ( ) string {
// This is our logic for deciding whether to behave like a literal string.
// We lean on our AbsTraversalForExpr implementation here, which already
// deals with some awkward cases like the expression being the result
// of the keywords "null", "true" and "false" which we'd want to interpret
// as keys here too.
return hcl . ExprAsKeyword ( e . Wrapped )
}
func ( e * ObjectConsKeyExpr ) walkChildNodes ( w internalWalkFunc ) {
// We only treat our wrapped expression as a real expression if we're
// not going to interpret it as a literal.
if e . literalName ( ) == "" {
2018-09-26 14:38:43 +00:00
w ( e . Wrapped )
2018-02-26 16:38:35 +00:00
}
}
func ( e * ObjectConsKeyExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
hcl/hclsyntax: Better handling of naked attribute keys with dots
To make things read better in the normal case, we treat naked identifiers
in the place of object keys as literal strings containing the identifier
text rather than as references. However, this had a couple sub-optimal
implications:
- If a user would try to create a key containing a period, the evaluator
would see that it wasn't a valid keyword and try to resolve it as a
normal scope traversal, causing a confusing error that didn't align
with the user's intent.
- In the rarer case where the attempted key contains a period followed by
a digit, the parser would trip over what seems to be an unexpected
identifier following the colon and produce, again, a confusing error
that doesn't align with what the user intended.
To address the first of these problems, it is now invalid to use a naked
traversal with more than one step as an object key, which allows us to
produce a targeted error message that directs the user to either put the
expression in parentheses to force interpretation as a scope traversal
or in quotes to force interpretation as a literal.
The second problem can't be addressed exactly due to it being a parser
problem, but we improve the situation slightly here by adding an extra
hint to the parse error message in this case so that a user making this
mistake might understand better how the error relates to what they were
trying to express.
2018-12-12 00:48:31 +00:00
// Because we accept a naked identifier as a literal key rather than a
// reference, it's confusing to accept a traversal containing periods
// here since we can't tell if the user intends to create a key with
// periods or actually reference something. To avoid confusing downstream
// errors we'll just prohibit a naked multi-step traversal here and
// require the user to state their intent more clearly.
// (This is handled at evaluation time rather than parse time because
// an application using static analysis _can_ accept a naked multi-step
// traversal here, if desired.)
2019-09-11 22:31:32 +00:00
if ! e . ForceNonLiteral {
if travExpr , isTraversal := e . Wrapped . ( * ScopeTraversalExpr ) ; isTraversal && len ( travExpr . Traversal ) > 1 {
var diags hcl . Diagnostics
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Ambiguous attribute key" ,
Detail : "If this expression is intended to be a reference, wrap it in parentheses. If it's instead intended as a literal name containing periods, wrap it in quotes to create a string literal." ,
Subject : e . Range ( ) . Ptr ( ) ,
} )
return cty . DynamicVal , diags
}
hcl/hclsyntax: Better handling of naked attribute keys with dots
To make things read better in the normal case, we treat naked identifiers
in the place of object keys as literal strings containing the identifier
text rather than as references. However, this had a couple sub-optimal
implications:
- If a user would try to create a key containing a period, the evaluator
would see that it wasn't a valid keyword and try to resolve it as a
normal scope traversal, causing a confusing error that didn't align
with the user's intent.
- In the rarer case where the attempted key contains a period followed by
a digit, the parser would trip over what seems to be an unexpected
identifier following the colon and produce, again, a confusing error
that doesn't align with what the user intended.
To address the first of these problems, it is now invalid to use a naked
traversal with more than one step as an object key, which allows us to
produce a targeted error message that directs the user to either put the
expression in parentheses to force interpretation as a scope traversal
or in quotes to force interpretation as a literal.
The second problem can't be addressed exactly due to it being a parser
problem, but we improve the situation slightly here by adding an extra
hint to the parse error message in this case so that a user making this
mistake might understand better how the error relates to what they were
trying to express.
2018-12-12 00:48:31 +00:00
2019-09-11 22:31:32 +00:00
if ln := e . literalName ( ) ; ln != "" {
return cty . StringVal ( ln ) , nil
}
2018-02-26 16:38:35 +00:00
}
return e . Wrapped . Value ( ctx )
}
func ( e * ObjectConsKeyExpr ) Range ( ) hcl . Range {
return e . Wrapped . Range ( )
}
func ( e * ObjectConsKeyExpr ) StartRange ( ) hcl . Range {
return e . Wrapped . StartRange ( )
}
// Implementation for hcl.AbsTraversalForExpr.
func ( e * ObjectConsKeyExpr ) AsTraversal ( ) hcl . Traversal {
2019-09-11 22:31:32 +00:00
// If we're forcing a non-literal then we can never be interpreted
// as a traversal.
if e . ForceNonLiteral {
return nil
}
2018-02-26 16:38:35 +00:00
// We can produce a traversal only if our wrappee can.
st , diags := hcl . AbsTraversalForExpr ( e . Wrapped )
if diags . HasErrors ( ) {
return nil
}
return st
}
func ( e * ObjectConsKeyExpr ) UnwrapExpression ( ) Expression {
return e . Wrapped
}
2017-06-13 15:42:36 +00:00
// 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
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
OpenRange hcl . Range
CloseRange hcl . Range
2017-06-13 15:42:36 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * ForExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
var diags hcl . Diagnostics
2017-06-14 15:56:28 +00:00
collVal , collDiags := e . CollExpr . Value ( ctx )
diags = append ( diags , collDiags ... )
if collVal . IsNull ( ) {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 22:24:39 +00:00
Expression : e . CollExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-14 15:56:28 +00:00
} )
return cty . DynamicVal , diags
}
2017-06-18 15:14:36 +00:00
if collVal . Type ( ) == cty . DynamicPseudoType {
2017-06-14 15:56:28 +00:00
return cty . DynamicVal , diags
}
2020-10-08 20:07:45 +00:00
// Unmark collection before checking for iterability, because marked
// values cannot be iterated
collVal , marks := collVal . Unmark ( )
2017-06-14 15:56:28 +00:00
if ! collVal . CanIterateElements ( ) {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
2017-06-14 15:56:28 +00:00
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 ( ) ,
) ,
2018-07-28 20:14:36 +00:00
Subject : e . CollExpr . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 22:24:39 +00:00
Expression : e . CollExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-14 15:56:28 +00:00
} )
return cty . DynamicVal , diags
}
2017-06-18 15:14:36 +00:00
if ! collVal . IsKnown ( ) {
return cty . DynamicVal , diags
}
2017-06-14 15:56:28 +00:00
2017-06-18 16:36:06 +00:00
// Before we start we'll do an early check to see if any CondExpr we've
// been given is of the wrong type. This isn't 100% reliable (it may
// be DynamicVal until real values are given) but it should catch some
// straightforward cases and prevent a barrage of repeated errors.
if e . CondExpr != nil {
2018-07-28 22:24:39 +00:00
childCtx := ctx . NewChild ( )
childCtx . Variables = map [ string ] cty . Value { }
2017-06-18 16:36:06 +00:00
if e . KeyVar != "" {
childCtx . Variables [ e . KeyVar ] = cty . DynamicVal
}
childCtx . Variables [ e . ValVar ] = cty . DynamicVal
result , condDiags := e . CondExpr . Value ( childCtx )
diags = append ( diags , condDiags ... )
if result . IsNull ( ) {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Condition is null" ,
Detail : "The value of the 'if' clause must not be null." ,
Subject : e . CondExpr . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 22:24:39 +00:00
Expression : e . CondExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-18 16:36:06 +00:00
} )
return cty . DynamicVal , diags
}
_ , err := convert . Convert ( result , cty . Bool )
if err != nil {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 22:24:39 +00:00
Expression : e . CondExpr ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-18 16:36:06 +00:00
} )
return cty . DynamicVal , diags
}
if condDiags . HasErrors ( ) {
return cty . DynamicVal , diags
}
}
2017-06-14 15:56:28 +00:00
if e . KeyExpr != nil {
// Producing an object
2017-06-18 15:45:45 +00:00
var vals map [ string ] cty . Value
var groupVals map [ string ] [ ] cty . Value
if e . Group {
groupVals = map [ string ] [ ] cty . Value { }
} else {
vals = map [ string ] cty . Value { }
}
2017-06-14 15:56:28 +00:00
it := collVal . ElementIterator ( )
known := true
for it . Next ( ) {
k , v := it . Element ( )
2018-07-28 22:24:39 +00:00
childCtx := ctx . NewChild ( )
childCtx . Variables = map [ string ] cty . Value { }
2017-06-14 15:56:28 +00:00
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 {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Invalid 'for' condition" ,
Detail : "The value of the 'if' clause must not be null." ,
Subject : e . CondExpr . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 22:24:39 +00:00
Expression : e . CondExpr ,
EvalContext : childCtx ,
2017-06-14 15:56:28 +00:00
} )
}
known = false
continue
}
include , err := convert . Convert ( includeRaw , cty . Bool )
if err != nil {
if known {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 22:24:39 +00:00
Expression : e . CondExpr ,
EvalContext : childCtx ,
2017-06-14 15:56:28 +00:00
} )
}
known = false
continue
}
2017-06-18 16:36:06 +00:00
if ! include . IsKnown ( ) {
known = false
continue
}
2017-06-14 15:56:28 +00:00
if include . False ( ) {
// Skip this element
continue
}
}
keyRaw , keyDiags := e . KeyExpr . Value ( childCtx )
diags = append ( diags , keyDiags ... )
if keyRaw . IsNull ( ) {
if known {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 22:24:39 +00:00
Expression : e . KeyExpr ,
EvalContext : childCtx ,
2017-06-14 15:56:28 +00:00
} )
}
known = false
continue
}
if ! keyRaw . IsKnown ( ) {
known = false
continue
}
key , err := convert . Convert ( keyRaw , cty . String )
if err != nil {
if known {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 22:24:39 +00:00
Expression : e . KeyExpr ,
EvalContext : childCtx ,
2017-06-14 15:56:28 +00:00
} )
}
known = false
continue
}
val , valDiags := e . ValExpr . Value ( childCtx )
diags = append ( diags , valDiags ... )
2017-06-18 15:45:45 +00:00
if e . Group {
k := key . AsString ( )
groupVals [ k ] = append ( groupVals [ k ] , val )
} else {
2017-06-18 15:49:52 +00:00
k := key . AsString ( )
if _ , exists := vals [ k ] ; exists {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
2017-06-18 15:49:52 +00:00
Summary : "Duplicate object key" ,
Detail : fmt . Sprintf (
2018-07-28 20:14:36 +00:00
"Two different items produced the key %q in this 'for' expression. If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key." ,
2017-06-18 15:49:52 +00:00
k ,
) ,
2018-07-28 20:14:36 +00:00
Subject : e . KeyExpr . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 22:24:39 +00:00
Expression : e . KeyExpr ,
EvalContext : childCtx ,
2017-06-18 15:49:52 +00:00
} )
} else {
vals [ key . AsString ( ) ] = val
}
2017-06-18 15:45:45 +00:00
}
2017-06-14 15:56:28 +00:00
}
if ! known {
return cty . DynamicVal , diags
}
2017-06-18 15:45:45 +00:00
if e . Group {
vals = map [ string ] cty . Value { }
for k , gvs := range groupVals {
vals [ k ] = cty . TupleVal ( gvs )
}
}
2020-10-08 20:07:45 +00:00
return cty . ObjectVal ( vals ) . WithMarks ( marks ) , diags
2017-06-14 15:56:28 +00:00
} else {
// Producing a tuple
vals := [ ] cty . Value { }
2017-06-18 15:14:36 +00:00
it := collVal . ElementIterator ( )
known := true
for it . Next ( ) {
k , v := it . Element ( )
2018-07-28 22:24:39 +00:00
childCtx := ctx . NewChild ( )
childCtx . Variables = map [ string ] cty . Value { }
2017-06-18 15:14:36 +00:00
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 {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Invalid 'for' condition" ,
Detail : "The value of the 'if' clause must not be null." ,
Subject : e . CondExpr . Range ( ) . Ptr ( ) ,
Context : & e . SrcRange ,
2018-07-28 22:24:39 +00:00
Expression : e . CondExpr ,
EvalContext : childCtx ,
2017-06-18 15:14:36 +00:00
} )
}
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 {
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . 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 ,
2018-07-28 22:24:39 +00:00
Expression : e . CondExpr ,
EvalContext : childCtx ,
2017-06-18 15:14:36 +00:00
} )
}
known = false
continue
}
if include . False ( ) {
// Skip this element
continue
}
}
val , valDiags := e . ValExpr . Value ( childCtx )
diags = append ( diags , valDiags ... )
vals = append ( vals , val )
}
if ! known {
return cty . DynamicVal , diags
}
2020-10-08 20:07:45 +00:00
return cty . TupleVal ( vals ) . WithMarks ( marks ) , diags
2017-06-14 15:56:28 +00:00
}
2017-06-13 15:42:36 +00:00
}
func ( e * ForExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
w ( e . CollExpr )
2017-06-14 15:03:32 +00:00
scopeNames := map [ string ] struct { } { }
if e . KeyVar != "" {
scopeNames [ e . KeyVar ] = struct { } { }
}
if e . ValVar != "" {
scopeNames [ e . ValVar ] = struct { } { }
}
2017-06-13 15:42:36 +00:00
if e . KeyExpr != nil {
2017-06-14 15:03:32 +00:00
w ( ChildScope {
LocalNames : scopeNames ,
2018-09-26 14:38:43 +00:00
Expr : e . KeyExpr ,
2017-06-14 15:03:32 +00:00
} )
2017-06-13 15:42:36 +00:00
}
2017-06-14 15:03:32 +00:00
w ( ChildScope {
LocalNames : scopeNames ,
2018-09-26 14:38:43 +00:00
Expr : e . ValExpr ,
2017-06-14 15:03:32 +00:00
} )
2017-06-13 15:42:36 +00:00
if e . CondExpr != nil {
2017-06-14 15:03:32 +00:00
w ( ChildScope {
LocalNames : scopeNames ,
2018-09-26 14:38:43 +00:00
Expr : e . CondExpr ,
2017-06-14 15:03:32 +00:00
} )
2017-06-13 15:42:36 +00:00
}
}
2017-09-11 23:40:37 +00:00
func ( e * ForExpr ) Range ( ) hcl . Range {
2017-06-13 15:42:36 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * ForExpr ) StartRange ( ) hcl . Range {
2017-06-13 15:42:36 +00:00
return e . OpenRange
}
2017-06-16 15:31:01 +00:00
type SplatExpr struct {
Source Expression
Each Expression
Item * AnonSymbolExpr
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
MarkerRange hcl . Range
2017-06-16 15:31:01 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * SplatExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-06-16 15:31:01 +00:00
sourceVal , diags := e . Source . Value ( ctx )
if diags . HasErrors ( ) {
// We'll evaluate our "Each" expression here just to see if it
// produces any more diagnostics we can report. Since we're not
// assigning a value to our AnonSymbolExpr here it will return
// DynamicVal, which should short-circuit any use of it.
_ , itemDiags := e . Item . Value ( ctx )
diags = append ( diags , itemDiags ... )
return cty . DynamicVal , diags
}
2019-01-03 16:44:27 +00:00
sourceTy := sourceVal . Type ( )
// A "special power" of splat expressions is that they can be applied
// both to tuples/lists and to other values, and in the latter case
// the value will be treated as an implicit single-item tuple, or as
// an empty tuple if the value is null.
autoUpgrade := ! ( sourceTy . IsTupleType ( ) || sourceTy . IsListType ( ) || sourceTy . IsSetType ( ) )
2017-06-16 15:31:01 +00:00
if sourceVal . IsNull ( ) {
2019-01-03 16:44:27 +00:00
if autoUpgrade {
return cty . EmptyTupleVal , diags
}
2017-09-11 23:40:37 +00:00
diags = append ( diags , & hcl . Diagnostic {
2018-07-28 20:14:36 +00:00
Severity : hcl . DiagError ,
Summary : "Splat of null value" ,
2019-01-03 16:44:27 +00:00
Detail : "Splat expressions (with the * symbol) cannot be applied to null sequences." ,
2018-07-28 20:14:36 +00:00
Subject : e . Source . Range ( ) . Ptr ( ) ,
Context : hcl . RangeBetween ( e . Source . Range ( ) , e . MarkerRange ) . Ptr ( ) ,
2018-07-28 22:24:39 +00:00
Expression : e . Source ,
2018-07-28 20:14:36 +00:00
EvalContext : ctx ,
2017-06-16 15:31:01 +00:00
} )
return cty . DynamicVal , diags
}
2018-12-06 00:59:33 +00:00
2020-11-18 22:26:15 +00:00
if sourceTy == cty . DynamicPseudoType {
// If we don't even know the _type_ of our source value yet then
// we'll need to defer all processing, since we can't decide our
// result type either.
return cty . DynamicVal , diags
}
2019-01-03 16:44:27 +00:00
if autoUpgrade {
2018-12-06 00:59:33 +00:00
sourceVal = cty . TupleVal ( [ ] cty . Value { sourceVal } )
sourceTy = sourceVal . Type ( )
}
// We'll compute our result type lazily if we need it. In the normal case
// it's inferred automatically from the value we construct.
resultTy := func ( ) ( cty . Type , hcl . Diagnostics ) {
chiCtx := ctx . NewChild ( )
var diags hcl . Diagnostics
switch {
case sourceTy . IsListType ( ) || sourceTy . IsSetType ( ) :
ety := sourceTy . ElementType ( )
e . Item . setValue ( chiCtx , cty . UnknownVal ( ety ) )
val , itemDiags := e . Each . Value ( chiCtx )
diags = append ( diags , itemDiags ... )
e . Item . clearValue ( chiCtx ) // clean up our temporary value
return cty . List ( val . Type ( ) ) , diags
case sourceTy . IsTupleType ( ) :
etys := sourceTy . TupleElementTypes ( )
resultTys := make ( [ ] cty . Type , 0 , len ( etys ) )
for _ , ety := range etys {
e . Item . setValue ( chiCtx , cty . UnknownVal ( ety ) )
val , itemDiags := e . Each . Value ( chiCtx )
diags = append ( diags , itemDiags ... )
e . Item . clearValue ( chiCtx ) // clean up our temporary value
resultTys = append ( resultTys , val . Type ( ) )
}
return cty . Tuple ( resultTys ) , diags
default :
// Should never happen because of our promotion to list above.
return cty . DynamicPseudoType , diags
}
}
if ! sourceVal . IsKnown ( ) {
// We can't produce a known result in this case, but we'll still
// indicate what the result type would be, allowing any downstream type
// checking to proceed.
ty , tyDiags := resultTy ( )
diags = append ( diags , tyDiags ... )
return cty . UnknownVal ( ty ) , diags
2017-06-16 15:31:01 +00:00
}
vals := make ( [ ] cty . Value , 0 , sourceVal . LengthInt ( ) )
it := sourceVal . ElementIterator ( )
if ctx == nil {
// we need a context to use our AnonSymbolExpr, so we'll just
// make an empty one here to use as a placeholder.
ctx = ctx . NewChild ( )
}
isKnown := true
for it . Next ( ) {
_ , sourceItem := it . Element ( )
e . Item . setValue ( ctx , sourceItem )
newItem , itemDiags := e . Each . Value ( ctx )
diags = append ( diags , itemDiags ... )
if itemDiags . HasErrors ( ) {
isKnown = false
}
vals = append ( vals , newItem )
}
e . Item . clearValue ( ctx ) // clean up our temporary value
if ! isKnown {
2018-12-06 00:59:33 +00:00
// We'll ingore the resultTy diagnostics in this case since they
// will just be the same errors we saw while iterating above.
ty , _ := resultTy ( )
return cty . UnknownVal ( ty ) , diags
2017-06-16 15:31:01 +00:00
}
2018-12-06 00:59:33 +00:00
switch {
case sourceTy . IsListType ( ) || sourceTy . IsSetType ( ) :
if len ( vals ) == 0 {
ty , tyDiags := resultTy ( )
diags = append ( diags , tyDiags ... )
return cty . ListValEmpty ( ty . ElementType ( ) ) , diags
}
return cty . ListVal ( vals ) , diags
default :
return cty . TupleVal ( vals ) , diags
}
2017-06-16 15:31:01 +00:00
}
func ( e * SplatExpr ) walkChildNodes ( w internalWalkFunc ) {
2018-09-26 14:38:43 +00:00
w ( e . Source )
w ( e . Each )
2017-06-16 15:31:01 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * SplatExpr ) Range ( ) hcl . Range {
2017-06-16 15:31:01 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * SplatExpr ) StartRange ( ) hcl . Range {
2017-06-16 15:31:01 +00:00
return e . MarkerRange
}
// AnonSymbolExpr is used as a placeholder for a value in an expression that
// can be applied dynamically to any value at runtime.
//
// This is a rather odd, synthetic expression. It is used as part of the
// representation of splat expressions as a placeholder for the current item
// being visited in the splat evaluation.
//
// AnonSymbolExpr cannot be evaluated in isolation. If its Value is called
// directly then cty.DynamicVal will be returned. Instead, it is evaluated
// in terms of another node (i.e. a splat expression) which temporarily
// assigns it a value.
type AnonSymbolExpr struct {
2017-09-11 23:40:37 +00:00
SrcRange hcl . Range
2018-05-23 23:38:39 +00:00
// values and its associated lock are used to isolate concurrent
// evaluations of a symbol from one another. It is the calling application's
// responsibility to ensure that the same splat expression is not evalauted
// concurrently within the _same_ EvalContext, but it is fine and safe to
// do cuncurrent evaluations with distinct EvalContexts.
values map [ * hcl . EvalContext ] cty . Value
valuesLock sync . RWMutex
2017-06-16 15:31:01 +00:00
}
2017-09-11 23:40:37 +00:00
func ( e * AnonSymbolExpr ) Value ( ctx * hcl . EvalContext ) ( cty . Value , hcl . Diagnostics ) {
2017-06-16 15:31:01 +00:00
if ctx == nil {
return cty . DynamicVal , nil
}
2018-05-23 23:38:39 +00:00
e . valuesLock . RLock ( )
defer e . valuesLock . RUnlock ( )
2017-06-16 15:31:01 +00:00
val , exists := e . values [ ctx ]
if ! exists {
return cty . DynamicVal , nil
}
return val , nil
}
// setValue sets a temporary local value for the expression when evaluated
// in the given context, which must be non-nil.
2017-09-11 23:40:37 +00:00
func ( e * AnonSymbolExpr ) setValue ( ctx * hcl . EvalContext , val cty . Value ) {
2018-05-23 23:38:39 +00:00
e . valuesLock . Lock ( )
defer e . valuesLock . Unlock ( )
2017-06-16 15:31:01 +00:00
if e . values == nil {
2017-09-11 23:40:37 +00:00
e . values = make ( map [ * hcl . EvalContext ] cty . Value )
2017-06-16 15:31:01 +00:00
}
if ctx == nil {
panic ( "can't setValue for a nil EvalContext" )
}
e . values [ ctx ] = val
}
2017-09-11 23:40:37 +00:00
func ( e * AnonSymbolExpr ) clearValue ( ctx * hcl . EvalContext ) {
2018-05-23 23:38:39 +00:00
e . valuesLock . Lock ( )
defer e . valuesLock . Unlock ( )
2017-06-16 15:31:01 +00:00
if e . values == nil {
return
}
if ctx == nil {
panic ( "can't clearValue for a nil EvalContext" )
}
delete ( e . values , ctx )
}
func ( e * AnonSymbolExpr ) walkChildNodes ( w internalWalkFunc ) {
// AnonSymbolExpr is a leaf node in the tree
}
2017-09-11 23:40:37 +00:00
func ( e * AnonSymbolExpr ) Range ( ) hcl . Range {
2017-06-16 15:31:01 +00:00
return e . SrcRange
}
2017-09-11 23:40:37 +00:00
func ( e * AnonSymbolExpr ) StartRange ( ) hcl . Range {
2017-06-16 15:31:01 +00:00
return e . SrcRange
}