7 "github.com/hashicorp/hil/ast"
10 // TypeCheck implements ast.Visitor for type checking an AST tree.
11 // It requires some configuration to look up the type of nodes.
13 // It also optionally will not type error and will insert an implicit
14 // type conversions for specific types if specified by the Implicit
15 // field. Note that this is kind of organizationally weird to put into
16 // this structure but we'd rather do that than duplicate the type checking
17 // logic multiple times.
18 type TypeCheck struct {
21 // Implicit is a map of implicit type conversions that we can do,
22 // and that shouldn't error. The key of the first map is the from type,
23 // the key of the second map is the to type, and the final string
24 // value is the function to call (which must be registered in the Scope).
25 Implicit map[ast.Type]map[ast.Type]string
27 // Stack of types. This shouldn't be used directly except by implementations
35 // TypeCheckNode is the interface that must be implemented by any
36 // ast.Node that wants to support type-checking. If the type checker
37 // encounters a node that doesn't implement this, it will error.
38 type TypeCheckNode interface {
39 TypeCheck(*TypeCheck) (ast.Node, error)
42 func (v *TypeCheck) Visit(root ast.Node) error {
48 // If the resulting type is unknown, then just let the whole thing go.
49 if v.err == errExitUnknown {
56 func (v *TypeCheck) visit(raw ast.Node) ast.Node {
63 switch n := raw.(type) {
65 tc := &typeCheckArithmetic{n}
66 result, err = tc.TypeCheck(v)
68 tc := &typeCheckCall{n}
69 result, err = tc.TypeCheck(v)
70 case *ast.Conditional:
71 tc := &typeCheckConditional{n}
72 result, err = tc.TypeCheck(v)
74 tc := &typeCheckIndex{n}
75 result, err = tc.TypeCheck(v)
77 tc := &typeCheckOutput{n}
78 result, err = tc.TypeCheck(v)
79 case *ast.LiteralNode:
80 tc := &typeCheckLiteral{n}
81 result, err = tc.TypeCheck(v)
82 case *ast.VariableAccess:
83 tc := &typeCheckVariableAccess{n}
84 result, err = tc.TypeCheck(v)
86 tc, ok := raw.(TypeCheckNode)
88 err = fmt.Errorf("unknown node for type check: %#v", raw)
92 result, err = tc.TypeCheck(v)
97 v.err = fmt.Errorf("At column %d, line %d: %s",
98 pos.Column, pos.Line, err)
104 type typeCheckArithmetic struct {
108 func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
109 // The arguments are on the stack in reverse order, so pop them off.
110 exprs := make([]ast.Type, len(tc.n.Exprs))
111 for i, _ := range tc.n.Exprs {
112 exprs[len(tc.n.Exprs)-1-i] = v.StackPop()
115 // If any operand is unknown then our result is automatically unknown
116 for _, ty := range exprs {
117 if ty == ast.TypeUnknown {
118 v.StackPush(ast.TypeUnknown)
124 case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr:
125 return tc.checkLogical(v, exprs)
126 case ast.ArithmeticOpEqual, ast.ArithmeticOpNotEqual,
127 ast.ArithmeticOpLessThan, ast.ArithmeticOpGreaterThan,
128 ast.ArithmeticOpGreaterThanOrEqual, ast.ArithmeticOpLessThanOrEqual:
129 return tc.checkComparison(v, exprs)
131 return tc.checkNumeric(v, exprs)
136 func (tc *typeCheckArithmetic) checkNumeric(v *TypeCheck, exprs []ast.Type) (ast.Node, error) {
137 // Determine the resulting type we want. We do this by going over
138 // every expression until we find one with a type we recognize.
139 // We do this because the first expr might be a string ("var.foo")
140 // and we need to know what to implicit to.
141 mathFunc := "__builtin_IntMath"
142 mathType := ast.TypeInt
143 for _, v := range exprs {
144 // We assume int math but if we find ANY float, the entire
145 // expression turns into floating point math.
146 if v == ast.TypeFloat {
147 mathFunc = "__builtin_FloatMath"
154 for i, arg := range exprs {
156 cn := v.ImplicitConversion(exprs[i], mathType, tc.n.Exprs[i])
162 return nil, fmt.Errorf(
163 "operand %d should be %s, got %s",
168 // Modulo doesn't work for floats
169 if mathType == ast.TypeFloat && tc.n.Op == ast.ArithmeticOpMod {
170 return nil, fmt.Errorf("modulo cannot be used with floats")
174 v.StackPush(mathType)
176 // Replace our node with a call to the proper function. This isn't
177 // type checked but we already verified types.
178 args := make([]ast.Node, len(tc.n.Exprs)+1)
179 args[0] = &ast.LiteralNode{
184 copy(args[1:], tc.n.Exprs)
192 func (tc *typeCheckArithmetic) checkComparison(v *TypeCheck, exprs []ast.Type) (ast.Node, error) {
194 // This should never happen, because the parser never produces
195 // nodes that violate this.
196 return nil, fmt.Errorf(
197 "comparison operators must have exactly two operands",
201 // The first operand always dictates the type for a comparison.
203 compareType := exprs[0]
206 compareFunc = "__builtin_BoolCompare"
208 compareFunc = "__builtin_FloatCompare"
210 compareFunc = "__builtin_IntCompare"
212 compareFunc = "__builtin_StringCompare"
214 return nil, fmt.Errorf(
215 "comparison operators apply only to bool, float, int, and string",
219 // For non-equality comparisons, we will do implicit conversions to
220 // integer types if possible. In this case, we need to go through and
221 // determine the type of comparison we're doing to enable the implicit
223 if tc.n.Op != ast.ArithmeticOpEqual && tc.n.Op != ast.ArithmeticOpNotEqual {
224 compareFunc = "__builtin_IntCompare"
225 compareType = ast.TypeInt
226 for _, expr := range exprs {
227 if expr == ast.TypeFloat {
228 compareFunc = "__builtin_FloatCompare"
229 compareType = ast.TypeFloat
235 // Verify (and possibly, convert) the args
236 for i, arg := range exprs {
237 if arg != compareType {
238 cn := v.ImplicitConversion(exprs[i], compareType, tc.n.Exprs[i])
244 return nil, fmt.Errorf(
245 "operand %d should be %s, got %s",
246 i+1, compareType, arg,
251 // Only ints and floats can have the <, >, <= and >= operators applied
253 case ast.ArithmeticOpEqual, ast.ArithmeticOpNotEqual:
257 case ast.TypeFloat, ast.TypeInt:
260 return nil, fmt.Errorf(
261 "<, >, <= and >= may apply only to int and float values",
266 // Comparison operators always return bool
267 v.StackPush(ast.TypeBool)
269 // Replace our node with a call to the proper function. This isn't
270 // type checked but we already verified types.
271 args := make([]ast.Node, len(tc.n.Exprs)+1)
272 args[0] = &ast.LiteralNode{
277 copy(args[1:], tc.n.Exprs)
285 func (tc *typeCheckArithmetic) checkLogical(v *TypeCheck, exprs []ast.Type) (ast.Node, error) {
286 for i, t := range exprs {
287 if t != ast.TypeBool {
288 cn := v.ImplicitConversion(t, ast.TypeBool, tc.n.Exprs[i])
290 return nil, fmt.Errorf(
291 "logical operators require boolean operands, not %s",
299 // Return type is always boolean
300 v.StackPush(ast.TypeBool)
302 // Arithmetic nodes are replaced with a call to a built-in function
303 args := make([]ast.Node, len(tc.n.Exprs)+1)
304 args[0] = &ast.LiteralNode{
309 copy(args[1:], tc.n.Exprs)
311 Func: "__builtin_Logical",
317 type typeCheckCall struct {
321 func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
322 // Look up the function in the map
323 function, ok := v.Scope.LookupFunc(tc.n.Func)
325 return nil, fmt.Errorf("unknown function called: %s", tc.n.Func)
328 // The arguments are on the stack in reverse order, so pop them off.
329 args := make([]ast.Type, len(tc.n.Args))
330 for i, _ := range tc.n.Args {
331 args[len(tc.n.Args)-1-i] = v.StackPop()
335 for i, expected := range function.ArgTypes {
336 if expected == ast.TypeAny {
340 if args[i] == ast.TypeUnknown {
341 v.StackPush(ast.TypeUnknown)
345 if args[i] != expected {
346 cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
352 return nil, fmt.Errorf(
353 "%s: argument %d should be %s, got %s",
354 tc.n.Func, i+1, expected.Printable(), args[i].Printable())
358 // If we're variadic, then verify the types there
359 if function.Variadic && function.VariadicType != ast.TypeAny {
360 args = args[len(function.ArgTypes):]
361 for i, t := range args {
362 if t == ast.TypeUnknown {
363 v.StackPush(ast.TypeUnknown)
367 if t != function.VariadicType {
368 realI := i + len(function.ArgTypes)
369 cn := v.ImplicitConversion(
370 t, function.VariadicType, tc.n.Args[realI])
372 tc.n.Args[realI] = cn
376 return nil, fmt.Errorf(
377 "%s: argument %d should be %s, got %s",
379 function.VariadicType.Printable(), t.Printable())
385 v.StackPush(function.ReturnType)
390 type typeCheckConditional struct {
394 func (tc *typeCheckConditional) TypeCheck(v *TypeCheck) (ast.Node, error) {
395 // On the stack we have the types of the condition, true and false
396 // expressions, but they are in reverse order.
397 falseType := v.StackPop()
398 trueType := v.StackPop()
399 condType := v.StackPop()
401 if condType == ast.TypeUnknown {
402 v.StackPush(ast.TypeUnknown)
406 if condType != ast.TypeBool {
407 cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr)
409 return nil, fmt.Errorf(
410 "condition must be type bool, not %s", condType.Printable(),
416 // The types of the true and false expression must match
417 if trueType != falseType && trueType != ast.TypeUnknown && falseType != ast.TypeUnknown {
419 // Since passing around stringified versions of other types is
420 // common, we pragmatically allow the false expression to dictate
421 // the result type when the true expression is a string.
422 if trueType == ast.TypeString {
423 cn := v.ImplicitConversion(trueType, falseType, tc.n.TrueExpr)
425 return nil, fmt.Errorf(
426 "true and false expression types must match; have %s and %s",
427 trueType.Printable(), falseType.Printable(),
433 cn := v.ImplicitConversion(falseType, trueType, tc.n.FalseExpr)
435 return nil, fmt.Errorf(
436 "true and false expression types must match; have %s and %s",
437 trueType.Printable(), falseType.Printable(),
445 // Currently list and map types cannot be used, because we cannot
446 // generally assert that their element types are consistent.
447 // Such support might be added later, either by improving the type
448 // system or restricting usage to only variable and literal expressions,
449 // but for now this is simply prohibited because it doesn't seem to
450 // be a common enough case to be worth the complexity.
453 return nil, fmt.Errorf(
454 "conditional operator cannot be used with list values",
457 return nil, fmt.Errorf(
458 "conditional operator cannot be used with map values",
462 // Result type (guaranteed to also match falseType due to the above)
463 if trueType == ast.TypeUnknown {
464 // falseType may also be unknown, but that's okay because two
465 // unknowns means our result is unknown anyway.
466 v.StackPush(falseType)
468 v.StackPush(trueType)
474 type typeCheckOutput struct {
478 func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
480 types := make([]ast.Type, len(n.Exprs))
481 for i, _ := range n.Exprs {
482 types[len(n.Exprs)-1-i] = v.StackPop()
485 for _, ty := range types {
486 if ty == ast.TypeUnknown {
487 v.StackPush(ast.TypeUnknown)
492 // If there is only one argument and it is a list, we evaluate to a list
494 switch t := types[0]; t {
503 // Otherwise, all concat args must be strings, so validate that
504 resultType := ast.TypeString
505 for i, t := range types {
507 if t == ast.TypeUnknown {
508 resultType = ast.TypeUnknown
512 if t != ast.TypeString {
513 cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i])
519 return nil, fmt.Errorf(
520 "output of an HIL expression must be a string, or a single list (argument %d is %s)", i+1, t)
524 // This always results in type string, unless there are unknowns
525 v.StackPush(resultType)
530 type typeCheckLiteral struct {
534 func (tc *typeCheckLiteral) TypeCheck(v *TypeCheck) (ast.Node, error) {
535 v.StackPush(tc.n.Typex)
539 type typeCheckVariableAccess struct {
540 n *ast.VariableAccess
543 func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) {
544 // Look up the variable in the map
545 variable, ok := v.Scope.LookupVar(tc.n.Name)
547 return nil, fmt.Errorf(
548 "unknown variable accessed: %s", tc.n.Name)
551 // Add the type to the stack
552 v.StackPush(variable.Type)
557 type typeCheckIndex struct {
561 func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) {
562 keyType := v.StackPop()
563 targetType := v.StackPop()
565 if keyType == ast.TypeUnknown || targetType == ast.TypeUnknown {
566 v.StackPush(ast.TypeUnknown)
570 // Ensure we have a VariableAccess as the target
571 varAccessNode, ok := tc.n.Target.(*ast.VariableAccess)
573 return nil, fmt.Errorf(
574 "target of an index must be a VariableAccess node, was %T", tc.n.Target)
578 variable, ok := v.Scope.LookupVar(varAccessNode.Name)
580 return nil, fmt.Errorf(
581 "unknown variable accessed: %s", varAccessNode.Name)
586 if keyType != ast.TypeInt {
587 tc.n.Key = v.ImplicitConversion(keyType, ast.TypeInt, tc.n.Key)
589 return nil, fmt.Errorf(
590 "key of an index must be an int, was %s", keyType)
594 valType, err := ast.VariableListElementTypesAreHomogenous(
595 varAccessNode.Name, variable.Value.([]ast.Variable))
603 if keyType != ast.TypeString {
604 tc.n.Key = v.ImplicitConversion(keyType, ast.TypeString, tc.n.Key)
606 return nil, fmt.Errorf(
607 "key of an index must be a string, was %s", keyType)
611 valType, err := ast.VariableMapValueTypesAreHomogenous(
612 varAccessNode.Name, variable.Value.(map[string]ast.Variable))
620 return nil, fmt.Errorf("invalid index operation into non-indexable type: %s", variable.Type)
624 func (v *TypeCheck) ImplicitConversion(
625 actual ast.Type, expected ast.Type, n ast.Node) ast.Node {
626 if v.Implicit == nil {
630 fromMap, ok := v.Implicit[actual]
635 toFunc, ok := fromMap[expected]
647 func (v *TypeCheck) reset() {
652 func (v *TypeCheck) StackPush(t ast.Type) {
653 v.Stack = append(v.Stack, t)
656 func (v *TypeCheck) StackPop() ast.Type {
658 x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1]
662 func (v *TypeCheck) StackPeek() ast.Type {
663 if len(v.Stack) == 0 {
664 return ast.TypeInvalid
667 return v.Stack[len(v.Stack)-1]