govendor fetch github.com/hashicorp/hil/...

This commit is contained in:
Martin Atkins 2017-04-28 16:14:13 -07:00
parent b28fb766db
commit edb362cfb3
5 changed files with 149 additions and 58 deletions

View File

@ -77,3 +77,12 @@ func (n *LiteralNode) String() string {
func (n *LiteralNode) Type(Scope) (Type, error) { func (n *LiteralNode) Type(Scope) (Type, error) {
return n.Typex, nil return n.Typex, nil
} }
// IsUnknown returns true either if the node's value is itself unknown
// of if it is a collection containing any unknown elements, deeply.
func (n *LiteralNode) IsUnknown() bool {
return IsUnknown(Variable{
Type: n.Typex,
Value: n.Value,
})
}

View File

@ -3,54 +3,61 @@ package ast
import "fmt" import "fmt"
func VariableListElementTypesAreHomogenous(variableName string, list []Variable) (Type, error) { func VariableListElementTypesAreHomogenous(variableName string, list []Variable) (Type, error) {
listTypes := make(map[Type]struct{}) if len(list) == 0 {
return TypeInvalid, fmt.Errorf("list %q does not have any elements so cannot determine type.", variableName)
}
elemType := TypeUnknown
for _, v := range list { for _, v := range list {
// Allow unknown
if v.Type == TypeUnknown { if v.Type == TypeUnknown {
continue continue
} }
if _, ok := listTypes[v.Type]; ok { if elemType == TypeUnknown {
elemType = v.Type
continue continue
} }
listTypes[v.Type] = struct{}{}
if v.Type != elemType {
return TypeInvalid, fmt.Errorf(
"list %q does not have homogenous types. found %s and then %s",
variableName,
elemType, v.Type,
)
}
elemType = v.Type
} }
if len(listTypes) != 1 && len(list) != 0 { return elemType, nil
return TypeInvalid, fmt.Errorf("list %q does not have homogenous types. found %s", variableName, reportTypes(listTypes))
}
if len(list) > 0 {
return list[0].Type, nil
}
return TypeInvalid, fmt.Errorf("list %q does not have any elements so cannot determine type.", variableName)
} }
func VariableMapValueTypesAreHomogenous(variableName string, vmap map[string]Variable) (Type, error) { func VariableMapValueTypesAreHomogenous(variableName string, vmap map[string]Variable) (Type, error) {
valueTypes := make(map[Type]struct{}) if len(vmap) == 0 {
return TypeInvalid, fmt.Errorf("map %q does not have any elements so cannot determine type.", variableName)
}
elemType := TypeUnknown
for _, v := range vmap { for _, v := range vmap {
// Allow unknown
if v.Type == TypeUnknown { if v.Type == TypeUnknown {
continue continue
} }
if _, ok := valueTypes[v.Type]; ok { if elemType == TypeUnknown {
elemType = v.Type
continue continue
} }
valueTypes[v.Type] = struct{}{} if v.Type != elemType {
return TypeInvalid, fmt.Errorf(
"map %q does not have homogenous types. found %s and then %s",
variableName,
elemType, v.Type,
)
}
elemType = v.Type
} }
if len(valueTypes) != 1 && len(vmap) != 0 { return elemType, nil
return TypeInvalid, fmt.Errorf("map %q does not have homogenous value types. found %s", variableName, reportTypes(valueTypes))
}
// For loop here is an easy way to get a single key, we return immediately.
for _, v := range vmap {
return v.Type, nil
}
// This means the map is empty
return TypeInvalid, fmt.Errorf("map %q does not have any elements so cannot determine type.", variableName)
} }

View File

@ -98,10 +98,6 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node {
pos.Column, pos.Line, err) pos.Column, pos.Line, err)
} }
if v.StackPeek() == ast.TypeUnknown {
v.err = errExitUnknown
}
return result return result
} }
@ -116,6 +112,14 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
exprs[len(tc.n.Exprs)-1-i] = v.StackPop() exprs[len(tc.n.Exprs)-1-i] = v.StackPop()
} }
// If any operand is unknown then our result is automatically unknown
for _, ty := range exprs {
if ty == ast.TypeUnknown {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
}
switch tc.n.Op { switch tc.n.Op {
case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr: case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr:
return tc.checkLogical(v, exprs) return tc.checkLogical(v, exprs)
@ -333,6 +337,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
continue continue
} }
if args[i] == ast.TypeUnknown {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
if args[i] != expected { if args[i] != expected {
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
if cn != nil { if cn != nil {
@ -350,6 +359,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
if function.Variadic && function.VariadicType != ast.TypeAny { if function.Variadic && function.VariadicType != ast.TypeAny {
args = args[len(function.ArgTypes):] args = args[len(function.ArgTypes):]
for i, t := range args { for i, t := range args {
if t == ast.TypeUnknown {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
if t != function.VariadicType { if t != function.VariadicType {
realI := i + len(function.ArgTypes) realI := i + len(function.ArgTypes)
cn := v.ImplicitConversion( cn := v.ImplicitConversion(
@ -384,6 +398,11 @@ func (tc *typeCheckConditional) TypeCheck(v *TypeCheck) (ast.Node, error) {
trueType := v.StackPop() trueType := v.StackPop()
condType := v.StackPop() condType := v.StackPop()
if condType == ast.TypeUnknown {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
if condType != ast.TypeBool { if condType != ast.TypeBool {
cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr) cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr)
if cn == nil { if cn == nil {
@ -457,6 +476,13 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
types[len(n.Exprs)-1-i] = v.StackPop() types[len(n.Exprs)-1-i] = v.StackPop()
} }
for _, ty := range types {
if ty == ast.TypeUnknown {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
}
// If there is only one argument and it is a list, we evaluate to a list // If there is only one argument and it is a list, we evaluate to a list
if len(types) == 1 { if len(types) == 1 {
switch t := types[0]; t { switch t := types[0]; t {
@ -469,7 +495,14 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
} }
// Otherwise, all concat args must be strings, so validate that // Otherwise, all concat args must be strings, so validate that
resultType := ast.TypeString
for i, t := range types { for i, t := range types {
if t == ast.TypeUnknown {
resultType = ast.TypeUnknown
continue
}
if t != ast.TypeString { if t != ast.TypeString {
cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i]) cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i])
if cn != nil { if cn != nil {
@ -482,8 +515,8 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
} }
} }
// This always results in type string // This always results in type string, unless there are unknowns
v.StackPush(ast.TypeString) v.StackPush(resultType)
return n, nil return n, nil
} }
@ -509,13 +542,6 @@ func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) {
"unknown variable accessed: %s", tc.n.Name) "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 // Add the type to the stack
v.StackPush(variable.Type) v.StackPush(variable.Type)
@ -530,6 +556,11 @@ func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) {
keyType := v.StackPop() keyType := v.StackPop()
targetType := v.StackPop() targetType := v.StackPop()
if keyType == ast.TypeUnknown || targetType == ast.TypeUnknown {
v.StackPush(ast.TypeUnknown)
return tc.n, nil
}
// Ensure we have a VariableAccess as the target // Ensure we have a VariableAccess as the target
varAccessNode, ok := tc.n.Target.(*ast.VariableAccess) varAccessNode, ok := tc.n.Target.(*ast.VariableAccess)
if !ok { if !ok {

View File

@ -54,6 +54,14 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
return InvalidResult, err return InvalidResult, err
} }
// If the result contains any nested unknowns then the result as a whole
// is unknown, so that callers only have to deal with "entirely known"
// or "entirely unknown" as outcomes.
if ast.IsUnknown(ast.Variable{Type: outputType, Value: output}) {
outputType = ast.TypeUnknown
output = UnknownValue
}
switch outputType { switch outputType {
case ast.TypeList: case ast.TypeList:
val, err := VariableToInterface(ast.Variable{ val, err := VariableToInterface(ast.Variable{
@ -264,6 +272,10 @@ func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, e
args := make([]interface{}, len(v.Args)) args := make([]interface{}, len(v.Args))
for i, _ := range v.Args { for i, _ := range v.Args {
node := stack.Pop().(*ast.LiteralNode) node := stack.Pop().(*ast.LiteralNode)
if node.IsUnknown() {
// If any arguments are unknown then the result is automatically unknown
return UnknownValue, ast.TypeUnknown, nil
}
args[len(v.Args)-1-i] = node.Value args[len(v.Args)-1-i] = node.Value
} }
@ -286,6 +298,11 @@ func (v *evalConditional) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.
trueLit := stack.Pop().(*ast.LiteralNode) trueLit := stack.Pop().(*ast.LiteralNode)
condLit := stack.Pop().(*ast.LiteralNode) condLit := stack.Pop().(*ast.LiteralNode)
if condLit.IsUnknown() {
// If our conditional is unknown then our result is also unknown
return UnknownValue, ast.TypeUnknown, nil
}
if condLit.Value.(bool) { if condLit.Value.(bool) {
return trueLit.Value, trueLit.Typex, nil return trueLit.Value, trueLit.Typex, nil
} else { } else {
@ -301,6 +318,17 @@ func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Ty
variableName := v.Index.Target.(*ast.VariableAccess).Name variableName := v.Index.Target.(*ast.VariableAccess).Name
if key.IsUnknown() {
// If our key is unknown then our result is also unknown
return UnknownValue, ast.TypeUnknown, nil
}
// For target, we'll accept collections containing unknown values but
// we still need to catch when the collection itself is unknown, shallowly.
if target.Typex == ast.TypeUnknown {
return UnknownValue, ast.TypeUnknown, nil
}
switch target.Typex { switch target.Typex {
case ast.TypeList: case ast.TypeList:
return v.evalListIndex(variableName, target.Value, key.Value) return v.evalListIndex(variableName, target.Value, key.Value)
@ -377,8 +405,22 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type,
// The expressions should all be on the stack in reverse // The expressions should all be on the stack in reverse
// order. So pop them off, reverse their order, and concatenate. // order. So pop them off, reverse their order, and concatenate.
nodes := make([]*ast.LiteralNode, 0, len(v.Exprs)) nodes := make([]*ast.LiteralNode, 0, len(v.Exprs))
haveUnknown := false
for range v.Exprs { for range v.Exprs {
nodes = append(nodes, stack.Pop().(*ast.LiteralNode)) n := stack.Pop().(*ast.LiteralNode)
nodes = append(nodes, n)
// If we have any unknowns then the whole result is unknown
// (we must deal with this first, because the type checker can
// skip type conversions in the presence of unknowns, and thus
// any of our other nodes may be incorrectly typed.)
if n.IsUnknown() {
haveUnknown = true
}
}
if haveUnknown {
return UnknownValue, ast.TypeUnknown, nil
} }
// Special case the single list and map // Special case the single list and map
@ -396,6 +438,14 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type,
// Otherwise concatenate the strings // Otherwise concatenate the strings
var buf bytes.Buffer var buf bytes.Buffer
for i := len(nodes) - 1; i >= 0; i-- { for i := len(nodes) - 1; i >= 0; i-- {
if nodes[i].Typex != ast.TypeString {
return nil, ast.TypeInvalid, fmt.Errorf(
"invalid output with %s value at index %d: %#v",
nodes[i].Typex,
i,
nodes[i].Value,
)
}
buf.WriteString(nodes[i].Value.(string)) buf.WriteString(nodes[i].Value.(string))
} }
@ -418,11 +468,5 @@ func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, a
"unknown variable accessed: %s", v.Name) "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 return variable.Value, variable.Type, nil
} }

20
vendor/vendor.json vendored
View File

@ -2138,28 +2138,28 @@
"revisionTime": "2017-05-04T19:02:34Z" "revisionTime": "2017-05-04T19:02:34Z"
}, },
{ {
"checksumSHA1": "2Nrl/YKrmowkRgCDLhA6UTFgYEY=", "checksumSHA1": "zz3/f3YpHHBN78uLhnhLBW2aF8o=",
"path": "github.com/hashicorp/hil", "path": "github.com/hashicorp/hil",
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2", "revision": "747a6e1523d6808f91144df070435b16865cd333",
"revisionTime": "2016-12-21T19:20:42Z" "revisionTime": "2017-05-01T20:07:50Z"
}, },
{ {
"checksumSHA1": "oZ2a2x9qyHqvqJdv/Du3LGeaFdA=", "checksumSHA1": "0S0KeBcfqVFYBPeZkuJ4fhQ5mCA=",
"path": "github.com/hashicorp/hil/ast", "path": "github.com/hashicorp/hil/ast",
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2", "revision": "747a6e1523d6808f91144df070435b16865cd333",
"revisionTime": "2016-12-21T19:20:42Z" "revisionTime": "2017-05-01T20:07:50Z"
}, },
{ {
"checksumSHA1": "P5PZ3k7SmqWmxgJ8Q0gLzeNpGhE=", "checksumSHA1": "P5PZ3k7SmqWmxgJ8Q0gLzeNpGhE=",
"path": "github.com/hashicorp/hil/parser", "path": "github.com/hashicorp/hil/parser",
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2", "revision": "747a6e1523d6808f91144df070435b16865cd333",
"revisionTime": "2016-12-21T19:20:42Z" "revisionTime": "2017-05-01T20:07:50Z"
}, },
{ {
"checksumSHA1": "DC1k5kOua4oFqmo+JRt0YzfP44o=", "checksumSHA1": "DC1k5kOua4oFqmo+JRt0YzfP44o=",
"path": "github.com/hashicorp/hil/scanner", "path": "github.com/hashicorp/hil/scanner",
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2", "revision": "747a6e1523d6808f91144df070435b16865cd333",
"revisionTime": "2016-12-21T19:20:42Z" "revisionTime": "2017-05-01T20:07:50Z"
}, },
{ {
"checksumSHA1": "vt+P9D2yWDO3gdvdgCzwqunlhxU=", "checksumSHA1": "vt+P9D2yWDO3gdvdgCzwqunlhxU=",