Forráskód Böngészése

pkg/eval/vals: Avoid the .Name hack for allowing using $nil for callable.

Also add a test.
Qi Xiao 1 éve
szülő
commit
973e8e76e2
3 módosított fájl, 35 hozzáadás és 3 törlés
  1. 10 0
      pkg/eval/callable.go
  2. 8 3
      pkg/eval/vals/conversion.go
  3. 17 0
      pkg/eval/vals/conversion_test.go

+ 10 - 0
pkg/eval/callable.go

@@ -1,5 +1,11 @@
 package eval
 
+import (
+	"reflect"
+
+	"src.elv.sh/pkg/eval/vals"
+)
+
 // Callable wraps the Call method.
 type Callable interface {
 	// Call calls the receiver in a Frame with arguments and options.
@@ -12,3 +18,7 @@ var (
 	// NoOpts is an empty option map. It can be used as an argument to Call.
 	NoOpts = map[string]any{}
 )
+
+func init() {
+	vals.CallableType = reflect.TypeOf((*Callable)(nil)).Elem()
+}

+ 8 - 3
pkg/eval/vals/conversion.go

@@ -62,6 +62,10 @@ var (
 	errMustBeInteger      = errors.New("must be integer")
 )
 
+// CallableType should be set from the eval package to the type of
+// eval.Callable. It is not initialized here to avoid creating an import cycle.
+var CallableType reflect.Type
+
 // ScanToGo converts an Elvish value, and stores it in the destination of ptr,
 // which must be a pointer.
 //
@@ -102,9 +106,10 @@ func ScanToGo(src any, ptr any) error {
 			return fmt.Errorf("internal bug: need pointer to scan to, got %T", ptr)
 		}
 		dstType := ptrType.Elem()
-		if dstType.String() == "eval.Callable" && ValueOf(src) == ValueOf(nil) {
-			// A Callable option is a special-case that allows assignment from $nil.
-			ptr = nil
+		if dstType == CallableType && ValueOf(src) == ValueOf(nil) {
+			// Allow using the nil value of an empty interface (which is the
+			// value of Elvish's $nil) as a Callable.
+			ValueOf(ptr).Elem().Set(reflect.Zero(dstType))
 			return nil
 		}
 		if !TypeOf(src).AssignableTo(dstType) {

+ 17 - 0
pkg/eval/vals/conversion_test.go

@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"src.elv.sh/pkg/eval/errs"
+	"src.elv.sh/pkg/testutil"
 	"src.elv.sh/pkg/tt"
 )
 
@@ -93,6 +94,22 @@ func TestScanToGo_InterfaceDst(t *testing.T) {
 	})
 }
 
+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
+	}
+
+	tt.Test(t, tt.Fn("ScanToGo", scanToGo), tt.Table{
+		Args(nil).Rets(mockCallable(nil)),
+	})
+}
+
 func TestScanToGo_ErrorsWithNonPointerDst(t *testing.T) {
 	err := ScanToGo("", 1)
 	if err == nil {