From 7ff626dcee6db11edff6f43c018b25d915f3982f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 31 Jul 2024 19:53:37 +0200 Subject: [PATCH] reflect: fully implement Type.AssignableTo This fully implements AssignableTo, fixing a number of bugs in the previous implementation. In particular, it now supports AssignableTo for interface types. --- src/reflect/set_test.go | 56 ++++++++++++++++++--------------------- src/reflect/type.go | 43 +++++++++++++++++++++++++++--- src/reflect/value_test.go | 9 ------- 3 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/reflect/set_test.go b/src/reflect/set_test.go index 9517ae8f68..e31ba6c281 100644 --- a/src/reflect/set_test.go +++ b/src/reflect/set_test.go @@ -196,34 +196,30 @@ func TestImplements(t *testing.T) { } } -// TODO: AssignableTo should be possible to implement now that Implements() -// mostly works. +var assignableTests = []struct { + x any + t any + b bool +}{ + {new(chan int), new(<-chan int), true}, + {new(<-chan int), new(chan int), false}, + {new(*int), new(IntPtr), true}, + {new(IntPtr), new(*int), true}, + {new(IntPtr), new(IntPtr1), false}, + {new(Ch), new(<-chan any), true}, + // test runs implementsTests too +} -//var assignableTests = []struct { -// x any -// t any -// b bool -//}{ -// {new(chan int), new(<-chan int), true}, -// {new(<-chan int), new(chan int), false}, -// {new(*int), new(IntPtr), true}, -// {new(IntPtr), new(*int), true}, -// {new(IntPtr), new(IntPtr1), false}, -// {new(Ch), new(<-chan any), true}, -// // test runs implementsTests too -//} -// -//type IntPtr *int -//type IntPtr1 *int -//type Ch <-chan any -// -//func TestAssignableTo(t *testing.T) { -// for _, tt := range append(assignableTests, implementsTests...) { -// xv := TypeOf(tt.x).Elem() -// xt := TypeOf(tt.t).Elem() -// if b := xv.AssignableTo(xt); b != tt.b { -// t.Errorf("(%s).AssignableTo(%s) = %v, want %v", xv.String(), xt.String(), b, tt.b) -// } -// } -//} -// +type IntPtr *int +type IntPtr1 *int +type Ch <-chan any + +func TestAssignableTo(t *testing.T) { + for _, tt := range append(assignableTests, implementsTests...) { + xv := TypeOf(tt.x).Elem() + xt := TypeOf(tt.t).Elem() + if b := xv.AssignableTo(xt); b != tt.b { + t.Errorf("(%s).AssignableTo(%s) = %v, want %v", xv.String(), xt.String(), b, tt.b) + } + } +} diff --git a/src/reflect/type.go b/src/reflect/type.go index 2ce030af6d..3a5518caf6 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -972,17 +972,52 @@ func (t *rawType) FieldAlign() int { // AssignableTo returns whether a value of type t can be assigned to a variable // of type u. func (t *rawType) AssignableTo(u Type) bool { - if t == u.(*rawType) { + // Quotes come from the language spec: + // https://go.dev/ref/spec#Assignability + // > A value x of type V is assignable to a variable of type T ("x is + // > assignable to T") if one of the following conditions applies: + // [...] + // (Replace T with t, and x with u). + u_raw := u.(*rawType) + if t == u_raw { + // > V and T are identical. return true } - if u.Kind() == Interface && u.NumMethod() == 0 { + if u.Kind() == Interface { + // > T is an interface type, but not a type parameter, and x implements + // > T. + u_itf := (*interfaceType)(unsafe.Pointer(u_raw.underlying())) + res := typeImplementsMethodSet(unsafe.Pointer(t), unsafe.Pointer(&u_itf.methods)) + return res + } + + t_named := t.isNamed() + u_named := u_raw.isNamed() + if t_named && u_named { + return false + } + if t.underlying() == u_raw.underlying() { + // > V and T have identical underlying types but are not type parameters + // > and at least one of V or T is not a named type. return true } - if u.Kind() == Interface { - panic("reflect: unimplemented: AssignableTo with interface") + if t.Kind() == Chan && u_raw.Kind() == Chan { + // > V and T are channel types with identical element types, V is a + // > bidirectional channel, and at least one of V or T is not a named + // > type. + t_chan := (*elemType)(unsafe.Pointer(t.underlying())) + u_chan := (*elemType)(unsafe.Pointer(u_raw.underlying())) + if t_chan.elem != u_chan.elem { + return false + } + if t_chan.ChanDir() != BothDir { + return false + } + return true } + return false } diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 40f0919bdc..eb28082999 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -592,15 +592,6 @@ func TestTinyNumMethods(t *testing.T) { } } -func TestAssignableTo(t *testing.T) { - var a any - refa := ValueOf(&a).Elem() - refa.Set(ValueOf(4)) - if got, want := refa.Interface().(int), 4; got != want { - t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) - } -} - func TestConvert(t *testing.T) { v := ValueOf(int64(3)) c := v.Convert(TypeOf(byte(0)))