From 4fcf03414c31f483d9c5469f2cce7dd3ae815e85 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 22 Jul 2026 13:00:51 -0700 Subject: [PATCH] compiler: exclude generic methods from runtime method sets Go 1.27 promoted "generic methods" (methods with their own type parameters, independent of any type parameters on the receiver) out of the GenericMethods experiment, e.g.: func (r *Rand) N[Int intType](n Int) Int Boxing a value of a type with such a method into an interface caused tinygo to panic while building the runtime type's method table: getTypeCode -> getMethodSetValue -> getTypeCodeName getTypeCodeName's type switch has no case for *types.TypeParam, and a generic method's Signature carries the method's own type parameters in its parameter/result types (e.g. "Int" above), so encoding it into a type code name panicked with "unknown type: ". This affected any package using math/rand/v2.Rand.N, including much of the math/rand/v2 test suite, since printing or otherwise boxing a *Rand into an interface is common. Upstream reflect deliberately excludes generic methods from Type.NumMethod()/Type.Method() (they can't be instantiated implicitly, and a generic method can never satisfy an interface method), so mirror that behavior: add isGenericMethod, which checks whether a method's Signature has its own type parameters, and skip such methods when building the runtime method count, method set value, and method set in compiler/interface.go. Fixes a panic isolated to: package main type T struct{} func (t T) M[X int](n X) X { return n } func main() { var t T var i interface{} = t _ = i } --- compiler/interface.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index b93032015..f40db8b11 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -136,6 +136,16 @@ func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { return pkgPathPtr } +// isGenericMethod returns true for a method that has its own type parameters +// (independent of any type parameters on its receiver), e.g. the N method in +// "func (r *Rand) N[Int intType](n Int) Int { ... }". Like the reflect +// package, such methods are excluded from runtime method sets: they aren't +// instantiated, so their signature can't be represented in a type code, and +// they can never satisfy an interface method anyway. +func isGenericMethod(fn *types.Func) bool { + return fn.Signature().TypeParams().Len() > 0 +} + // getTypeCode returns a reference to a type code. // A type code is a pointer to a constant global that describes the type. // This function returns a pointer to the 'kind' field (which might not be the @@ -157,6 +167,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { // For an interface type, it returns the number of exported and unexported methods. var numMethods int for method := range ms.Methods() { + if isGenericMethod(method.Obj().(*types.Func)) { + continue + } if isInterface || method.Obj().Exported() { numMethods++ } @@ -205,7 +218,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { // Compute the method set value for types that support methods. var methods []*types.Func for method := range ms.Methods() { - methods = append(methods, method.Obj().(*types.Func)) + fn := method.Obj().(*types.Func) + if isGenericMethod(fn) { + continue + } + methods = append(methods, fn) } methodSetType := types.NewStruct([]*types.Var{ types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), @@ -961,6 +978,9 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { // Create method set. var signatures, wrappers []llvm.Value for method := range ms.Methods() { + if isGenericMethod(method.Obj().(*types.Func)) { + continue + } signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatures = append(signatures, signatureGlobal) fn := c.program.MethodValue(method) @@ -975,7 +995,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { // Construct global value. globalValue := c.ctx.ConstStruct([]llvm.Value{ - llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), + llvm.ConstInt(c.uintptrType, uint64(len(signatures)), false), llvm.ConstArray(c.dataPtrType, signatures), c.ctx.ConstStruct(wrappers, false), }, false)