2018-02-15 22:38:28 +08:00
|
|
|
package vals
|
|
|
|
|
|
|
|
import (
|
2021-04-05 08:50:22 +08:00
|
|
|
"math/big"
|
2018-02-15 22:38:28 +08:00
|
|
|
"reflect"
|
|
|
|
"testing"
|
2018-02-15 22:41:36 +08:00
|
|
|
|
2021-12-30 06:09:51 +08:00
|
|
|
"src.elv.sh/pkg/eval/errs"
|
2022-11-21 03:31:07 +08:00
|
|
|
"src.elv.sh/pkg/testutil"
|
2022-03-25 10:41:49 +08:00
|
|
|
"src.elv.sh/pkg/tt"
|
2018-02-15 22:38:28 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
type someType struct {
|
2022-03-12 06:49:32 +08:00
|
|
|
Foo string
|
2018-02-15 22:38:28 +08:00
|
|
|
}
|
|
|
|
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
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.
|
2022-03-20 23:50:25 +08:00
|
|
|
scanToGo := func(src any, dstInit any) (any, error) {
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
ptr := reflect.New(TypeOf(dstInit))
|
|
|
|
err := ScanToGo(src, ptr.Interface())
|
|
|
|
return ptr.Elem().Interface(), err
|
|
|
|
}
|
2018-03-02 07:59:19 +08:00
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
2021-04-05 08:50:22 +08:00
|
|
|
// int
|
2020-01-07 07:14:51 +08:00
|
|
|
Args("12", 0).Rets(12),
|
|
|
|
Args("0x12", 0).Rets(0x12),
|
2021-04-05 08:50:22 +08:00
|
|
|
Args(12.0, 0).Rets(0, errMustBeInteger),
|
2020-01-07 07:14:51 +08:00
|
|
|
Args(0.5, 0).Rets(0, errMustBeInteger),
|
2022-03-25 10:41:49 +08:00
|
|
|
Args(someType{}, 0).Rets(tt.Any, errMustBeInteger),
|
|
|
|
Args("x", 0).Rets(tt.Any, cannotParseAs{"integer", "x"}),
|
2021-04-05 08:50:22 +08:00
|
|
|
|
|
|
|
// float64
|
2021-05-09 21:32:50 +08:00
|
|
|
Args(23, 0.0).Rets(23.0),
|
|
|
|
Args(big.NewRat(1, 2), 0.0).Rets(0.5),
|
|
|
|
Args(1.2, 0.0).Rets(1.2),
|
2021-04-05 08:50:22 +08:00
|
|
|
Args("23", 0.0).Rets(23.0),
|
|
|
|
Args("0x23", 0.0).Rets(float64(0x23)),
|
2022-03-25 10:41:49 +08:00
|
|
|
Args(someType{}, 0.0).Rets(tt.Any, errMustBeNumber),
|
|
|
|
Args("x", 0.0).Rets(tt.Any, cannotParseAs{"number", "x"}),
|
2021-04-05 08:50:22 +08:00
|
|
|
|
|
|
|
// rune
|
|
|
|
Args("x", ' ').Rets('x'),
|
2022-03-25 10:41:49 +08:00
|
|
|
Args(someType{}, ' ').Rets(tt.Any, errMustBeString),
|
|
|
|
Args("\xc3\x28", ' ').Rets(tt.Any, errMustBeValidUTF8), // Invalid UTF8
|
|
|
|
Args("ab", ' ').Rets(tt.Any, errMustHaveSingleRune),
|
2021-04-05 08:50:22 +08:00
|
|
|
|
|
|
|
// Other types don't undergo any conversion, as long as the types match
|
|
|
|
Args("foo", "").Rets("foo"),
|
|
|
|
Args(someType{"foo"}, someType{}).Rets(someType{"foo"}),
|
|
|
|
Args(nil, nil).Rets(nil),
|
2022-03-25 10:41:49 +08:00
|
|
|
Args("x", someType{}).Rets(tt.Any, WrongType{"!!vals.someType", "string"}),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2021-04-05 08:50:22 +08:00
|
|
|
}
|
|
|
|
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
func TestScanToGo_NumDst(t *testing.T) {
|
2022-03-20 23:50:25 +08:00
|
|
|
scanToGo := func(src any) (Num, error) {
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
var n Num
|
|
|
|
err := ScanToGo(src, &n)
|
|
|
|
return n, err
|
|
|
|
}
|
2021-04-05 08:50:22 +08:00
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
2021-04-05 08:50:22 +08:00
|
|
|
// Strings are automatically converted
|
|
|
|
Args("12").Rets(12),
|
|
|
|
Args(z).Rets(bigInt(z)),
|
|
|
|
Args("1/2").Rets(big.NewRat(1, 2)),
|
|
|
|
Args("12.0").Rets(12.0),
|
|
|
|
// Already numbers
|
|
|
|
Args(12).Rets(12),
|
|
|
|
Args(bigInt(z)).Rets(bigInt(z)),
|
|
|
|
Args(big.NewRat(1, 2)).Rets(big.NewRat(1, 2)),
|
|
|
|
Args(12.0).Rets(12.0),
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
|
2022-03-25 10:41:49 +08:00
|
|
|
Args("bad").Rets(tt.Any, cannotParseAs{"number", "bad"}),
|
|
|
|
Args(EmptyList).Rets(tt.Any, errMustBeNumber),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestScanToGo_InterfaceDst(t *testing.T) {
|
2022-03-20 23:50:25 +08:00
|
|
|
scanToGo := func(src any) (any, error) {
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
var l List
|
|
|
|
err := ScanToGo(src, &l)
|
|
|
|
return l, err
|
|
|
|
}
|
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
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.
2021-12-06 07:49:49 +08:00
|
|
|
Args(EmptyList).Rets(EmptyList),
|
|
|
|
|
2022-03-25 10:41:49 +08:00
|
|
|
Args("foo").Rets(tt.Any, WrongType{"!!vector.Vector", "string"}),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2018-02-15 22:41:36 +08:00
|
|
|
}
|
|
|
|
|
2022-11-21 03:31:07 +08:00
|
|
|
func TestScanToGo_CallableDstAdmitsNil(t *testing.T) {
|
|
|
|
type mockCallable interface {
|
|
|
|
Call()
|
|
|
|
}
|
|
|
|
testutil.Set(t, &CallableType, reflect.TypeOf((*mockCallable)(nil)).Elem())
|
|
|
|
scanToGo := func(src any) (any, error) {
|
|
|
|
var c mockCallable
|
|
|
|
err := ScanToGo(src, &c)
|
|
|
|
return c, err
|
|
|
|
}
|
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanToGo).Named("scanToGo"),
|
2022-11-21 03:31:07 +08:00
|
|
|
Args(nil).Rets(mockCallable(nil)),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2022-11-21 03:31:07 +08:00
|
|
|
}
|
|
|
|
|
2021-09-11 09:54:34 +08:00
|
|
|
func TestScanToGo_ErrorsWithNonPointerDst(t *testing.T) {
|
|
|
|
err := ScanToGo("", 1)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("did not return error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-30 06:09:51 +08:00
|
|
|
func TestScanListToGo(t *testing.T) {
|
|
|
|
// A wrapper around ScanListToGo, to make it easier to test.
|
2022-03-20 23:50:25 +08:00
|
|
|
scanListToGo := func(src List, dstInit any) (any, error) {
|
2021-12-30 06:09:51 +08:00
|
|
|
ptr := reflect.New(TypeOf(dstInit))
|
|
|
|
ptr.Elem().Set(reflect.ValueOf(dstInit))
|
|
|
|
err := ScanListToGo(src, ptr.Interface())
|
|
|
|
return ptr.Elem().Interface(), err
|
|
|
|
}
|
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanListToGo).Named("scanListToGo"),
|
2021-12-30 06:09:51 +08:00
|
|
|
Args(MakeList("1", "2"), []int{}).Rets([]int{1, 2}),
|
|
|
|
Args(MakeList("1", "2"), []string{}).Rets([]string{"1", "2"}),
|
|
|
|
|
|
|
|
Args(MakeList("1", "a"), []int{}).Rets([]int{}, cannotParseAs{"integer", "a"}),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2021-12-30 06:09:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestScanListElementsToGo(t *testing.T) {
|
|
|
|
// A wrapper around ScanListElementsToGo, to make it easier to test.
|
2022-03-20 23:50:25 +08:00
|
|
|
scanListElementsToGo := func(src List, inits ...any) ([]any, error) {
|
|
|
|
ptrs := make([]any, len(inits))
|
2021-12-30 06:09:51 +08:00
|
|
|
for i, init := range inits {
|
|
|
|
if o, ok := init.(optional); ok {
|
|
|
|
// Wrapping the init value with Optional translates to wrapping
|
|
|
|
// the pointer with Optional.
|
|
|
|
ptrs[i] = Optional(reflect.New(TypeOf(o.ptr)).Interface())
|
|
|
|
} else {
|
|
|
|
ptrs[i] = reflect.New(TypeOf(init)).Interface()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err := ScanListElementsToGo(src, ptrs...)
|
2022-03-20 23:50:25 +08:00
|
|
|
vals := make([]any, len(ptrs))
|
2021-12-30 06:09:51 +08:00
|
|
|
for i, ptr := range ptrs {
|
|
|
|
if o, ok := ptr.(optional); ok {
|
|
|
|
vals[i] = reflect.ValueOf(o.ptr).Elem().Interface()
|
|
|
|
} else {
|
|
|
|
vals[i] = reflect.ValueOf(ptr).Elem().Interface()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return vals, err
|
|
|
|
}
|
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanListElementsToGo).Named("scanListElementsToGo"),
|
2022-03-20 23:50:25 +08:00
|
|
|
Args(MakeList("1", "2"), 0, 0).Rets([]any{1, 2}),
|
|
|
|
Args(MakeList("1", "2"), "", "").Rets([]any{"1", "2"}),
|
|
|
|
Args(MakeList("1", "2"), 0, Optional(0)).Rets([]any{1, 2}),
|
|
|
|
Args(MakeList("1"), 0, Optional(0)).Rets([]any{1, 0}),
|
2021-12-30 06:09:51 +08:00
|
|
|
|
2022-03-20 23:50:25 +08:00
|
|
|
Args(MakeList("a"), 0).Rets([]any{0},
|
2021-12-30 06:09:51 +08:00
|
|
|
cannotParseAs{"integer", "a"}),
|
2022-03-20 23:50:25 +08:00
|
|
|
Args(MakeList("1"), 0, 0).Rets([]any{0, 0},
|
2021-12-30 06:09:51 +08:00
|
|
|
errs.ArityMismatch{What: "list elements",
|
|
|
|
ValidLow: 2, ValidHigh: 2, Actual: 1}),
|
2022-03-20 23:50:25 +08:00
|
|
|
Args(MakeList("1"), 0, 0, Optional(0)).Rets([]any{0, 0, 0},
|
2021-12-30 06:09:51 +08:00
|
|
|
errs.ArityMismatch{What: "list elements",
|
|
|
|
ValidLow: 2, ValidHigh: 3, Actual: 1}),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2021-12-30 06:09:51 +08:00
|
|
|
}
|
|
|
|
|
2022-01-01 00:50:44 +08:00
|
|
|
type aStruct struct {
|
|
|
|
Foo int
|
2022-03-20 23:50:25 +08:00
|
|
|
bar any
|
2022-01-01 00:50:44 +08:00
|
|
|
}
|
|
|
|
|
2022-03-12 06:49:32 +08:00
|
|
|
// Equal is required by cmp.Diff, since aStruct contains unexported fields.
|
|
|
|
func (a aStruct) Equal(b aStruct) bool { return a == b }
|
|
|
|
|
2022-01-01 00:50:44 +08:00
|
|
|
func TestScanMapToGo(t *testing.T) {
|
|
|
|
// A wrapper around ScanMapToGo, to make it easier to test.
|
2022-03-20 23:50:25 +08:00
|
|
|
scanMapToGo := func(src Map, dstInit any) (any, error) {
|
2022-01-01 00:50:44 +08:00
|
|
|
ptr := reflect.New(TypeOf(dstInit))
|
|
|
|
ptr.Elem().Set(reflect.ValueOf(dstInit))
|
|
|
|
err := ScanMapToGo(src, ptr.Interface())
|
|
|
|
return ptr.Elem().Interface(), err
|
|
|
|
}
|
|
|
|
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, tt.Fn(scanMapToGo).Named("scanMapToGo"),
|
2022-01-01 00:50:44 +08:00
|
|
|
Args(MakeMap("foo", "1"), aStruct{}).Rets(aStruct{Foo: 1}),
|
|
|
|
// More fields is OK
|
|
|
|
Args(MakeMap("foo", "1", "bar", "x"), aStruct{}).Rets(aStruct{Foo: 1}),
|
|
|
|
// Fewer fields is OK
|
|
|
|
Args(MakeMap(), aStruct{}).Rets(aStruct{}),
|
|
|
|
// Unexported fields are ignored
|
|
|
|
Args(MakeMap("bar", 20), aStruct{bar: 10}).Rets(aStruct{bar: 10}),
|
|
|
|
|
|
|
|
// Conversion error
|
|
|
|
Args(MakeMap("foo", "a"), aStruct{}).
|
|
|
|
Rets(aStruct{}, cannotParseAs{"integer", "a"}),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2022-01-01 00:50:44 +08:00
|
|
|
}
|
|
|
|
|
2018-02-15 22:41:36 +08:00
|
|
|
func TestFromGo(t *testing.T) {
|
2024-01-11 23:42:43 +08:00
|
|
|
tt.Test(t, FromGo,
|
2021-04-05 08:50:22 +08:00
|
|
|
// BigInt -> int, when in range
|
|
|
|
Args(bigInt(z)).Rets(bigInt(z)),
|
|
|
|
Args(big.NewInt(100)).Rets(100),
|
|
|
|
// BigRat -> BigInt or int, when denominator is 1
|
2024-01-11 23:42:43 +08:00
|
|
|
Args(bigRat(z1+"/"+z)).Rets(bigRat(z1+"/"+z)),
|
|
|
|
Args(bigRat(z+"/1")).Rets(bigInt(z)),
|
2021-04-05 08:50:22 +08:00
|
|
|
Args(bigRat("2/1")).Rets(2),
|
|
|
|
// rune -> string
|
2020-01-07 07:14:51 +08:00
|
|
|
Args('x').Rets("x"),
|
2021-04-05 08:50:22 +08:00
|
|
|
|
|
|
|
// Other types don't undergo any conversion
|
2020-01-07 07:14:51 +08:00
|
|
|
Args(nil).Rets(nil),
|
|
|
|
Args(someType{"foo"}).Rets(someType{"foo"}),
|
2024-01-11 23:42:43 +08:00
|
|
|
)
|
2018-02-15 22:41:36 +08:00
|
|
|
}
|