mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
config/lang: start implementing type checking
This commit is contained in:
parent
8f925b93e0
commit
d3b1010444
@ -22,4 +22,5 @@ type Type uint
|
||||
const (
|
||||
TypeInvalid Type = 0
|
||||
TypeString = 1 << iota
|
||||
TypeInt
|
||||
)
|
||||
|
100
config/lang/visitor_types.go
Normal file
100
config/lang/visitor_types.go
Normal file
@ -0,0 +1,100 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
// TypeVisitor implements ast.Visitor for type checking an AST tree.
|
||||
// It requires some configuration to look up the type of nodes.
|
||||
type TypeVisitor struct {
|
||||
VarMap map[string]Variable
|
||||
FuncMap map[string]Function
|
||||
|
||||
stack []ast.Type
|
||||
err error
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) Visit(root ast.Node) error {
|
||||
v.lock.Lock()
|
||||
defer v.lock.Unlock()
|
||||
defer v.reset()
|
||||
root.Accept(v.visit)
|
||||
return v.err
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) visit(raw ast.Node) {
|
||||
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:
|
||||
v.err = fmt.Errorf("unknown node: %#v", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) visitCall(n *ast.Call) {
|
||||
// TODO
|
||||
v.stackPush(ast.TypeString)
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) visitConcat(n *ast.Concat) {
|
||||
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 {
|
||||
v.err = fmt.Errorf("%s: argument %d must be a sting", n, i+1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This always results in type string
|
||||
v.stackPush(ast.TypeString)
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) visitLiteral(n *ast.LiteralNode) {
|
||||
v.stackPush(n.Type)
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) visitVariableAccess(n *ast.VariableAccess) {
|
||||
// Look up the variable in the map
|
||||
variable, ok := v.VarMap[n.Name]
|
||||
if !ok {
|
||||
v.err = fmt.Errorf("unknown variable accessed: %s", n.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// Add the type to the stack
|
||||
v.stackPush(variable.Type)
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) reset() {
|
||||
v.stack = nil
|
||||
v.err = nil
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) stackPush(t ast.Type) {
|
||||
v.stack = append(v.stack, t)
|
||||
}
|
||||
|
||||
func (v *TypeVisitor) stackPop() ast.Type {
|
||||
var x ast.Type
|
||||
x, v.stack = v.stack[len(v.stack)-1], v.stack[:len(v.stack)-1]
|
||||
return x
|
||||
}
|
74
config/lang/visitor_types_test.go
Normal file
74
config/lang/visitor_types_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
)
|
||||
|
||||
func TestTypeVisitor(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Visitor *TypeVisitor
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
"foo",
|
||||
&TypeVisitor{},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"foo ${bar}",
|
||||
&TypeVisitor{
|
||||
VarMap: map[string]Variable{
|
||||
"bar": Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"foo ${rand()}",
|
||||
&TypeVisitor{
|
||||
FuncMap: map[string]Function{
|
||||
"rand": Function{
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func([]interface{}) (interface{}, error) {
|
||||
return "42", nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"foo ${bar}",
|
||||
&TypeVisitor{
|
||||
VarMap: map[string]Variable{
|
||||
"bar": Variable{
|
||||
Value: 42,
|
||||
Type: ast.TypeInt,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
node, err := Parse(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
||||
}
|
||||
|
||||
err = tc.Visitor.Visit(node)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user