diff options
-rw-r--r-- | resolve/resolve.go | 52 | ||||
-rw-r--r-- | resolve/testdata/resolve.star | 18 | ||||
-rw-r--r-- | starlark/eval.go | 153 | ||||
-rw-r--r-- | starlark/eval_test.go | 14 | ||||
-rw-r--r-- | starlark/testdata/function.star | 2 | ||||
-rw-r--r-- | syntax/parse.go | 2 | ||||
-rw-r--r-- | syntax/testdata/errors.star | 18 |
7 files changed, 138 insertions, 121 deletions
diff --git a/resolve/resolve.go b/resolve/resolve.go index 4b104c3..4933e3d 100644 --- a/resolve/resolve.go +++ b/resolve/resolve.go @@ -735,56 +735,60 @@ func (r *resolver) function(pos syntax.Position, name string, function *syntax.F r.push(b) const allowRebind = false - var seenVarargs, seenKwargs, seenOptional bool + var seenOptional bool + var star, starStar *syntax.Ident // *args, **kwargs for _, param := range function.Params { switch param := param.(type) { case *syntax.Ident: // e.g. x - if seenKwargs { - r.errorf(pos, "parameter may not follow **kwargs") - } else if seenVarargs { - r.errorf(pos, "parameter may not follow *args") + if starStar != nil { + r.errorf(param.NamePos, "required parameter may not follow **%s", starStar.Name) + } else if star != nil { + r.errorf(param.NamePos, "required parameter may not follow * parameter") } else if seenOptional { - r.errorf(pos, "required parameter may not follow optional") + r.errorf(param.NamePos, "required parameter may not follow optional") } if r.bind(param, allowRebind) { - r.errorf(pos, "duplicate parameter: %s", param.Name) + r.errorf(param.NamePos, "duplicate parameter: %s", param.Name) } case *syntax.BinaryExpr: // e.g. y=dflt - if seenKwargs { - r.errorf(pos, "parameter may not follow **kwargs") - } else if seenVarargs { - r.errorf(pos, "parameter may not follow *args") + if starStar != nil { + r.errorf(param.OpPos, "optional parameter may not follow **%s", starStar.Name) + } else if star != nil { + r.errorf(param.OpPos, "optional parameter may not follow *%s", star.Name) } if id := param.X.(*syntax.Ident); r.bind(id, allowRebind) { - r.errorf(pos, "duplicate parameter: %s", id.Name) + r.errorf(param.OpPos, "duplicate parameter: %s", id.Name) } seenOptional = true case *syntax.UnaryExpr: // *args or **kwargs + id := param.X.(*syntax.Ident) if param.Op == syntax.STAR { - if seenKwargs { - r.errorf(pos, "*args may not follow **kwargs") - } else if seenVarargs { - r.errorf(pos, "multiple *args not allowed") + if starStar != nil { + r.errorf(param.OpPos, "*%s parameter may not follow **%s", id.Name, starStar.Name) + } else if star != nil { + r.errorf(param.OpPos, "multiple * parameters not allowed") + } else { + star = id } - seenVarargs = true } else { - if seenKwargs { - r.errorf(pos, "multiple **kwargs not allowed") + if starStar != nil { + r.errorf(param.OpPos, "multiple ** parameters not allowed") + } else { + starStar = id } - seenKwargs = true } - if id := param.X.(*syntax.Ident); r.bind(id, allowRebind) { - r.errorf(pos, "duplicate parameter: %s", id.Name) + if r.bind(id, allowRebind) { + r.errorf(id.NamePos, "duplicate parameter: %s", id.Name) } } } - function.HasVarargs = seenVarargs - function.HasKwargs = seenKwargs + function.HasVarargs = star != nil + function.HasKwargs = starStar != nil r.stmts(function.Body) // Resolve all uses of this function's local vars, diff --git a/resolve/testdata/resolve.star b/resolve/testdata/resolve.star index 1a83115..9d19869 100644 --- a/resolve/testdata/resolve.star +++ b/resolve/testdata/resolve.star @@ -184,31 +184,31 @@ M(x=1, 2) ### `positional argument may not follow named` def f(x=1, y): pass ### `required parameter may not follow optional` --- -# No parameters may follow **kwargs +# No parameters may follow **kwargs in a declaration. def f(**kwargs, x): ### `parameter may not follow \*\*kwargs` pass -def g(**kwargs, *args): ### `\*args may not follow \*\*kwargs` +def g(**kwargs, *args): ### `\*args parameter may not follow \*\*kwargs` pass -def h(**kwargs1, **kwargs2): ### `multiple \*\*kwargs not allowed` +def h(**kwargs1, **kwargs2): ### `multiple \*\* parameters not allowed` pass --- -# Only **kwargs may follow *args +# Only keyword-only params and **kwargs may follow *args in a declaration. -def f(*args, x): ### `parameter may not follow \*args` +def f(*args, x): ### `required parameter may not follow \* parameter` pass -def g(*args1, *args2): ### `multiple \*args not allowed` +def g(*args1, *args2): ### `multiple \* parameters not allowed` pass -def h(*args, **kwargs): # ok +def h(*args, a=1, **kwargs): ### `optional parameter may not follow \*args` pass --- -# No arguments may follow **kwargs +# No arguments may follow **kwargs in a call. def f(*args, **kwargs): pass @@ -218,7 +218,7 @@ f(**{}, *[]) ### `\*args may not follow \*\*kwargs` f(**{}, **{}) ### `multiple \*\*kwargs not allowed` --- -# Only keyword arguments may follow *args +# Only keyword arguments may follow *args in a call. def f(*args, **kwargs): pass diff --git a/starlark/eval.go b/starlark/eval.go index 70d4414..9ed9e27 100644 --- a/starlark/eval.go +++ b/starlark/eval.go @@ -1011,6 +1011,16 @@ func asIndex(v Value, len int, result *int) error { // setArgs sets the values of the formal parameters of function fn in // based on the actual parameter values in args and kwargs. func setArgs(locals []Value, fn *Function, args Tuple, kwargs []Tuple) error { + // This is adapted from the algorithm from PyEval_EvalCodeEx. + + // Nullary function? + if fn.NumParams() == 0 { + if nactual := len(args) + len(kwargs); nactual > 0 { + return fmt.Errorf("function %s takes no arguments (%d given)", fn.Name(), nactual) + } + return nil + } + cond := func(x bool, y, z interface{}) interface{} { if x { return y @@ -1020,101 +1030,94 @@ func setArgs(locals []Value, fn *Function, args Tuple, kwargs []Tuple) error { // nparams is the number of ordinary parameters (sans * or **). nparams := fn.NumParams() - if fn.HasVarargs() { + var kwdict *Dict + if fn.HasKwargs() { nparams-- + kwdict = new(Dict) + locals[nparams] = kwdict } - if fn.HasKwargs() { + if fn.HasVarargs() { nparams-- } - // This is the algorithm from PyEval_EvalCodeEx. - var kwdict *Dict + // Too many positional args? n := len(args) - if nparams > 0 || fn.HasVarargs() || fn.HasKwargs() { - if fn.HasKwargs() { - kwdict = new(Dict) - locals[fn.NumParams()-1] = kwdict - } - - // too many args? - if len(args) > nparams { - if !fn.HasVarargs() { - return fmt.Errorf("function %s takes %s %d argument%s (%d given)", - fn.Name(), - cond(len(fn.defaults) > 0, "at most", "exactly"), - nparams, - cond(nparams == 1, "", "s"), - len(args)+len(kwargs)) - } - n = nparams + maxpos := nparams + if len(args) > maxpos { + if !fn.HasVarargs() { + return fmt.Errorf("function %s takes %s %d positional argument%s (%d given)", + fn.Name(), + cond(len(fn.defaults) > 0, "at most", "exactly"), + maxpos, + cond(nparams == 1, "", "s"), + len(args)+len(kwargs)) } + n = maxpos + } - // set of defined (regular) parameters - var defined intset - defined.init(nparams) + // set of defined (regular) parameters + var defined intset + defined.init(nparams) - // ordinary parameters - for i := 0; i < n; i++ { - locals[i] = args[i] - defined.set(i) - } + // ordinary parameters + for i := 0; i < n; i++ { + locals[i] = args[i] + defined.set(i) + } - // variadic arguments - if fn.HasVarargs() { - tuple := make(Tuple, len(args)-n) - for i := n; i < len(args); i++ { - tuple[i-n] = args[i] - } - locals[nparams] = tuple + // variadic arguments + if fn.HasVarargs() { + tuple := make(Tuple, len(args)-n) + for i := n; i < len(args); i++ { + tuple[i-n] = args[i] } + locals[nparams] = tuple + } - // keyword arguments - paramIdents := fn.funcode.Locals[:nparams] - for _, pair := range kwargs { - k, v := pair[0].(String), pair[1] - if i := findParam(paramIdents, string(k)); i >= 0 { - if defined.set(i) { - return fmt.Errorf("function %s got multiple values for keyword argument %s", fn.Name(), k) - } - locals[i] = v - continue - } - if kwdict == nil { - return fmt.Errorf("function %s got an unexpected keyword argument %s", fn.Name(), k) - } - n := kwdict.Len() - kwdict.SetKey(k, v) - if kwdict.Len() == n { + // keyword arguments + paramIdents := fn.funcode.Locals[:nparams] + for _, pair := range kwargs { + k, v := pair[0].(String), pair[1] + if i := findParam(paramIdents, string(k)); i >= 0 { + if defined.set(i) { return fmt.Errorf("function %s got multiple values for keyword argument %s", fn.Name(), k) } + locals[i] = v + continue } + if kwdict == nil { + return fmt.Errorf("function %s got an unexpected keyword argument %s", fn.Name(), k) + } + n := kwdict.Len() + kwdict.SetKey(k, v) + if kwdict.Len() == n { + return fmt.Errorf("function %s got multiple values for keyword argument %s", fn.Name(), k) + } + } - // default values - if len(args) < nparams { - m := nparams - len(fn.defaults) // first default - - // report errors for missing non-optional arguments - i := len(args) - for ; i < m; i++ { - if !defined.get(i) { - return fmt.Errorf("function %s takes %s %d argument%s (%d given)", - fn.Name(), - cond(fn.HasVarargs() || len(fn.defaults) > 0, "at least", "exactly"), - m, - cond(m == 1, "", "s"), - defined.len()) - } + // default values + if n < nparams { + m := nparams - len(fn.defaults) // first default + + // report errors for missing non-optional arguments + i := n + for ; i < m; i++ { + if !defined.get(i) { + return fmt.Errorf("function %s takes %s %d positional argument%s (%d given)", + fn.Name(), + cond(fn.HasVarargs() || len(fn.defaults) > 0, "at least", "exactly"), + m, + cond(m == 1, "", "s"), + defined.len()) } + } - // set default values - for ; i < nparams; i++ { - if !defined.get(i) { - locals[i] = fn.defaults[i-m] - } + // set default values + for ; i < nparams; i++ { + if !defined.get(i) { + locals[i] = fn.defaults[i-m] } } - } else if nactual := len(args) + len(kwargs); nactual > 0 { - return fmt.Errorf("function %s takes no arguments (%d given)", fn.Name(), nactual) } return nil } diff --git a/starlark/eval_test.go b/starlark/eval_test.go index dde92f5..09c6bfa 100644 --- a/starlark/eval_test.go +++ b/starlark/eval_test.go @@ -258,11 +258,11 @@ def f(a, b=42, *args, **kwargs): for _, test := range []struct{ src, want string }{ {`a()`, `None`}, {`a(1)`, `function a takes no arguments (1 given)`}, - {`b()`, `function b takes exactly 2 arguments (0 given)`}, - {`b(1)`, `function b takes exactly 2 arguments (1 given)`}, + {`b()`, `function b takes exactly 2 positional arguments (0 given)`}, + {`b(1)`, `function b takes exactly 2 positional arguments (1 given)`}, {`b(1, 2)`, `(1, 2)`}, {`b`, `<function b>`}, // asserts that b's parameter b was treated as a local variable - {`b(1, 2, 3)`, `function b takes exactly 2 arguments (3 given)`}, + {`b(1, 2, 3)`, `function b takes exactly 2 positional arguments (3 given)`}, {`b(1, b=2)`, `(1, 2)`}, {`b(1, a=2)`, `function b got multiple values for keyword argument "a"`}, {`b(1, x=2)`, `function b got an unexpected keyword argument "x"`}, @@ -270,10 +270,10 @@ def f(a, b=42, *args, **kwargs): {`b(b=1, a=2)`, `(2, 1)`}, {`b(b=1, a=2, x=1)`, `function b got an unexpected keyword argument "x"`}, {`b(x=1, b=1, a=2)`, `function b got an unexpected keyword argument "x"`}, - {`c()`, `function c takes at least 1 argument (0 given)`}, + {`c()`, `function c takes at least 1 positional argument (0 given)`}, {`c(1)`, `(1, 42)`}, {`c(1, 2)`, `(1, 2)`}, - {`c(1, 2, 3)`, `function c takes at most 2 arguments (3 given)`}, + {`c(1, 2, 3)`, `function c takes at most 2 positional arguments (3 given)`}, {`c(1, b=2)`, `(1, 2)`}, {`c(1, a=2)`, `function c got multiple values for keyword argument "a"`}, {`c(a=1, b=2)`, `(1, 2)`}, @@ -284,10 +284,10 @@ def f(a, b=42, *args, **kwargs): {`d(1, 2, k=3)`, `function d got an unexpected keyword argument "k"`}, {`d(args=[])`, `function d got an unexpected keyword argument "args"`}, {`e()`, `{}`}, - {`e(1)`, `function e takes exactly 0 arguments (1 given)`}, + {`e(1)`, `function e takes exactly 0 positional arguments (1 given)`}, {`e(k=1)`, `{"k": 1}`}, {`e(kwargs={})`, `{"kwargs": {}}`}, - {`f()`, `function f takes at least 1 argument (0 given)`}, + {`f()`, `function f takes at least 1 positional argument (0 given)`}, {`f(0)`, `(0, 42, (), {})`}, {`f(0)`, `(0, 42, (), {})`}, {`f(0, 1)`, `(0, 1, (), {})`}, diff --git a/starlark/testdata/function.star b/starlark/testdata/function.star index 5d5fe37..daa87e4 100644 --- a/starlark/testdata/function.star +++ b/starlark/testdata/function.star @@ -161,7 +161,7 @@ assert.fails(lambda: f( 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64), "takes exactly 65 arguments .64 given.") + 57, 58, 59, 60, 61, 62, 63, 64), "takes exactly 65 positional arguments .64 given.") assert.fails(lambda: f( 1, 2, 3, 4, 5, 6, 7, 8, diff --git a/syntax/parse.go b/syntax/parse.go index 82c6ab0..af79cc1 100644 --- a/syntax/parse.go +++ b/syntax/parse.go @@ -11,7 +11,7 @@ package syntax // package. Verify that error positions are correct using the // chunkedfile mechanism. -import log "log" +import "log" // Enable this flag to print the token stream and log.Fatal on the first error. const debug = false diff --git a/syntax/testdata/errors.star b/syntax/testdata/errors.star index 558845c..6ff3ea6 100644 --- a/syntax/testdata/errors.star +++ b/syntax/testdata/errors.star @@ -27,6 +27,12 @@ def f(**kwargs, ): ### `got '\)', want parameter` --- +# Parameters are validated later. +def f(**kwargs, *args, b=1, a, **kwargs, *args, b=1, a): + pass + +--- + def pass(): ### "not an identifier" pass @@ -48,6 +54,10 @@ f(**kwargs, ) ### `got '\)', want argument` --- +f(a=1, *, b=2) ### `got ',', want primary` + +--- + _ = {x:y for y in z} # ok _ = {x for y in z} ### `got for, want ':'` @@ -109,7 +119,7 @@ _ = 0 == 1 == 2 ### "== does not associate with ==" --- _ = (0 <= i) < n # ok -_ = 0 <= (i < n) # ok +_ = 0 <= (i < n) # ok _ = 0 <= i < n ### "<= does not associate with <" --- @@ -152,14 +162,14 @@ load("a", x2="x", "y") # => positional-before-named arg check happens later (!) # 'load' is not an identifier load = 1 ### `got '=', want '\('` --- -# 'load' is not an identifier +# 'load' is not an identifier f(load()) ### `got load, want primary` --- -# 'load' is not an identifier +# 'load' is not an identifier def load(): ### `not an identifier` pass --- -# 'load' is not an identifier +# 'load' is not an identifier def f(load): ### `not an identifier` pass --- |