2015-01-12 08:38:21 +01:00
|
|
|
package lang
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/config/lang/ast"
|
|
|
|
)
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
// TypeCheck implements ast.Visitor for type checking an AST tree.
|
2015-01-12 08:38:21 +01:00
|
|
|
// It requires some configuration to look up the type of nodes.
|
2015-01-13 20:25:46 +01:00
|
|
|
type TypeCheck struct {
|
2015-01-12 19:21:18 +01:00
|
|
|
Scope *Scope
|
2015-01-12 08:38:21 +01:00
|
|
|
|
|
|
|
stack []ast.Type
|
|
|
|
err error
|
|
|
|
lock sync.Mutex
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) Visit(root ast.Node) error {
|
2015-01-12 08:38:21 +01:00
|
|
|
v.lock.Lock()
|
|
|
|
defer v.lock.Unlock()
|
|
|
|
defer v.reset()
|
|
|
|
root.Accept(v.visit)
|
|
|
|
return v.err
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) visit(raw ast.Node) {
|
2015-01-12 08:38:21 +01:00
|
|
|
if v.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch n := raw.(type) {
|
|
|
|
case *ast.Call:
|
|
|
|
v.visitCall(n)
|
|
|
|
case *ast.Concat:
|
|
|
|
v.visitConcat(n)
|
|
|
|
case *ast.LiteralNode:
|
|
|
|
v.visitLiteral(n)
|
|
|
|
case *ast.VariableAccess:
|
|
|
|
v.visitVariableAccess(n)
|
|
|
|
default:
|
2015-01-12 09:28:47 +01:00
|
|
|
v.createErr(n, fmt.Sprintf("unknown node: %#v", raw))
|
2015-01-12 08:38:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) visitCall(n *ast.Call) {
|
2015-01-12 09:35:43 +01:00
|
|
|
// Look up the function in the map
|
2015-01-12 19:21:18 +01:00
|
|
|
function, ok := v.Scope.LookupFunc(n.Func)
|
2015-01-12 09:35:43 +01:00
|
|
|
if !ok {
|
|
|
|
v.createErr(n, fmt.Sprintf("unknown function called: %s", n.Func))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// The arguments are on the stack in reverse order, so pop them off.
|
|
|
|
args := make([]ast.Type, len(n.Args))
|
|
|
|
for i, _ := range n.Args {
|
|
|
|
args[len(n.Args)-1-i] = v.stackPop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the args
|
|
|
|
for i, expected := range function.ArgTypes {
|
|
|
|
if args[i] != expected {
|
|
|
|
v.createErr(n, fmt.Sprintf(
|
|
|
|
"%s: argument %d should be %s, got %s",
|
|
|
|
n.Func, i+1, expected, args[i]))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return type
|
|
|
|
v.stackPush(function.ReturnType)
|
2015-01-12 08:38:21 +01:00
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) visitConcat(n *ast.Concat) {
|
2015-01-12 08:38:21 +01:00
|
|
|
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 {
|
2015-01-12 09:28:47 +01:00
|
|
|
v.createErr(n, fmt.Sprintf(
|
|
|
|
"argument %d must be a sting", n, i+1))
|
2015-01-12 08:38:21 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This always results in type string
|
|
|
|
v.stackPush(ast.TypeString)
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) visitLiteral(n *ast.LiteralNode) {
|
2015-01-12 08:38:21 +01:00
|
|
|
v.stackPush(n.Type)
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) visitVariableAccess(n *ast.VariableAccess) {
|
2015-01-12 08:38:21 +01:00
|
|
|
// Look up the variable in the map
|
2015-01-12 19:21:18 +01:00
|
|
|
variable, ok := v.Scope.LookupVar(n.Name)
|
2015-01-12 08:38:21 +01:00
|
|
|
if !ok {
|
2015-01-12 09:28:47 +01:00
|
|
|
v.createErr(n, fmt.Sprintf(
|
|
|
|
"unknown variable accessed: %s", n.Name))
|
2015-01-12 08:38:21 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the type to the stack
|
|
|
|
v.stackPush(variable.Type)
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) createErr(n ast.Node, str string) {
|
2015-01-12 09:28:47 +01:00
|
|
|
v.err = fmt.Errorf("%s: %s", n.Pos(), str)
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) reset() {
|
2015-01-12 08:38:21 +01:00
|
|
|
v.stack = nil
|
|
|
|
v.err = nil
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) stackPush(t ast.Type) {
|
2015-01-12 08:38:21 +01:00
|
|
|
v.stack = append(v.stack, t)
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:25:46 +01:00
|
|
|
func (v *TypeCheck) stackPop() ast.Type {
|
2015-01-12 08:38:21 +01:00
|
|
|
var x ast.Type
|
|
|
|
x, v.stack = v.stack[len(v.stack)-1], v.stack[:len(v.stack)-1]
|
|
|
|
return x
|
|
|
|
}
|