govendor fetch github.com/hashicorp/hil/...
This commit is contained in:
parent
b28fb766db
commit
edb362cfb3
|
@ -77,3 +77,12 @@ func (n *LiteralNode) String() string {
|
|||
func (n *LiteralNode) Type(Scope) (Type, error) {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,54 +3,61 @@ package ast
|
|||
import "fmt"
|
||||
|
||||
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 {
|
||||
// Allow unknown
|
||||
if v.Type == TypeUnknown {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := listTypes[v.Type]; ok {
|
||||
if elemType == TypeUnknown {
|
||||
elemType = v.Type
|
||||
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 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)
|
||||
return elemType, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
// Allow unknown
|
||||
if v.Type == TypeUnknown {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := valueTypes[v.Type]; ok {
|
||||
if elemType == TypeUnknown {
|
||||
elemType = v.Type
|
||||
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 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)
|
||||
return elemType, nil
|
||||
}
|
||||
|
|
|
@ -98,10 +98,6 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node {
|
|||
pos.Column, pos.Line, err)
|
||||
}
|
||||
|
||||
if v.StackPeek() == ast.TypeUnknown {
|
||||
v.err = errExitUnknown
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -116,6 +112,14 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
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 {
|
||||
case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr:
|
||||
return tc.checkLogical(v, exprs)
|
||||
|
@ -333,6 +337,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if args[i] == ast.TypeUnknown {
|
||||
v.StackPush(ast.TypeUnknown)
|
||||
return tc.n, nil
|
||||
}
|
||||
|
||||
if args[i] != expected {
|
||||
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
|
||||
if cn != nil {
|
||||
|
@ -350,6 +359,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
if function.Variadic && function.VariadicType != ast.TypeAny {
|
||||
args = args[len(function.ArgTypes):]
|
||||
for i, t := range args {
|
||||
if t == ast.TypeUnknown {
|
||||
v.StackPush(ast.TypeUnknown)
|
||||
return tc.n, nil
|
||||
}
|
||||
|
||||
if t != function.VariadicType {
|
||||
realI := i + len(function.ArgTypes)
|
||||
cn := v.ImplicitConversion(
|
||||
|
@ -384,6 +398,11 @@ func (tc *typeCheckConditional) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
trueType := v.StackPop()
|
||||
condType := v.StackPop()
|
||||
|
||||
if condType == ast.TypeUnknown {
|
||||
v.StackPush(ast.TypeUnknown)
|
||||
return tc.n, nil
|
||||
}
|
||||
|
||||
if condType != ast.TypeBool {
|
||||
cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr)
|
||||
if cn == nil {
|
||||
|
@ -457,6 +476,13 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
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 len(types) == 1 {
|
||||
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
|
||||
resultType := ast.TypeString
|
||||
for i, t := range types {
|
||||
|
||||
if t == ast.TypeUnknown {
|
||||
resultType = ast.TypeUnknown
|
||||
continue
|
||||
}
|
||||
|
||||
if t != ast.TypeString {
|
||||
cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i])
|
||||
if cn != nil {
|
||||
|
@ -482,8 +515,8 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// This always results in type string
|
||||
v.StackPush(ast.TypeString)
|
||||
// This always results in type string, unless there are unknowns
|
||||
v.StackPush(resultType)
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
@ -509,13 +542,6 @@ 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)
|
||||
|
||||
|
@ -530,6 +556,11 @@ func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
|||
keyType := 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
|
||||
varAccessNode, ok := tc.n.Target.(*ast.VariableAccess)
|
||||
if !ok {
|
||||
|
|
|
@ -54,6 +54,14 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) {
|
|||
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 {
|
||||
case ast.TypeList:
|
||||
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))
|
||||
for i, _ := range v.Args {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -286,6 +298,11 @@ func (v *evalConditional) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.
|
|||
trueLit := 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) {
|
||||
return trueLit.Value, trueLit.Typex, nil
|
||||
} 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
|
||||
|
||||
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 {
|
||||
case ast.TypeList:
|
||||
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
|
||||
// order. So pop them off, reverse their order, and concatenate.
|
||||
nodes := make([]*ast.LiteralNode, 0, len(v.Exprs))
|
||||
haveUnknown := false
|
||||
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
|
||||
|
@ -396,6 +438,14 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type,
|
|||
// Otherwise concatenate the strings
|
||||
var buf bytes.Buffer
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -418,11 +468,5 @@ 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
|
||||
}
|
||||
|
|
|
@ -2138,28 +2138,28 @@
|
|||
"revisionTime": "2017-05-04T19:02:34Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2Nrl/YKrmowkRgCDLhA6UTFgYEY=",
|
||||
"checksumSHA1": "zz3/f3YpHHBN78uLhnhLBW2aF8o=",
|
||||
"path": "github.com/hashicorp/hil",
|
||||
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2",
|
||||
"revisionTime": "2016-12-21T19:20:42Z"
|
||||
"revision": "747a6e1523d6808f91144df070435b16865cd333",
|
||||
"revisionTime": "2017-05-01T20:07:50Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "oZ2a2x9qyHqvqJdv/Du3LGeaFdA=",
|
||||
"checksumSHA1": "0S0KeBcfqVFYBPeZkuJ4fhQ5mCA=",
|
||||
"path": "github.com/hashicorp/hil/ast",
|
||||
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2",
|
||||
"revisionTime": "2016-12-21T19:20:42Z"
|
||||
"revision": "747a6e1523d6808f91144df070435b16865cd333",
|
||||
"revisionTime": "2017-05-01T20:07:50Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "P5PZ3k7SmqWmxgJ8Q0gLzeNpGhE=",
|
||||
"path": "github.com/hashicorp/hil/parser",
|
||||
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2",
|
||||
"revisionTime": "2016-12-21T19:20:42Z"
|
||||
"revision": "747a6e1523d6808f91144df070435b16865cd333",
|
||||
"revisionTime": "2017-05-01T20:07:50Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "DC1k5kOua4oFqmo+JRt0YzfP44o=",
|
||||
"path": "github.com/hashicorp/hil/scanner",
|
||||
"revision": "5b8d13c8c5c2753e109fab25392a1dbfa2db93d2",
|
||||
"revisionTime": "2016-12-21T19:20:42Z"
|
||||
"revision": "747a6e1523d6808f91144df070435b16865cd333",
|
||||
"revisionTime": "2017-05-01T20:07:50Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vt+P9D2yWDO3gdvdgCzwqunlhxU=",
|
||||
|
|
Loading…
Reference in New Issue