Skip to content

Commit

Permalink
feat: add builtin type checking
Browse files Browse the repository at this point in the history
This adds type checking for builtin functions. It also refactors builtin names into constants due to the number of times they are used.
  • Loading branch information
nrwiersma committed Aug 27, 2020
1 parent e332a6b commit f3f9ffa
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 59 deletions.
11 changes: 8 additions & 3 deletions interp/cfg.go
Expand Up @@ -39,9 +39,9 @@ var constOp = map[action]func(*node){
}

var constBltn = map[string]func(*node){
"complex": complexConst,
"imag": imagConst,
"real": realConst,
bltnComplex: complexConst,
bltnImag: imagConst,
bltnReal: realConst,
}

var identifier = regexp.MustCompile(`([\pL_][\pL_\d]*)$`)
Expand Down Expand Up @@ -799,6 +799,11 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
wireChild(n)
switch {
case interp.isBuiltinCall(n):
err = check.builtin(n.child[0].ident, n, n.child[1:], n.action == aCallSlice)
if err != nil {
break
}

n.gen = n.child[0].sym.builtin
n.child[0].typ = &itype{cat: builtinT}
if n.typ, err = nodeType(interp, sc, n); err != nil {
Expand Down
48 changes: 33 additions & 15 deletions interp/interp.go
Expand Up @@ -252,6 +252,24 @@ func New(options Options) *Interpreter {
return &i
}

const (
bltnAppend = "append"
bltnCap = "cap"
bltnClose = "close"
bltnComplex = "complex"
bltnImag = "imag"
bltnCopy = "copy"
bltnDelete = "delete"
bltnLen = "len"
bltnMake = "make"
bltnNew = "new"
bltnPanic = "panic"
bltnPrint = "print"
bltnPrintln = "println"
bltnReal = "real"
bltnRecover = "recover"
)

func initUniverse() *scope {
sc := &scope{global: true, sym: map[string]*symbol{
// predefined Go types
Expand Down Expand Up @@ -286,21 +304,21 @@ func initUniverse() *scope {
"nil": {typ: &itype{cat: nilT, untyped: true}},

// predefined Go builtins
"append": {kind: bltnSym, builtin: _append},
"cap": {kind: bltnSym, builtin: _cap},
"close": {kind: bltnSym, builtin: _close},
"complex": {kind: bltnSym, builtin: _complex},
"imag": {kind: bltnSym, builtin: _imag},
"copy": {kind: bltnSym, builtin: _copy},
"delete": {kind: bltnSym, builtin: _delete},
"len": {kind: bltnSym, builtin: _len},
"make": {kind: bltnSym, builtin: _make},
"new": {kind: bltnSym, builtin: _new},
"panic": {kind: bltnSym, builtin: _panic},
"print": {kind: bltnSym, builtin: _print},
"println": {kind: bltnSym, builtin: _println},
"real": {kind: bltnSym, builtin: _real},
"recover": {kind: bltnSym, builtin: _recover},
bltnAppend: {kind: bltnSym, builtin: _append},
bltnCap: {kind: bltnSym, builtin: _cap},
bltnClose: {kind: bltnSym, builtin: _close},
bltnComplex: {kind: bltnSym, builtin: _complex},
bltnImag: {kind: bltnSym, builtin: _imag},
bltnCopy: {kind: bltnSym, builtin: _copy},
bltnDelete: {kind: bltnSym, builtin: _delete},
bltnLen: {kind: bltnSym, builtin: _len},
bltnMake: {kind: bltnSym, builtin: _make},
bltnNew: {kind: bltnSym, builtin: _new},
bltnPanic: {kind: bltnSym, builtin: _panic},
bltnPrint: {kind: bltnSym, builtin: _print},
bltnPrintln: {kind: bltnSym, builtin: _println},
bltnReal: {kind: bltnSym, builtin: _real},
bltnRecover: {kind: bltnSym, builtin: _recover},
}}
return sc
}
Expand Down
32 changes: 32 additions & 0 deletions interp/interp_eval_test.go
Expand Up @@ -99,7 +99,39 @@ func TestEvalBuiltin(t *testing.T) {
{src: `c := []int{1}; d := []int{2, 3}; c = append(c, d...); c`, res: "[1 2 3]"},
{src: `string(append([]byte("hello "), "world"...))`, res: "hello world"},
{src: `e := "world"; string(append([]byte("hello "), e...))`, res: "hello world"},
{src: `b := []int{1}; b = append(1, 2, 3); b`, err: "1:54: first argument to append must be slice; have int"},
{src: `g := len(a)`, res: "1"},
{src: `g := cap(a)`, res: "1"},
{src: `g := len("test")`, res: "4"},
{src: `g := len(map[string]string{"a": "b"})`, res: "1"},
{src: `a := len()`, err: "not enough arguments in call to len"},
{src: `a := len([]int, 0)`, err: "too many arguments for len"},
{src: `g := cap("test")`, err: "1:37: invalid argument for cap"},
{src: `g := cap(map[string]string{"a": "b"})`, err: "1:37: invalid argument for cap"},
{src: `h := make(chan int, 1); close(h); len(h)`, res: "0"},
{src: `close(a)`, err: "1:34: invalid operation: non-chan type []int"},
{src: `h := make(chan int, 1); var i <-chan int = h; close(i)`, err: "1:80: invalid operation: cannot close receive-only channel"},
{src: `j := make([]int, 2)`, res: "[0 0]"},
{src: `j := make([]int, 2, 3)`, res: "[0 0]"},
{src: `j := make(int)`, err: "1:38: cannot make int; type must be slice, map, or channel"},
{src: `j := make([]int)`, err: "1:33: not enough arguments in call to make"},
{src: `j := make([]int, 0, 1, 2)`, err: "1:33: too many arguments for make"},
{src: `j := make([]int, 2, 1)`, err: "1:33: len larger than cap in make"},
{src: `j := make([]int, "test")`, err: "1:45: cannot convert \"test\" to int"},
{src: `k := []int{3, 4}; copy(k, []int{1,2}); k`, res: "[1 2]"},
{src: `f := []byte("Hello"); copy(f, "world"); string(f)`, res: "world"},
{src: `copy(g, g)`, err: "1:28: copy expects slice arguments"},
{src: `copy(a, "world")`, err: "1:28: arguments to copy have different element types []int and string"},
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, "a"); l`, res: "map[b:2]"},
{src: `delete(a, 1)`, err: "1:35: first argument to delete must be map; have []int"},
{src: `l := map[string]int{"a": 1, "b": 2}; delete(l, 1)`, err: "1:75: cannot use int as type string in delete"},
{src: `a := []int{1,2}; println(a...)`, err: "invalid use of ... with builtin println"},
{src: `m := complex(3, 2); real(m)`, res: "3"},
{src: `m := complex(3, 2); imag(m)`, res: "2"},
{src: `m := complex("test", 2)`, err: "1:33: invalid types string and int"},
{src: `imag("test")`, err: "1:33: cannot convert \"test\" to complex128"},
{src: `imag(a)`, err: "1:33: invalid argument type []int for imag"},
{src: `real(a)`, err: "1:33: invalid argument type []int for real"},
})
}

Expand Down
14 changes: 7 additions & 7 deletions interp/type.go
Expand Up @@ -282,7 +282,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
// Builtin types are special and may depend from their input arguments.
t.cat = builtinT
switch n.child[0].ident {
case "complex":
case bltnComplex:
var nt0, nt1 *itype
if nt0, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
Expand All @@ -299,7 +299,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
case isFloat64(t0) && isFloat64(t1):
t = sc.getType("complex128")
case nt0.untyped && isNumber(t0) && nt1.untyped && isNumber(t1):
t = &itype{cat: valueT, rtype: complexType, scope: sc}
t = untypedComplex
case nt0.untyped && isFloat32(t1) || nt1.untyped && isFloat32(t0):
t = sc.getType("complex64")
case nt0.untyped && isFloat64(t1) || nt1.untyped && isFloat64(t0):
Expand All @@ -311,7 +311,7 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
t.untyped = true
}
}
case "real", "imag":
case bltnReal, bltnImag:
if t, err = nodeType(interp, sc, n.child[1]); err != nil {
return nil, err
}
Expand All @@ -327,14 +327,14 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) {
err = n.cfgErrorf("invalid complex type %s", k)
}
}
case "cap", "copy", "len":
case bltnCap, bltnCopy, bltnLen:
t = sc.getType("int")
case "append", "make":
case bltnAppend, bltnMake:
t, err = nodeType(interp, sc, n.child[1])
case "new":
case bltnNew:
t, err = nodeType(interp, sc, n.child[1])
t = &itype{cat: ptrT, val: t, incomplete: t.incomplete, scope: sc}
case "recover":
case bltnRecover:
t = sc.getType("interface{}")
}
if err != nil {
Expand Down

0 comments on commit f3f9ffa

Please sign in to comment.