From cd10c3690c3804a66f489833913c67f62be003cb Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:10:55 -0700 Subject: [PATCH 1/2] reflect, compiler: add signature based methods, fix String --- compiler/interface.go | 33 +++++++++- src/internal/reflectlite/type.go | 104 +++++++++++++++++++++++++++++++ src/reflect/type.go | 10 +-- testdata/reflect.go | 28 +++++++++ testdata/reflect.txt | 9 +++ 5 files changed, 176 insertions(+), 8 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index 5f7e7e345b..153df165bd 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -282,10 +282,14 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "methods", methodSetType), ) case *types.Signature: + numIn := typ.Params().Len() + numOut := typ.Results().Len() typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "numIn", types.Typ[types.Uint16]), // high bit = variadic + types.NewVar(token.NoPos, nil, "numOut", types.Typ[types.Uint16]), + types.NewVar(token.NoPos, nil, "inOut", types.NewArray(types.Typ[types.UnsafePointer], int64(numIn+numOut))), ) - // TODO: signature params and return values } if hasMethodSet { // This method set is appended at the start of the struct. It is @@ -477,8 +481,31 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { methodSetValue, } case *types.Signature: - typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} - // TODO: params, return values, etc + params := typ.Params() + results := typ.Results() + if params.Len() >= 0x8000 { + c.addError(token.NoPos, fmt.Sprintf("too many function parameters for typecode (%d): %s", params.Len(), typ.String())) + } + if results.Len() >= 0x10000 { + c.addError(token.NoPos, fmt.Sprintf("too many function results for typecode (%d): %s", results.Len(), typ.String())) + } + numIn := uint64(params.Len()) + if typ.Variadic() { + numIn |= 0x8000 // variadic flag in high bit + } + inOut := make([]llvm.Value, 0, params.Len()+results.Len()) + for i := 0; i < params.Len(); i++ { + inOut = append(inOut, c.getTypeCode(params.At(i).Type())) + } + for i := 0; i < results.Len(); i++ { + inOut = append(inOut, c.getTypeCode(results.At(i).Type())) + } + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), + llvm.ConstInt(c.ctx.Int16Type(), numIn, false), + llvm.ConstInt(c.ctx.Int16Type(), uint64(results.Len()), false), + llvm.ConstArray(c.dataPtrType, inOut), + } } // Prepend metadata byte. typeFields = append([]llvm.Value{ diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index e282f16f3b..7ee539d72f 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -252,6 +252,21 @@ type structField struct { data unsafe.Pointer // various bits of information, packed in a byte array } +// funcType is the type descriptor for function types. The numIn field uses +// bit 15 (funcTypeVariadic) to indicate whether the function is variadic; the +// remaining bits hold the number of input parameters. The inOut array contains +// numIn+numOut entries: input parameter types followed by output parameter +// types. +type funcType struct { + RawType + ptrTo *RawType + numIn uint16 + numOut uint16 + inOut [0]*RawType +} + +const funcTypeVariadic = 0x8000 + // Method set, as emitted by the compiler. type methodSet struct { length uintptr @@ -370,6 +385,41 @@ func (t *RawType) String() string { case Interface: // TODO(dgryski): Needs actual method set info return "interface {}" + case Func: + ft := t.funcDescriptor() + numIn := int(ft.numIn &^ funcTypeVariadic) + numOut := int(ft.numOut) + variadic := ft.numIn&funcTypeVariadic != 0 + arr := (*[1 << 16]*RawType)(unsafe.Pointer(&ft.inOut)) + s := "func(" + for i := 0; i < numIn; i++ { + if i > 0 { + s += ", " + } + if variadic && i == numIn-1 { + // final variadic parameter is stored as []T but printed as ...T + s += "..." + arr[i].elem().String() + } else { + s += arr[i].String() + } + } + s += ")" + switch numOut { + case 0: + // no result + case 1: + s += " " + arr[numIn].String() + default: + s += " (" + for i := 0; i < numOut; i++ { + if i > 0 { + s += ", " + } + s += arr[numIn+i].String() + } + s += ")" + } + return s default: return t.Kind().String() } @@ -901,6 +951,60 @@ func (t *RawType) ChanDir() ChanDir { return ChanDir(dir) } +// funcDescriptor returns the funcType descriptor for t. If t is a named func +// type, it walks through to the underlying signature. +func (t *RawType) funcDescriptor() *funcType { + return (*funcType)(unsafe.Pointer(t.underlying())) +} + +func (t *RawType) NumIn() int { + if t.Kind() != Func { + panic(TypeError{"NumIn"}) + } + return int(t.funcDescriptor().numIn &^ funcTypeVariadic) +} + +func (t *RawType) NumOut() int { + if t.Kind() != Func { + panic(TypeError{"NumOut"}) + } + return int(t.funcDescriptor().numOut) +} + +func (t *RawType) IsVariadic() bool { + if t.Kind() != Func { + panic(TypeError{"IsVariadic"}) + } + return t.funcDescriptor().numIn&funcTypeVariadic != 0 +} + +func (t *RawType) In(i int) Type { + if t.Kind() != Func { + panic(TypeError{"In"}) + } + ft := t.funcDescriptor() + n := int(ft.numIn &^ funcTypeVariadic) + if i < 0 || i >= n { + panic("reflect: Type.In: index out of range") + } + arr := (*[1 << 16]*RawType)(unsafe.Pointer(&ft.inOut)) + return arr[i] +} + +func (t *RawType) Out(i int) Type { + if t.Kind() != Func { + panic(TypeError{"Out"}) + } + ft := t.funcDescriptor() + numIn := int(ft.numIn &^ funcTypeVariadic) + numOut := int(ft.numOut) + if i < 0 || i >= numOut { + panic("reflect: Type.Out: index out of range") + } + arr := (*[1 << 16]*RawType)(unsafe.Pointer(&ft.inOut)) + return arr[numIn+i] +} + func (t *RawType) NumMethod() int { if t.isNamed() { diff --git a/src/reflect/type.go b/src/reflect/type.go index 884f89dc84..3ea9747e50 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -434,11 +434,11 @@ func (t *rawType) Implements(u Type) bool { } func (t *rawType) In(i int) Type { - panic("unimplemented: (reflect.Type).In()") + return toType(t.RawType.In(i).(*reflectlite.RawType)) } func (t *rawType) IsVariadic() bool { - panic("unimplemented: (reflect.Type).IsVariadic()") + return t.RawType.IsVariadic() } func (t *rawType) Key() Type { @@ -454,15 +454,15 @@ func (t *rawType) MethodByName(name string) (Method, bool) { } func (t *rawType) NumIn() int { - panic("unimplemented: (reflect.Type).NumIn()") + return t.RawType.NumIn() } func (t *rawType) NumOut() int { - panic("unimplemented: (reflect.Type).NumOut()") + return t.RawType.NumOut() } func (t *rawType) Out(i int) Type { - panic("unimplemented: (reflect.Type).Out()") + return toType(t.RawType.Out(i).(*reflectlite.RawType)) } // A StructField describes a single field in a struct. diff --git a/testdata/reflect.go b/testdata/reflect.go index 873d60f787..8a3b944db2 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -396,6 +396,34 @@ func main() { } } } + + // Test reflect on function types (issue #4458). + println("\nfunc type reflection") + for _, fn := range []interface{}{ + func() {}, + func(int) {}, + func(int) int { return 0 }, + func(int, string) (bool, error) { return false, nil }, + func(...int) {}, + func(string, ...int) error { return nil }, + } { + t := reflect.TypeOf(fn) + print(t.String(), " NumIn=", t.NumIn(), " NumOut=", t.NumOut(), " Variadic=", t.IsVariadic()) + for i := 0; i < t.NumIn(); i++ { + print(" In(", i, ")=", t.In(i).String()) + } + for i := 0; i < t.NumOut(); i++ { + print(" Out(", i, ")=", t.Out(i).String()) + } + println() + } + // Named func type still resolves to underlying signature for In/Out. + { + type namedFunc func(int) string + var f namedFunc + t := reflect.TypeOf(f) + println(t.String(), "NumIn=", t.NumIn(), "In(0)=", t.In(0).String(), "NumOut=", t.NumOut(), "Out(0)=", t.Out(0).String()) + } } func emptyFunc() { diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 3024568c3d..b15238191e 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -512,3 +512,12 @@ blue gopher v.Interface() method kind: interface int 5 + +func type reflection +func() NumIn=0 NumOut=0 Variadic=false +func(int) NumIn=1 NumOut=0 Variadic=false In(0)=int +func(int) int NumIn=1 NumOut=1 Variadic=false In(0)=int Out(0)=int +func(int, string) (bool, error) NumIn=2 NumOut=2 Variadic=false In(0)=int In(1)=string Out(0)=bool Out(1)=error +func(...int) NumIn=1 NumOut=0 Variadic=true In(0)=[]int +func(string, ...int) error NumIn=2 NumOut=1 Variadic=true In(0)=string In(1)=[]int Out(0)=error +main.namedFunc NumIn= 1 In(0)= int NumOut= 1 Out(0)= string From 5e7cb89360f9c24458fd369d1c361b86dff916eb Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:42:42 -0700 Subject: [PATCH 2/2] Use uint8s, move bit flag to Out since that's less likely to have 256 items --- compiler/interface.go | 16 ++++++------ src/internal/reflectlite/type.go | 42 +++++++++++++++++++------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index 153df165bd..b823725642 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -285,9 +285,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { numIn := typ.Params().Len() numOut := typ.Results().Len() typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "numIn", types.Typ[types.Uint8]), + types.NewVar(token.NoPos, nil, "numOut", types.Typ[types.Uint8]), // high bit = variadic types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), - types.NewVar(token.NoPos, nil, "numIn", types.Typ[types.Uint16]), // high bit = variadic - types.NewVar(token.NoPos, nil, "numOut", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "inOut", types.NewArray(types.Typ[types.UnsafePointer], int64(numIn+numOut))), ) } @@ -483,15 +483,15 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { case *types.Signature: params := typ.Params() results := typ.Results() - if params.Len() >= 0x8000 { + if params.Len() >= 0x100 { c.addError(token.NoPos, fmt.Sprintf("too many function parameters for typecode (%d): %s", params.Len(), typ.String())) } - if results.Len() >= 0x10000 { + if results.Len() >= 0x80 { c.addError(token.NoPos, fmt.Sprintf("too many function results for typecode (%d): %s", results.Len(), typ.String())) } - numIn := uint64(params.Len()) + numOut := uint64(results.Len()) if typ.Variadic() { - numIn |= 0x8000 // variadic flag in high bit + numOut |= 0x80 // variadic flag in high bit } inOut := make([]llvm.Value, 0, params.Len()+results.Len()) for i := 0; i < params.Len(); i++ { @@ -501,9 +501,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { inOut = append(inOut, c.getTypeCode(results.At(i).Type())) } typeFields = []llvm.Value{ + llvm.ConstInt(c.ctx.Int8Type(), uint64(params.Len()), false), + llvm.ConstInt(c.ctx.Int8Type(), numOut, false), c.getTypeCode(types.NewPointer(typ)), - llvm.ConstInt(c.ctx.Int16Type(), numIn, false), - llvm.ConstInt(c.ctx.Int16Type(), uint64(results.Len()), false), llvm.ConstArray(c.dataPtrType, inOut), } } diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index 7ee539d72f..14a5088618 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -252,20 +252,26 @@ type structField struct { data unsafe.Pointer // various bits of information, packed in a byte array } -// funcType is the type descriptor for function types. The numIn field uses -// bit 15 (funcTypeVariadic) to indicate whether the function is variadic; the -// remaining bits hold the number of input parameters. The inOut array contains -// numIn+numOut entries: input parameter types followed by output parameter -// types. +// funcType is the type descriptor for function types. The numOut field uses +// bit 7 (funcTypeVariadic) to indicate whether the function is variadic; the +// remaining bits hold the number of output parameters. The variadic flag is +// stored in numOut rather than numIn because functions are more likely to have +// a large number of parameters than results, so leaving numIn with a full +// uint8 range is preferable. The inOut array contains numIn+numOut entries: +// input parameter types followed by output parameter types. +// +// The numIn and numOut fields are placed before ptrTo so that they fit in the +// padding between RawType (1 byte) and the pointer-aligned ptrTo field, which +// keeps ptrTo at the same offset as in elemType (used by pointerTo). type funcType struct { RawType + numIn uint8 + numOut uint8 ptrTo *RawType - numIn uint16 - numOut uint16 inOut [0]*RawType } -const funcTypeVariadic = 0x8000 +const funcTypeVariadic = 0x80 // Method set, as emitted by the compiler. type methodSet struct { @@ -323,6 +329,8 @@ func pointerTo(t *RawType) *RawType { panic("reflect: cannot make *****T type") case Struct: return (*structType)(unsafe.Pointer(t)).ptrTo + case Func: + return (*funcType)(unsafe.Pointer(t)).ptrTo default: return (*elemType)(unsafe.Pointer(t)).ptrTo } @@ -387,9 +395,9 @@ func (t *RawType) String() string { return "interface {}" case Func: ft := t.funcDescriptor() - numIn := int(ft.numIn &^ funcTypeVariadic) - numOut := int(ft.numOut) - variadic := ft.numIn&funcTypeVariadic != 0 + numIn := int(ft.numIn) + numOut := int(ft.numOut &^ funcTypeVariadic) + variadic := ft.numOut&funcTypeVariadic != 0 arr := (*[1 << 16]*RawType)(unsafe.Pointer(&ft.inOut)) s := "func(" for i := 0; i < numIn; i++ { @@ -961,21 +969,21 @@ func (t *RawType) NumIn() int { if t.Kind() != Func { panic(TypeError{"NumIn"}) } - return int(t.funcDescriptor().numIn &^ funcTypeVariadic) + return int(t.funcDescriptor().numIn) } func (t *RawType) NumOut() int { if t.Kind() != Func { panic(TypeError{"NumOut"}) } - return int(t.funcDescriptor().numOut) + return int(t.funcDescriptor().numOut &^ funcTypeVariadic) } func (t *RawType) IsVariadic() bool { if t.Kind() != Func { panic(TypeError{"IsVariadic"}) } - return t.funcDescriptor().numIn&funcTypeVariadic != 0 + return t.funcDescriptor().numOut&funcTypeVariadic != 0 } func (t *RawType) In(i int) Type { @@ -983,7 +991,7 @@ func (t *RawType) In(i int) Type { panic(TypeError{"In"}) } ft := t.funcDescriptor() - n := int(ft.numIn &^ funcTypeVariadic) + n := int(ft.numIn) if i < 0 || i >= n { panic("reflect: Type.In: index out of range") } @@ -996,8 +1004,8 @@ func (t *RawType) Out(i int) Type { panic(TypeError{"Out"}) } ft := t.funcDescriptor() - numIn := int(ft.numIn &^ funcTypeVariadic) - numOut := int(ft.numOut) + numIn := int(ft.numIn) + numOut := int(ft.numOut &^ funcTypeVariadic) if i < 0 || i >= numOut { panic("reflect: Type.Out: index out of range") }