mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
pkg/eval: Report detailed ArityMismatch errors.
This commit is contained in:
parent
15a5745636
commit
07a9e781a6
|
@ -1,10 +1,10 @@
|
||||||
package eval
|
package eval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/elves/elvish/pkg/eval/errs"
|
||||||
"github.com/elves/elvish/pkg/eval/vals"
|
"github.com/elves/elvish/pkg/eval/vals"
|
||||||
"github.com/elves/elvish/pkg/eval/vars"
|
"github.com/elves/elvish/pkg/eval/vars"
|
||||||
"github.com/elves/elvish/pkg/parse"
|
"github.com/elves/elvish/pkg/parse"
|
||||||
|
@ -12,10 +12,6 @@ import (
|
||||||
"github.com/xiaq/persistent/hash"
|
"github.com/xiaq/persistent/hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrArityMismatch is thrown by a closure when the number of arguments the user
|
|
||||||
// supplies does not match with what is required.
|
|
||||||
var ErrArityMismatch = errors.New("arity mismatch")
|
|
||||||
|
|
||||||
// Closure is a closure defined in Elvish script. Each closure has its unique
|
// Closure is a closure defined in Elvish script. Each closure has its unique
|
||||||
// identity.
|
// identity.
|
||||||
type Closure struct {
|
type Closure struct {
|
||||||
|
@ -93,12 +89,16 @@ func (c *Closure) IterateKeys(f func(interface{}) bool) {
|
||||||
// Call calls a closure.
|
// Call calls a closure.
|
||||||
func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error {
|
func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error {
|
||||||
if c.RestArg != "" {
|
if c.RestArg != "" {
|
||||||
if len(c.ArgNames) > len(args) {
|
if len(args) < len(c.ArgNames) {
|
||||||
return fmt.Errorf("need %d or more arguments, got %d", len(c.ArgNames), len(args))
|
return errs.ArityMismatch{
|
||||||
|
What: "arguments here",
|
||||||
|
ValidLow: len(c.ArgNames), ValidHigh: -1, Actual: len(args)}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(c.ArgNames) != len(args) {
|
if len(args) != len(c.ArgNames) {
|
||||||
return fmt.Errorf("need %d arguments, got %d", len(c.ArgNames), len(args))
|
return errs.ArityMismatch{
|
||||||
|
What: "arguments here",
|
||||||
|
ValidLow: len(c.ArgNames), ValidHigh: len(c.ArgNames), Actual: len(args)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,33 @@
|
||||||
package eval
|
package eval
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/elves/elvish/pkg/eval/errs"
|
||||||
|
)
|
||||||
|
|
||||||
func TestClosure(t *testing.T) {
|
func TestClosure(t *testing.T) {
|
||||||
Test(t,
|
Test(t,
|
||||||
That("kind-of { }").Puts("fn"),
|
That("kind-of { }").Puts("fn"),
|
||||||
That("eq { } { }").Puts(false),
|
That("eq { } { }").Puts(false),
|
||||||
That("x = { }; put [&$x= foo][$x]").Puts("foo"),
|
That("x = { }; put [&$x= foo][$x]").Puts("foo"),
|
||||||
That("[x]{ } a b").ThrowsAny(),
|
|
||||||
That("[x y]{ } a").ThrowsAny(),
|
That("f = [x]{ }", "$f a b").Throws(
|
||||||
That("[x y @rest]{ } a").ThrowsAny(),
|
errs.ArityMismatch{
|
||||||
|
What: "arguments here",
|
||||||
|
ValidLow: 1, ValidHigh: 1, Actual: 2},
|
||||||
|
"$f a b"),
|
||||||
|
That("f = [x y]{ }", "$f a").Throws(
|
||||||
|
errs.ArityMismatch{
|
||||||
|
What: "arguments here",
|
||||||
|
ValidLow: 2, ValidHigh: 2, Actual: 1},
|
||||||
|
"$f a"),
|
||||||
|
That("f = [x y @rest]{ }", "$f a").Throws(
|
||||||
|
errs.ArityMismatch{
|
||||||
|
What: "arguments here",
|
||||||
|
ValidLow: 2, ValidHigh: -1, Actual: 1},
|
||||||
|
"$f a"),
|
||||||
|
|
||||||
That("[]{ } &k=v").ThrowsAny(),
|
That("[]{ } &k=v").ThrowsAny(),
|
||||||
|
|
||||||
That("explode [a b]{ }[arg-names]").Puts("a", "b"),
|
That("explode [a b]{ }[arg-names]").Puts("a", "b"),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/elves/elvish/pkg/diag"
|
"github.com/elves/elvish/pkg/diag"
|
||||||
|
"github.com/elves/elvish/pkg/eval/errs"
|
||||||
"github.com/elves/elvish/pkg/eval/vals"
|
"github.com/elves/elvish/pkg/eval/vals"
|
||||||
"github.com/elves/elvish/pkg/eval/vars"
|
"github.com/elves/elvish/pkg/eval/vars"
|
||||||
"github.com/elves/elvish/pkg/parse"
|
"github.com/elves/elvish/pkg/parse"
|
||||||
|
@ -372,9 +373,6 @@ func (op *formOp) invoke(fm *Frame) (errRet error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if headFn != nil {
|
if headFn != nil {
|
||||||
if _, isClosure := headFn.(*Closure); isClosure {
|
|
||||||
fm.traceback = fm.addTraceback(op)
|
|
||||||
}
|
|
||||||
return headFn.Call(fm, args, convertedOpts)
|
return headFn.Call(fm, args, convertedOpts)
|
||||||
}
|
}
|
||||||
return op.spaceyAssignOp.exec(fm)
|
return op.spaceyAssignOp.exec(fm)
|
||||||
|
@ -432,12 +430,16 @@ func (op *assignmentOp) invoke(fm *Frame) (errRet error) {
|
||||||
return ErrMoreThanOneRest
|
return ErrMoreThanOneRest
|
||||||
}
|
}
|
||||||
if len(rest) == 1 {
|
if len(rest) == 1 {
|
||||||
if len(variables) > len(values) {
|
if len(values) < len(variables) {
|
||||||
return ErrArityMismatch
|
return errs.ArityMismatch{
|
||||||
|
What: "assignment right-hand-side",
|
||||||
|
ValidLow: len(variables), ValidHigh: -1, Actual: len(values)}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(variables) != len(values) {
|
if len(variables) != len(values) {
|
||||||
return ErrArityMismatch
|
return errs.ArityMismatch{
|
||||||
|
What: "assignment right-hand-side",
|
||||||
|
ValidLow: len(variables), ValidHigh: len(variables), Actual: len(values)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,9 +97,21 @@ func TestCompileEffect(t *testing.T) {
|
||||||
// Assignment errors when the RHS errors.
|
// Assignment errors when the RHS errors.
|
||||||
That("x = [][1]").Throws(errWithType{errs.OutOfRange{}}, "[][1]"),
|
That("x = [][1]").Throws(errWithType{errs.OutOfRange{}}, "[][1]"),
|
||||||
// Arity mismatch.
|
// Arity mismatch.
|
||||||
That("x = 1 2").ThrowsCause(ErrArityMismatch),
|
That("x = 1 2").Throws(
|
||||||
That("x y = 1").ThrowsCause(ErrArityMismatch),
|
errs.ArityMismatch{
|
||||||
That("x y @z = 1").ThrowsCause(ErrArityMismatch),
|
What: "assignment right-hand-side",
|
||||||
|
ValidLow: 1, ValidHigh: 1, Actual: 2},
|
||||||
|
"x = 1 2"),
|
||||||
|
That("x y = 1").Throws(
|
||||||
|
errs.ArityMismatch{
|
||||||
|
What: "assignment right-hand-side",
|
||||||
|
ValidLow: 2, ValidHigh: 2, Actual: 1},
|
||||||
|
"x y = 1"),
|
||||||
|
That("x y @z = 1").Throws(
|
||||||
|
errs.ArityMismatch{
|
||||||
|
What: "assignment right-hand-side",
|
||||||
|
ValidLow: 2, ValidHigh: -1, Actual: 1},
|
||||||
|
"x y @z = 1"),
|
||||||
|
|
||||||
// Redirections
|
// Redirections
|
||||||
// ------------
|
// ------------
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/elves/elvish/pkg/eval/errs"
|
||||||
"github.com/elves/elvish/pkg/eval/vals"
|
"github.com/elves/elvish/pkg/eval/vals"
|
||||||
"github.com/xiaq/persistent/hash"
|
"github.com/xiaq/persistent/hash"
|
||||||
)
|
)
|
||||||
|
@ -155,16 +156,20 @@ var errNoOptions = errors.New("function does not accept any options")
|
||||||
func (b *GoFn) Call(f *Frame, args []interface{}, opts map[string]interface{}) error {
|
func (b *GoFn) Call(f *Frame, args []interface{}, opts map[string]interface{}) error {
|
||||||
if b.variadicArg != nil {
|
if b.variadicArg != nil {
|
||||||
if len(args) < len(b.normalArgs) {
|
if len(args) < len(b.normalArgs) {
|
||||||
return fmt.Errorf("want %d or more arguments, got %d",
|
return errs.ArityMismatch{
|
||||||
len(b.normalArgs), len(args))
|
What: "arguments here",
|
||||||
|
ValidLow: len(b.normalArgs), ValidHigh: -1, Actual: len(args)}
|
||||||
}
|
}
|
||||||
} else if b.inputs {
|
} else if b.inputs {
|
||||||
if len(args) != len(b.normalArgs) && len(args) != len(b.normalArgs)+1 {
|
if len(args) != len(b.normalArgs) && len(args) != len(b.normalArgs)+1 {
|
||||||
return fmt.Errorf("want %d or %d arguments, got %d",
|
return errs.ArityMismatch{
|
||||||
len(b.normalArgs), len(b.normalArgs)+1, len(args))
|
What: "arguments here",
|
||||||
|
ValidLow: len(b.normalArgs), ValidHigh: len(b.normalArgs) + 1, Actual: len(args)}
|
||||||
}
|
}
|
||||||
} else if len(args) != len(b.normalArgs) {
|
} else if len(args) != len(b.normalArgs) {
|
||||||
return fmt.Errorf("want %d arguments, got %d", len(b.normalArgs), len(args))
|
return errs.ArityMismatch{
|
||||||
|
What: "arguments here",
|
||||||
|
ValidLow: len(b.normalArgs), ValidHigh: len(b.normalArgs), Actual: len(args)}
|
||||||
}
|
}
|
||||||
if !b.rawOptions && b.options == nil && len(opts) > 0 {
|
if !b.rawOptions && b.options == nil && len(opts) > 0 {
|
||||||
return ErrNoOptAccepted
|
return ErrNoOptAccepted
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/elves/elvish/pkg/eval/errs"
|
||||||
"github.com/elves/elvish/pkg/eval/vals"
|
"github.com/elves/elvish/pkg/eval/vals"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,9 +26,9 @@ func TestGoFnCall(t *testing.T) {
|
||||||
t.Errorf("Failed to call f: %v", err)
|
t.Errorf("Failed to call f: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callBad := func(fm *Frame, args []interface{}, opts map[string]interface{}) {
|
callBad := func(fm *Frame, args []interface{}, opts map[string]interface{}, wantErr error) {
|
||||||
err := f.Call(fm, args, opts)
|
err := f.Call(fm, args, opts)
|
||||||
if err == nil {
|
if !matchErr(wantErr, err) {
|
||||||
t.Errorf("Calling f didn't return error")
|
t.Errorf("Calling f didn't return error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,39 +176,42 @@ func TestGoFnCall(t *testing.T) {
|
||||||
f = NewGoFn("f", func() {
|
f = NewGoFn("f", func() {
|
||||||
t.Errorf("Function called when there are too many arguments")
|
t.Errorf("Function called when there are too many arguments")
|
||||||
})
|
})
|
||||||
callBad(theFrame, []interface{}{"x"}, theOptions)
|
callBad(theFrame, []interface{}{"x"}, theOptions, errs.ArityMismatch{
|
||||||
|
What: "arguments here", ValidLow: 0, ValidHigh: 0, Actual: 1})
|
||||||
|
|
||||||
// Too few arguments.
|
// Too few arguments.
|
||||||
f = NewGoFn("f", func(x string) {
|
f = NewGoFn("f", func(x string) {
|
||||||
t.Errorf("Function called when there are too few arguments")
|
t.Errorf("Function called when there are too few arguments")
|
||||||
})
|
})
|
||||||
callBad(theFrame, nil, theOptions)
|
callBad(theFrame, nil, theOptions, errs.ArityMismatch{
|
||||||
|
What: "arguments here", ValidLow: 1, ValidHigh: 1, Actual: 0})
|
||||||
f = NewGoFn("f", func(x string, y ...string) {
|
f = NewGoFn("f", func(x string, y ...string) {
|
||||||
t.Errorf("Function called when there are too few arguments")
|
t.Errorf("Function called when there are too few arguments")
|
||||||
})
|
})
|
||||||
callBad(theFrame, nil, theOptions)
|
callBad(theFrame, nil, theOptions, errs.ArityMismatch{
|
||||||
|
What: "arguments here", ValidLow: 1, ValidHigh: -1, Actual: 0})
|
||||||
|
|
||||||
// Options when the function does not accept options.
|
// Options when the function does not accept options.
|
||||||
f = NewGoFn("f", func() {
|
f = NewGoFn("f", func() {
|
||||||
t.Errorf("Function called when there are extra options")
|
t.Errorf("Function called when there are extra options")
|
||||||
})
|
})
|
||||||
callBad(theFrame, nil, RawOptions{"foo": "bar"})
|
callBad(theFrame, nil, RawOptions{"foo": "bar"}, errNoOptions)
|
||||||
|
|
||||||
// Wrong argument type.
|
// Wrong argument type.
|
||||||
f = NewGoFn("f", func(x string) {
|
f = NewGoFn("f", func(x string) {
|
||||||
t.Errorf("Function called when arguments have wrong type")
|
t.Errorf("Function called when arguments have wrong type")
|
||||||
})
|
})
|
||||||
callBad(theFrame, []interface{}{1}, theOptions)
|
callBad(theFrame, []interface{}{1}, theOptions, anyError{})
|
||||||
|
|
||||||
// Wrong argument type: cannot convert to int.
|
// Wrong argument type: cannot convert to int.
|
||||||
f = NewGoFn("f", func(x int) {
|
f = NewGoFn("f", func(x int) {
|
||||||
t.Errorf("Function called when arguments have wrong type")
|
t.Errorf("Function called when arguments have wrong type")
|
||||||
})
|
})
|
||||||
callBad(theFrame, []interface{}{"x"}, theOptions)
|
callBad(theFrame, []interface{}{"x"}, theOptions, anyError{})
|
||||||
|
|
||||||
// Wrong argument type: cannot convert to float64.
|
// Wrong argument type: cannot convert to float64.
|
||||||
f = NewGoFn("f", func(x float64) {
|
f = NewGoFn("f", func(x float64) {
|
||||||
t.Errorf("Function called when arguments have wrong type")
|
t.Errorf("Function called when arguments have wrong type")
|
||||||
})
|
})
|
||||||
callBad(theFrame, []interface{}{"x"}, theOptions)
|
callBad(theFrame, []interface{}{"x"}, theOptions, anyError{})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user