config/lang: implicit type conversions
This commit is contained in:
parent
8ae14f06b3
commit
36b6601baf
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue