compiler: implement method-set based AssignableTo and Implements (#5304)

* reflect: implement method-set based AssignableTo and Implements

Based on the design from #4376 by aykevl.
Fixes #4277, fixes #3580.

Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>

* builder: update expected binary sizes for reflect changes

* Make interface checks similar to invoke, allowing typeImplementsMethodSet and method info to be dropped when reflect is not present

* Add more tests that BigGo reflect tests

* Even more pruning

* Add go/token and net/url to passing tests

* Prune even further, I am less happy with this, though

* Update size test now that we are smaller

* Skip some tests

* elide method lists

* format, oops

* fix tests

* Add a panic, pull out constant to keep in sync

* Add debug info

* Remove code that was leftover from a previous refactor

---------

Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>
This commit is contained in:
Jake Bailey
2026-04-17 12:57:03 -07:00
committed by GitHub
parent b792c10680
commit 5ba8766cbc
16 changed files with 853 additions and 438 deletions
+226 -6
View File
@@ -344,12 +344,8 @@ func main() {
println("PtrTo failed for type myslice")
}
if reflect.TypeOf(errorValue).Implements(errorType) != true {
println("errorValue.Implements(errorType) was false, expected true")
}
if reflect.TypeOf(errorValue).Implements(stringerType) != false {
println("errorValue.Implements(errorType) was true, expected false")
}
println("\ninterface implements")
testImplements()
println("\nalignment / offset:")
v2 := struct {
@@ -573,6 +569,230 @@ func testInterfaceMethod() {
}
}
// Types for interface Implements/AssignableTo tests.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type ReadCloser interface {
Read(p []byte) (n int, err error)
Close() error
}
type myReader struct{}
func (myReader) Read(p []byte) (int, error) { return 0, nil }
type myWriter struct{}
func (*myWriter) Write(p []byte) (int, error) { return 0, nil }
type myReadWriter struct{}
func (myReadWriter) Read(p []byte) (int, error) { return 0, nil }
func (*myReadWriter) Write(p []byte) (int, error) { return 0, nil }
type myStringer struct{}
func (myStringer) String() string { return "mystringer" }
type myErrorStringer struct{}
func (myErrorStringer) Error() string { return "err" }
func (myErrorStringer) String() string { return "str" }
// Interface with unexported method (from upstream set_test.go).
type exprLike interface {
Pos() int
End() int
exprNode()
}
type notAnExpr struct{}
func (notAnExpr) Pos() int { return 0 }
func (notAnExpr) End() int { return 0 }
func (notAnExpr) exprNode() {}
// Named types for assignability tests (from upstream set_test.go).
type IntPtr *int
type IntPtr1 *int
type Ch <-chan interface{}
func testImplements() {
readerType := reflect.TypeOf((*Reader)(nil)).Elem()
writerType := reflect.TypeOf((*Writer)(nil)).Elem()
readWriterType := reflect.TypeOf((*ReadWriter)(nil)).Elem()
closerType := reflect.TypeOf((*Closer)(nil)).Elem()
readCloserType := reflect.TypeOf((*ReadCloser)(nil)).Elem()
emptyItf := reflect.TypeOf((*interface{})(nil)).Elem()
// --- Concrete type implements interface ---
println("concrete implements:")
// myReader has value receiver Read → implements Reader
println("myReader → Reader:", reflect.TypeOf(myReader{}).Implements(readerType)) // true
println("*myReader → Reader:", reflect.TypeOf(new(myReader)).Elem().Implements(readerType)) // true (value method in pointer set)
// myWriter has pointer receiver Write → only *myWriter implements Writer
println("myWriter → Writer:", reflect.TypeOf(myWriter{}).Implements(writerType)) // false (pointer receiver)
println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).Implements(writerType)) // true
// myReadWriter: Read on value, Write on pointer
println("myReadWriter → Reader:", reflect.TypeOf(myReadWriter{}).Implements(readerType)) // true
println("myReadWriter → Writer:", reflect.TypeOf(myReadWriter{}).Implements(writerType)) // false (Write is ptr recv)
println("myReadWriter → ReadWriter:", reflect.TypeOf(myReadWriter{}).Implements(readWriterType)) // false
println("*myReadWriter → Reader:", reflect.TypeOf(&myReadWriter{}).Implements(readerType)) // true
println("*myReadWriter → Writer:", reflect.TypeOf(&myReadWriter{}).Implements(writerType)) // true
println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).Implements(readWriterType)) // true
// Nothing implements Closer (none of our types have Close)
println("myReader → Closer:", reflect.TypeOf(myReader{}).Implements(closerType)) // false
println("*myReadWriter → Closer:", reflect.TypeOf(&myReadWriter{}).Implements(closerType)) // false
// errorValue (*errors.errorString) implements error but not Stringer
println("errorValue → error:", reflect.TypeOf(errorValue).Implements(errorType)) // true
println("errorValue → Stringer:", reflect.TypeOf(errorValue).Implements(stringerType)) // false
// myErrorStringer implements both error and Stringer
println("myErrorStringer → error:", reflect.TypeOf(myErrorStringer{}).Implements(errorType)) // true
println("myErrorStringer → Stringer:", reflect.TypeOf(myErrorStringer{}).Implements(stringerType)) // true
// Everything implements empty interface
println("myReader → interface{}:", reflect.TypeOf(myReader{}).Implements(emptyItf)) // true
println("int → interface{}:", reflect.TypeOf(0).Implements(emptyItf)) // true
// --- Interface implements interface (superset check, issue #3580) ---
println("interface implements interface:")
// ReadWriter is a superset of Reader and Writer
println("ReadWriter → Reader:", readWriterType.Implements(readerType)) // true
println("ReadWriter → Writer:", readWriterType.Implements(writerType)) // true
println("Reader → ReadWriter:", readerType.Implements(readWriterType)) // false
println("Writer → ReadWriter:", writerType.Implements(readWriterType)) // false
// ReadCloser has Read+Close, Reader has Read
println("ReadCloser → Reader:", readCloserType.Implements(readerType)) // true
println("ReadCloser → Closer:", readCloserType.Implements(closerType)) // true
println("ReadCloser → Writer:", readCloserType.Implements(writerType)) // false
println("Reader → ReadCloser:", readerType.Implements(readCloserType)) // false
// Self-implements
println("Reader → Reader:", readerType.Implements(readerType)) // true
println("ReadWriter → ReadWriter:", readWriterType.Implements(readWriterType)) // true
// error and Stringer are unrelated
println("error → Stringer:", errorType.Implements(stringerType)) // false
println("Stringer → error:", stringerType.Implements(errorType)) // false
// Everything implements empty interface
println("Reader → interface{}:", readerType.Implements(emptyItf)) // true
println("ReadWriter → interface{}:", readWriterType.Implements(emptyItf)) // true
// --- AssignableTo ---
println("assignable to:")
// Identical types
println("int → int:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(0))) // true
println("string → string:", reflect.TypeOf("").AssignableTo(reflect.TypeOf(""))) // true
// Different types
println("int → string:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(""))) // false
println("int → int64:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(int64(0)))) // false
// Concrete assignable to interface (implements check)
println("myReader → Reader:", reflect.TypeOf(myReader{}).AssignableTo(readerType)) // true
println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).AssignableTo(writerType)) // true
println("myWriter → Writer:", reflect.TypeOf(myWriter{}).AssignableTo(writerType)) // false
println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).AssignableTo(readWriterType)) // true
// Interface assignable to interface
println("ReadWriter → Reader:", readWriterType.AssignableTo(readerType)) // true
println("Reader → ReadWriter:", readerType.AssignableTo(readWriterType)) // false
// Everything assignable to empty interface
println("int → interface{}:", reflect.TypeOf(0).AssignableTo(emptyItf)) // true
println("Reader → interface{}:", readerType.AssignableTo(emptyItf)) // true
// --- Upstream set_test.go: unexported method interfaces ---
println("unexported method interface:")
exprType := reflect.TypeOf((*exprLike)(nil)).Elem()
println("*notAnExpr → exprLike:", reflect.TypeOf(new(notAnExpr)).Implements(exprType)) // true
println("notAnExpr → exprLike:", reflect.TypeOf(notAnExpr{}).Implements(exprType)) // true
println("*notAnExpr → exprLike (AssignableTo):", reflect.TypeOf(new(notAnExpr)).AssignableTo(exprType)) // true
// --- Upstream set_test.go: channel direction assignability ---
println("channel direction:")
println("chan int → <-chan int:", reflect.TypeOf(make(chan int)).AssignableTo(reflect.TypeOf(make(<-chan int)))) // true
println("<-chan int → chan int:", reflect.TypeOf(make(<-chan int)).AssignableTo(reflect.TypeOf(make(chan int)))) // false
// --- Upstream set_test.go: named type assignability ---
println("named types:")
println("*int → IntPtr:", reflect.TypeOf(new(int)).AssignableTo(reflect.TypeOf(IntPtr(nil)))) // true
println("IntPtr → *int:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(new(int)))) // true
println("IntPtr → IntPtr1:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(IntPtr1(nil)))) // false
println("Ch → <-chan interface{}:", reflect.TypeOf(Ch(nil)).AssignableTo(reflect.TypeOf(make(<-chan interface{})))) // true
// --- reflect.Value.Set with interface (issue #4277) ---
println("value set interface:")
type Node interface{ node() }
type FooNode struct{ V int }
type BarNode struct{ V int }
// Make FooNode and BarNode implement Node with pointer receivers
// (can't add methods to local types in function, use a different approach)
testValueSetInterface()
}
type IfaceNode interface {
ifaceNode()
}
type FooNode struct{ V int }
type BarNode struct{ V int }
func (*FooNode) ifaceNode() {}
func (*BarNode) ifaceNode() {}
type NodeContainer struct {
Nodes []IfaceNode
}
func testValueSetInterface() {
c := &NodeContainer{
Nodes: []IfaceNode{&FooNode{V: 1}, &FooNode{V: 2}},
}
// Use reflect to replace elements
v := reflect.ValueOf(c).Elem().FieldByName("Nodes")
v.Index(0).Set(reflect.ValueOf(&BarNode{V: 10}))
switch n := c.Nodes[0].(type) {
case *BarNode:
println("Set[0] to BarNode:", n.V) // 10
default:
println("FAIL: expected *BarNode")
}
switch n := c.Nodes[1].(type) {
case *FooNode:
println("Set[1] still FooNode:", n.V) // 2
default:
println("FAIL: expected *FooNode")
}
}
var xorshift32State uint32 = 1
func xorshift32(x uint32) uint32 {
+64
View File
@@ -439,6 +439,70 @@ offset for int64 matches: true
offset for complex128 matches: true
type assertion succeeded for unreferenced type
interface implements
concrete implements:
myReader → Reader: true
*myReader → Reader: true
myWriter → Writer: false
*myWriter → Writer: true
myReadWriter → Reader: true
myReadWriter → Writer: false
myReadWriter → ReadWriter: false
*myReadWriter → Reader: true
*myReadWriter → Writer: true
*myReadWriter → ReadWriter: true
myReader → Closer: false
*myReadWriter → Closer: false
errorValue → error: true
errorValue → Stringer: false
myErrorStringer → error: true
myErrorStringer → Stringer: true
myReader → interface{}: true
int → interface{}: true
interface implements interface:
ReadWriter → Reader: true
ReadWriter → Writer: true
Reader → ReadWriter: false
Writer → ReadWriter: false
ReadCloser → Reader: true
ReadCloser → Closer: true
ReadCloser → Writer: false
Reader → ReadCloser: false
Reader → Reader: true
ReadWriter → ReadWriter: true
error → Stringer: false
Stringer → error: false
Reader → interface{}: true
ReadWriter → interface{}: true
assignable to:
int → int: true
string → string: true
int → string: false
int → int64: false
myReader → Reader: true
*myWriter → Writer: true
myWriter → Writer: false
*myReadWriter → ReadWriter: true
ReadWriter → Reader: true
Reader → ReadWriter: false
int → interface{}: true
Reader → interface{}: true
unexported method interface:
*notAnExpr → exprLike: true
notAnExpr → exprLike: true
*notAnExpr → exprLike (AssignableTo): true
channel direction:
chan int → <-chan int: true
<-chan int → chan int: false
named types:
*int → IntPtr: true
IntPtr → *int: true
IntPtr → IntPtr1: false
Ch → <-chan interface{}: true
value set interface:
Set[0] to BarNode: 10
Set[1] still FooNode: 2
alignment / offset:
struct{[0]func(); byte}: true