Fix ScanToGo's error message when ptr points to an interface.

When the destination to scan into is an interface, its zero value is simply a
nil interface, losing the information of the original type and resulting in
error messages like "need nil, got $actual-type".

ScanToGo now handles this case specifically, and uses the string representation
of the interface type in the error message.

Before:

~> ns []
Exception: wrong type of argument 0: wrong type: need nil, got list
~> var x~ = x
Exception: wrong type: need nil, got string

After:

~> ns []
Exception: wrong type of argument 0: wrong type: need !!hashmap.Map, got list
~> var x~ = x
Exception: wrong type: need !!eval.Callable, got string

This is still not ideal, since the "need" type is not given as an Elvish "kind",
but it's much less confusing than than old "need nil" messages.

This fixes #715.
This commit is contained in:
Qi Xiao 2021-12-05 23:49:49 +00:00
parent 82dda13def
commit ab88de7c15
2 changed files with 41 additions and 20 deletions

View File

@ -94,7 +94,13 @@ func ScanToGo(src interface{}, ptr interface{}) error {
}
dstType := ptrType.Elem()
if !TypeOf(src).AssignableTo(dstType) {
return wrongType{Kind(reflect.Zero(dstType).Interface()), Kind(src)}
var dstKind string
if dstType.Kind() == reflect.Interface {
dstKind = "!!" + dstType.String()
} else {
dstKind = Kind(reflect.Zero(dstType).Interface())
}
return wrongType{dstKind, Kind(src)}
}
ValueOf(ptr).Elem().Set(ValueOf(src))
return nil

View File

@ -12,17 +12,17 @@ type someType struct {
foo string
}
// A wrapper around ScanToGo, to make it easier to test. Instead of supplying a
// pointer to the destination, an initial value to the destination is supplied
// and the result is returned.
func scanToGo2(src interface{}, dstInit interface{}) (interface{}, error) {
ptr := reflect.New(TypeOf(dstInit))
err := ScanToGo(src, ptr.Interface())
return ptr.Elem().Interface(), err
}
func TestScanToGo_ConcreteTypeDst(t *testing.T) {
// A wrapper around ScanToGo, to make it easier to test. Instead of
// supplying a pointer to the destination, an initial value to the
// destination is supplied and the result is returned.
scanToGo := func(src interface{}, dstInit interface{}) (interface{}, error) {
ptr := reflect.New(TypeOf(dstInit))
err := ScanToGo(src, ptr.Interface())
return ptr.Elem().Interface(), err
}
func TestScanToGo(t *testing.T) {
Test(t, Fn("ScanToGo", scanToGo2), Table{
Test(t, Fn("ScanToGo", scanToGo), Table{
// int
Args("12", 0).Rets(12),
Args("0x12", 0).Rets(0x12),
@ -40,8 +40,6 @@ func TestScanToGo(t *testing.T) {
Args(someType{}, 0.0).Rets(Any, errMustBeNumber),
Args("x", 0.0).Rets(Any, cannotParseAs{"number", "x"}),
// Num is tested below
// rune
Args("x", ' ').Rets('x'),
Args(someType{}, ' ').Rets(Any, errMustBeString),
@ -56,14 +54,14 @@ func TestScanToGo(t *testing.T) {
})
}
func scanToGoNum(src interface{}) (Num, error) {
var n Num
err := ScanToGo(src, &n)
return n, err
}
func TestScanToGo_NumDst(t *testing.T) {
scanToGo := func(src interface{}) (Num, error) {
var n Num
err := ScanToGo(src, &n)
return n, err
}
func TestScanToGo_Num(t *testing.T) {
Test(t, Fn("ScanToGo", scanToGoNum), Table{
Test(t, Fn("ScanToGo", scanToGo), Table{
// Strings are automatically converted
Args("12").Rets(12),
Args(z).Rets(bigInt(z)),
@ -74,6 +72,23 @@ func TestScanToGo_Num(t *testing.T) {
Args(bigInt(z)).Rets(bigInt(z)),
Args(big.NewRat(1, 2)).Rets(big.NewRat(1, 2)),
Args(12.0).Rets(12.0),
Args("bad").Rets(Any, cannotParseAs{"number", "bad"}),
Args(EmptyList).Rets(Any, errMustBeNumber),
})
}
func TestScanToGo_InterfaceDst(t *testing.T) {
scanToGo := func(src interface{}) (interface{}, error) {
var l List
err := ScanToGo(src, &l)
return l, err
}
Test(t, Fn("ScanToGo", scanToGo), Table{
Args(EmptyList).Rets(EmptyList),
Args("foo").Rets(Any, wrongType{"!!vector.Vector", "string"}),
})
}