Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions linkname_go121.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//go:build !go1.23 || linknamefix || llgo
// +build !go1.23 linknamefix llgo
//go:build (!go1.23 || linknamefix) && !llgo
// +build !go1.23 linknamefix
// +build !llgo

package reflectx

Expand Down
72 changes: 2 additions & 70 deletions method.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build !llgo

package reflectx

import (
Expand Down Expand Up @@ -316,35 +314,7 @@ func SetInterfaceType(typ reflect.Type, embedded []reflect.Type, methods []refle
})
rt := totype(typ)
st := (*interfaceType)(toKindType(rt))
st.Methods = nil
var info []string
var lastname string
var unnamed bool
if typ.Name() == "" {
unnamed = true
}
for _, m := range methods {
if m.Name == lastname {
continue
}
lastname = m.Name
isexport := methodIsExported(m.Name)
var mname nameOff
if unnamed {
nm := newNameEx(m.Name, "", isexport, !isexport)
mname = resolveReflectName(nm)
if !isexport {
setPkgPath(nm, m.PkgPath)
}
} else {
mname = resolveReflectName(newName(m.Name, "", isexport))
}
st.Methods = append(st.Methods, imethod{
Name: mname,
Typ: resolveReflectType(totype(m.Type)),
})
info = append(info, methodStr(m.Name, m.Type))
}
setInterfaceMethods(st, typ.Name() == "", methods)
return nil
}

Expand Down Expand Up @@ -372,45 +342,7 @@ func (ctx *Context) InterfaceOf(embedded []reflect.Type, methods []reflect.Metho
}
return n < 0
})
rt, _ := newType("", "", tyEmptyInterface, 0, 0)
st := (*interfaceType)(toKindType(rt))
st.Methods = nil
var info []string
var lastname string
for _, m := range methods {
if m.Name == lastname {
continue
}
lastname = m.Name
isexport := methodIsExported(m.Name)
var mname nameOff
nm := newNameEx(m.Name, "", isexport, !isexport)
mname = resolveReflectName(nm)
if !isexport {
setPkgPath(nm, m.PkgPath)
}
st.Methods = append(st.Methods, imethod{
Name: mname,
Typ: resolveReflectType(totype(m.Type)),
})
info = append(info, methodStr(m.Name, m.Type))
}
if len(st.Methods) > 0 {
rt.Equal = interequal
}
var str string
if len(info) > 0 {
str = fmt.Sprintf("*interface { %v }", strings.Join(info, "; "))
} else {
str = "*interface {}"
}
if t, ok := ctx.interfceLookupCache[str]; ok {
return t
}
rt.Str = resolveReflectName(newName(str, "", false))
typ := toType(rt)
ctx.interfceLookupCache[str] = typ
return typ
return ctx.newInterface(methods)
}

func methodIsExported(name string) bool {
Expand Down
2 changes: 0 additions & 2 deletions method_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build !llgo

package reflectx_test

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing //go:build !llgo means tests calling reflectx.NewMethodSet (e.g. TestIntMethodOf, TestSliceMethodOf, TestStructMethodOf) will panic under llgo because newMethodSet always panics. Consider splitting llgo-compatible tests into a separate file or adding t.Skip guards for the NewMethodSet-dependent tests until that path is implemented.


import (
Expand Down
68 changes: 68 additions & 0 deletions methodof.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,71 @@ func argsTypeSize(typ reflect.Type, offset bool) (off uintptr) {
}
return
}

func setInterfaceMethods(st *interfaceType, unnamed bool, methods []reflect.Method) {
st.Methods = nil
var lastname string
for _, m := range methods {
if m.Name == lastname {
continue
}
lastname = m.Name
isexport := methodIsExported(m.Name)
var mname nameOff
if unnamed {
nm := newNameEx(m.Name, "", isexport, !isexport)
mname = resolveReflectName(nm)
if !isexport {
setPkgPath(nm, m.PkgPath)
}
} else {
mname = resolveReflectName(newName(m.Name, "", isexport))
}
st.Methods = append(st.Methods, imethod{
Name: mname,
Typ: resolveReflectType(totype(m.Type)),
})
}
}

func (ctx *Context) newInterface(methods []reflect.Method) reflect.Type {
rt, _ := newType("", "", tyEmptyInterface, 0, 0)
st := (*interfaceType)(toKindType(rt))
st.Methods = nil
var info []string
var lastname string
for _, m := range methods {
if m.Name == lastname {
continue
}
lastname = m.Name
isexport := methodIsExported(m.Name)
var mname nameOff
nm := newNameEx(m.Name, "", isexport, !isexport)
mname = resolveReflectName(nm)
if !isexport {
setPkgPath(nm, m.PkgPath)
}
st.Methods = append(st.Methods, imethod{
Name: mname,
Typ: resolveReflectType(totype(m.Type)),
})
info = append(info, methodStr(m.Name, m.Type))
}
if len(st.Methods) > 0 {
rt.Equal = interequal
}
var str string
if len(info) > 0 {
str = fmt.Sprintf("*interface { %v }", strings.Join(info, "; "))
} else {
str = "*interface {}"
}
if t, ok := ctx.interfceLookupCache[str]; ok {
return t
}
rt.Str = resolveReflectName(newName(str, "", false))
typ := toType(rt)
ctx.interfceLookupCache[str] = typ
return typ
}
Comment on lines +425 to +465

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The newInterface function allocates rt and populates st.Methods before checking the cache. This results in unnecessary heap allocations on every cache hit. We can optimize this by constructing the interface signature string first, checking the cache, and only performing the allocations on a cache miss. Additionally, we can use strings.Builder to construct the signature string efficiently without allocating an intermediate slice of strings.

func (ctx *Context) newInterface(methods []reflect.Method) reflect.Type {
	var str string
	var sb strings.Builder
	var lastname string
	first := true
	for _, m := range methods {
		if m.Name == lastname {
			continue
		}
		lastname = m.Name
		if first {
			sb.WriteString("*interface { ")
			first = false
		} else {
			sb.WriteString("; ")
		}
		sb.WriteString(methodStr(m.Name, m.Type))
	}
	if first {
		str = "*interface {}"
	} else {
		sb.WriteString(" }")
		str = sb.String()
	}

	if t, ok := ctx.interfceLookupCache[str]; ok {
		return t
	}

	rt, _ := newType("", "", tyEmptyInterface, 0, 0)
	st := (*interfaceType)(toKindType(rt))
	st.Methods = nil
	lastname = ""
	for _, m := range methods {
		if m.Name == lastname {
			continue
		}
		lastname = m.Name
		isexport := methodIsExported(m.Name)
		var mname nameOff
		nm := newNameEx(m.Name, "", isexport, !isexport)
		mname = resolveReflectName(nm)
		if !isexport {
			setPkgPath(nm, m.PkgPath)
		}
		st.Methods = append(st.Methods, imethod{
			Name: mname,
			Typ:  resolveReflectType(totype(m.Type)),
		})
	}
	if len(st.Methods) > 0 {
		rt.Equal = interequal
	}
	rt.Str = resolveReflectName(newName(str, "", false))
	typ := toType(rt)
	ctx.interfceLookupCache[str] = typ
	return typ
}

102 changes: 102 additions & 0 deletions rtype_llgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
package reflectx

import (
"fmt"
"path"
"reflect"
"sort"
"strconv"
"strings"
"unsafe"
)

Expand Down Expand Up @@ -131,6 +134,12 @@ func rtypeMethodByNameX(t *rtype, name string) (m reflect.Method, ok bool) {
return reflect.Method{}, false
}

//go:linkname interequal github.com/goplus/llgo/runtime/internal/runtime.interequal
func interequal(p, q unsafe.Pointer) bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//go:linkname interequal github.com/goplus/llgo/runtime/internal/runtime.interequal links to an internal/ package of an external module (llgo). This symbol carries no stability contract: llgo can rename or change its ABI without it being a breaking change. If the signature differs from func(unsafe.Pointer, unsafe.Pointer) bool, assigning it to rtype.Equal is undefined behaviour. Consider copying the implementation inline (as done in x/reflect/reflect.go) or linking to a stable exported shim.


//go:linkname haveIdenticalType reflect.haveIdenticalType
func haveIdenticalType(T, V *rtype, cmpTags bool) bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unconditionally links to reflect.haveIdenticalType regardless of Go version. The non-llgo path splits this across linkname_go121.go / linkname_go123.go specifically because the symbol is not directly accessible in Go 1.23+. If llgo can run on Go 1.23+, this will produce a link error. Please add an equivalent version guard.


//go:linkname closureOf reflect.closureOf
func closureOf(ftyp *funcType) *rtype

Expand Down Expand Up @@ -296,3 +305,96 @@ func newType(pkg string, name string, styp reflect.Type, mcount int, xcount int)
func toWord(i interface{}) unsafe.Pointer {
return (*emptyInterface)(unsafe.Pointer(&i)).word
}

func (ctx *Context) Reset() {
}

func resetAll() {
}

func newMethodSet(styp reflect.Type, maxmfunc, maxpfunc int) reflect.Type {
// rt, _ := newType("", "", styp, maxmfunc, 0)
// prt, _ := newType("", "", PtrTo(styp), maxpfunc, 0)
// rt.PtrToThis = resolveReflectType(prt)
// (*ptrType)(unsafe.Pointer(prt)).Elem = rt
// setTypeName(rt, styp.PkgPath(), styp.Name())
// prt.Uncommon().PkgPath = resolveReflectName(newName(styp.PkgPath(), "", false))
// return toType(rt)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

panic("TODO newMethodSet")method.go no longer has //go:build !llgo, so NewMethodSet and StructToMethodSet are now active for llgo builds and will reach this panic. Either restore a build guard on method.go for these functions, or add t.Skip in tests, until the implementation is ready.

panic("TODO newMethodSet")
return nil
}

func (ctx *Context) setMethodSet(typ reflect.Type, methods []Method, sortMethods bool) error {
if sortMethods {
sort.Slice(methods, func(i, j int) bool {
n := strings.Compare(methods[i].Name, methods[j].Name)
if n == 0 && methods[i].PkgPath == methods[j].PkgPath {
panic(fmt.Sprintf("method redeclared: %v", methods[j].Name))
}
return n < 0
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMethodSet sorts the input but never applies any methods and returns nil. Callers receive a false success signal. Please add a // TODO: not yet implemented for llgo comment (or equivalent) so callers understand the semantics differ from the non-llgo path.

}
return nil
}
Comment on lines +327 to +338

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The setMethodSet function currently silently returns nil on llgo. This can lead to hard-to-debug runtime issues where methods are expected to be set but are silently ignored. Since setMethodSet returns an error, it should return an unimplemented error (or panic like newMethodSet does) to make the lack of implementation explicit.

func (ctx *Context) setMethodSet(typ reflect.Type, methods []Method, sortMethods bool) error {
	return fmt.Errorf("setMethodSet: not implemented on llgo")
}


func setInterfaceMethods(st *interfaceType, unnamed bool, methods []reflect.Method) {
st.Methods = nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unnamed parameter is accepted but never read — the llgo path always encodes names the same way regardless. This is likely intentional (different internal type representation), but a short comment would prevent future confusion since the non-llgo setInterfaceMethods uses it to control export/unexport handling.

var lastname string
var mname string
for _, m := range methods {
if m.Name == lastname {
continue
}
lastname = m.Name
if !methodIsExported(m.Name) {
mname = m.PkgPath + "." + m.Name
} else {
mname = m.Name
}
st.Methods = append(st.Methods, imethod{
Name_: mname,
Typ_: totype(m.Type).FuncType(),
})
}
}

func (ctx *Context) newInterface(methods []reflect.Method) reflect.Type {
rt, _ := newType("", "", tyEmptyInterface, 0, 0)
st := (*interfaceType)(toKindType(rt))
st.Methods = nil
var info []string
var lastname string
var mname string
for _, m := range methods {
if m.Name == lastname {
continue
}
lastname = m.Name
if !methodIsExported(m.Name) {
mname = m.PkgPath + "." + m.Name
} else {
mname = m.Name
}
st.Methods = append(st.Methods, imethod{
Name_: mname,
Typ_: totype(m.Type).FuncType(),
})
info = append(info, methodStr(m.Name, m.Type))
}
if len(st.Methods) > 0 {
rt.Equal = interequal
}
var str string
if len(info) > 0 {
str = fmt.Sprintf("*interface { %v }", strings.Join(info, "; "))
} else {
str = "*interface {}"
}
if t, ok := ctx.interfceLookupCache[str]; ok {
return t
}
rt.Str_ = str
typ := toType(rt)
ctx.interfceLookupCache[str] = typ
return typ
}
Comment on lines +361 to +400

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the non-llgo implementation, newInterface in rtype_llgo.go allocates rt and populates st.Methods before checking the cache. We can optimize this by constructing the interface signature string first using strings.Builder, checking the cache, and only performing the allocations on a cache miss.

func (ctx *Context) newInterface(methods []reflect.Method) reflect.Type {
	var str string
	var sb strings.Builder
	var lastname string
	first := true
	for _, m := range methods {
		if m.Name == lastname {
			continue
		}
		lastname = m.Name
		if first {
			sb.WriteString("*interface { ")
			first = false
		} else {
			sb.WriteString("; ")
		}
		sb.WriteString(methodStr(m.Name, m.Type))
	}
	if first {
		str = "*interface {}"
	} else {
		sb.WriteString(" }")
		str = sb.String()
	}

	if t, ok := ctx.interfceLookupCache[str]; ok {
		return t
	}

	rt, _ := newType("", "", tyEmptyInterface, 0, 0)
	st := (*interfaceType)(toKindType(rt))
	st.Methods = nil
	lastname = ""
	var mname string
	for _, m := range methods {
		if m.Name == lastname {
			continue
		}
		lastname = m.Name
		if !methodIsExported(m.Name) {
			mname = m.PkgPath + "." + m.Name
		} else {
			mname = m.Name
		}
		st.Methods = append(st.Methods, imethod{
			Name_: mname,
			Typ_:  totype(m.Type).FuncType(),
		})
	}
	if len(st.Methods) > 0 {
		rt.Equal = interequal
	}
	rt.Str_ = str
	typ := toType(rt)
	ctx.interfceLookupCache[str] = typ
	return typ
}

Loading