elvish/pkg/eval/vals/conversion.go
Qi Xiao ab88de7c15 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-05 23:49:49 +00:00

172 lines
4.4 KiB
Go

package vals
import (
"errors"
"fmt"
"math/big"
"reflect"
"strconv"
"unicode/utf8"
)
// Conversion between native and Elvish values.
//
// Elvish uses native Go types most of the time - string, bool, hashmap.Map,
// vector.Vector, etc., and there is no need for any conversions. There are some
// exceptions, for instance int and rune, since Elvish currently lacks integer
// types.
//
// There is a many-to-one relationship between Go types and Elvish types. A
// Go value can always be converted to an Elvish value unambiguously, but to
// convert an Elvish value into a Go value one must know the destination type
// first. For example, all of the Go values int(1), rune('1') and string("1")
// convert to Elvish "1"; conversely, Elvish "1" may be converted to any of the
// aforementioned three possible values, depending on the destination type.
//
// In future, Elvish may gain distinct types for integers and characters, making
// the examples above unnecessary; however, the conversion logic may not
// entirely go away, as there might always be some mismatch between Elvish's
// type system and Go's.
type wrongType struct {
wantKind string
gotKind string
}
func (err wrongType) Error() string {
return fmt.Sprintf("wrong type: need %s, got %s", err.wantKind, err.gotKind)
}
type cannotParseAs struct {
want string
repr string
}
func (err cannotParseAs) Error() string {
return fmt.Sprintf("cannot parse as %s: %s", err.want, err.repr)
}
var (
errMustBeString = errors.New("must be string")
errMustBeValidUTF8 = errors.New("must be valid UTF-8")
errMustHaveSingleRune = errors.New("must have a single rune")
errMustBeNumber = errors.New("must be number")
errMustBeInteger = errors.New("must be integer")
)
// ScanToGo converts an Elvish value to a Go value that the pointer refers to. It
// uses the type of the pointer to determine the destination type, and puts the
// converted value in the location the pointer points to. Conversion only
// happens when the destination type is int, float64 or rune; in other cases,
// this function just checks that the source value is already assignable to the
// destination.
func ScanToGo(src interface{}, ptr interface{}) error {
switch ptr := ptr.(type) {
case *int:
i, err := elvToInt(src)
if err == nil {
*ptr = i
}
return err
case *float64:
n, err := elvToNum(src)
if err == nil {
*ptr = ConvertToFloat64(n)
}
return err
case *Num:
n, err := elvToNum(src)
if err == nil {
*ptr = n
}
return err
case *rune:
r, err := elvToRune(src)
if err == nil {
*ptr = r
}
return err
default:
// Do a generic `*ptr = src` via reflection
ptrType := TypeOf(ptr)
if ptrType.Kind() != reflect.Ptr {
return fmt.Errorf("internal bug: need pointer to scan to, got %T", ptr)
}
dstType := ptrType.Elem()
if !TypeOf(src).AssignableTo(dstType) {
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
}
}
// FromGo converts a Go value to an Elvish value. Most types are returned as
// is, but exact numerical types are normalized to one of int, *big.Int and
// *big.Rat, using the small representation that can hold the value, and runes
// are converted to strings.
func FromGo(a interface{}) interface{} {
switch a := a.(type) {
case *big.Int:
return NormalizeBigInt(a)
case *big.Rat:
return NormalizeBigRat(a)
case rune:
return string(a)
default:
return a
}
}
func elvToInt(arg interface{}) (int, error) {
switch arg := arg.(type) {
case int:
return arg, nil
case string:
num, err := strconv.ParseInt(arg, 0, 0)
if err == nil {
return int(num), nil
}
return 0, cannotParseAs{"integer", Repr(arg, -1)}
default:
return 0, errMustBeInteger
}
}
func elvToNum(arg interface{}) (Num, error) {
switch arg := arg.(type) {
case int, *big.Int, *big.Rat, float64:
return arg, nil
case string:
n := ParseNum(arg)
if n == nil {
return 0, cannotParseAs{"number", Repr(arg, -1)}
}
return n, nil
default:
return 0, errMustBeNumber
}
}
func elvToRune(arg interface{}) (rune, error) {
ss, ok := arg.(string)
if !ok {
return -1, errMustBeString
}
s := ss
r, size := utf8.DecodeRuneInString(s)
if r == utf8.RuneError {
return -1, errMustBeValidUTF8
}
if size != len(s) {
return -1, errMustHaveSingleRune
}
return r, nil
}