update github.com/hashicorp/hil

This commit is contained in:
James Bardin 2016-10-27 11:34:00 -04:00 committed by Mitchell Hashimoto
parent 5ed1b5fc89
commit da09dcfc79
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
8 changed files with 139 additions and 20 deletions

View File

@ -54,6 +54,13 @@ const (
TypeFloat
TypeList
TypeMap
// This is a special type used by Terraform to mark "unknown" values.
// It is impossible for this type to be introduced into your HIL programs
// unless you explicitly set a variable to this value. In that case,
// any operation including the variable will return "TypeUnknown" as the
// type.
TypeUnknown
)
func (t Type) Printable() string {
@ -72,6 +79,8 @@ func (t Type) Printable() string {
return "type list"
case TypeMap:
return "type map"
case TypeUnknown:
return "type unknown"
default:
return "unknown type"
}

30
vendor/github.com/hashicorp/hil/ast/unknown.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package ast
// IsUnknown reports whether a variable is unknown or contains any value
// that is unknown. This will recurse into lists and maps and so on.
func IsUnknown(v Variable) bool {
// If it is unknown itself, return true
if v.Type == TypeUnknown {
return true
}
// If it is a container type, check the values
switch v.Type {
case TypeList:
for _, el := range v.Value.([]Variable) {
if IsUnknown(el) {
return true
}
}
case TypeMap:
for _, el := range v.Value.(map[string]Variable) {
if IsUnknown(el) {
return true
}
}
default:
}
// Not a container type or survive the above checks
return false
}

View File

@ -5,6 +5,11 @@ import "fmt"
func VariableListElementTypesAreHomogenous(variableName string, list []Variable) (Type, error) {
listTypes := make(map[Type]struct{})
for _, v := range list {
// Allow unknown
if v.Type == TypeUnknown {
continue
}
if _, ok := listTypes[v.Type]; ok {
continue
}
@ -25,9 +30,15 @@ func VariableListElementTypesAreHomogenous(variableName string, list []Variable)
func VariableMapValueTypesAreHomogenous(variableName string, vmap map[string]Variable) (Type, error) {
valueTypes := make(map[Type]struct{})
for _, v := range vmap {
// Allow unknown
if v.Type == TypeUnknown {
continue
}
if _, ok := valueTypes[v.Type]; ok {
continue
}
valueTypes[v.Type] = struct{}{}
}

View File

@ -44,6 +44,12 @@ func (v *TypeCheck) Visit(root ast.Node) error {
defer v.lock.Unlock()
defer v.reset()
root.Accept(v.visit)
// If the resulting type is unknown, then just let the whole thing go.
if v.err == errExitUnknown {
v.err = nil
}
return v.err
}
@ -89,6 +95,10 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node {
pos.Column, pos.Line, err)
}
if v.StackPeek() == ast.TypeUnknown {
v.err = errExitUnknown
}
return result
}
@ -242,15 +252,14 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
}
// If there is only one argument and it is a list, we evaluate to a list
if len(types) == 1 && types[0] == ast.TypeList {
v.StackPush(ast.TypeList)
return n, nil
}
// If there is only one argument and it is a map, we evaluate to a map
if len(types) == 1 && types[0] == ast.TypeMap {
v.StackPush(ast.TypeMap)
return n, nil
if len(types) == 1 {
switch t := types[0]; t {
case ast.TypeList:
fallthrough
case ast.TypeMap:
v.StackPush(t)
return n, nil
}
}
// Otherwise, all concat args must be strings, so validate that
@ -294,6 +303,13 @@ func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) {
"unknown variable accessed: %s", tc.n.Name)
}
// Check if the variable contains any unknown types. If so, then
// mark it as unknown.
if ast.IsUnknown(variable) {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
// Add the type to the stack
v.StackPush(variable.Type)
@ -399,3 +415,11 @@ func (v *TypeCheck) StackPop() ast.Type {
x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1]
return x
}
func (v *TypeCheck) StackPeek() ast.Type {
if len(v.Stack) == 0 {
return ast.TypeInvalid
}
return v.Stack[len(v.Stack)-1]
}

View File

@ -8,6 +8,11 @@ import (
"github.com/mitchellh/mapstructure"
)
// UnknownValue is a sentinel value that can be used to denote
// that a value of a variable (or map element, list element, etc.)
// is unknown. This will always have the type ast.TypeUnknown.
const UnknownValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
var hilMapstructureDecodeHookSlice []interface{}
var hilMapstructureDecodeHookStringSlice []string
var hilMapstructureDecodeHookMap map[string]interface{}
@ -48,6 +53,12 @@ func InterfaceToVariable(input interface{}) (ast.Variable, error) {
var stringVal string
if err := hilMapstructureWeakDecode(input, &stringVal); err == nil {
// Special case the unknown value to turn into "unknown"
if stringVal == UnknownValue {
return ast.Variable{Type: ast.TypeUnknown}, nil
}
// Otherwise return the string value
return ast.Variable{
Type: ast.TypeString,
Value: stringVal,

View File

@ -2,6 +2,7 @@ package hil
import (
"bytes"
"errors"
"fmt"
"sync"
@ -42,6 +43,10 @@ type EvaluationResult struct {
// The error is described out of band in the accompanying error return value.
var InvalidResult = EvaluationResult{Type: TypeInvalid, Value: nil}
// errExitUnknown is an internal error that when returned means the result
// is an unknown value. We use this for early exit.
var errExitUnknown = errors.New("unknown value")
func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
output, outputType, err := internalEval(root, config)
if err != nil {
@ -72,6 +77,11 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
Type: TypeString,
Value: output,
}, nil
case ast.TypeUnknown:
return EvaluationResult{
Type: TypeUnknown,
Value: UnknownValue,
}, nil
default:
return InvalidResult, fmt.Errorf("unknown type %s as interpolation output", outputType)
}
@ -154,6 +164,12 @@ func (v *evalVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) {
result = new(ast.LiteralNode)
}
resultErr := v.err
if resultErr == errExitUnknown {
// This means the return value is unknown and we used the error
// as an early exit mechanism. Reset since the value on the stack
// should be the unknown value.
resultErr = nil
}
// Clear everything else so we aren't just dangling
v.Stack.Reset()
@ -188,6 +204,13 @@ func (v *evalVisitor) visit(raw ast.Node) ast.Node {
Value: out,
Typex: outType,
})
if outType == ast.TypeUnknown {
// Halt immediately
v.err = errExitUnknown
return raw
}
return raw
}
@ -330,11 +353,15 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type,
}
// Special case the single list and map
if len(nodes) == 1 && nodes[0].Typex == ast.TypeList {
return nodes[0].Value, ast.TypeList, nil
}
if len(nodes) == 1 && nodes[0].Typex == ast.TypeMap {
return nodes[0].Value, ast.TypeMap, nil
if len(nodes) == 1 {
switch t := nodes[0].Typex; t {
case ast.TypeList:
fallthrough
case ast.TypeMap:
fallthrough
case ast.TypeUnknown:
return nodes[0].Value, t, nil
}
}
// Otherwise concatenate the strings
@ -362,5 +389,11 @@ func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, a
"unknown variable accessed: %s", v.Name)
}
// Check if the variable contains any unknown types. If so, then
// mark it as unknown and return that type.
if ast.IsUnknown(variable) {
return nil, ast.TypeUnknown, nil
}
return variable.Value, variable.Type, nil
}

View File

@ -11,4 +11,5 @@ const (
TypeString EvalType = 1 << iota
TypeList
TypeMap
TypeUnknown
)

12
vendor/vendor.json vendored
View File

@ -1386,16 +1386,16 @@
"revisionTime": "2016-11-09T00:00:27Z"
},
{
"checksumSHA1": "RYz/9y1RMZfg+oMgEyJIWiSl1dU=",
"checksumSHA1": "mZhRldYjh9MAXzdi3zihMX0A/JU=",
"path": "github.com/hashicorp/hil",
"revision": "3e00ff29065d64c0f8e9ef7efed82686bbda81ca",
"revisionTime": "2016-10-14T17:08:44Z"
"revision": "ce4ab742a9dd2bb6e55050337333b2c56666e5a0",
"revisionTime": "2016-10-27T15:25:34Z"
},
{
"checksumSHA1": "WYIQ+nJPa191qpQIUsauF4wXYSw=",
"checksumSHA1": "FFroNUb6Nn6xUQJMsVDTb4Cqzo4=",
"path": "github.com/hashicorp/hil/ast",
"revision": "3e00ff29065d64c0f8e9ef7efed82686bbda81ca",
"revisionTime": "2016-10-14T17:08:44Z"
"revision": "ce4ab742a9dd2bb6e55050337333b2c56666e5a0",
"revisionTime": "2016-10-27T15:25:34Z"
},
{
"path": "github.com/hashicorp/logutils",