mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
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: <param name>".
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
}
This commit is contained in:
+22
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user