config/lang: implicit type conversions

This commit is contained in:
Mitchell Hashimoto 2015-01-14 11:47:20 -08:00
parent 8ae14f06b3
commit 36b6601baf
2 changed files with 146 additions and 2 deletions

View File

@ -9,9 +9,21 @@ import (
// 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 *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 []ast.Type
err error
lock sync.Mutex
@ -61,6 +73,12 @@ func (v *TypeCheck) visitCall(n *ast.Call) {
// Verify the args
for i, expected := range function.ArgTypes {
if args[i] != expected {
cn := v.implicitConversion(args[i], expected, n.Args[i])
if cn != nil {
n.Args[i] = cn
continue
}
v.createErr(n, fmt.Sprintf(
"%s: argument %d should be %s, got %s",
n.Func, i+1, expected, args[i]))
@ -73,9 +91,17 @@ func (v *TypeCheck) visitCall(n *ast.Call) {
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, n.Args[realI])
if cn != nil {
n.Args[realI] = cn
continue
}
v.createErr(n, fmt.Sprintf(
"%s: argument %d should be %s, got %s",
n.Func, i+len(function.ArgTypes),
n.Func, realI,
function.VariadicType, t))
return
}
@ -95,8 +121,14 @@ func (v *TypeCheck) visitConcat(n *ast.Concat) {
// 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
}
v.createErr(n, fmt.Sprintf(
"argument %d must be a sting", n, i+1))
"argument %d must be a string", i+1))
return
}
}
@ -126,6 +158,29 @@ func (v *TypeCheck) createErr(n ast.Node, str string) {
v.err = fmt.Errorf("%s: %s", n.Pos(), str)
}
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

View File

@ -174,3 +174,92 @@ func TestTypeCheck(t *testing.T) {
}
}
}
func TestTypeCheck_implicit(t *testing.T) {
implicitMap := map[ast.Type]map[ast.Type]string{
ast.TypeInt: {
ast.TypeString: "intToString",
},
}
cases := []struct {
Input string
Scope *Scope
Error bool
}{
{
"foo ${bar}",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
false,
},
{
"foo ${foo(42)}",
&Scope{
FuncMap: map[string]Function{
"foo": Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
},
},
},
false,
},
{
`foo ${foo("42", 42)}`,
&Scope{
FuncMap: map[string]Function{
"foo": Function{
ArgTypes: []ast.Type{ast.TypeString},
Variadic: true,
VariadicType: ast.TypeString,
ReturnType: ast.TypeString,
},
},
},
false,
},
}
for _, tc := range cases {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
// Modify the scope to add our conversion functions.
if tc.Scope.FuncMap == nil {
tc.Scope.FuncMap = make(map[string]Function)
}
tc.Scope.FuncMap["intToString"] = Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
}
// Do the first pass...
visitor := &TypeCheck{Scope: tc.Scope, Implicit: implicitMap}
err = visitor.Visit(node)
if (err != nil) != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
if err != nil {
continue
}
// If we didn't error, then the next type check should not fail
// WITHOUT implicits.
visitor = &TypeCheck{Scope: tc.Scope}
err = visitor.Visit(node)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
}
}