Skip to content

Commit

Permalink
reflect: fully implement Type.AssignableTo
Browse files Browse the repository at this point in the history
This fully implements AssignableTo, fixing a number of bugs in the
previous implementation. In particular, it now supports AssignableTo for
interface types.
  • Loading branch information
aykevl committed Jul 31, 2024
1 parent 19e2d12 commit 7ff626d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 43 deletions.
56 changes: 26 additions & 30 deletions src/reflect/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
43 changes: 39 additions & 4 deletions src/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
9 changes: 0 additions & 9 deletions src/reflect/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down

0 comments on commit 7ff626d

Please sign in to comment.