package lang import ( "fmt" "sync" "github.com/hashicorp/terraform/config/lang/ast" ) // TypeCheck implements ast.Visitor for type checking an AST tree. // It requires some configuration to look up the type of nodes. // // It also optionally will not type error and will insert an implicit // type conversions for specific types if specified by the Implicit // field. Note that this is kind of organizationally weird to put into // this structure but we'd rather do that than duplicate the type checking // logic multiple times. type TypeCheck struct { Scope ast.Scope // Implicit is a map of implicit type conversions that we can do, // and that shouldn't error. The key of the first map is the from type, // the key of the second map is the to type, and the final string // value is the function to call (which must be registered in the Scope). Implicit map[ast.Type]map[ast.Type]string // Stack of types. This shouldn't be used directly except by implementations // of TypeCheckNode. Stack []ast.Type err error lock sync.Mutex } // TypeCheckNode is the interface that must be implemented by any // ast.Node that wants to support type-checking. If the type checker // encounters a node that doesn't implement this, it will error. type TypeCheckNode interface { TypeCheck(*TypeCheck) (ast.Node, error) } func (v *TypeCheck) Visit(root ast.Node) error { v.lock.Lock() defer v.lock.Unlock() defer v.reset() root.Accept(v.visit) return v.err } func (v *TypeCheck) visit(raw ast.Node) ast.Node { if v.err != nil { return raw } var result ast.Node var err error switch n := raw.(type) { case *ast.Call: tc := &typeCheckCall{n} result, err = tc.TypeCheck(v) case *ast.Concat: tc := &typeCheckConcat{n} result, err = tc.TypeCheck(v) case *ast.LiteralNode: tc := &typeCheckLiteral{n} result, err = tc.TypeCheck(v) case *ast.VariableAccess: tc := &typeCheckVariableAccess{n} result, err = tc.TypeCheck(v) default: tc, ok := raw.(TypeCheckNode) if !ok { err = fmt.Errorf("unknown node: %#v", raw) break } result, err = tc.TypeCheck(v) } if err != nil { pos := raw.Pos() v.err = fmt.Errorf("At column %d, line %d: %s", pos.Column, pos.Line, err) } return result } type typeCheckCall struct { n *ast.Call } func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { // Look up the function in the map function, ok := v.Scope.LookupFunc(tc.n.Func) if !ok { return nil, fmt.Errorf("unknown function called: %s", tc.n.Func) } // The arguments are on the stack in reverse order, so pop them off. args := make([]ast.Type, len(tc.n.Args)) for i, _ := range tc.n.Args { args[len(tc.n.Args)-1-i] = v.StackPop() } // Verify the args for i, expected := range function.ArgTypes { if args[i] != expected { cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) if cn != nil { tc.n.Args[i] = cn continue } return nil, fmt.Errorf( "%s: argument %d should be %s, got %s", tc.n.Func, i+1, expected, args[i]) } } // If we're variadic, then verify the types there if function.Variadic { args = args[len(function.ArgTypes):] for i, t := range args { if t != function.VariadicType { realI := i + len(function.ArgTypes) cn := v.ImplicitConversion( t, function.VariadicType, tc.n.Args[realI]) if cn != nil { tc.n.Args[realI] = cn continue } return nil, fmt.Errorf( "%s: argument %d should be %s, got %s", tc.n.Func, realI, function.VariadicType, t) } } } // Return type v.StackPush(function.ReturnType) return tc.n, nil } type typeCheckConcat struct { n *ast.Concat } func (tc *typeCheckConcat) TypeCheck(v *TypeCheck) (ast.Node, error) { n := tc.n types := make([]ast.Type, len(n.Exprs)) for i, _ := range n.Exprs { types[len(n.Exprs)-1-i] = v.StackPop() } // All concat args must be strings, so validate that for i, t := range types { if t != ast.TypeString { cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i]) if cn != nil { n.Exprs[i] = cn continue } return nil, fmt.Errorf( "argument %d must be a string", i+1) } } // This always results in type string v.StackPush(ast.TypeString) return n, nil } type typeCheckLiteral struct { n *ast.LiteralNode } func (tc *typeCheckLiteral) TypeCheck(v *TypeCheck) (ast.Node, error) { v.StackPush(tc.n.Typex) return tc.n, nil } type typeCheckVariableAccess struct { n *ast.VariableAccess } func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) { // Look up the variable in the map variable, ok := v.Scope.LookupVar(tc.n.Name) if !ok { return nil, fmt.Errorf( "unknown variable accessed: %s", tc.n.Name) } // Add the type to the stack v.StackPush(variable.Type) return tc.n, nil } func (v *TypeCheck) ImplicitConversion( actual ast.Type, expected ast.Type, n ast.Node) ast.Node { if v.Implicit == nil { return nil } fromMap, ok := v.Implicit[actual] if !ok { return nil } toFunc, ok := fromMap[expected] if !ok { return nil } return &ast.Call{ Func: toFunc, Args: []ast.Node{n}, Posx: n.Pos(), } } func (v *TypeCheck) reset() { v.Stack = nil v.err = nil } func (v *TypeCheck) StackPush(t ast.Type) { v.Stack = append(v.Stack, t) } func (v *TypeCheck) StackPop() ast.Type { var x ast.Type x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1] return x }