github.com/jgadling/terraform@v0.3.8-0.20150227214559-abd68c2c87bc/config/lang/check_types.go (about) 1 package lang 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/hashicorp/terraform/config/lang/ast" 8 ) 9 10 // TypeCheck implements ast.Visitor for type checking an AST tree. 11 // It requires some configuration to look up the type of nodes. 12 // 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 { 19 Scope ast.Scope 20 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 26 27 // Stack of types. This shouldn't be used directly except by implementations 28 // of TypeCheckNode. 29 Stack []ast.Type 30 31 err error 32 lock sync.Mutex 33 } 34 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) 40 } 41 42 func (v *TypeCheck) Visit(root ast.Node) error { 43 v.lock.Lock() 44 defer v.lock.Unlock() 45 defer v.reset() 46 root.Accept(v.visit) 47 return v.err 48 } 49 50 func (v *TypeCheck) visit(raw ast.Node) ast.Node { 51 if v.err != nil { 52 return raw 53 } 54 55 var result ast.Node 56 var err error 57 switch n := raw.(type) { 58 case *ast.Arithmetic: 59 tc := &typeCheckArithmetic{n} 60 result, err = tc.TypeCheck(v) 61 case *ast.Call: 62 tc := &typeCheckCall{n} 63 result, err = tc.TypeCheck(v) 64 case *ast.Concat: 65 tc := &typeCheckConcat{n} 66 result, err = tc.TypeCheck(v) 67 case *ast.LiteralNode: 68 tc := &typeCheckLiteral{n} 69 result, err = tc.TypeCheck(v) 70 case *ast.VariableAccess: 71 tc := &typeCheckVariableAccess{n} 72 result, err = tc.TypeCheck(v) 73 default: 74 tc, ok := raw.(TypeCheckNode) 75 if !ok { 76 err = fmt.Errorf("unknown node for type check: %#v", raw) 77 break 78 } 79 80 result, err = tc.TypeCheck(v) 81 } 82 83 if err != nil { 84 pos := raw.Pos() 85 v.err = fmt.Errorf("At column %d, line %d: %s", 86 pos.Column, pos.Line, err) 87 } 88 89 return result 90 } 91 92 type typeCheckArithmetic struct { 93 n *ast.Arithmetic 94 } 95 96 func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { 97 // The arguments are on the stack in reverse order, so pop them off. 98 exprs := make([]ast.Type, len(tc.n.Exprs)) 99 for i, _ := range tc.n.Exprs { 100 exprs[len(tc.n.Exprs)-1-i] = v.StackPop() 101 } 102 103 // Determine the resulting type we want 104 mathFunc := "__builtin_IntMath" 105 mathType := ast.TypeInt 106 switch v := exprs[0]; v { 107 case ast.TypeInt: 108 mathFunc = "__builtin_IntMath" 109 mathType = v 110 case ast.TypeFloat: 111 mathFunc = "__builtin_FloatMath" 112 mathType = v 113 default: 114 return nil, fmt.Errorf( 115 "Math operations can only be done with ints and floats, got %s", 116 v) 117 } 118 119 // Verify the args 120 for i, arg := range exprs { 121 if arg != mathType { 122 cn := v.ImplicitConversion(exprs[i], mathType, tc.n.Exprs[i]) 123 if cn != nil { 124 tc.n.Exprs[i] = cn 125 continue 126 } 127 128 return nil, fmt.Errorf( 129 "operand %d should be %s, got %s", 130 i+1, mathType, arg) 131 } 132 } 133 134 // Modulo doesn't work for floats 135 if mathType == ast.TypeFloat && tc.n.Op == ast.ArithmeticOpMod { 136 return nil, fmt.Errorf("modulo cannot be used with floats") 137 } 138 139 // Return type 140 v.StackPush(mathType) 141 142 // Replace our node with a call to the proper function. This isn't 143 // type checked but we already verified types. 144 args := make([]ast.Node, len(tc.n.Exprs)+1) 145 args[0] = &ast.LiteralNode{ 146 Value: tc.n.Op, 147 Typex: ast.TypeInt, 148 Posx: tc.n.Pos(), 149 } 150 copy(args[1:], tc.n.Exprs) 151 return &ast.Call{ 152 Func: mathFunc, 153 Args: args, 154 Posx: tc.n.Pos(), 155 }, nil 156 } 157 158 type typeCheckCall struct { 159 n *ast.Call 160 } 161 162 func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { 163 // Look up the function in the map 164 function, ok := v.Scope.LookupFunc(tc.n.Func) 165 if !ok { 166 return nil, fmt.Errorf("unknown function called: %s", tc.n.Func) 167 } 168 169 // The arguments are on the stack in reverse order, so pop them off. 170 args := make([]ast.Type, len(tc.n.Args)) 171 for i, _ := range tc.n.Args { 172 args[len(tc.n.Args)-1-i] = v.StackPop() 173 } 174 175 // Verify the args 176 for i, expected := range function.ArgTypes { 177 if args[i] != expected { 178 cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) 179 if cn != nil { 180 tc.n.Args[i] = cn 181 continue 182 } 183 184 return nil, fmt.Errorf( 185 "%s: argument %d should be %s, got %s", 186 tc.n.Func, i+1, expected, args[i]) 187 } 188 } 189 190 // If we're variadic, then verify the types there 191 if function.Variadic { 192 args = args[len(function.ArgTypes):] 193 for i, t := range args { 194 if t != function.VariadicType { 195 realI := i + len(function.ArgTypes) 196 cn := v.ImplicitConversion( 197 t, function.VariadicType, tc.n.Args[realI]) 198 if cn != nil { 199 tc.n.Args[realI] = cn 200 continue 201 } 202 203 return nil, fmt.Errorf( 204 "%s: argument %d should be %s, got %s", 205 tc.n.Func, realI, 206 function.VariadicType, t) 207 } 208 } 209 } 210 211 // Return type 212 v.StackPush(function.ReturnType) 213 214 return tc.n, nil 215 } 216 217 type typeCheckConcat struct { 218 n *ast.Concat 219 } 220 221 func (tc *typeCheckConcat) TypeCheck(v *TypeCheck) (ast.Node, error) { 222 n := tc.n 223 types := make([]ast.Type, len(n.Exprs)) 224 for i, _ := range n.Exprs { 225 types[len(n.Exprs)-1-i] = v.StackPop() 226 } 227 228 // All concat args must be strings, so validate that 229 for i, t := range types { 230 if t != ast.TypeString { 231 cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i]) 232 if cn != nil { 233 n.Exprs[i] = cn 234 continue 235 } 236 237 return nil, fmt.Errorf( 238 "argument %d must be a string", i+1) 239 } 240 } 241 242 // This always results in type string 243 v.StackPush(ast.TypeString) 244 245 return n, nil 246 } 247 248 type typeCheckLiteral struct { 249 n *ast.LiteralNode 250 } 251 252 func (tc *typeCheckLiteral) TypeCheck(v *TypeCheck) (ast.Node, error) { 253 v.StackPush(tc.n.Typex) 254 return tc.n, nil 255 } 256 257 type typeCheckVariableAccess struct { 258 n *ast.VariableAccess 259 } 260 261 func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) { 262 // Look up the variable in the map 263 variable, ok := v.Scope.LookupVar(tc.n.Name) 264 if !ok { 265 return nil, fmt.Errorf( 266 "unknown variable accessed: %s", tc.n.Name) 267 } 268 269 // Add the type to the stack 270 v.StackPush(variable.Type) 271 272 return tc.n, nil 273 } 274 275 func (v *TypeCheck) ImplicitConversion( 276 actual ast.Type, expected ast.Type, n ast.Node) ast.Node { 277 if v.Implicit == nil { 278 return nil 279 } 280 281 fromMap, ok := v.Implicit[actual] 282 if !ok { 283 return nil 284 } 285 286 toFunc, ok := fromMap[expected] 287 if !ok { 288 return nil 289 } 290 291 return &ast.Call{ 292 Func: toFunc, 293 Args: []ast.Node{n}, 294 Posx: n.Pos(), 295 } 296 } 297 298 func (v *TypeCheck) reset() { 299 v.Stack = nil 300 v.err = nil 301 } 302 303 func (v *TypeCheck) StackPush(t ast.Type) { 304 v.Stack = append(v.Stack, t) 305 } 306 307 func (v *TypeCheck) StackPop() ast.Type { 308 var x ast.Type 309 x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1] 310 return x 311 }