From 6698cc2b5d60fd3a409ecf17072b64f2427700ad Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 27 Jan 2026 16:18:48 -0700 Subject: [PATCH 01/48] Redirecting link for code moved from reflect to abi --- tests/linkname_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/linkname_test.go b/tests/linkname_test.go index 98d3ccadf..02a2351ca 100644 --- a/tests/linkname_test.go +++ b/tests/linkname_test.go @@ -40,7 +40,7 @@ func TestLinknameMethods(t *testing.T) { } type ( - name struct{ bytes *byte } + name struct{ Bytes *byte } nameOff int32 rtype struct{} ) @@ -51,7 +51,7 @@ func rtype_nameOff(r *rtype, off nameOff) name //go:linkname newName reflect.newName func newName(n, tag string, exported bool) name -//go:linkname name_name reflect.name.name +//go:linkname name_name internal/abi.Name.Name func name_name(name) string //go:linkname resolveReflectName reflect.resolveReflectName From 5e955eabdedd1d8e3b221e1977a585ab9a1e9c30 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 29 Jan 2026 11:55:23 -0700 Subject: [PATCH 02/48] working on reflectlite and abi --- compiler/natives/src/internal/abi/type.go | 22 ++++ .../src/internal/reflectlite/reflectlite.go | 107 +++++------------- .../natives/src/internal/reflectlite/type.go | 39 +++++-- tests/linkname_test.go | 2 +- 4 files changed, 78 insertions(+), 92 deletions(-) create mode 100644 compiler/natives/src/internal/abi/type.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go new file mode 100644 index 000000000..b88af9e13 --- /dev/null +++ b/compiler/natives/src/internal/abi/type.go @@ -0,0 +1,22 @@ +package abi + +var UncommonTypeMap = make(map[*Type]*UncommonType) + +func (t *Type) Uncommon() *UncommonType { + return UncommonTypeMap[t] +} + +type UncommonType struct { + PkgPath NameOff // import path + Mcount uint16 // method count + Xcount uint16 // exported method count + Methods_ []Method +} + +func (t *UncommonType) Methods() []Method { + return t.Methods_ +} + +func (t *UncommonType) ExportedMethods() []Method { + return t.Methods_[:t.Xcount:t.Xcount] +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index eb425d857..a11c8014a 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -5,6 +5,8 @@ package reflectlite import ( "unsafe" + "internal/abi" + "github.com/gopherjs/gopherjs/js" ) @@ -14,8 +16,6 @@ func init() { // avoid dead code elimination used := func(i any) {} used(rtype{}) - used(uncommonType{}) - used(method{}) used(arrayType{}) used(chanType{}) used(funcType{}) @@ -25,7 +25,6 @@ func init() { used(sliceType{}) used(structType{}) used(imethod{}) - used(structField{}) initialized = true uint8Type = TypeOf(uint8(0)).(*rtype) // set for real @@ -47,29 +46,29 @@ func jsType(typ Type) *js.Object { func reflectType(typ *js.Object) *rtype { if typ.Get(idReflectType) == js.Undefined { rt := &rtype{ - size: uintptr(typ.Get("size").Int()), - kind: uint8(typ.Get("kind").Int()), - str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + Size: uintptr(typ.Get("size").Int()), + Kind: uint8(typ.Get("kind").Int()), + Str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(rt).Set(idJsType, typ) typ.Set(idReflectType, js.InternalObject(rt)) methodSet := js.Global.Call("$methodSet", typ) if methodSet.Length() != 0 || typ.Get("named").Bool() { - rt.tflag |= tflagUncommon + rt.TFlag |= abi.TFlagUncommon if typ.Get("named").Bool() { - rt.tflag |= tflagNamed + rt.TFlag |= abi.TFlagNamed } - var reflectMethods []method + var reflectMethods []abi.Method for i := 0; i < methodSet.Length(); i++ { // Exported methods first. m := methodSet.Index(i) exported := internalStr(m.Get("pkg")) == "" if !exported { continue } - reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), + reflectMethods = append(reflectMethods, abi.Method{ + Name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: newTypeOff(reflectType(m.Get("typ"))), }) } xcount := uint16(len(reflectMethods)) @@ -79,18 +78,18 @@ func reflectType(typ *js.Object) *rtype { if exported { continue } - reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), + reflectMethods = append(reflectMethods, abi.Method{ + Name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: newTypeOff(reflectType(m.Get("typ"))), }) } - ut := &uncommonType{ - pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false, false)), - mcount: uint16(methodSet.Length()), - xcount: xcount, - _methods: reflectMethods, + ut := &abi.UncommonType{ + PkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false, false)), + Mcount: uint16(methodSet.Length()), + Xcount: xcount, + Methods_: reflectMethods, } - uncommonTypeMap[rt] = ut + abi.UncommonTypeMap[rt] = ut js.InternalObject(ut).Set(idJsType, typ) } @@ -164,13 +163,13 @@ func reflectType(typ *js.Object) *rtype { }) case Struct: fields := typ.Get("fields") - reflectFields := make([]structField, fields.Length()) + reflectFields := make([]abi.StructField, fields.Length()) for i := range reflectFields { f := fields.Index(i) - reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - typ: reflectType(f.Get("typ")), - offset: uintptr(i), + reflectFields[i] = abi.StructField{ + Name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + Typ: reflectType(f.Get("typ")), + Offset: uintptr(i), } } setKindType(rt, &structType{ @@ -189,29 +188,6 @@ func setKindType(rt *rtype, kindType any) { js.InternalObject(kindType).Set(idRtype, js.InternalObject(rt)) } -type uncommonType struct { - pkgPath nameOff - mcount uint16 - xcount uint16 - moff uint32 - - _methods []method -} - -func (t *uncommonType) methods() []method { - return t._methods -} - -func (t *uncommonType) exportedMethods() []method { - return t._methods[:t.xcount:t.xcount] -} - -var uncommonTypeMap = make(map[*rtype]*uncommonType) - -func (t *rtype) uncommon() *uncommonType { - return uncommonTypeMap[t] -} - type funcType struct { rtype `reflect:"func"` inCount uint16 @@ -229,10 +205,6 @@ func (t *funcType) out() []*rtype { return t._out } -type name struct { - bytes *byte -} - type nameData struct { name string tag string @@ -244,7 +216,6 @@ var nameMap = make(map[*byte]*nameData) func (n name) name() (s string) { return nameMap[n.bytes].name } func (n name) tag() (s string) { return nameMap[n.bytes].tag } -func (n name) pkgPath() string { return "" } func (n name) isExported() bool { return nameMap[n.bytes].exported } func (n name) embedded() bool { return nameMap[n.bytes].embedded } @@ -261,29 +232,7 @@ func newName(n, tag string, exported, embedded bool) name { } } -var nameOffList []name - -func (t *rtype) nameOff(off nameOff) name { - return nameOffList[int(off)] -} - -func newNameOff(n name) nameOff { - i := len(nameOffList) - nameOffList = append(nameOffList, n) - return nameOff(i) -} - -var typeOffList []*rtype - -func (t *rtype) typeOff(off typeOff) *rtype { - return typeOffList[int(off)] -} - -func newTypeOff(t *rtype) typeOff { - i := len(typeOffList) - typeOffList = append(typeOffList, t) - return typeOff(i) -} +func pkgPath(n abi.Name) string { return "" } func internalStr(strObj *js.Object) string { var c struct{ str string } @@ -390,7 +339,7 @@ func Zero(typ Type) Value { return makeValue(typ, jsType(typ).Call("zero"), 0) } -func unsafe_New(typ *rtype) unsafe.Pointer { +func unsafe_New(typ *abi.Type) unsafe.Pointer { switch typ.Kind() { case Struct: return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) @@ -457,7 +406,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} } -func typedmemmove(t *rtype, dst, src unsafe.Pointer) { +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) } diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 6cb14b899..da5227b68 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -5,10 +5,36 @@ package reflectlite import ( "unsafe" + "internal/abi" + "github.com/gopherjs/gopherjs/js" ) -func (t *rtype) Comparable() bool { +var nameOffList []abi.Name + +func (t rtype) nameOff(off nameOff) abi.Name { + return nameOffList[int(off)] +} + +func newNameOff(n abi.Name) nameOff { + i := len(nameOffList) + nameOffList = append(nameOffList, n) + return nameOff(i) +} + +var typeOffList []*abi.Type + +func (t *rtype) typeOff(off typeOff) *abi.Type { + return typeOffList[int(off)] +} + +func newTypeOff(t *rtype) typeOff { + i := len(typeOffList) + typeOffList = append(typeOffList, t) + return typeOff(i) +} + +func (t rtype) Comparable() bool { switch t.Kind() { case Func, Slice, Map: return false @@ -37,17 +63,6 @@ func (t *rtype) kindType() *rtype { return (*rtype)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) } -func (t *rtype) Field(i int) structField { - if t.Kind() != Struct { - panic("reflect: Field of non-struct type") - } - tt := (*structType)(unsafe.Pointer(t)) - if i < 0 || i >= len(tt.fields) { - panic("reflect: Field index out of bounds") - } - return tt.fields[i] -} - func (t *rtype) Key() Type { if t.Kind() != Map { panic("reflect: Key of non-map type") diff --git a/tests/linkname_test.go b/tests/linkname_test.go index 02a2351ca..111c9eed7 100644 --- a/tests/linkname_test.go +++ b/tests/linkname_test.go @@ -62,6 +62,6 @@ func TestLinknameReflectName(t *testing.T) { off := resolveReflectName(newName(info, "", false)) n := rtype_nameOff(nil, off) if s := name_name(n); s != info { - t.Fatalf("to reflect.name got %q: want %q", s, info) + t.Fatalf("to reflect's abi.Name got %q: want %q", s, info) } } From ff4bd2698a337d4297b57f4d66c70c21fac6bc17 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 29 Jan 2026 12:17:26 -0700 Subject: [PATCH 03/48] working on reflectlite and abi --- compiler/natives/src/internal/abi/type.go | 17 +++ .../src/internal/reflectlite/reflectlite.go | 122 ++++++++---------- 2 files changed, 70 insertions(+), 69 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index b88af9e13..46e73f195 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -20,3 +20,20 @@ func (t *UncommonType) Methods() []Method { func (t *UncommonType) ExportedMethods() []Method { return t.Methods_[:t.Xcount:t.Xcount] } + +type FuncType struct { + Type `reflect:"func"` + InCount uint16 + OutCount uint16 + + In_ []*Type + Out_ []*Type +} + +func (t *FuncType) InSlice() []*Type { + return t.In_ +} + +func (t *FuncType) OutSlice() []*Type { + return t.Out_ +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index a11c8014a..b2e9003b3 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -94,12 +94,12 @@ func reflectType(typ *js.Object) *rtype { } switch rt.Kind() { - case Array: + case abi.Array: setKindType(rt, &arrayType{ elem: reflectType(typ.Get("elem")), len: uintptr(typ.Get("len").Int()), }) - case Chan: + case abi.Chan: dir := BothDir if typ.Get("sendOnly").Bool() { dir = SendDir @@ -111,7 +111,7 @@ func reflectType(typ *js.Object) *rtype { elem: reflectType(typ.Get("elem")), dir: uintptr(dir), }) - case Func: + case abi.Func: params := typ.Get("params") in := make([]*rtype, params.Length()) for i := range in { @@ -126,14 +126,14 @@ func reflectType(typ *js.Object) *rtype { if typ.Get("variadic").Bool() { outCount |= 1 << 15 } - setKindType(rt, &funcType{ - rtype: *rt, - inCount: uint16(params.Length()), - outCount: outCount, - _in: in, - _out: out, + setKindType(rt, &abi.FuncType{ + Type: *rt, + InCount: uint16(params.Length()), + OutCount: outCount, + In_: in, + Out_: out, }) - case Interface: + case abi.Interface: methods := typ.Get("methods") imethods := make([]imethod, methods.Length()) for i := range imethods { @@ -148,20 +148,20 @@ func reflectType(typ *js.Object) *rtype { pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), methods: imethods, }) - case Map: + case abi.Map: setKindType(rt, &mapType{ key: reflectType(typ.Get("key")), elem: reflectType(typ.Get("elem")), }) - case Ptr: + case abi.Pointer: setKindType(rt, &ptrType{ elem: reflectType(typ.Get("elem")), }) - case Slice: + case abi.Slice: setKindType(rt, &sliceType{ elem: reflectType(typ.Get("elem")), }) - case Struct: + case abi.Struct: fields := typ.Get("fields") reflectFields := make([]abi.StructField, fields.Length()) for i := range reflectFields { @@ -188,23 +188,6 @@ func setKindType(rt *rtype, kindType any) { js.InternalObject(kindType).Set(idRtype, js.InternalObject(rt)) } -type funcType struct { - rtype `reflect:"func"` - inCount uint16 - outCount uint16 - - _in []*rtype - _out []*rtype -} - -func (t *funcType) in() []*rtype { - return t._in -} - -func (t *funcType) out() []*rtype { - return t._out -} - type nameData struct { name string tag string @@ -254,14 +237,15 @@ func copyStruct(dst, src *js.Object, typ Type) { func makeValue(t Type, v *js.Object, fl flag) Value { rt := t.common() - if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} } return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} } func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { + if typ.Kind() != abi.Slice { panic("reflect.MakeSlice of non-slice type") } if len < 0 { @@ -303,7 +287,7 @@ func ChanOf(dir ChanDir, t Type) Type { } func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { + if variadic && (len(in) == 0 || in[len(in)-1].Kind() != abi.Slice) { panic("reflect.FuncOf: last arg of variadic func must be slice") } @@ -320,7 +304,7 @@ func FuncOf(in, out []Type, variadic bool) Type { func MapOf(key, elem Type) Type { switch key.Kind() { - case Func, Map, Slice: + case abi.Func, abi.Map, abi.Slice: panic("reflect.MapOf: invalid key type " + key.String()) } @@ -341,9 +325,9 @@ func Zero(typ Type) Value { func unsafe_New(typ *abi.Type) unsafe.Pointer { switch typ.Kind() { - case Struct: + case abi.Struct: return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) - case Array: + case abi.Array: return unsafe.Pointer(jsType(typ).Call("zero").Unsafe()) default: return unsafe.Pointer(js.Global.Call("$newDataPointer", jsType(typ).Call("zero"), jsType(typ.ptrTo())).Unsafe()) @@ -354,28 +338,28 @@ func makeInt(f flag, bits uint64, t Type) Value { typ := t.common() ptr := unsafe_New(typ) switch typ.Kind() { - case Int8: + case abi.Int8: *(*int8)(ptr) = int8(bits) - case Int16: + case abi.Int16: *(*int16)(ptr) = int16(bits) - case Int, Int32: + case abi.Int, abi.Int32: *(*int32)(ptr) = int32(bits) - case Int64: + case abi.Int64: *(*int64)(ptr) = int64(bits) - case Uint8: + case abi.Uint8: *(*uint8)(ptr) = uint8(bits) - case Uint16: + case abi.Uint16: *(*uint16)(ptr) = uint16(bits) - case Uint, Uint32, Uintptr: + case abi.Uint, abi.Uint32, abi.Uintptr: *(*uint32)(ptr) = uint32(bits) - case Uint64: + case abi.Uint64: *(*uint64)(ptr) = uint64(bits) } return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} } func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { - if typ.Kind() != Func { + if typ.Kind() != abi.Func { panic("reflect: call of MakeFunc with non-Func type") } @@ -445,7 +429,7 @@ func mapassign(t *rtype, m, key, val unsafe.Pointer) { kv, k := keyFor(t, key) jsVal := js.InternalObject(val).Call("$get") et := t.Elem() - if et.Kind() == Struct { + if et.Kind() == abi.Struct { newVal := jsType(et).Call("zero") copyStruct(newVal, jsVal, et) jsVal = newVal @@ -530,14 +514,14 @@ func cvtDirect(v Value, typ Type) Value { var val *js.Object switch k := typ.Kind(); k { - case Slice: + case abi.Slice: slice := jsType(typ).New(srcVal.Get("$array")) slice.Set("$offset", srcVal.Get("$offset")) slice.Set("$length", srcVal.Get("$length")) slice.Set("$capacity", srcVal.Get("$capacity")) val = js.Global.Call("$newDataPointer", slice, jsType(PtrTo(typ))) - case Ptr: - if typ.Elem().Kind() == Struct { + case abi.Pointer: + if typ.Elem().Kind() == abi.Struct { if typ.Elem() == v.typ.Elem() { val = srcVal break @@ -547,10 +531,10 @@ func cvtDirect(v Value, typ Type) Value { break } val = jsType(typ).New(srcVal.Get("$get"), srcVal.Get("$set")) - case Struct: + case abi.Struct: val = jsType(typ).Get("ptr").New() copyStruct(val, srcVal, typ) - case Array, Bool, Chan, Func, Interface, Map, String: + case abi.Array, abi.Bool, abi.Chan, abi.Func, abi.Interface, abi.Map, abi.String: val = js.InternalObject(v.ptr) default: panic(&ValueError{"reflect.Convert", k}) @@ -560,18 +544,18 @@ func cvtDirect(v Value, typ Type) Value { func Copy(dst, src Value) int { dk := dst.kind() - if dk != Array && dk != Slice { + if dk != abi.Array && dk != abi.Slice { panic(&ValueError{"reflect.Copy", dk}) } - if dk == Array { + if dk == abi.Array { dst.mustBeAssignable() } dst.mustBeExported() sk := src.kind() var stringCopy bool - if sk != Array && sk != Slice { - stringCopy = sk == String && dst.typ.Elem().Kind() == Uint8 + if sk != abi.Array && sk != abi.Slice { + stringCopy = sk == abi.String && dst.typ.Elem().Kind() == abi.Uint8 if !stringCopy { panic(&ValueError{"reflect.Copy", sk}) } @@ -583,12 +567,12 @@ func Copy(dst, src Value) int { } dstVal := dst.object() - if dk == Array { + if dk == abi.Array { dstVal = jsType(SliceOf(dst.typ.Elem())).New(dstVal) } srcVal := src.object() - if sk == Array { + if sk == abi.Array { srcVal = jsType(SliceOf(src.typ.Elem())).New(srcVal) } @@ -600,7 +584,7 @@ func Copy(dst, src Value) int { func methodReceiver(op string, v Value, i int) (_ *rtype, t *funcType, fn unsafe.Pointer) { var prop string - if v.typ.Kind() == Interface { + if v.typ.Kind() == abi.Interface { tt := (*interfaceType)(unsafe.Pointer(v.typ)) if i < 0 || i >= len(tt.methods) { panic("reflect: internal error: invalid method index") @@ -641,7 +625,7 @@ func valueInterface(v Value) any { } if isWrapped(v.typ) { - if v.flag&flagIndir != 0 && v.Kind() == Struct { + if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { cv := jsType(v.typ).Call("zero") copyStruct(cv, v.object(), v.typ) return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) @@ -840,7 +824,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { } switch v1.Kind() { - case Array, Map, Slice, Struct: + case abi.Array, abi.Map, abi.Slice, abi.Struct: for _, entry := range visited { if v1.ptr == entry[0] && v2.ptr == entry[1] { return true @@ -850,8 +834,8 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { } switch v1.Kind() { - case Array, Slice: - if v1.Kind() == Slice { + case abi.Array, abi.Slice: + if v1.Kind() == abi.Slice { if v1.IsNil() != v2.IsNil() { return false } @@ -869,14 +853,14 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { } } return true - case Interface: + case abi.Interface: if v1.IsNil() || v2.IsNil() { return v1.IsNil() && v2.IsNil() } return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) - case Ptr: + case abi.Pointer: return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) - case Struct: + case abi.Struct: n := v1.NumField() for i := 0; i < n; i++ { if !deepValueEqualJs(v1.Field(i), v2.Field(i), visited) { @@ -884,7 +868,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { } } return true - case Map: + case abi.Map: if v1.IsNil() != v2.IsNil() { return false } @@ -903,9 +887,9 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { } } return true - case Func: + case abi.Func: return v1.IsNil() && v2.IsNil() - case UnsafePointer: + case abi.UnsafePointer: return v1.object() == v2.object() } From 1e9a41bcab965c288f757485fd4f24a3ef6ff467 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 29 Jan 2026 15:02:17 -0700 Subject: [PATCH 04/48] working on reflectlite and abi --- .../src/internal/reflectlite/reflectlite.go | 20 +-- .../natives/src/internal/reflectlite/type.go | 14 +-- .../natives/src/internal/reflectlite/value.go | 118 +++++++++--------- 3 files changed, 80 insertions(+), 72 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index b2e9003b3..7f20648d4 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -39,10 +39,14 @@ var ( idRtype = "_rtype" ) -func jsType(typ Type) *js.Object { +func jsType(typ *abi.Type) *js.Object { return js.InternalObject(typ).Get(idJsType) } +func jsPtrTo(typ *abi.Type) *js.Object { + return jsType(PtrTo(toRType(typ)).(*rtype).Type) +} + func reflectType(typ *js.Object) *rtype { if typ.Get(idReflectType) == js.Undefined { rt := &rtype{ @@ -223,11 +227,11 @@ func internalStr(strObj *js.Object) string { return c.str } -func isWrapped(typ Type) bool { +func isWrapped(typ *abi.Type) bool { return jsType(typ).Get("wrapped").Bool() } -func copyStruct(dst, src *js.Object, typ Type) { +func copyStruct(dst, src *js.Object, typ *abi.Type) { fields := jsType(typ).Get("fields") for i := 0; i < fields.Length(); i++ { prop := fields.Index(i).Get("prop").String() @@ -235,7 +239,7 @@ func copyStruct(dst, src *js.Object, typ Type) { } } -func makeValue(t Type, v *js.Object, fl flag) Value { +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { rt := t.common() switch t.Kind() { case abi.Array, abi.Struct, abi.Pointer: @@ -422,7 +426,7 @@ func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { if entry == js.Undefined { return nil } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsPtrTo(t.Elem())).Unsafe()) } func mapassign(t *rtype, m, key, val unsafe.Pointer) { @@ -493,7 +497,7 @@ func mapiterkey(it unsafe.Pointer) unsafe.Pointer { // Record the key-value pair for later accesses. iter.last = kv } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(iter.t.(TypeEx).Key()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsPtrTo(iter.t.(TypeEx).Key())).Unsafe()) } func mapiternext(it unsafe.Pointer) { @@ -519,7 +523,7 @@ func cvtDirect(v Value, typ Type) Value { slice.Set("$offset", srcVal.Get("$offset")) slice.Set("$length", srcVal.Get("$length")) slice.Set("$capacity", srcVal.Get("$capacity")) - val = js.Global.Call("$newDataPointer", slice, jsType(PtrTo(typ))) + val = js.Global.Call("$newDataPointer", slice, jsPtrTo(typ)) case abi.Pointer: if typ.Elem().Kind() == abi.Struct { if typ.Elem() == v.typ.Elem() { @@ -635,7 +639,7 @@ func valueInterface(v Value) any { return any(unsafe.Pointer(v.object().Unsafe())) } -func ifaceE2I(t *rtype, src any, dst unsafe.Pointer) { +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { js.InternalObject(dst).Call("$set", js.InternalObject(src)) } diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index da5227b68..b9477b105 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -36,11 +36,11 @@ func newTypeOff(t *rtype) typeOff { func (t rtype) Comparable() bool { switch t.Kind() { - case Func, Slice, Map: + case abi.Func, abi.Slice, abi.Map: return false - case Array: + case abi.Array: return t.Elem().Comparable() - case Struct: + case abi.Struct: for i := 0; i < t.NumField(); i++ { ft := t.Field(i) if !ft.typ.Comparable() { @@ -52,7 +52,7 @@ func (t rtype) Comparable() bool { } func (t *rtype) IsVariadic() bool { - if t.Kind() != Func { + if t.Kind() != abi.Func { panic("reflect: IsVariadic of non-func type") } tt := (*funcType)(unsafe.Pointer(t)) @@ -64,7 +64,7 @@ func (t *rtype) kindType() *rtype { } func (t *rtype) Key() Type { - if t.Kind() != Map { + if t.Kind() != abi.Map { panic("reflect: Key of non-map type") } tt := (*mapType)(unsafe.Pointer(t)) @@ -72,7 +72,7 @@ func (t *rtype) Key() Type { } func (t *rtype) NumField() int { - if t.Kind() != Struct { + if t.Kind() != abi.Struct { panic("reflect: NumField of non-struct type") } tt := (*structType)(unsafe.Pointer(t)) @@ -80,7 +80,7 @@ func (t *rtype) NumField() int { } func (t *rtype) Method(i int) (m Method) { - if t.Kind() == Interface { + if t.Kind() == abi.Interface { tt := (*interfaceType)(unsafe.Pointer(t)) return tt.Method(i) } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index db3eb3563..0061541e6 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -5,22 +5,24 @@ package reflectlite import ( "unsafe" + "internal/abi" + "github.com/gopherjs/gopherjs/js" ) func (v Value) object() *js.Object { - if v.typ.Kind() == Array || v.typ.Kind() == Struct { + if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { return js.InternalObject(v.ptr) } if v.flag&flagIndir != 0 { val := js.InternalObject(v.ptr).Call("$get") if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { switch v.typ.Kind() { - case Uint64, Int64: + case abi.Uint64, abi.Int64: val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) - case Complex64, Complex128: + case abi.Complex64, abi.Complex128: val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) - case Slice: + case abi.Slice: if val == val.Get("constructor").Get("nil") { val = jsType(v.typ).Get("nil") break @@ -37,7 +39,7 @@ func (v Value) object() *js.Object { return js.InternalObject(v.ptr) } -func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { if v.flag&flagMethod != 0 { v = makeMethodValue(context, v) } @@ -66,7 +68,7 @@ func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value } // Failed. - panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) + panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) } var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) @@ -117,20 +119,21 @@ func (v Value) call(op string, in []Value) []Value { } } for _, x := range in { - if x.Kind() == Invalid { + if x.Kind() == abi.Invalid { panic("reflect: " + op + " using zero Value argument") } } for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { + if xt, targ := in[i].Type(), toRType(t.In(i)); !xt.AssignableTo(targ) { panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) } } if !isSlice && t.IsVariadic() { // prepare slice for remaining values m := len(in) - n - slice := MakeSlice(t.In(n), m, m) - elem := t.In(n).Elem() + targ := toRType(t.In(n)) + slice := MakeSlice(targ, m, m) + elem := targ.Elem() for i := 0; i < m; i++ { x := in[n+i] if xt := x.Type(); !xt.AssignableTo(elem) { @@ -152,7 +155,8 @@ func (v Value) call(op string, in []Value) []Value { argsArray := js.Global.Get("Array").New(t.NumIn()) for i, arg := range in { - argsArray.SetIndex(i, unwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i).common(), nil).object())) + targ := toRType(t.In(i)) + argsArray.SetIndex(i, unwrapJsObject(targ, arg.assignTo("reflect.Value.Call", targ.common(), nil).object())) } results := callHelper(js.InternalObject(fn), rcvr, argsArray) @@ -160,11 +164,11 @@ func (v Value) call(op string, in []Value) []Value { case 0: return nil case 1: - return []Value{makeValue(t.Out(0), wrapJsObject(t.Out(0), results), 0)} + return []Value{makeValue(t.Out(0), wrapJsObject(toRType(t.Out(0)), results), 0)} default: ret := make([]Value, nout) for i := range ret { - ret[i] = makeValue(t.Out(i), wrapJsObject(t.Out(i), results.Index(i)), 0) + ret[i] = makeValue(t.Out(i), wrapJsObject(toRType(t.Out(i)), results.Index(i)), 0) } return ret } @@ -173,9 +177,9 @@ func (v Value) call(op string, in []Value) []Value { func (v Value) Cap() int { k := v.kind() switch k { - case Array: + case abi.Array: return v.typ.Len() - case Chan, Slice: + case abi.Chan, abi.Slice: return v.object().Get("$capacity").Int() } panic(&ValueError{"reflect.Value.Cap", k}) @@ -183,24 +187,24 @@ func (v Value) Cap() int { func (v Value) Index(i int) Value { switch k := v.kind(); k { - case Array: + case abi.Array: tt := (*arrayType)(unsafe.Pointer(v.typ)) - if i < 0 || i > int(tt.len) { + if i < 0 || i > int(tt.Len) { panic("reflect: array index out of range") } - typ := tt.elem + typ := tt.Elem fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) a := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), ).Unsafe()), fl} } return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) - case Slice: + case abi.Slice: s := v.object() if i < 0 || i >= s.Get("$length").Int() { panic("reflect: slice index out of range") @@ -211,15 +215,15 @@ func (v Value) Index(i int) Value { i += s.Get("$offset").Int() a := s.Get("$array") - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), ).Unsafe()), fl} } return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) - case String: + case abi.String: str := *(*string)(v.ptr) if i < 0 || i >= len(str) { panic("reflect: string index out of range") @@ -239,17 +243,17 @@ func (v Value) InterfaceData() [2]uintptr { func (v Value) IsNil() bool { switch k := v.kind(); k { - case Ptr, Slice: + case abi.Pointer, abi.Slice: return v.object() == jsType(v.typ).Get("nil") - case Chan: + case abi.Chan: return v.object() == js.Global.Get("$chanNil") - case Func: + case abi.Func: return v.object() == js.Global.Get("$throwNilPointerError") - case Map: + case abi.Map: return v.object() == js.InternalObject(false) - case Interface: + case abi.Interface: return v.object() == js.Global.Get("$ifaceNil") - case UnsafePointer: + case abi.UnsafePointer: return v.object().Unsafe() == 0 default: panic(&ValueError{"reflect.Value.IsNil", k}) @@ -258,13 +262,13 @@ func (v Value) IsNil() bool { func (v Value) Len() int { switch k := v.kind(); k { - case Array, String: + case abi.Array, abi.String: return v.object().Length() - case Slice: + case abi.Slice: return v.object().Get("$length").Int() - case Chan: + case abi.Chan: return v.object().Get("$buffer").Get("length").Int() - case Map: + case abi.Map: return v.object().Get("size").Int() default: panic(&ValueError{"reflect.Value.Len", k}) @@ -273,17 +277,17 @@ func (v Value) Len() int { func (v Value) Pointer() uintptr { switch k := v.kind(); k { - case Chan, Map, Ptr, UnsafePointer: + case abi.Chan, abi.Map, abi.Pointer, abi.UnsafePointer: if v.IsNil() { return 0 } return v.object().Unsafe() - case Func: + case abi.Func: if v.IsNil() { return 0 } return 1 - case Slice: + case abi.Slice: if v.IsNil() { return 0 } @@ -299,11 +303,11 @@ func (v Value) Set(x Value) { x = x.assignTo("reflect.Set", v.typ, nil) if v.flag&flagIndir != 0 { switch v.typ.Kind() { - case Array: + case abi.Array: jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) - case Interface: + case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x))) - case Struct: + case abi.Struct: copyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) default: js.InternalObject(v.ptr).Call("$set", x.object()) @@ -315,8 +319,8 @@ func (v Value) Set(x Value) { func (v Value) SetBytes(x []byte) { v.mustBeAssignable() - v.mustBe(Slice) - if v.typ.Elem().Kind() != Uint8 { + v.mustBe(abi.Slice) + if v.typ.Elem().Kind() != abi.Uint8 { panic("reflect.Value.SetBytes of non-byte slice") } slice := js.InternalObject(x) @@ -332,7 +336,7 @@ func (v Value) SetBytes(x []byte) { func (v Value) SetCap(n int) { v.mustBeAssignable() - v.mustBe(Slice) + v.mustBe(abi.Slice) s := js.InternalObject(v.ptr).Call("$get") if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { panic("reflect: slice capacity out of range in SetCap") @@ -346,7 +350,7 @@ func (v Value) SetCap(n int) { func (v Value) SetLen(n int) { v.mustBeAssignable() - v.mustBe(Slice) + v.mustBe(abi.Slice) s := js.InternalObject(v.ptr).Call("$get") if n < 0 || n > s.Get("$capacity").Int() { panic("reflect: slice length out of range in SetLen") @@ -365,7 +369,7 @@ func (v Value) Slice(i, j int) Value { s *js.Object ) switch kind := v.kind(); kind { - case Array: + case abi.Array: if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } @@ -374,12 +378,12 @@ func (v Value) Slice(i, j int) Value { typ = SliceOf(tt.elem) s = jsType(typ).New(v.object()) - case Slice: + case abi.Slice: typ = v.typ s = v.object() cap = s.Get("$capacity").Int() - case String: + case abi.String: str := *(*string)(v.ptr) if i < 0 || j < i || j > len(str) { panic("reflect.Value.Slice: string slice index out of bounds") @@ -404,7 +408,7 @@ func (v Value) Slice3(i, j, k int) Value { s *js.Object ) switch kind := v.kind(); kind { - case Array: + case abi.Array: if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } @@ -413,7 +417,7 @@ func (v Value) Slice3(i, j, k int) Value { typ = SliceOf(tt.elem) s = jsType(typ).New(v.object()) - case Slice: + case abi.Slice: typ = v.typ s = v.object() cap = s.Get("$capacity").Int() @@ -437,7 +441,7 @@ func (v Value) Close() { func (v Value) Elem() Value { switch k := v.kind(); k { - case Interface: + case abi.Interface: val := v.object() if val == js.Global.Get("$ifaceNil") { return Value{} @@ -445,7 +449,7 @@ func (v Value) Elem() Value { typ := reflectType(val.Get("constructor")) return makeValue(typ, val.Get("$val"), v.flag.ro()) - case Ptr: + case abi.Pointer: if v.IsNil() { return Value{} } @@ -463,7 +467,7 @@ func (v Value) Elem() Value { // NumField returns the number of fields in the struct v. // It panics if v's Kind is not Struct. func (v Value) NumField() int { - v.mustBe(Struct) + v.mustBe(abi.Struct) tt := (*structType)(unsafe.Pointer(v.typ)) return len(tt.fields) } @@ -473,7 +477,7 @@ func (v Value) NumField() int { // It panics if v's Kind is not Map. // It returns an empty slice if v represents a nil map. func (v Value) MapKeys() []Value { - v.mustBe(Map) + v.mustBe(abi.Map) tt := (*mapType)(unsafe.Pointer(v.typ)) keyType := tt.key @@ -506,7 +510,7 @@ func (v Value) MapKeys() []Value { // It returns the zero Value if key is not found in the map or if v represents a nil map. // As in Go, the key's value must be assignable to the map's key type. func (v Value) MapIndex(key Value) Value { - v.mustBe(Map) + v.mustBe(abi.Map) tt := (*mapType)(unsafe.Pointer(v.typ)) // Do not require key to be exported, so that DeepEqual @@ -562,7 +566,7 @@ func (v Value) Field(i int) Value { v = v.Field(0) if v.typ == jsObjectPtr { o := v.object().Get("object") - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), ).Unsafe()), fl} @@ -575,8 +579,8 @@ func (v Value) Field(i int) Value { } s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), ).Unsafe()), fl} From 2dd4e8a98d9d7f7e653b505a6e7ea10cc73acd00 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 29 Jan 2026 15:55:59 -0700 Subject: [PATCH 05/48] working on reflectlite and abi --- compiler/errlist/errlist.go | 14 ++- .../src/internal/reflectlite/reflectlite.go | 52 +++++---- .../natives/src/internal/reflectlite/type.go | 22 ++-- .../natives/src/internal/reflectlite/value.go | 109 +++++++++++------- 4 files changed, 123 insertions(+), 74 deletions(-) diff --git a/compiler/errlist/errlist.go b/compiler/errlist/errlist.go index 891a8f92d..753a14265 100644 --- a/compiler/errlist/errlist.go +++ b/compiler/errlist/errlist.go @@ -3,6 +3,8 @@ package errlist import ( "errors" "fmt" + "strconv" + "strings" ) // ErrTooManyErrors is added to the ErrorList by the Trim method. @@ -15,7 +17,17 @@ func (errs ErrorList) Error() string { if len(errs) == 0 { return "" } - return fmt.Sprintf("%s (and %d more errors)", errs[0].Error(), len(errs[1:])) + // TODO(grantnelson-wf): Remove below and uncomment last line + if len(errs) == 1 { + return errs[0].Error() + } + width := len(strconv.Itoa(len(errs))) + buf := &strings.Builder{} + for i, err := range errs { + fmt.Fprintf(buf, "\n(%*d) %s", width, i, err.Error()) + } + return buf.String() + // return fmt.Sprintf("%s (and %d more errors)", errs[0].Error(), len(errs[1:])) } // ErrOrNil returns nil if ErrorList is empty, or the error otherwise. diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 7f20648d4..2f7375e87 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -27,10 +27,10 @@ func init() { used(imethod{}) initialized = true - uint8Type = TypeOf(uint8(0)).(*rtype) // set for real + uint8Type = TypeOf(uint8(0)).(rtype) // set for real } -var uint8Type *rtype +var uint8Type rtype var ( idJsType = "_jsType" @@ -44,15 +44,21 @@ func jsType(typ *abi.Type) *js.Object { } func jsPtrTo(typ *abi.Type) *js.Object { - return jsType(PtrTo(toRType(typ)).(*rtype).Type) + return jsType(toAbiType(PtrTo(toRType(typ)))) } -func reflectType(typ *js.Object) *rtype { +func toAbiType(typ Type) *abi.Type { + return typ.(rtype).Type +} + +func reflectType(typ *js.Object) rtype { if typ.Get(idReflectType) == js.Undefined { - rt := &rtype{ - Size: uintptr(typ.Get("size").Int()), - Kind: uint8(typ.Get("kind").Int()), - Str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + rt := rtype{ + Type: &abi.Type{ + Size: uintptr(typ.Get("size").Int()), + Kind: uint8(typ.Get("kind").Int()), + Str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + }, } js.InternalObject(rt).Set(idJsType, typ) typ.Set(idReflectType, js.InternalObject(rt)) @@ -117,12 +123,12 @@ func reflectType(typ *js.Object) *rtype { }) case abi.Func: params := typ.Get("params") - in := make([]*rtype, params.Length()) + in := make([]rtype, params.Length()) for i := range in { in[i] = reflectType(params.Index(i)) } results := typ.Get("results") - out := make([]*rtype, results.Length()) + out := make([]rtype, results.Length()) for i := range out { out[i] = reflectType(results.Index(i)) } @@ -184,10 +190,10 @@ func reflectType(typ *js.Object) *rtype { } } - return (*rtype)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) + return (rtype)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) } -func setKindType(rt *rtype, kindType any) { +func setKindType(rt rtype, kindType any) { js.InternalObject(rt).Set(idKindType, js.InternalObject(kindType)) js.InternalObject(kindType).Set(idRtype, js.InternalObject(rt)) } @@ -315,7 +321,7 @@ func MapOf(key, elem Type) Type { return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) } -func (t *rtype) ptrTo() *rtype { +func (t rtype) ptrTo() rtype { return reflectType(js.Global.Call("$ptrType", jsType(t))) } @@ -402,16 +408,16 @@ func loadScalar(p unsafe.Pointer, n uintptr) uintptr { return js.InternalObject(p).Call("$get").Unsafe() } -func makechan(typ *rtype, size int) (ch unsafe.Pointer) { +func makechan(typ rtype, size int) (ch unsafe.Pointer) { ctyp := (*chanType)(unsafe.Pointer(typ)) return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.elem), size).Unsafe()) } -func makemap(t *rtype, cap int) (m unsafe.Pointer) { +func makemap(t rtype, cap int) (m unsafe.Pointer) { return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) } -func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, string) { +func keyFor(t rtype, key unsafe.Pointer) (*js.Object, string) { kv := js.InternalObject(key) if kv.Get("$get") != js.Undefined { kv = kv.Call("$get") @@ -420,7 +426,7 @@ func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, string) { return kv, k } -func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { +func mapaccess(t rtype, m, key unsafe.Pointer) unsafe.Pointer { _, k := keyFor(t, key) entry := js.InternalObject(m).Call("get", k) if entry == js.Undefined { @@ -429,7 +435,7 @@ func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsPtrTo(t.Elem())).Unsafe()) } -func mapassign(t *rtype, m, key, val unsafe.Pointer) { +func mapassign(t rtype, m, key, val unsafe.Pointer) { kv, k := keyFor(t, key) jsVal := js.InternalObject(val).Call("$get") et := t.Elem() @@ -444,7 +450,7 @@ func mapassign(t *rtype, m, key, val unsafe.Pointer) { js.InternalObject(m).Call("set", k, entry) } -func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) { +func mapdelete(t rtype, m unsafe.Pointer, key unsafe.Pointer) { _, k := keyFor(t, key) js.InternalObject(m).Call("delete", k) } @@ -472,7 +478,7 @@ func (iter *mapIter) skipUntilValidKey() { } } -func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer { +func mapiterinit(t rtype, m unsafe.Pointer) unsafe.Pointer { return unsafe.Pointer(&mapIter{t, js.InternalObject(m), js.Global.Get("Array").Call("from", js.InternalObject(m).Call("keys")), 0, nil}) } @@ -586,7 +592,7 @@ func Copy(dst, src Value) int { return js.Global.Call("$copySlice", dstVal, srcVal).Int() } -func methodReceiver(op string, v Value, i int) (_ *rtype, t *funcType, fn unsafe.Pointer) { +func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe.Pointer) { var prop string if v.typ.Kind() == abi.Interface { tt := (*interfaceType)(unsafe.Pointer(v.typ)) @@ -728,12 +734,12 @@ func getJsTag(tag string) string { // PtrTo returns the pointer type with element t. // For example, if t represents type Foo, PtrTo(t) represents *Foo. func PtrTo(t Type) Type { - return t.(*rtype).ptrTo() + return t.(rtype).ptrTo() } // copyVal returns a Value containing the map key or value at ptr, // allocating a new variable as needed. -func copyVal(typ *rtype, fl flag, ptr unsafe.Pointer) Value { +func copyVal(typ rtype, fl flag, ptr unsafe.Pointer) Value { if ifaceIndir(typ) { // Copy result so future changes to the map // won't change the underlying value. diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index b9477b105..231004e2f 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -24,11 +24,11 @@ func newNameOff(n abi.Name) nameOff { var typeOffList []*abi.Type -func (t *rtype) typeOff(off typeOff) *abi.Type { +func (t rtype) typeOff(off typeOff) *abi.Type { return typeOffList[int(off)] } -func newTypeOff(t *rtype) typeOff { +func newTypeOff(t rtype) typeOff { i := len(typeOffList) typeOffList = append(typeOffList, t) return typeOff(i) @@ -51,7 +51,7 @@ func (t rtype) Comparable() bool { return true } -func (t *rtype) IsVariadic() bool { +func (t rtype) IsVariadic() bool { if t.Kind() != abi.Func { panic("reflect: IsVariadic of non-func type") } @@ -59,11 +59,11 @@ func (t *rtype) IsVariadic() bool { return tt.outCount&(1<<15) != 0 } -func (t *rtype) kindType() *rtype { - return (*rtype)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) +func (t rtype) kindType() rtype { + return (rtype)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) } -func (t *rtype) Key() Type { +func (t rtype) Key() Type { if t.Kind() != abi.Map { panic("reflect: Key of non-map type") } @@ -71,7 +71,7 @@ func (t *rtype) Key() Type { return toType(tt.key) } -func (t *rtype) NumField() int { +func (t rtype) NumField() int { if t.Kind() != abi.Struct { panic("reflect: NumField of non-struct type") } @@ -79,7 +79,7 @@ func (t *rtype) NumField() int { return len(tt.fields) } -func (t *rtype) Method(i int) (m Method) { +func (t rtype) Method(i int) (m Method) { if t.Kind() == abi.Interface { tt := (*interfaceType)(unsafe.Pointer(t)) return tt.Method(i) @@ -110,7 +110,11 @@ func (t *rtype) Method(i int) (m Method) { rcvr := arguments[0] return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) }) - m.Func = Value{mt.(*rtype), unsafe.Pointer(fn.Unsafe()), fl} + m.Func = Value{ + typ: toAbiType(mt), + ptr: unsafe.Pointer(fn.Unsafe()), + flag: fl, + } m.Index = i return m diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 0061541e6..04cf2e5ef 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -182,7 +182,7 @@ func (v Value) Cap() int { case abi.Chan, abi.Slice: return v.object().Get("$capacity").Int() } - panic(&ValueError{"reflect.Value.Cap", k}) + panic(&ValueError{Method: "reflect.Value.Cap", Kind: k}) } func (v Value) Index(i int) Value { @@ -196,13 +196,18 @@ func (v Value) Index(i int) Value { fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) a := js.InternalObject(v.ptr) + rtyp := toRType(typ) if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} + return Value{ + typ: typ, + ptr: unsafe.Pointer(jsPtrTo(typ).New( + js.InternalObject(func() *js.Object { return wrapJsObject(rtyp, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(rtyp, x)) }), + ).Unsafe()), + flag: fl, + } } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + return makeValue(typ, wrapJsObject(rtyp, a.Index(i)), fl) case abi.Slice: s := v.object() @@ -210,30 +215,39 @@ func (v Value) Index(i int) Value { panic("reflect: slice index out of range") } tt := (*sliceType)(unsafe.Pointer(v.typ)) - typ := tt.elem + typ := tt.Elem fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) i += s.Get("$offset").Int() a := s.Get("$array") + rtyp := toRType(typ) if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} + return Value{ + typ: typ, + ptr: unsafe.Pointer(jsPtrTo(typ).New( + js.InternalObject(func() *js.Object { return wrapJsObject(rtyp, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(rtyp, x)) }), + ).Unsafe()), + flag: fl, + } } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + return makeValue(typ, wrapJsObject(rtyp, a.Index(i)), fl) case abi.String: str := *(*string)(v.ptr) if i < 0 || i >= len(str) { panic("reflect: string index out of range") } - fl := v.flag.ro() | flag(Uint8) | flagIndir + fl := v.flag.ro() | flag(abi.Uint8) | flagIndir c := str[i] - return Value{uint8Type, unsafe.Pointer(&c), fl} + return Value{ + typ: uint8Type.Type, + ptr: unsafe.Pointer(&c), + flag: fl, + } default: - panic(&ValueError{"reflect.Value.Index", k}) + panic(&ValueError{Method: "reflect.Value.Index", Kind: k}) } } @@ -256,7 +270,7 @@ func (v Value) IsNil() bool { case abi.UnsafePointer: return v.object().Unsafe() == 0 default: - panic(&ValueError{"reflect.Value.IsNil", k}) + panic(&ValueError{Method: "reflect.Value.IsNil", Kind: k}) } } @@ -271,7 +285,7 @@ func (v Value) Len() int { case abi.Map: return v.object().Get("size").Int() default: - panic(&ValueError{"reflect.Value.Len", k}) + panic(&ValueError{Method: "reflect.Value.Len", Kind: k}) } } @@ -293,7 +307,7 @@ func (v Value) Pointer() uintptr { } return v.object().Get("$array").Unsafe() default: - panic(&ValueError{"reflect.Value.Pointer", k}) + panic(&ValueError{Method: "reflect.Value.Pointer", Kind: k}) } } @@ -320,11 +334,12 @@ func (v Value) Set(x Value) { func (v Value) SetBytes(x []byte) { v.mustBeAssignable() v.mustBe(abi.Slice) - if v.typ.Elem().Kind() != abi.Uint8 { + rtyp := toRType(v.typ) + if rtyp.Elem().Kind() != abi.Uint8 { panic("reflect.Value.SetBytes of non-byte slice") } slice := js.InternalObject(x) - if v.typ.Name() != "" || v.typ.Elem().Name() != "" { + if rtyp.Name() != "" || rtyp.Elem().Name() != "" { typedSlice := jsType(v.typ).New(slice.Get("$array")) typedSlice.Set("$offset", slice.Get("$offset")) typedSlice.Set("$length", slice.Get("$length")) @@ -374,12 +389,12 @@ func (v Value) Slice(i, j int) Value { panic("reflect.Value.Slice: slice of unaddressable array") } tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)) + s = jsType(toAbiType(typ)).New(v.object()) case abi.Slice: - typ = v.typ + typ = toRType(v.typ) s = v.object() cap = s.Get("$capacity").Int() @@ -391,14 +406,14 @@ func (v Value) Slice(i, j int) Value { return ValueOf(str[i:j]) default: - panic(&ValueError{"reflect.Value.Slice", kind}) + panic(&ValueError{Method: "reflect.Value.Slice", Kind: kind}) } if i < 0 || j < i || j > cap { panic("reflect.Value.Slice: slice index out of bounds") } - return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) + return makeValue(toAbiType(typ), js.Global.Call("$subslice", s, i, j), v.flag.ro()) } func (v Value) Slice3(i, j, k int) Value { @@ -413,8 +428,8 @@ func (v Value) Slice3(i, j, k int) Value { panic("reflect.Value.Slice: slice of unaddressable array") } tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) + cap = int(tt.Len) + typ = SliceOf(tt.Elem) s = jsType(typ).New(v.object()) case abi.Slice: @@ -423,7 +438,7 @@ func (v Value) Slice3(i, j, k int) Value { cap = s.Get("$capacity").Int() default: - panic(&ValueError{"reflect.Value.Slice3", kind}) + panic(&ValueError{Method: "reflect.Value.Slice3", Kind: kind}) } if i < 0 || j < i || k < j || k > cap { @@ -457,10 +472,14 @@ func (v Value) Elem() Value { tt := (*ptrType)(unsafe.Pointer(v.typ)) fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.elem.Kind()) - return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} + return Value{ + typ: tt.elem, + ptr: unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), + flag: fl, + } default: - panic(&ValueError{"reflect.Value.Elem", k}) + panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) } } @@ -540,7 +559,7 @@ func (v Value) MapIndex(key Value) Value { func (v Value) Field(i int) Value { if v.kind() != Struct { - panic(&ValueError{"reflect.Value.Field", v.kind()}) + panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) } tt := (*structType)(unsafe.Pointer(v.typ)) if uint(i) >= uint(len(tt.fields)) { @@ -566,12 +585,16 @@ func (v Value) Field(i int) Value { v = v.Field(0) if v.typ == jsObjectPtr { o := v.object().Get("object") - return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), - ).Unsafe()), fl} + return Value{ + typ: typ, + ptr: unsafe.Pointer(jsPtrTo(typ).New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), + ).Unsafe()), + flag: fl, + } } - if v.typ.Kind() == Ptr { + if v.typ.Kind() == abi.Pointer { v = v.Elem() } } @@ -580,10 +603,14 @@ func (v Value) Field(i int) Value { s := js.InternalObject(v.ptr) if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} + return Value{ + typ: typ, + ptr: unsafe.Pointer(jsPtrTo(typ).New( + js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), + ).Unsafe()), + flag: fl, + } } return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) } From 2f0e4279ae30f456f3fce8f4d2c2807922ebb251 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 29 Jan 2026 16:16:43 -0700 Subject: [PATCH 06/48] working on reflectlite and abi --- .../natives/src/internal/reflectlite/value.go | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 04cf2e5ef..ce2129966 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -429,11 +429,11 @@ func (v Value) Slice3(i, j, k int) Value { } tt := (*arrayType)(unsafe.Pointer(v.typ)) cap = int(tt.Len) - typ = SliceOf(tt.Elem) - s = jsType(typ).New(v.object()) + typ = SliceOf(toRType(tt.Elem)) + s = jsType(toAbiType(typ)).New(v.object()) case abi.Slice: - typ = v.typ + typ = toRType(v.typ) s = v.object() cap = s.Get("$capacity").Int() @@ -445,11 +445,11 @@ func (v Value) Slice3(i, j, k int) Value { panic("reflect.Value.Slice3: slice index out of bounds") } - return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) + return makeValue(toAbiType(typ), js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) } func (v Value) Close() { - v.mustBe(Chan) + v.mustBe(abi.Chan) v.mustBeExported() js.Global.Call("$close", v.object()) } @@ -462,7 +462,7 @@ func (v Value) Elem() Value { return Value{} } typ := reflectType(val.Get("constructor")) - return makeValue(typ, val.Get("$val"), v.flag.ro()) + return makeValue(toAbiType(typ), val.Get("$val"), v.flag.ro()) case abi.Pointer: if v.IsNil() { @@ -471,10 +471,10 @@ func (v Value) Elem() Value { val := v.object() tt := (*ptrType)(unsafe.Pointer(v.typ)) fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.elem.Kind()) + fl |= flag(tt.Elem.Kind()) return Value{ - typ: tt.elem, - ptr: unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), + typ: tt.Elem, + ptr: unsafe.Pointer(wrapJsObject(toRType(tt.Elem), val).Unsafe()), flag: fl, } @@ -488,7 +488,7 @@ func (v Value) Elem() Value { func (v Value) NumField() int { v.mustBe(abi.Struct) tt := (*structType)(unsafe.Pointer(v.typ)) - return len(tt.fields) + return len(tt.Fields) } // MapKeys returns a slice containing all the keys present in the map, @@ -498,7 +498,7 @@ func (v Value) NumField() int { func (v Value) MapKeys() []Value { v.mustBe(abi.Map) tt := (*mapType)(unsafe.Pointer(v.typ)) - keyType := tt.key + keyType := tt.Key fl := v.flag.ro() | flag(keyType.Kind()) @@ -507,7 +507,7 @@ func (v Value) MapKeys() []Value { if m != nil { mlen = maplen(m) } - it := mapiterinit(v.typ, m) + it := mapiterinit(toRType(v.typ), m) a := make([]Value, mlen) var i int for i = 0; i < len(a); i++ { @@ -518,7 +518,7 @@ func (v Value) MapKeys() []Value { // we can do about it. break } - a[i] = copyVal(keyType, fl, key) + a[i] = copyVal(toRType(keyType), fl, key) mapiternext(it) } return a[:i] @@ -539,7 +539,7 @@ func (v Value) MapIndex(key Value) Value { // considered unexported. This is consistent with the // behavior for structs, which allow read but not write // of unexported fields. - key = key.assignTo("reflect.Value.MapIndex", tt.key, nil) + key = key.assignTo("reflect.Value.MapIndex", tt.Key, nil) var k unsafe.Pointer if key.flag&flagIndir != 0 { @@ -547,14 +547,14 @@ func (v Value) MapIndex(key Value) Value { } else { k = unsafe.Pointer(&key.ptr) } - e := mapaccess(v.typ, v.pointer(), k) + e := mapaccess(toRType(v.typ), v.pointer(), k) if e == nil { return Value{} } - typ := tt.elem + typ := tt.Elem fl := (v.flag | key.flag).ro() fl |= flag(typ.Kind()) - return copyVal(typ, fl, e) + return copyVal(toRType(typ), fl, e) } func (v Value) Field(i int) Value { @@ -562,12 +562,12 @@ func (v Value) Field(i int) Value { panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) } tt := (*structType)(unsafe.Pointer(v.typ)) - if uint(i) >= uint(len(tt.fields)) { + if uint(i) >= uint(len(tt.Fields)) { panic("reflect: Field index out of range") } prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.fields[i] + field := &tt.Fields[i] typ := field.typ fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) From c548151c8560a90104b5e8a78f855f92cefe12eb Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Fri, 30 Jan 2026 10:11:02 -0700 Subject: [PATCH 07/48] working on reflectlite and abi --- .../natives/src/internal/reflectlite/value.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index ce2129966..fe1bf3d27 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -568,22 +568,22 @@ func (v Value) Field(i int) Value { prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() field := &tt.Fields[i] - typ := field.typ + typ := field.Typ fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.name.isExported() { - if field.embedded() { + if !field.Name.IsExported() { + if field.Embedded() { fl |= flagEmbedRO } else { fl |= flagStickyRO } } - if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { if jsTag := getJsTag(tag); jsTag != "" { for { v = v.Field(0) - if v.typ == jsObjectPtr { + if toRType(v.typ) == jsObjectPtr { o := v.object().Get("object") return Value{ typ: typ, @@ -606,11 +606,11 @@ func (v Value) Field(i int) Value { return Value{ typ: typ, ptr: unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), + js.InternalObject(func() *js.Object { return wrapJsObject(toRType(typ), s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(toRType(typ), x)) }), ).Unsafe()), flag: fl, } } - return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) + return makeValue(typ, wrapJsObject(toRType(typ), s.Get(prop)), fl) } From 490c7dc46c7eef61c5161d9fee30978da3697e3a Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Fri, 30 Jan 2026 16:24:39 -0700 Subject: [PATCH 08/48] Started thinning out reflectlite and moved Name to abi --- compiler/natives/src/internal/abi/type.go | 35 ++++ .../src/internal/reflectlite/reflectlite.go | 171 ++---------------- .../natives/src/internal/reflectlite/type.go | 80 +------- .../natives/src/internal/reflectlite/value.go | 33 ---- 4 files changed, 58 insertions(+), 261 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 46e73f195..adcbe0baf 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -37,3 +37,38 @@ func (t *FuncType) InSlice() []*Type { func (t *FuncType) OutSlice() []*Type { return t.Out_ } + +type Name struct { + name string + tag string + exported bool + embedded bool +} + +func (n Name) IsExported() bool { return n.exported } +func (n Name) HasTag() bool { return len(n.tag) > 0 } +func (n Name) IsEmbedded() bool { return n.embedded } +func (n Name) IsBlank() bool { return n.Name() == `_` } +func (n Name) Name() string { return n.name } +func (n Name) Tag() string { return n.tag } + +//gopherjs:purge Used for byte encoding of name, not used in JS +func writeVarint(buf []byte, n int) int + +//gopherjs:purge Used for byte encoding of name, not used in JS +func (n Name) DataChecked(off int, whySafe string) *byte + +//gopherjs:purge Used for byte encoding of name, not used in JS +func (n Name) Data(off int) *byte + +//gopherjs:purge Used for byte encoding of name, not used in JS +func (n Name) ReadVarint(off int) (int, int) + +func NewName(n, tag string, exported, embedded bool) Name { + return Name{ + name: n, + tag: tag, + exported: exported, + embedded: embedded, + } +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 2f7375e87..8ad1668f5 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -35,7 +35,6 @@ var uint8Type rtype var ( idJsType = "_jsType" idReflectType = "_reflectType" - idKindType = "kindType" idRtype = "_rtype" ) @@ -193,40 +192,6 @@ func reflectType(typ *js.Object) rtype { return (rtype)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) } -func setKindType(rt rtype, kindType any) { - js.InternalObject(rt).Set(idKindType, js.InternalObject(kindType)) - js.InternalObject(kindType).Set(idRtype, js.InternalObject(rt)) -} - -type nameData struct { - name string - tag string - exported bool - embedded bool -} - -var nameMap = make(map[*byte]*nameData) - -func (n name) name() (s string) { return nameMap[n.bytes].name } -func (n name) tag() (s string) { return nameMap[n.bytes].tag } -func (n name) isExported() bool { return nameMap[n.bytes].exported } -func (n name) embedded() bool { return nameMap[n.bytes].embedded } - -func newName(n, tag string, exported, embedded bool) name { - b := new(byte) - nameMap[b] = &nameData{ - name: n, - tag: tag, - exported: exported, - embedded: embedded, - } - return name{ - bytes: b, - } -} - -func pkgPath(n abi.Name) string { return "" } - func internalStr(strObj *js.Object) string { var c struct{ str string } js.InternalObject(c).Set("str", strObj) // get string without internalizing @@ -321,10 +286,6 @@ func MapOf(key, elem Type) Type { return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) } -func (t rtype) ptrTo() rtype { - return reflectType(js.Global.Call("$ptrType", jsType(t))) -} - func SliceOf(t Type) Type { return reflectType(js.Global.Call("$sliceType", jsType(t))) } @@ -369,13 +330,11 @@ func makeInt(f flag, bits uint64, t Type) Value { } func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { - if typ.Kind() != abi.Func { + ftyp := typ.FuncType() + if ftyp == nil { panic("reflect: call of MakeFunc with non-Func type") } - t := typ.common() - ftyp := (*funcType)(unsafe.Pointer(t)) - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { args := make([]Value, ftyp.NumIn()) for i := range args { @@ -408,114 +367,6 @@ func loadScalar(p unsafe.Pointer, n uintptr) uintptr { return js.InternalObject(p).Call("$get").Unsafe() } -func makechan(typ rtype, size int) (ch unsafe.Pointer) { - ctyp := (*chanType)(unsafe.Pointer(typ)) - return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.elem), size).Unsafe()) -} - -func makemap(t rtype, cap int) (m unsafe.Pointer) { - return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) -} - -func keyFor(t rtype, key unsafe.Pointer) (*js.Object, string) { - kv := js.InternalObject(key) - if kv.Get("$get") != js.Undefined { - kv = kv.Call("$get") - } - k := jsType(t.Key()).Call("keyFor", kv).String() - return kv, k -} - -func mapaccess(t rtype, m, key unsafe.Pointer) unsafe.Pointer { - _, k := keyFor(t, key) - entry := js.InternalObject(m).Call("get", k) - if entry == js.Undefined { - return nil - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsPtrTo(t.Elem())).Unsafe()) -} - -func mapassign(t rtype, m, key, val unsafe.Pointer) { - kv, k := keyFor(t, key) - jsVal := js.InternalObject(val).Call("$get") - et := t.Elem() - if et.Kind() == abi.Struct { - newVal := jsType(et).Call("zero") - copyStruct(newVal, jsVal, et) - jsVal = newVal - } - entry := js.Global.Get("Object").New() - entry.Set("k", kv) - entry.Set("v", jsVal) - js.InternalObject(m).Call("set", k, entry) -} - -func mapdelete(t rtype, m unsafe.Pointer, key unsafe.Pointer) { - _, k := keyFor(t, key) - js.InternalObject(m).Call("delete", k) -} - -type mapIter struct { - t Type - m *js.Object - keys *js.Object - i int - - // last is the last object the iterator indicates. If this object exists, the functions that return the - // current key or value returns this object, regardless of the current iterator. It is because the current - // iterator might be stale due to key deletion in a loop. - last *js.Object -} - -func (iter *mapIter) skipUntilValidKey() { - for iter.i < iter.keys.Length() { - k := iter.keys.Index(iter.i) - if iter.m.Call("get", k) != js.Undefined { - break - } - // The key is already deleted. Move on the next item. - iter.i++ - } -} - -func mapiterinit(t rtype, m unsafe.Pointer) unsafe.Pointer { - return unsafe.Pointer(&mapIter{t, js.InternalObject(m), js.Global.Get("Array").Call("from", js.InternalObject(m).Call("keys")), 0, nil}) -} - -type TypeEx interface { - Type - Key() Type -} - -func mapiterkey(it unsafe.Pointer) unsafe.Pointer { - iter := (*mapIter)(it) - var kv *js.Object - if iter.last != nil { - kv = iter.last - } else { - iter.skipUntilValidKey() - if iter.i == iter.keys.Length() { - return nil - } - k := iter.keys.Index(iter.i) - kv = iter.m.Call("get", k) - - // Record the key-value pair for later accesses. - iter.last = kv - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsPtrTo(iter.t.(TypeEx).Key())).Unsafe()) -} - -func mapiternext(it unsafe.Pointer) { - iter := (*mapIter)(it) - iter.last = nil - iter.i++ -} - -func maplen(m unsafe.Pointer) int { - return js.InternalObject(m).Get("size").Int() -} - func cvtDirect(v Value, typ Type) Value { srcVal := v.object() if srcVal == jsType(v.typ).Get("nil") { @@ -549,7 +400,11 @@ func cvtDirect(v Value, typ Type) Value { default: panic(&ValueError{"reflect.Convert", k}) } - return Value{typ.common(), unsafe.Pointer(val.Unsafe()), v.flag.ro() | v.flag&flagIndir | flag(typ.Kind())} + return Value{ + typ: typ.common(), + ptr: unsafe.Pointer(val.Unsafe()), + flag: v.flag.ro() | v.flag&flagIndir | flag(typ.Kind()), + } } func Copy(dst, src Value) int { @@ -567,7 +422,7 @@ func Copy(dst, src Value) int { if sk != abi.Array && sk != abi.Slice { stringCopy = sk == abi.String && dst.typ.Elem().Kind() == abi.Uint8 if !stringCopy { - panic(&ValueError{"reflect.Copy", sk}) + panic(&ValueError{Method: "reflect.Copy", Kind: sk}) } } src.mustBeExported() @@ -595,7 +450,7 @@ func Copy(dst, src Value) int { func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe.Pointer) { var prop string if v.typ.Kind() == abi.Interface { - tt := (*interfaceType)(unsafe.Pointer(v.typ)) + tt := v.typ.InterfaceType() if i < 0 || i >= len(tt.methods) { panic("reflect: internal error: invalid method index") } @@ -603,7 +458,7 @@ func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe. if !tt.nameOff(m.name).isExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ))) + t = tt.typeOff(m.typ).FuncType() prop = tt.nameOff(m.name).name() } else { ms := v.typ.exportedMethods() @@ -614,7 +469,7 @@ func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe. if !v.typ.nameOff(m.name).isExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp))) + t = v.typ.typeOff(m.mtyp).FuncType() prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() } rcvr := v.object() @@ -627,7 +482,7 @@ func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe. func valueInterface(v Value) any { if v.flag == 0 { - panic(&ValueError{"reflect.Value.Interface", 0}) + panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) } if v.flag&flagMethod != 0 { @@ -650,6 +505,8 @@ func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { } func methodName() string { + // TODO(grantnelson-wf): methodName returns the name of the calling method, + // assumed to be two stack frames above. return "?FIXME?" } diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 231004e2f..f0d386bff 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -3,8 +3,6 @@ package reflectlite import ( - "unsafe" - "internal/abi" "github.com/gopherjs/gopherjs/js" @@ -28,12 +26,16 @@ func (t rtype) typeOff(off typeOff) *abi.Type { return typeOffList[int(off)] } -func newTypeOff(t rtype) typeOff { +func newTypeOff(t *abi.Type) typeOff { i := len(typeOffList) typeOffList = append(typeOffList, t) return typeOff(i) } +func (t rtype) ptrTo() rtype { + return reflectType(js.Global.Call("$ptrType", jsType(t.Type))) +} + func (t rtype) Comparable() bool { switch t.Kind() { case abi.Func, abi.Slice, abi.Map: @@ -43,7 +45,7 @@ func (t rtype) Comparable() bool { case abi.Struct: for i := 0; i < t.NumField(); i++ { ft := t.Field(i) - if !ft.typ.Comparable() { + if !toRType(ft.Typ).Comparable() { return false } } @@ -51,71 +53,7 @@ func (t rtype) Comparable() bool { return true } -func (t rtype) IsVariadic() bool { - if t.Kind() != abi.Func { - panic("reflect: IsVariadic of non-func type") - } - tt := (*funcType)(unsafe.Pointer(t)) - return tt.outCount&(1<<15) != 0 -} - -func (t rtype) kindType() rtype { - return (rtype)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) -} - -func (t rtype) Key() Type { - if t.Kind() != abi.Map { - panic("reflect: Key of non-map type") - } - tt := (*mapType)(unsafe.Pointer(t)) - return toType(tt.key) -} - -func (t rtype) NumField() int { - if t.Kind() != abi.Struct { - panic("reflect: NumField of non-struct type") - } - tt := (*structType)(unsafe.Pointer(t)) - return len(tt.fields) -} - -func (t rtype) Method(i int) (m Method) { - if t.Kind() == abi.Interface { - tt := (*interfaceType)(unsafe.Pointer(t)) - return tt.Method(i) - } - methods := t.exportedMethods() - if i < 0 || i >= len(methods) { - panic("reflect: Method index out of range") - } - p := methods[i] - pname := t.nameOff(p.name) - m.Name = pname.name() - fl := flag(Func) - mtyp := t.typeOff(p.mtyp) - ft := (*funcType)(unsafe.Pointer(mtyp)) - in := make([]Type, 0, 1+len(ft.in())) - in = append(in, t) - for _, arg := range ft.in() { - in = append(in, arg) - } - out := make([]Type, 0, len(ft.out())) - for _, ret := range ft.out() { - out = append(out, ret) - } - mt := FuncOf(in, out, ft.IsVariadic()) - m.Type = mt - prop := js.Global.Call("$methodSet", js.InternalObject(t).Get(idJsType)).Index(i).Get("prop").String() - fn := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - rcvr := arguments[0] - return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) - }) - m.Func = Value{ - typ: toAbiType(mt), - ptr: unsafe.Pointer(fn.Unsafe()), - flag: fl, - } +//gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. +type name struct{} - m.Index = i - return m -} +func pkgPath(n abi.Name) string { return "" } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index fe1bf3d27..440b3bef4 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -491,39 +491,6 @@ func (v Value) NumField() int { return len(tt.Fields) } -// MapKeys returns a slice containing all the keys present in the map, -// in unspecified order. -// It panics if v's Kind is not Map. -// It returns an empty slice if v represents a nil map. -func (v Value) MapKeys() []Value { - v.mustBe(abi.Map) - tt := (*mapType)(unsafe.Pointer(v.typ)) - keyType := tt.Key - - fl := v.flag.ro() | flag(keyType.Kind()) - - m := v.pointer() - mlen := int(0) - if m != nil { - mlen = maplen(m) - } - it := mapiterinit(toRType(v.typ), m) - a := make([]Value, mlen) - var i int - for i = 0; i < len(a); i++ { - key := mapiterkey(it) - if key == nil { - // Someone deleted an entry from the map since we - // called maplen above. It's a data race, but nothing - // we can do about it. - break - } - a[i] = copyVal(toRType(keyType), fl, key) - mapiternext(it) - } - return a[:i] -} - // MapIndex returns the value associated with key in the map v. // It panics if v's Kind is not Map. // It returns the zero Value if key is not found in the map or if v represents a nil map. From aa74f4528263423ab87e9bae7f3474df72d9a896 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 3 Feb 2026 12:27:08 -0700 Subject: [PATCH 09/48] adding in check overrides to help confirm changes --- build/build.go | 76 ++- build/build_test.go | 3 +- compiler/astutil/astutil.go | 32 ++ compiler/natives/src/internal/abi/type.go | 261 +++++++++- .../src/internal/reflectlite/reflectlite.go | 455 +++-------------- .../natives/src/internal/reflectlite/type.go | 52 +- .../natives/src/internal/reflectlite/utils.go | 20 +- .../natives/src/internal/reflectlite/value.go | 459 +----------------- 8 files changed, 464 insertions(+), 894 deletions(-) diff --git a/build/build.go b/build/build.go index f218bfb5d..34d20da55 100644 --- a/build/build.go +++ b/build/build.go @@ -124,13 +124,26 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // overrideInfo is used by parseAndAugment methods to manage // directives and how the overlay and original are merged. type overrideInfo struct { + // replace indicates that the original code is expected to exist. + // If the original code is not present, then error to let us know + // the original code has been removed or renamed. + // This will be set to false once the original code has been found. + replace bool + + // new indicates that the override code is expected to be new. + // If the original code is present, then error to let us know + // that we are overriding something that we did not expect to replace. + // This lets us know of new or renamed code in the original that happens + // to collide with our overrides. + new bool + // keepOriginal indicates that the original code should be kept // but the identifier will be prefixed by `_gopherjs_original_foo`. // If false the original code is removed. keepOriginal bool - // purgeMethods indicates that this info is for a type and - // if a method has this type as a receiver should also be removed. + // purgeMethods indicates that this info is for a type and if a method has + // this type as a receiver then the method should also be removed. // If the method is defined in the overlays and therefore has its // own overrides, this will be ignored. purgeMethods bool @@ -165,8 +178,11 @@ type overrideInfo struct { // to match the overridden function signature. This allows the receiver, // type parameters, parameter, and return values to be modified as needed. // - Otherwise for identifiers that exist in the original and the overrides, -// the original is removed. +// the original is removed. Use `gopherjs:replace` to ensure that the +// original existed so that a replace is made. // - New identifiers that don't exist in original package get added. +// Use `gopherjs:new` to ensure that the identifier is new and there was +// no original code for it. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []incjs.File, error) { jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet) @@ -186,8 +202,13 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke } if len(overrides) > 0 { + found := make(map[string]struct{}, len(overrides)) for _, file := range originalFiles { - augmentOriginalFile(file, overrides) + augmentOriginalFile(file, overrides, found) + } + err := checkOverrides(overrides, found, pkg.ImportPath) + if err != nil { + return nil, nil, err } } @@ -284,12 +305,17 @@ func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { anyChange := false for i, decl := range file.Decls { + replaceDecl := astutil.DirectiveReplace(decl) + newDecl := astutil.DirectiveNew(decl) purgeDecl := astutil.Purge(decl) + switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) oi := overrideInfo{ keepOriginal: astutil.KeepOriginal(d), + replace: replaceDecl, + new: newDecl, } if astutil.OverrideSignature(d) { oi.overrideSignature = d @@ -299,14 +325,21 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { case *ast.GenDecl: for j, spec := range d.Specs { purgeSpec := purgeDecl || astutil.Purge(spec) + replaceSpec := replaceDecl || astutil.DirectiveReplace(spec) + newSpec := newDecl || astutil.DirectiveNew(spec) switch s := spec.(type) { case *ast.TypeSpec: overrides[s.Name.Name] = overrideInfo{ purgeMethods: purgeSpec, + replace: replaceSpec, + new: newSpec, } case *ast.ValueSpec: for _, name := range s.Names { - overrides[name.Name] = overrideInfo{} + overrides[name.Name] = overrideInfo{ + replace: replaceSpec, + new: newSpec, + } } } if purgeSpec { @@ -346,12 +379,14 @@ func augmentOriginalImports(importPath string, file *ast.File) { // augmentOriginalFile is the part of parseAndAugment that processes an // original file AST to augment the source code using the overrides from // the overlay files. -func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { +func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, found map[string]struct{}) { anyChange := false for i, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: - if info, ok := overrides[astutil.FuncKey(d)]; ok { + funcKey := astutil.FuncKey(d) + if info, ok := overrides[funcKey]; ok { + found[funcKey] = struct{}{} anyChange = true removeFunc := true if info.keepOriginal { @@ -373,6 +408,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 { // check if the receiver has been purged, if so, remove the method too. if info, ok := overrides[recvKey]; ok && info.purgeMethods { + found[recvKey] = struct{}{} anyChange = true file.Decls[i] = nil } @@ -382,6 +418,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { switch s := spec.(type) { case *ast.TypeSpec: if _, ok := overrides[s.Name.Name]; ok { + found[s.Name.Name] = struct{}{} anyChange = true d.Specs[j] = nil } @@ -395,6 +432,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { // to be run, add the call into the overlay. for k, name := range s.Names { if _, ok := overrides[name.Name]; ok { + found[name.Name] = struct{}{} anyChange = true s.Names[k] = nil s.Values[k] = nil @@ -410,6 +448,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { nameRemoved := false for _, name := range s.Names { if _, ok := overrides[name.Name]; ok { + found[name.Name] = struct{}{} nameRemoved = true name.Name = `_` } @@ -438,6 +477,29 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } } +// checkOverrides performs a final check of the overrides to ensure that +// all overrides that were expected to be found were found and all overrides +// that were not expected to be found were not found. +func checkOverrides(overrides map[string]overrideInfo, found map[string]struct{}, pkgPath string) error { + el := errlist.ErrorList{} + for name, info := range overrides { + _, wasFound := found[name] + switch { + case wasFound && info.new: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was found, but override had `new` directive", pkgPath, name)) + case !wasFound && info.replace: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `replace` directive", pkgPath, name)) + case !wasFound && info.keepOriginal: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `keep-original` directive", pkgPath, name)) + case !wasFound && info.purgeMethods: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `purge` directive", pkgPath, name)) + case !wasFound && info.overrideSignature != nil: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `override-signature` directive", pkgPath, name)) + } + } + return el.ErrOrNil() +} + // isOnlyImports determines if this file is empty except for imports. func isOnlyImports(file *ast.File) bool { for _, decl := range file.Decls { diff --git a/build/build_test.go b/build/build_test.go index a3e4800d9..949d7cabe 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -724,7 +724,8 @@ func TestOriginalAugmentation(t *testing.T) { fileSrc := f.Parse("test.go", pkgName+test.src) augmentOriginalImports(importPath, fileSrc) - augmentOriginalFile(fileSrc, test.info) + found := make(map[string]struct{}) + augmentOriginalFile(fileSrc, test.info, found) pruneImports(fileSrc) got := srctesting.Format(t, f.FileSet, fileSrc) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 5f259bfff..ba4b5c940 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -133,6 +133,28 @@ func FuncReceiverKey(d *ast.FuncDecl) string { } } +// DirectiveReplace returns true if gopherjs:replace directive is present +// on a struct, interface, type, variable, constant, or function. +// +// `//gopherjs:replace` is a GopherJS-specific directive, which can be +// applied in native overlays and will instruct the augmentation logic to +// ensure that the original code is present and has not been removed nor renamed, +// otherwise an error will be raised. +func DirectiveReplace(d ast.Node) bool { + return hasDirective(d, `replace`) +} + +// DirectiveNew returns true if gopherjs:new directive is +// present on a struct, interface, type, variable, constant, or function. +// +// `//gopherjs:new` is a GopherJS-specific directive, which can be +// applied in native overlays and will instruct the augmentation logic to +// ensure that the original code is not present so that this code does not +// override any original code, otherwise an error will be raised. +func DirectiveNew(d ast.Node) bool { + return hasDirective(d, `new`) +} + // KeepOriginal returns true if gopherjs:keep-original directive is present // before a function decl. // @@ -141,6 +163,9 @@ func FuncReceiverKey(d *ast.FuncDecl) string { // logic to expose the original function such that it can be called. For a // function in the original called `foo`, it will be accessible by the name // `_gopherjs_original_foo`. +// +// This will also ensure that the original function exists and hasn't been +// removed or renamed, otherwise an error will be raised. func KeepOriginal(d *ast.FuncDecl) bool { return hasDirective(d, `keep-original`) } @@ -156,6 +181,9 @@ func KeepOriginal(d *ast.FuncDecl) bool { // fully supported). It should be used with caution since it may remove needed // dependencies. If a type is purged, all methods using that type as // a receiver will also be purged. +// +// This will also ensure that the original code exists and hasn't been +// removed or renamed, otherwise an error will be raised. func Purge(d ast.Node) bool { return hasDirective(d, `purge`) } @@ -167,6 +195,7 @@ func Purge(d ast.Node) bool { // be applied in native overlays and will instruct the augmentation logic to // replace the original function signature which has the same FuncKey with the // signature defined in the native overlays. +// // This directive can be used to remove generics from a function signature or // to replace a receiver of a function with another one. The given native // overlay function will be removed, so no method body is needed in the overlay. @@ -174,6 +203,9 @@ func Purge(d ast.Node) bool { // The new signature may not contain types which require a new import since // the imports will not be automatically added when needed, only removed. // Use a type alias in the overlay to deal manage imports. +// +// This will also ensure that the original code exists and hasn't been +// removed or renamed, otherwise an error will be raised. func OverrideSignature(d *ast.FuncDecl) bool { return hasDirective(d, `override-signature`) } diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index adcbe0baf..aeaec25af 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -1,43 +1,69 @@ package abi +import ( + "unsafe" + + "github.com/gopherjs/gopherjs/js" +) + +//gopherjs:new +const ( + idJsType = "_jsType" + idReflectType = "_reflectType" + idKindType = "kindType" + idRtype = "_rtype" +) + +//gopherjs:new var UncommonTypeMap = make(map[*Type]*UncommonType) +//gopherjs:replace func (t *Type) Uncommon() *UncommonType { return UncommonTypeMap[t] } +//gopherjs:replace type UncommonType struct { - PkgPath NameOff // import path - Mcount uint16 // method count - Xcount uint16 // exported method count + PkgPath NameOff // import path + Mcount uint16 // method count + Xcount uint16 // exported method count + + // GOPHERJS: Added access to methods Methods_ []Method } +//gopherjs:replace func (t *UncommonType) Methods() []Method { return t.Methods_ } +//gopherjs:replace func (t *UncommonType) ExportedMethods() []Method { return t.Methods_[:t.Xcount:t.Xcount] } +//gopherjs:replace type FuncType struct { Type `reflect:"func"` InCount uint16 OutCount uint16 + // GOPHERJS: Add references to in and out args In_ []*Type Out_ []*Type } +//gopherjs:replace func (t *FuncType) InSlice() []*Type { return t.In_ } +//gopherjs:replace func (t *FuncType) OutSlice() []*Type { return t.Out_ } +//gopherjs:replace type Name struct { name string tag string @@ -45,12 +71,23 @@ type Name struct { embedded bool } +//gopherjs:replace func (n Name) IsExported() bool { return n.exported } -func (n Name) HasTag() bool { return len(n.tag) > 0 } + +//gopherjs:replace +func (n Name) HasTag() bool { return len(n.tag) > 0 } + +//gopherjs:replace func (n Name) IsEmbedded() bool { return n.embedded } -func (n Name) IsBlank() bool { return n.Name() == `_` } -func (n Name) Name() string { return n.name } -func (n Name) Tag() string { return n.tag } + +//gopherjs:replace +func (n Name) IsBlank() bool { return n.Name() == `_` } + +//gopherjs:replace +func (n Name) Name() string { return n.name } + +//gopherjs:replace +func (n Name) Tag() string { return n.tag } //gopherjs:purge Used for byte encoding of name, not used in JS func writeVarint(buf []byte, n int) int @@ -64,6 +101,7 @@ func (n Name) Data(off int) *byte //gopherjs:purge Used for byte encoding of name, not used in JS func (n Name) ReadVarint(off int) (int, int) +//gopherjs:replace func NewName(n, tag string, exported, embedded bool) Name { return Name{ name: n, @@ -72,3 +110,212 @@ func NewName(n, tag string, exported, embedded bool) Name { embedded: embedded, } } + +//gopherjs:new +var nameOffList []Name + +//gopherjs:new +func (typ *Type) NameOff(off NameOff) Name { + return nameOffList[int(off)] +} + +//gopherjs:new +func newNameOff(n Name) NameOff { + i := len(nameOffList) + nameOffList = append(nameOffList, n) + return NameOff(i) +} + +//gopherjs:new +var typeOffList []*Type + +//gopherjs:new +func (typ *Type) TypeOff(off TypeOff) *Type { + return typeOffList[int(off)] +} + +//gopherjs:new +func newTypeOff(t *Type) TypeOff { + i := len(typeOffList) + typeOffList = append(typeOffList, t) + return TypeOff(i) +} + +//gopherjs:new +func (typ *Type) JsType() *js.Object { + return js.InternalObject(typ).Get(idJsType) +} + +//gopherjs:new +func (typ *Type) PtrTo() *Type { + return ReflectType(js.Global.Call("$ptrType", typ.JsType())) +} + +//gopherjs:new +func (typ *Type) JsPtrTo() *js.Object { + return typ.PtrTo().JsType() +} + +//gopherjs:new +func (typ *Type) IsWrapped() bool { + return typ.JsType().Get("wrapped").Bool() +} + +//gopherjs:new +func internalStr(strObj *js.Object) string { + var c struct{ str string } + js.InternalObject(c).Set("str", strObj) // get string without internalizing + return c.str +} + +//gopherjs:new +func ReflectType(typ *js.Object) *Type { + if typ.Get(idReflectType) == js.Undefined { + abiTyp := &Type{ + Size_: uintptr(typ.Get("size").Int()), + Kind_: uint8(typ.Get("kind").Int()), + Str: newNameOff(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + } + js.InternalObject(abiTyp).Set(idJsType, typ) + typ.Set(idReflectType, js.InternalObject(abiTyp)) + + methodSet := js.Global.Call("$methodSet", typ) + if methodSet.Length() != 0 || typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagUncommon + if typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagNamed + } + var reflectMethods []Method + for i := 0; i < methodSet.Length(); i++ { // Exported methods first. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if !exported { + continue + } + reflectMethods = append(reflectMethods, Method{ + Name: newNameOff(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: newTypeOff(ReflectType(m.Get("typ"))), + }) + } + xcount := uint16(len(reflectMethods)) + for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if exported { + continue + } + reflectMethods = append(reflectMethods, Method{ + Name: newNameOff(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: newTypeOff(ReflectType(m.Get("typ"))), + }) + } + ut := &UncommonType{ + PkgPath: newNameOff(NewName(internalStr(typ.Get("pkg")), "", false, false)), + Mcount: uint16(methodSet.Length()), + Xcount: xcount, + Methods_: reflectMethods, + } + UncommonTypeMap[abiTyp] = ut + js.InternalObject(ut).Set(idJsType, typ) + } + + switch abiTyp.Kind() { + case Array: + setKindType(abiTyp, &ArrayType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + Len: uintptr(typ.Get("len").Int()), + }) + case Chan: + dir := BothDir + if typ.Get("sendOnly").Bool() { + dir = SendDir + } + if typ.Get("recvOnly").Bool() { + dir = RecvDir + } + setKindType(abiTyp, &ChanType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + Dir: dir, + }) + case Func: + params := typ.Get("params") + in := make([]*Type, params.Length()) + for i := range in { + in[i] = ReflectType(params.Index(i)) + } + results := typ.Get("results") + out := make([]*Type, results.Length()) + for i := range out { + out[i] = ReflectType(results.Index(i)) + } + outCount := uint16(results.Length()) + if typ.Get("variadic").Bool() { + outCount |= 1 << 15 + } + setKindType(abiTyp, &FuncType{ + Type: *abiTyp, + InCount: uint16(params.Length()), + OutCount: outCount, + In_: in, + Out_: out, + }) + case Interface: + methods := typ.Get("methods") + imethods := make([]Imethod, methods.Length()) + for i := range imethods { + m := methods.Index(i) + imethods[i] = Imethod{ + Name: newNameOff(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), + Typ: newTypeOff(ReflectType(m.Get("typ"))), + } + } + setKindType(abiTyp, &InterfaceType{ + Type: *abiTyp, + PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), + Methods: imethods, + }) + case Map: + setKindType(abiTyp, &MapType{ + Type: *abiTyp, + Key: ReflectType(typ.Get("key")), + Elem: ReflectType(typ.Get("elem")), + }) + case Pointer: + setKindType(abiTyp, &PtrType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + }) + case Slice: + setKindType(abiTyp, &SliceType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + }) + case Struct: + fields := typ.Get("fields") + reflectFields := make([]StructField, fields.Length()) + for i := range reflectFields { + f := fields.Index(i) + reflectFields[i] = StructField{ + Name: NewName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + Typ: ReflectType(f.Get("typ")), + Offset: uintptr(i), + } + } + setKindType(abiTyp, &StructType{ + Type: *abiTyp, + PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), + Fields: reflectFields, + }) + } + } + + return (*Type)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) +} + +//gopherjs:new +func setKindType(abiTyp *Type, kindType any) { + js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) + js.InternalObject(kindType).Set(idRtype, js.InternalObject(abiTyp)) +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 8ad1668f5..416aba5c7 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -24,218 +24,32 @@ func init() { used(ptrType{}) used(sliceType{}) used(structType{}) - used(imethod{}) initialized = true uint8Type = TypeOf(uint8(0)).(rtype) // set for real } +// GOPHERJS: For some reason they left mapType and aliased the rest but never +// used mapType so this is an alias to override the left over refactor cruft. +type mapType = abi.MapType + var uint8Type rtype var ( idJsType = "_jsType" idReflectType = "_reflectType" + idKindType = "kindType" idRtype = "_rtype" ) -func jsType(typ *abi.Type) *js.Object { - return js.InternalObject(typ).Get(idJsType) -} - -func jsPtrTo(typ *abi.Type) *js.Object { - return jsType(toAbiType(PtrTo(toRType(typ)))) -} - -func toAbiType(typ Type) *abi.Type { - return typ.(rtype).Type -} - -func reflectType(typ *js.Object) rtype { - if typ.Get(idReflectType) == js.Undefined { - rt := rtype{ - Type: &abi.Type{ - Size: uintptr(typ.Get("size").Int()), - Kind: uint8(typ.Get("kind").Int()), - Str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), - }, - } - js.InternalObject(rt).Set(idJsType, typ) - typ.Set(idReflectType, js.InternalObject(rt)) - - methodSet := js.Global.Call("$methodSet", typ) - if methodSet.Length() != 0 || typ.Get("named").Bool() { - rt.TFlag |= abi.TFlagUncommon - if typ.Get("named").Bool() { - rt.TFlag |= abi.TFlagNamed - } - var reflectMethods []abi.Method - for i := 0; i < methodSet.Length(); i++ { // Exported methods first. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if !exported { - continue - } - reflectMethods = append(reflectMethods, abi.Method{ - Name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - xcount := uint16(len(reflectMethods)) - for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if exported { - continue - } - reflectMethods = append(reflectMethods, abi.Method{ - Name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - ut := &abi.UncommonType{ - PkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false, false)), - Mcount: uint16(methodSet.Length()), - Xcount: xcount, - Methods_: reflectMethods, - } - abi.UncommonTypeMap[rt] = ut - js.InternalObject(ut).Set(idJsType, typ) - } - - switch rt.Kind() { - case abi.Array: - setKindType(rt, &arrayType{ - elem: reflectType(typ.Get("elem")), - len: uintptr(typ.Get("len").Int()), - }) - case abi.Chan: - dir := BothDir - if typ.Get("sendOnly").Bool() { - dir = SendDir - } - if typ.Get("recvOnly").Bool() { - dir = RecvDir - } - setKindType(rt, &chanType{ - elem: reflectType(typ.Get("elem")), - dir: uintptr(dir), - }) - case abi.Func: - params := typ.Get("params") - in := make([]rtype, params.Length()) - for i := range in { - in[i] = reflectType(params.Index(i)) - } - results := typ.Get("results") - out := make([]rtype, results.Length()) - for i := range out { - out[i] = reflectType(results.Index(i)) - } - outCount := uint16(results.Length()) - if typ.Get("variadic").Bool() { - outCount |= 1 << 15 - } - setKindType(rt, &abi.FuncType{ - Type: *rt, - InCount: uint16(params.Length()), - OutCount: outCount, - In_: in, - Out_: out, - }) - case abi.Interface: - methods := typ.Get("methods") - imethods := make([]imethod, methods.Length()) - for i := range imethods { - m := methods.Index(i) - imethods[i] = imethod{ - name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), - typ: newTypeOff(reflectType(m.Get("typ"))), - } - } - setKindType(rt, &interfaceType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), - methods: imethods, - }) - case abi.Map: - setKindType(rt, &mapType{ - key: reflectType(typ.Get("key")), - elem: reflectType(typ.Get("elem")), - }) - case abi.Pointer: - setKindType(rt, &ptrType{ - elem: reflectType(typ.Get("elem")), - }) - case abi.Slice: - setKindType(rt, &sliceType{ - elem: reflectType(typ.Get("elem")), - }) - case abi.Struct: - fields := typ.Get("fields") - reflectFields := make([]abi.StructField, fields.Length()) - for i := range reflectFields { - f := fields.Index(i) - reflectFields[i] = abi.StructField{ - Name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - Typ: reflectType(f.Get("typ")), - Offset: uintptr(i), - } - } - setKindType(rt, &structType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), - fields: reflectFields, - }) - } - } - - return (rtype)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) -} - -func internalStr(strObj *js.Object) string { - var c struct{ str string } - js.InternalObject(c).Set("str", strObj) // get string without internalizing - return c.str -} - -func isWrapped(typ *abi.Type) bool { - return jsType(typ).Get("wrapped").Bool() -} - func copyStruct(dst, src *js.Object, typ *abi.Type) { - fields := jsType(typ).Get("fields") + fields := typ.JsType().Get("fields") for i := 0; i < fields.Length(); i++ { prop := fields.Index(i).Get("prop").String() dst.Set(prop, src.Get(prop)) } } -func makeValue(t *abi.Type, v *js.Object, fl flag) Value { - rt := t.common() - switch t.Kind() { - case abi.Array, abi.Struct, abi.Pointer: - return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} - } - return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} -} - -func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != abi.Slice { - panic("reflect.MakeSlice of non-slice type") - } - if len < 0 { - panic("reflect.MakeSlice: negative len") - } - if cap < 0 { - panic("reflect.MakeSlice: negative cap") - } - if len > cap { - panic("reflect.MakeSlice: len > cap") - } - - return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) -} - func TypeOf(i any) Type { if !initialized { // avoid error of uint8Type return &rtype{} @@ -243,122 +57,43 @@ func TypeOf(i any) Type { if i == nil { return nil } - return reflectType(js.InternalObject(i).Get("constructor")) + return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) } func ValueOf(i any) Value { if i == nil { return Value{} } - return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) + return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) } -func ArrayOf(count int, elem Type) Type { - return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) -} - -func ChanOf(dir ChanDir, t Type) Type { - return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) -} - -func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != abi.Slice) { - panic("reflect.FuncOf: last arg of variadic func must be slice") - } - - jsIn := make([]*js.Object, len(in)) - for i, v := range in { - jsIn[i] = jsType(v) - } - jsOut := make([]*js.Object, len(out)) - for i, v := range out { - jsOut[i] = jsType(v) +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{ + typ: t, + ptr: unsafe.Pointer(v.Unsafe()), + flag: fl | flag(t.Kind()), + } } - return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) -} - -func MapOf(key, elem Type) Type { - switch key.Kind() { - case abi.Func, abi.Map, abi.Slice: - panic("reflect.MapOf: invalid key type " + key.String()) + return Value{ + typ: t, + ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), + flag: fl | flag(t.Kind()) | flagIndir, } - - return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) -} - -func SliceOf(t Type) Type { - return reflectType(js.Global.Call("$sliceType", jsType(t))) -} - -func Zero(typ Type) Value { - return makeValue(typ, jsType(typ).Call("zero"), 0) } func unsafe_New(typ *abi.Type) unsafe.Pointer { switch typ.Kind() { case abi.Struct: - return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) + return unsafe.Pointer(typ.JsType().Get("ptr").New().Unsafe()) case abi.Array: - return unsafe.Pointer(jsType(typ).Call("zero").Unsafe()) + return unsafe.Pointer(typ.JsType().Call("zero").Unsafe()) default: - return unsafe.Pointer(js.Global.Call("$newDataPointer", jsType(typ).Call("zero"), jsType(typ.ptrTo())).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", typ.JsType().Call("zero"), typ.JsPtrTo()).Unsafe()) } } -func makeInt(f flag, bits uint64, t Type) Value { - typ := t.common() - ptr := unsafe_New(typ) - switch typ.Kind() { - case abi.Int8: - *(*int8)(ptr) = int8(bits) - case abi.Int16: - *(*int16)(ptr) = int16(bits) - case abi.Int, abi.Int32: - *(*int32)(ptr) = int32(bits) - case abi.Int64: - *(*int64)(ptr) = int64(bits) - case abi.Uint8: - *(*uint8)(ptr) = uint8(bits) - case abi.Uint16: - *(*uint16)(ptr) = uint16(bits) - case abi.Uint, abi.Uint32, abi.Uintptr: - *(*uint32)(ptr) = uint32(bits) - case abi.Uint64: - *(*uint64)(ptr) = uint64(bits) - } - return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} -} - -func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { - ftyp := typ.FuncType() - if ftyp == nil { - panic("reflect: call of MakeFunc with non-Func type") - } - - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - args := make([]Value, ftyp.NumIn()) - for i := range args { - argType := ftyp.In(i).common() - args[i] = makeValue(argType, arguments[i], 0) - } - resultsSlice := fn(args) - switch ftyp.NumOut() { - case 0: - return nil - case 1: - return resultsSlice[0].object() - default: - results := js.Global.Get("Array").New(ftyp.NumOut()) - for i, r := range resultsSlice { - results.SetIndex(i, r.object()) - } - return results - } - }) - - return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} -} - func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) } @@ -367,99 +102,19 @@ func loadScalar(p unsafe.Pointer, n uintptr) uintptr { return js.InternalObject(p).Call("$get").Unsafe() } -func cvtDirect(v Value, typ Type) Value { - srcVal := v.object() - if srcVal == jsType(v.typ).Get("nil") { - return makeValue(typ, jsType(typ).Get("nil"), v.flag) - } - - var val *js.Object - switch k := typ.Kind(); k { - case abi.Slice: - slice := jsType(typ).New(srcVal.Get("$array")) - slice.Set("$offset", srcVal.Get("$offset")) - slice.Set("$length", srcVal.Get("$length")) - slice.Set("$capacity", srcVal.Get("$capacity")) - val = js.Global.Call("$newDataPointer", slice, jsPtrTo(typ)) - case abi.Pointer: - if typ.Elem().Kind() == abi.Struct { - if typ.Elem() == v.typ.Elem() { - val = srcVal - break - } - val = jsType(typ).New() - copyStruct(val, srcVal, typ.Elem()) - break - } - val = jsType(typ).New(srcVal.Get("$get"), srcVal.Get("$set")) - case abi.Struct: - val = jsType(typ).Get("ptr").New() - copyStruct(val, srcVal, typ) - case abi.Array, abi.Bool, abi.Chan, abi.Func, abi.Interface, abi.Map, abi.String: - val = js.InternalObject(v.ptr) - default: - panic(&ValueError{"reflect.Convert", k}) - } - return Value{ - typ: typ.common(), - ptr: unsafe.Pointer(val.Unsafe()), - flag: v.flag.ro() | v.flag&flagIndir | flag(typ.Kind()), - } -} - -func Copy(dst, src Value) int { - dk := dst.kind() - if dk != abi.Array && dk != abi.Slice { - panic(&ValueError{"reflect.Copy", dk}) - } - if dk == abi.Array { - dst.mustBeAssignable() - } - dst.mustBeExported() - - sk := src.kind() - var stringCopy bool - if sk != abi.Array && sk != abi.Slice { - stringCopy = sk == abi.String && dst.typ.Elem().Kind() == abi.Uint8 - if !stringCopy { - panic(&ValueError{Method: "reflect.Copy", Kind: sk}) - } - } - src.mustBeExported() - - if !stringCopy { - typesMustMatch("reflect.Copy", dst.typ.Elem(), src.typ.Elem()) - } - - dstVal := dst.object() - if dk == abi.Array { - dstVal = jsType(SliceOf(dst.typ.Elem())).New(dstVal) - } - - srcVal := src.object() - if sk == abi.Array { - srcVal = jsType(SliceOf(src.typ.Elem())).New(srcVal) - } - - if stringCopy { - return js.Global.Call("$copyString", dstVal, srcVal).Int() - } - return js.Global.Call("$copySlice", dstVal, srcVal).Int() -} - func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe.Pointer) { var prop string if v.typ.Kind() == abi.Interface { tt := v.typ.InterfaceType() - if i < 0 || i >= len(tt.methods) { + if i < 0 || i >= len(tt.Methods) { panic("reflect: internal error: invalid method index") } - m := &tt.methods[i] - if !tt.nameOff(m.name).isExported() { + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = tt.typeOff(m.typ).FuncType() - prop = tt.nameOff(m.name).name() + t = tt.TypeOff(m.Typ).FuncType() + prop = tt.NameOff(m.Name).Name() } else { ms := v.typ.exportedMethods() if uint(i) >= uint(len(ms)) { @@ -470,11 +125,11 @@ func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe. panic("reflect: " + op + " of unexported method") } t = v.typ.typeOff(m.mtyp).FuncType() - prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() + prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() } rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ.IsWrapped() { + rcvr = v.typ.JsType().New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) return @@ -489,13 +144,13 @@ func valueInterface(v Value) any { v = makeMethodValue("Interface", v) } - if isWrapped(v.typ) { + if v.typ.IsWrapped() { if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { - cv := jsType(v.typ).Call("zero") + cv := v.typ.JsType().Call("zero") copyStruct(cv, v.object(), v.typ) - return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) + return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) } - return any(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) + return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) } return any(unsafe.Pointer(v.object().Unsafe())) } @@ -517,20 +172,24 @@ func makeMethodValue(op string, v Value) Value { _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ.IsWrapped() { + rcvr = v.typ.JsType().New(rcvr) } fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { return js.InternalObject(fn).Call("apply", rcvr, arguments) }) - return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} + return Value{ + typ: v.Type().common(), + ptr: unsafe.Pointer(fv.Unsafe()), + flag: v.flag.ro() | flag(Func), + } } -var jsObjectPtr = reflectType(js.Global.Get("$jsObjectPtr")) +var jsObjectPtr = abi.ReflectType(js.Global.Get("$jsObjectPtr")) func wrapJsObject(typ Type, val *js.Object) *js.Object { if typ == jsObjectPtr { - return jsType(jsObjectPtr).New(val) + return jsObjectPtr.JsType().New(val) } return val } @@ -588,10 +247,22 @@ func getJsTag(tag string) string { return "" } +func toAbiType(typ Type) *abi.Type { + return typ.(rtype).common() +} + // PtrTo returns the pointer type with element t. // For example, if t represents type Foo, PtrTo(t) represents *Foo. func PtrTo(t Type) Type { - return t.(rtype).ptrTo() + return toRtype(toAbiType(t).PtrTo()) +} + +func jsType(t Type) *js.Object { + return toAbiType(t).JsType() +} + +func jsPtrTo(t Type) *js.Object { + return toAbiType(t).JsPtrTo() } // copyVal returns a Value containing the map key or value at ptr, @@ -602,9 +273,17 @@ func copyVal(typ rtype, fl flag, ptr unsafe.Pointer) Value { // won't change the underlying value. c := unsafe_New(typ) typedmemmove(typ, c, ptr) - return Value{typ, c, fl | flagIndir} + return Value{ + typ: typ, + ptr: c, + flag: fl | flagIndir, + } + } + return Value{ + typ: typ, + ptr: *(*unsafe.Pointer)(ptr), + flag: fl, } - return Value{typ, *(*unsafe.Pointer)(ptr), fl} } var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index f0d386bff..9a9da6276 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -3,39 +3,11 @@ package reflectlite import ( - "internal/abi" + "unsafe" - "github.com/gopherjs/gopherjs/js" + "internal/abi" ) -var nameOffList []abi.Name - -func (t rtype) nameOff(off nameOff) abi.Name { - return nameOffList[int(off)] -} - -func newNameOff(n abi.Name) nameOff { - i := len(nameOffList) - nameOffList = append(nameOffList, n) - return nameOff(i) -} - -var typeOffList []*abi.Type - -func (t rtype) typeOff(off typeOff) *abi.Type { - return typeOffList[int(off)] -} - -func newTypeOff(t *abi.Type) typeOff { - i := len(typeOffList) - typeOffList = append(typeOffList, t) - return typeOff(i) -} - -func (t rtype) ptrTo() rtype { - return reflectType(js.Global.Call("$ptrType", jsType(t.Type))) -} - func (t rtype) Comparable() bool { switch t.Kind() { case abi.Func, abi.Slice, abi.Map: @@ -43,9 +15,9 @@ func (t rtype) Comparable() bool { case abi.Array: return t.Elem().Comparable() case abi.Struct: - for i := 0; i < t.NumField(); i++ { - ft := t.Field(i) - if !toRType(ft.Typ).Comparable() { + st := t.StructType() + for i := 0; i < len(st.Fields); i++ { + if !toRType(st.Fields[i].Typ).Comparable() { return false } } @@ -56,4 +28,18 @@ func (t rtype) Comparable() bool { //gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. type name struct{} +func (t rtype) nameOff(off nameOff) abi.Name { + return t.NameOff(off) +} + +func (t rtype) typeOff(off typeOff) *abi.Type { + return t.TypeOff(off) +} + func pkgPath(n abi.Name) string { return "" } + +//gopherjs:purge Unused function because of nameOffList in internal/abi overrides +func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer + +//gopherjs:purge Unused function because of typeOffList in internal/abi overrides +func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer diff --git a/compiler/natives/src/internal/reflectlite/utils.go b/compiler/natives/src/internal/reflectlite/utils.go index c832bd4cf..e8354f524 100644 --- a/compiler/natives/src/internal/reflectlite/utils.go +++ b/compiler/natives/src/internal/reflectlite/utils.go @@ -4,14 +4,6 @@ package reflectlite import "unsafe" -type ChanDir int - -const ( - RecvDir ChanDir = 1 << iota // <-chan - SendDir // chan<- - BothDir = RecvDir | SendDir // chan -) - type errorString struct { s string } @@ -36,7 +28,7 @@ func unquote(s string) (string, error) { } // Method represents a single method. -type Method struct { +type Method struct { // TODO(grantnelson-wf): Should this be moved to ABI? // Name is the method name. // PkgPath is the package path that qualifies a lower case (unexported) // method name. It is empty for upper case (exported) method names. @@ -79,16 +71,6 @@ func (f flag) mustBe(expected Kind) { } } -// A StructTag is the tag string in a struct field. -// -// By convention, tag strings are a concatenation of -// optionally space-separated key:"value" pairs. -// Each key is a non-empty string consisting of non-control -// characters other than space (U+0020 ' '), quote (U+0022 '"'), -// and colon (U+003A ':'). Each value is quoted using U+0022 '"' -// characters and Go string literal syntax. -type StructTag string - func typesMustMatch(what string, t1, t2 Type) { if t1 != t2 { panic(what + ": " + t1.String() + " != " + t2.String()) diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 440b3bef4..76acc29f1 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -16,18 +16,19 @@ func (v Value) object() *js.Object { } if v.flag&flagIndir != 0 { val := js.InternalObject(v.ptr).Call("$get") - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { + jsTyp := v.typ.JsType() + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { switch v.typ.Kind() { case abi.Uint64, abi.Int64: - val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) + val = jsTyp.New(val.Get("$high"), val.Get("$low")) case abi.Complex64, abi.Complex128: - val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) + val = jsTyp.New(val.Get("$real"), val.Get("$imag")) case abi.Slice: if val == val.Get("constructor").Get("nil") { - val = jsType(v.typ).Get("nil") + val = jsTyp.Get("nil") break } - newVal := jsType(v.typ).New(val.Get("$array")) + newVal := jsTyp.New(val.Get("$array")) newVal.Set("$offset", val.Get("$offset")) newVal.Set("$length", val.Get("$length")) newVal.Set("$capacity", val.Get("$capacity")) @@ -49,7 +50,11 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va // Same memory layout, so no harm done. fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) - return Value{dst, v.ptr, fl} + return Value{ + typ: dst, + ptr: v.ptr, + flag: fl, + } case implements(dst, v.typ): if target == nil { @@ -64,201 +69,21 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } else { ifaceE2I(dst, x, target) } - return Value{dst, target, flagIndir | flag(Interface)} - } - - // Failed. - panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) -} - -var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) - -func (v Value) call(op string, in []Value) []Value { - var ( - t *funcType - fn unsafe.Pointer - rcvr *js.Object - ) - if v.flag&flagMethod != 0 { - _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr = v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) - } - } else { - t = (*funcType)(unsafe.Pointer(v.typ)) - fn = unsafe.Pointer(v.object().Unsafe()) - rcvr = js.Undefined - } - - if fn == nil { - panic("reflect.Value.Call: call of nil function") - } - - isSlice := op == "CallSlice" - n := t.NumIn() - if isSlice { - if !t.IsVariadic() { - panic("reflect: CallSlice of non-variadic function") - } - if len(in) < n { - panic("reflect: CallSlice with too few input arguments") - } - if len(in) > n { - panic("reflect: CallSlice with too many input arguments") - } - } else { - if t.IsVariadic() { - n-- - } - if len(in) < n { - panic("reflect: Call with too few input arguments") - } - if !t.IsVariadic() && len(in) > n { - panic("reflect: Call with too many input arguments") - } - } - for _, x := range in { - if x.Kind() == abi.Invalid { - panic("reflect: " + op + " using zero Value argument") - } - } - for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), toRType(t.In(i)); !xt.AssignableTo(targ) { - panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) - } - } - if !isSlice && t.IsVariadic() { - // prepare slice for remaining values - m := len(in) - n - targ := toRType(t.In(n)) - slice := MakeSlice(targ, m, m) - elem := targ.Elem() - for i := 0; i < m; i++ { - x := in[n+i] - if xt := x.Type(); !xt.AssignableTo(elem) { - panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op) - } - slice.Index(i).Set(x) - } - origIn := in - in = make([]Value, n+1) - copy(in[:n], origIn) - in[n] = slice - } - - nin := len(in) - if nin != t.NumIn() { - panic("reflect.Value.Call: wrong argument count") - } - nout := t.NumOut() - - argsArray := js.Global.Get("Array").New(t.NumIn()) - for i, arg := range in { - targ := toRType(t.In(i)) - argsArray.SetIndex(i, unwrapJsObject(targ, arg.assignTo("reflect.Value.Call", targ.common(), nil).object())) - } - results := callHelper(js.InternalObject(fn), rcvr, argsArray) - - switch nout { - case 0: - return nil - case 1: - return []Value{makeValue(t.Out(0), wrapJsObject(toRType(t.Out(0)), results), 0)} - default: - ret := make([]Value, nout) - for i := range ret { - ret[i] = makeValue(t.Out(i), wrapJsObject(toRType(t.Out(i)), results.Index(i)), 0) - } - return ret - } -} - -func (v Value) Cap() int { - k := v.kind() - switch k { - case abi.Array: - return v.typ.Len() - case abi.Chan, abi.Slice: - return v.object().Get("$capacity").Int() - } - panic(&ValueError{Method: "reflect.Value.Cap", Kind: k}) -} - -func (v Value) Index(i int) Value { - switch k := v.kind(); k { - case abi.Array: - tt := (*arrayType)(unsafe.Pointer(v.typ)) - if i < 0 || i > int(tt.Len) { - panic("reflect: array index out of range") - } - typ := tt.Elem - fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) - - a := js.InternalObject(v.ptr) - rtyp := toRType(typ) - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ: typ, - ptr: unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(rtyp, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(rtyp, x)) }), - ).Unsafe()), - flag: fl, - } - } - return makeValue(typ, wrapJsObject(rtyp, a.Index(i)), fl) - - case abi.Slice: - s := v.object() - if i < 0 || i >= s.Get("$length").Int() { - panic("reflect: slice index out of range") - } - tt := (*sliceType)(unsafe.Pointer(v.typ)) - typ := tt.Elem - fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) - - i += s.Get("$offset").Int() - a := s.Get("$array") - rtyp := toRType(typ) - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ: typ, - ptr: unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(rtyp, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(rtyp, x)) }), - ).Unsafe()), - flag: fl, - } - } - return makeValue(typ, wrapJsObject(rtyp, a.Index(i)), fl) - - case abi.String: - str := *(*string)(v.ptr) - if i < 0 || i >= len(str) { - panic("reflect: string index out of range") - } - fl := v.flag.ro() | flag(abi.Uint8) | flagIndir - c := str[i] return Value{ - typ: uint8Type.Type, - ptr: unsafe.Pointer(&c), - flag: fl, + typ: dst, + ptr: target, + flag: flagIndir | flag(Interface), } - - default: - panic(&ValueError{Method: "reflect.Value.Index", Kind: k}) } -} -func (v Value) InterfaceData() [2]uintptr { - panic("InterfaceData is not supported by GopherJS") + // Failed. + panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) } func (v Value) IsNil() bool { switch k := v.kind(); k { case abi.Pointer, abi.Slice: - return v.object() == jsType(v.typ).Get("nil") + return v.object() == v.typ.JsType().Get("nil") case abi.Chan: return v.object() == js.Global.Get("$chanNil") case abi.Func: @@ -289,28 +114,6 @@ func (v Value) Len() int { } } -func (v Value) Pointer() uintptr { - switch k := v.kind(); k { - case abi.Chan, abi.Map, abi.Pointer, abi.UnsafePointer: - if v.IsNil() { - return 0 - } - return v.object().Unsafe() - case abi.Func: - if v.IsNil() { - return 0 - } - return 1 - case abi.Slice: - if v.IsNil() { - return 0 - } - return v.object().Get("$array").Unsafe() - default: - panic(&ValueError{Method: "reflect.Value.Pointer", Kind: k}) - } -} - func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() @@ -318,7 +121,7 @@ func (v Value) Set(x Value) { if v.flag&flagIndir != 0 { switch v.typ.Kind() { case abi.Array: - jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) + v.typ.JsType().Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x))) case abi.Struct: @@ -331,129 +134,6 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } -func (v Value) SetBytes(x []byte) { - v.mustBeAssignable() - v.mustBe(abi.Slice) - rtyp := toRType(v.typ) - if rtyp.Elem().Kind() != abi.Uint8 { - panic("reflect.Value.SetBytes of non-byte slice") - } - slice := js.InternalObject(x) - if rtyp.Name() != "" || rtyp.Elem().Name() != "" { - typedSlice := jsType(v.typ).New(slice.Get("$array")) - typedSlice.Set("$offset", slice.Get("$offset")) - typedSlice.Set("$length", slice.Get("$length")) - typedSlice.Set("$capacity", slice.Get("$capacity")) - slice = typedSlice - } - js.InternalObject(v.ptr).Call("$set", slice) -} - -func (v Value) SetCap(n int) { - v.mustBeAssignable() - v.mustBe(abi.Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { - panic("reflect: slice capacity out of range in SetCap") - } - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", s.Get("$length")) - newSlice.Set("$capacity", n) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -func (v Value) SetLen(n int) { - v.mustBeAssignable() - v.mustBe(abi.Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < 0 || n > s.Get("$capacity").Int() { - panic("reflect: slice length out of range in SetLen") - } - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", n) - newSlice.Set("$capacity", s.Get("$capacity")) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -func (v Value) Slice(i, j int) Value { - var ( - cap int - typ Type - s *js.Object - ) - switch kind := v.kind(); kind { - case abi.Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.Len) - typ = SliceOf(toRType(tt.Elem)) - s = jsType(toAbiType(typ)).New(v.object()) - - case abi.Slice: - typ = toRType(v.typ) - s = v.object() - cap = s.Get("$capacity").Int() - - case abi.String: - str := *(*string)(v.ptr) - if i < 0 || j < i || j > len(str) { - panic("reflect.Value.Slice: string slice index out of bounds") - } - return ValueOf(str[i:j]) - - default: - panic(&ValueError{Method: "reflect.Value.Slice", Kind: kind}) - } - - if i < 0 || j < i || j > cap { - panic("reflect.Value.Slice: slice index out of bounds") - } - - return makeValue(toAbiType(typ), js.Global.Call("$subslice", s, i, j), v.flag.ro()) -} - -func (v Value) Slice3(i, j, k int) Value { - var ( - cap int - typ Type - s *js.Object - ) - switch kind := v.kind(); kind { - case abi.Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.Len) - typ = SliceOf(toRType(tt.Elem)) - s = jsType(toAbiType(typ)).New(v.object()) - - case abi.Slice: - typ = toRType(v.typ) - s = v.object() - cap = s.Get("$capacity").Int() - - default: - panic(&ValueError{Method: "reflect.Value.Slice3", Kind: kind}) - } - - if i < 0 || j < i || k < j || k > cap { - panic("reflect.Value.Slice3: slice index out of bounds") - } - - return makeValue(toAbiType(typ), js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) -} - -func (v Value) Close() { - v.mustBe(abi.Chan) - v.mustBeExported() - js.Global.Call("$close", v.object()) -} - func (v Value) Elem() Value { switch k := v.kind(); k { case abi.Interface: @@ -461,8 +141,8 @@ func (v Value) Elem() Value { if val == js.Global.Get("$ifaceNil") { return Value{} } - typ := reflectType(val.Get("constructor")) - return makeValue(toAbiType(typ), val.Get("$val"), v.flag.ro()) + typ := abi.ReflectType(val.Get("constructor")) + return makeValue(typ, val.Get("$val"), v.flag.ro()) case abi.Pointer: if v.IsNil() { @@ -482,102 +162,3 @@ func (v Value) Elem() Value { panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) } } - -// NumField returns the number of fields in the struct v. -// It panics if v's Kind is not Struct. -func (v Value) NumField() int { - v.mustBe(abi.Struct) - tt := (*structType)(unsafe.Pointer(v.typ)) - return len(tt.Fields) -} - -// MapIndex returns the value associated with key in the map v. -// It panics if v's Kind is not Map. -// It returns the zero Value if key is not found in the map or if v represents a nil map. -// As in Go, the key's value must be assignable to the map's key type. -func (v Value) MapIndex(key Value) Value { - v.mustBe(abi.Map) - tt := (*mapType)(unsafe.Pointer(v.typ)) - - // Do not require key to be exported, so that DeepEqual - // and other programs can use all the keys returned by - // MapKeys as arguments to MapIndex. If either the map - // or the key is unexported, though, the result will be - // considered unexported. This is consistent with the - // behavior for structs, which allow read but not write - // of unexported fields. - key = key.assignTo("reflect.Value.MapIndex", tt.Key, nil) - - var k unsafe.Pointer - if key.flag&flagIndir != 0 { - k = key.ptr - } else { - k = unsafe.Pointer(&key.ptr) - } - e := mapaccess(toRType(v.typ), v.pointer(), k) - if e == nil { - return Value{} - } - typ := tt.Elem - fl := (v.flag | key.flag).ro() - fl |= flag(typ.Kind()) - return copyVal(toRType(typ), fl, e) -} - -func (v Value) Field(i int) Value { - if v.kind() != Struct { - panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) - } - tt := (*structType)(unsafe.Pointer(v.typ)) - if uint(i) >= uint(len(tt.Fields)) { - panic("reflect: Field index out of range") - } - - prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.Fields[i] - typ := field.Typ - - fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.Name.IsExported() { - if field.Embedded() { - fl |= flagEmbedRO - } else { - fl |= flagStickyRO - } - } - - if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { - if jsTag := getJsTag(tag); jsTag != "" { - for { - v = v.Field(0) - if toRType(v.typ) == jsObjectPtr { - o := v.object().Get("object") - return Value{ - typ: typ, - ptr: unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), - ).Unsafe()), - flag: fl, - } - } - if v.typ.Kind() == abi.Pointer { - v = v.Elem() - } - } - } - } - - s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ: typ, - ptr: unsafe.Pointer(jsPtrTo(typ).New( - js.InternalObject(func() *js.Object { return wrapJsObject(toRType(typ), s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(toRType(typ), x)) }), - ).Unsafe()), - flag: fl, - } - } - return makeValue(typ, wrapJsObject(toRType(typ), s.Get(prop)), fl) -} From c6135143bfd6d0b960e073e9a0e94f4674c63001 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 3 Feb 2026 16:48:48 -0700 Subject: [PATCH 10/48] Working through reflectlite and got to tests requiring reflect --- compiler/natives/src/internal/abi/type.go | 3 + .../src/internal/reflectlite/export_test.go | 37 +- .../src/internal/reflectlite/reflectlite.go | 370 +----------------- .../natives/src/internal/reflectlite/type.go | 59 ++- .../natives/src/internal/reflectlite/utils.go | 56 +-- .../natives/src/internal/reflectlite/value.go | 352 +++++++++++++---- 6 files changed, 359 insertions(+), 518 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index aeaec25af..d6bb33567 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -32,6 +32,9 @@ type UncommonType struct { Methods_ []Method } +//gopherjs:purge Used for pointer arthmatic +func addChecked(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer + //gopherjs:replace func (t *UncommonType) Methods() []Method { return t.Methods_ diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index c80ba65a8..bb5b86e4a 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -2,31 +2,30 @@ package reflectlite -import "unsafe" +import "internal/abi" // Field returns the i'th field of the struct v. // It panics if v's Kind is not Struct or i is out of range. +// +//gopherjs:replace func Field(v Value, i int) Value { - if v.kind() != Struct { - panic(&ValueError{"reflect.Value.Field", v.kind()}) + if v.kind() != abi.Struct { + panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) } return v.Field(i) } -func TField(typ Type, i int) Type { - t := typ.(*rtype) - if t.Kind() != Struct { - panic("reflect: Field of non-struct type") - } - tt := (*structType)(unsafe.Pointer(t)) - return StructFieldType(tt, i) -} +//gopherjs:purge Used in FirstMethodNameBytes +type EmbedWithUnexpMeth struct{} -// Field returns the i'th struct field. -func StructFieldType(t *structType, i int) Type { - if i < 0 || i >= len(t.fields) { - panic("reflect: Field index out of bounds") - } - p := &t.fields[i] - return toType(p.typ) -} +//gopherjs:purge Used in FirstMethodNameBytes +type pinUnexpMeth interface{} + +//gopherjs:purge Used in FirstMethodNameBytes +var pinUnexpMethI pinUnexpMeth + +//gopherjs:purge Uses pointer arithmetic for names +func FirstMethodNameBytes(t Type) *byte + +//gopherjs:purge Was unused +type Buffer struct{} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 416aba5c7..872c0d6a4 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -3,19 +3,16 @@ package reflectlite import ( - "unsafe" - "internal/abi" "github.com/gopherjs/gopherjs/js" ) -var initialized = false - func init() { // avoid dead code elimination used := func(i any) {} used(rtype{}) + used(uncommonType{}) used(arrayType{}) used(chanType{}) used(funcType{}) @@ -24,183 +21,43 @@ func init() { used(ptrType{}) used(sliceType{}) used(structType{}) - - initialized = true - uint8Type = TypeOf(uint8(0)).(rtype) // set for real -} - -// GOPHERJS: For some reason they left mapType and aliased the rest but never -// used mapType so this is an alias to override the left over refactor cruft. -type mapType = abi.MapType - -var uint8Type rtype - -var ( - idJsType = "_jsType" - idReflectType = "_reflectType" - idKindType = "kindType" - idRtype = "_rtype" -) - -func copyStruct(dst, src *js.Object, typ *abi.Type) { - fields := typ.JsType().Get("fields") - for i := 0; i < fields.Length(); i++ { - prop := fields.Index(i).Get("prop").String() - dst.Set(prop, src.Get(prop)) - } -} - -func TypeOf(i any) Type { - if !initialized { // avoid error of uint8Type - return &rtype{} - } - if i == nil { - return nil - } - return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) -} - -func ValueOf(i any) Value { - if i == nil { - return Value{} - } - return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) -} - -func makeValue(t *abi.Type, v *js.Object, fl flag) Value { - switch t.Kind() { - case abi.Array, abi.Struct, abi.Pointer: - return Value{ - typ: t, - ptr: unsafe.Pointer(v.Unsafe()), - flag: fl | flag(t.Kind()), - } - } - return Value{ - typ: t, - ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), - flag: fl | flag(t.Kind()) | flagIndir, - } -} - -func unsafe_New(typ *abi.Type) unsafe.Pointer { - switch typ.Kind() { - case abi.Struct: - return unsafe.Pointer(typ.JsType().Get("ptr").New().Unsafe()) - case abi.Array: - return unsafe.Pointer(typ.JsType().Call("zero").Unsafe()) - default: - return unsafe.Pointer(js.Global.Call("$newDataPointer", typ.JsType().Call("zero"), typ.JsPtrTo()).Unsafe()) - } -} - -func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) -} - -func loadScalar(p unsafe.Pointer, n uintptr) uintptr { - return js.InternalObject(p).Call("$get").Unsafe() -} - -func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe.Pointer) { - var prop string - if v.typ.Kind() == abi.Interface { - tt := v.typ.InterfaceType() - if i < 0 || i >= len(tt.Methods) { - panic("reflect: internal error: invalid method index") - } - m := &tt.Methods[i] - if !tt.NameOff(m.Name).IsExported() { - panic("reflect: " + op + " of unexported method") - } - t = tt.TypeOff(m.Typ).FuncType() - prop = tt.NameOff(m.Name).Name() - } else { - ms := v.typ.exportedMethods() - if uint(i) >= uint(len(ms)) { - panic("reflect: internal error: invalid method index") - } - m := ms[i] - if !v.typ.nameOff(m.name).isExported() { - panic("reflect: " + op + " of unexported method") - } - t = v.typ.typeOff(m.mtyp).FuncType() - prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() - } - rcvr := v.object() - if v.typ.IsWrapped() { - rcvr = v.typ.JsType().New(rcvr) - } - fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) - return -} - -func valueInterface(v Value) any { - if v.flag == 0 { - panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) - } - - if v.flag&flagMethod != 0 { - v = makeMethodValue("Interface", v) - } - - if v.typ.IsWrapped() { - if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { - cv := v.typ.JsType().Call("zero") - copyStruct(cv, v.object(), v.typ) - return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) - } - return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) - } - return any(unsafe.Pointer(v.object().Unsafe())) } -func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src)) +//gopherjs:new +func toAbiType(typ Type) *abi.Type { + return typ.(rtype).common() } -func methodName() string { - // TODO(grantnelson-wf): methodName returns the name of the calling method, - // assumed to be two stack frames above. - return "?FIXME?" +//gopherjs:new +func jsType(t Type) *js.Object { + return toAbiType(t).JsType() } -func makeMethodValue(op string, v Value) Value { - if v.flag&flagMethod == 0 { - panic("reflect: internal error: invalid use of makePartialFunc") - } - - _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr := v.object() - if v.typ.IsWrapped() { - rcvr = v.typ.JsType().New(rcvr) - } - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - return js.InternalObject(fn).Call("apply", rcvr, arguments) - }) - return Value{ - typ: v.Type().common(), - ptr: unsafe.Pointer(fv.Unsafe()), - flag: v.flag.ro() | flag(Func), - } +//gopherjs:new +func jsPtrTo(t Type) *js.Object { + return toAbiType(t).JsPtrTo() } +//gopherjs:new var jsObjectPtr = abi.ReflectType(js.Global.Get("$jsObjectPtr")) -func wrapJsObject(typ Type, val *js.Object) *js.Object { +//gopherjs:new +func wrapJsObject(typ *abi.Type, val *js.Object) *js.Object { if typ == jsObjectPtr { return jsObjectPtr.JsType().New(val) } return val } -func unwrapJsObject(typ Type, val *js.Object) *js.Object { +//gopherjs:new +func unwrapJsObject(typ *abi.Type, val *js.Object) *js.Object { if typ == jsObjectPtr { return val.Get("object") } return val } +//gopherjs:new func getJsTag(tag string) string { for tag != "" { // skip leading space @@ -246,198 +103,3 @@ func getJsTag(tag string) string { } return "" } - -func toAbiType(typ Type) *abi.Type { - return typ.(rtype).common() -} - -// PtrTo returns the pointer type with element t. -// For example, if t represents type Foo, PtrTo(t) represents *Foo. -func PtrTo(t Type) Type { - return toRtype(toAbiType(t).PtrTo()) -} - -func jsType(t Type) *js.Object { - return toAbiType(t).JsType() -} - -func jsPtrTo(t Type) *js.Object { - return toAbiType(t).JsPtrTo() -} - -// copyVal returns a Value containing the map key or value at ptr, -// allocating a new variable as needed. -func copyVal(typ rtype, fl flag, ptr unsafe.Pointer) Value { - if ifaceIndir(typ) { - // Copy result so future changes to the map - // won't change the underlying value. - c := unsafe_New(typ) - typedmemmove(typ, c, ptr) - return Value{ - typ: typ, - ptr: c, - flag: fl | flagIndir, - } - } - return Value{ - typ: typ, - ptr: *(*unsafe.Pointer)(ptr), - flag: fl, - } -} - -var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) - -func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { - comms := [][]*js.Object{{js.InternalObject(ch)}} - if nb { - comms = append(comms, []*js.Object{}) - } - selectRes := selectHelper(comms) - if nb && selectRes.Index(0).Int() == 1 { - return false, false - } - recvRes := selectRes.Index(1) - js.InternalObject(val).Call("$set", recvRes.Index(0)) - return true, recvRes.Index(1).Bool() -} - -func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { - comms := [][]*js.Object{{js.InternalObject(ch), js.InternalObject(val).Call("$get")}} - if nb { - comms = append(comms, []*js.Object{}) - } - selectRes := selectHelper(comms) - if nb && selectRes.Index(0).Int() == 1 { - return false - } - return true -} - -func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { - comms := make([][]*js.Object, len(rselects)) - for i, s := range rselects { - switch SelectDir(s.dir) { - case SelectDefault: - comms[i] = []*js.Object{} - case SelectRecv: - ch := js.Global.Get("$chanNil") - if js.InternalObject(s.ch) != js.InternalObject(0) { - ch = js.InternalObject(s.ch) - } - comms[i] = []*js.Object{ch} - case SelectSend: - ch := js.Global.Get("$chanNil") - var val *js.Object - if js.InternalObject(s.ch) != js.InternalObject(0) { - ch = js.InternalObject(s.ch) - val = js.InternalObject(s.val).Call("$get") - } - comms[i] = []*js.Object{ch, val} - } - } - selectRes := selectHelper(comms) - c := selectRes.Index(0).Int() - if SelectDir(rselects[c].dir) == SelectRecv { - recvRes := selectRes.Index(1) - js.InternalObject(rselects[c].val).Call("$set", recvRes.Index(0)) - return c, recvRes.Index(1).Bool() - } - return c, false -} - -func DeepEqual(a1, a2 any) bool { - i1 := js.InternalObject(a1) - i2 := js.InternalObject(a2) - if i1 == i2 { - return true - } - if i1 == nil || i2 == nil || i1.Get("constructor") != i2.Get("constructor") { - return false - } - return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) -} - -func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { - if !v1.IsValid() || !v2.IsValid() { - return !v1.IsValid() && !v2.IsValid() - } - if v1.Type() != v2.Type() { - return false - } - if v1.Type() == jsObjectPtr { - return unwrapJsObject(jsObjectPtr, v1.object()) == unwrapJsObject(jsObjectPtr, v2.object()) - } - - switch v1.Kind() { - case abi.Array, abi.Map, abi.Slice, abi.Struct: - for _, entry := range visited { - if v1.ptr == entry[0] && v2.ptr == entry[1] { - return true - } - } - visited = append(visited, [2]unsafe.Pointer{v1.ptr, v2.ptr}) - } - - switch v1.Kind() { - case abi.Array, abi.Slice: - if v1.Kind() == abi.Slice { - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.object() == v2.object() { - return true - } - } - n := v1.Len() - if n != v2.Len() { - return false - } - for i := 0; i < n; i++ { - if !deepValueEqualJs(v1.Index(i), v2.Index(i), visited) { - return false - } - } - return true - case abi.Interface: - if v1.IsNil() || v2.IsNil() { - return v1.IsNil() && v2.IsNil() - } - return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) - case abi.Pointer: - return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) - case abi.Struct: - n := v1.NumField() - for i := 0; i < n; i++ { - if !deepValueEqualJs(v1.Field(i), v2.Field(i), visited) { - return false - } - } - return true - case abi.Map: - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.object() == v2.object() { - return true - } - keys := v1.MapKeys() - if len(keys) != v2.Len() { - return false - } - for _, k := range keys { - val1 := v1.MapIndex(k) - val2 := v2.MapIndex(k) - if !val1.IsValid() || !val2.IsValid() || !deepValueEqualJs(val1, val2, visited) { - return false - } - } - return true - case abi.Func: - return v1.IsNil() && v2.IsNil() - case abi.UnsafePointer: - return v1.object() == v2.object() - } - - return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1)), js.InternalObject(valueInterface(v2))).Bool() -} diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 9a9da6276..106d8f18c 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -6,8 +6,48 @@ import ( "unsafe" "internal/abi" + + "github.com/gopherjs/gopherjs/js" ) +// GOPHERJS: For some reason the original code left mapType and aliased the rest +// to the ABI version. mapType is not used so this is an alias to override the +// left over refactor cruft. +// +//gopherjs:replace +type mapType = abi.MapType + +//gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. +type name struct{} + +//gopherjs:replace +func pkgPath(n abi.Name) string { return "" } + +//gopherjs:purge Unused function because of nameOffList in internal/abi overrides +func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer + +//gopherjs:purge Unused function because of typeOffList in internal/abi overrides +func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer + +//gopherjs:replace +func (t rtype) nameOff(off nameOff) abi.Name { + return t.NameOff(off) +} + +//gopherjs:replace +func (t rtype) typeOff(off typeOff) *abi.Type { + return t.TypeOff(off) +} + +//gopherjs:replace +func TypeOf(i any) Type { + if i == nil { + return nil + } + return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) +} + +//gopherjs:replace func (t rtype) Comparable() bool { switch t.Kind() { case abi.Func, abi.Slice, abi.Map: @@ -24,22 +64,3 @@ func (t rtype) Comparable() bool { } return true } - -//gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. -type name struct{} - -func (t rtype) nameOff(off nameOff) abi.Name { - return t.NameOff(off) -} - -func (t rtype) typeOff(off typeOff) *abi.Type { - return t.TypeOff(off) -} - -func pkgPath(n abi.Name) string { return "" } - -//gopherjs:purge Unused function because of nameOffList in internal/abi overrides -func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer - -//gopherjs:purge Unused function because of typeOffList in internal/abi overrides -func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer diff --git a/compiler/natives/src/internal/reflectlite/utils.go b/compiler/natives/src/internal/reflectlite/utils.go index e8354f524..bdb345775 100644 --- a/compiler/natives/src/internal/reflectlite/utils.go +++ b/compiler/natives/src/internal/reflectlite/utils.go @@ -2,18 +2,20 @@ package reflectlite -import "unsafe" - +//gopherjs:new type errorString struct { s string } +//gopherjs:new func (e *errorString) Error() string { return e.s } +//gopherjs:new var ErrSyntax = &errorString{"invalid syntax"} +//gopherjs:new func unquote(s string) (string, error) { if len(s) < 2 { return s, nil @@ -26,53 +28,3 @@ func unquote(s string) (string, error) { } return s, nil } - -// Method represents a single method. -type Method struct { // TODO(grantnelson-wf): Should this be moved to ABI? - // Name is the method name. - // PkgPath is the package path that qualifies a lower case (unexported) - // method name. It is empty for upper case (exported) method names. - // The combination of PkgPath and Name uniquely identifies a method - // in a method set. - // See https://golang.org/ref/spec#Uniqueness_of_identifiers - Name string - PkgPath string - - Type Type // method type - Func Value // func with receiver as first argument - Index int // index for Type.Method -} - -// A SelectDir describes the communication direction of a select case. -type SelectDir int - -// NOTE: These values must match ../runtime/select.go:/selectDir. - -const ( - _ SelectDir = iota - SelectSend // case Chan <- Send - SelectRecv // case <-Chan: - SelectDefault // default -) - -// A runtimeSelect is a single case passed to rselect. -// This must match ../runtime/select.go:/runtimeSelect -type runtimeSelect struct { - dir SelectDir // SelectSend, SelectRecv or SelectDefault - typ *rtype // channel type - ch unsafe.Pointer // channel - val unsafe.Pointer // ptr to data (SendDir) or ptr to receive buffer (RecvDir) -} - -func (f flag) mustBe(expected Kind) { - // TODO(mvdan): use f.kind() again once mid-stack inlining gets better - if Kind(f&flagKindMask) != expected { - panic(&ValueError{methodName(), f.kind()}) - } -} - -func typesMustMatch(what string, t1, t2 Type) { - if t1 != t2 { - panic(what + ": " + t1.String() + " != " + t2.String()) - } -} diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 76acc29f1..1ad0c3348 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -10,76 +10,129 @@ import ( "github.com/gopherjs/gopherjs/js" ) -func (v Value) object() *js.Object { - if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { - return js.InternalObject(v.ptr) - } - if v.flag&flagIndir != 0 { - val := js.InternalObject(v.ptr).Call("$get") - jsTyp := v.typ.JsType() - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { - switch v.typ.Kind() { - case abi.Uint64, abi.Int64: - val = jsTyp.New(val.Get("$high"), val.Get("$low")) - case abi.Complex64, abi.Complex128: - val = jsTyp.New(val.Get("$real"), val.Get("$imag")) - case abi.Slice: - if val == val.Get("constructor").Get("nil") { - val = jsTyp.Get("nil") - break - } - newVal := jsTyp.New(val.Get("$array")) - newVal.Set("$offset", val.Get("$offset")) - newVal.Set("$length", val.Get("$length")) - newVal.Set("$capacity", val.Get("$capacity")) - val = newVal - } +//gopherjs:replace +func methodName() string { + // TODO(grantnelson-wf): methodName returns the name of the calling method, + // assumed to be two stack frames above. + return "?FIXME?" +} + +//gopherjs:replace +func (v Value) Elem() Value { + switch k := v.kind(); k { + case abi.Interface: + val := v.object() + if val == js.Global.Get("$ifaceNil") { + return Value{} } - return js.InternalObject(val.Unsafe()) + typ := abi.ReflectType(val.Get("constructor")) + return makeValue(typ, val.Get("$val"), v.flag.ro()) + + case abi.Pointer: + if v.IsNil() { + return Value{} + } + val := v.object() + tt := (*ptrType)(unsafe.Pointer(v.typ)) + fl := v.flag&flagRO | flagIndir | flagAddr + fl |= flag(tt.Elem.Kind()) + return Value{ + typ: tt.Elem, + ptr: unsafe.Pointer(wrapJsObject(tt.Elem, val).Unsafe()), + flag: fl, + } + + default: + panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) } - return js.InternalObject(v.ptr) } -func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { +//gopherjs:replace +func valueInterface(v Value) any { + if v.flag == 0 { + panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) + } + if v.flag&flagMethod != 0 { - v = makeMethodValue(context, v) + v = makeMethodValue("Interface", v) } - switch { - case directlyAssignable(dst, v.typ): - // Overwrite type so that they match. - // Same memory layout, so no harm done. - fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() - fl |= flag(dst.Kind()) - return Value{ - typ: dst, - ptr: v.ptr, - flag: fl, + + if v.typ.IsWrapped() { + if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { + cv := v.typ.JsType().Call("zero") + copyStruct(cv, v.object(), v.typ) + return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) } + return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) + } + return any(unsafe.Pointer(v.object().Unsafe())) +} - case implements(dst, v.typ): - if target == nil { - target = unsafe_New(dst) +//gopherjs:new +func copyStruct(dst, src *js.Object, typ *abi.Type) { + fields := typ.JsType().Get("fields") + for i := 0; i < fields.Length(); i++ { + prop := fields.Index(i).Get("prop").String() + dst.Set(prop, src.Get(prop)) + } +} + +//gopherjs:new This is new but there are commented out references in the original code. +func makeMethodValue(op string, v Value) Value { + if v.flag&flagMethod == 0 { + panic("reflect: internal error: invalid use of makePartialFunc") + } + + _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) + rcvr := v.object() + if v.typ.IsWrapped() { + rcvr = v.typ.JsType().New(rcvr) + } + fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { + return js.InternalObject(fn).Call("apply", rcvr, arguments) + }) + return Value{ + typ: v.Type().common(), + ptr: unsafe.Pointer(fv.Unsafe()), + flag: v.flag.ro() | flag(abi.Func), + } +} + +//gopherjs:new +func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe.Pointer) { + var prop string + if v.typ.Kind() == abi.Interface { + tt := v.typ.InterfaceType() + if i < 0 || i >= len(tt.Methods) { + panic("reflect: internal error: invalid method index") } - // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement - // from upstream. ifaceE2I below does not panic, and it needs - // to run, given its custom implementation. - x := valueInterface(v) - if dst.NumMethod() == 0 { - *(*any)(target) = x - } else { - ifaceE2I(dst, x, target) + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { + panic("reflect: " + op + " of unexported method") } - return Value{ - typ: dst, - ptr: target, - flag: flagIndir | flag(Interface), + t = tt.TypeOff(m.Typ).FuncType() + prop = tt.NameOff(m.Name).Name() + } else { + ms := v.typ.ExportedMethods() + if uint(i) >= uint(len(ms)) { + panic("reflect: internal error: invalid method index") } + m := ms[i] + if !v.typ.NameOff(m.Name).IsExported() { + panic("reflect: " + op + " of unexported method") + } + t = v.typ.TypeOff(m.Mtyp).FuncType() + prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() } - - // Failed. - panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) + rcvr := v.object() + if v.typ.IsWrapped() { + rcvr = v.typ.JsType().New(rcvr) + } + fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) + return } +//gopherjs:replace func (v Value) IsNil() bool { switch k := v.kind(); k { case abi.Pointer, abi.Slice: @@ -99,6 +152,7 @@ func (v Value) IsNil() bool { } } +//gopherjs:replace func (v Value) Len() int { switch k := v.kind(); k { case abi.Array, abi.String: @@ -114,6 +168,7 @@ func (v Value) Len() int { } } +//gopherjs:replace func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() @@ -134,31 +189,180 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } -func (v Value) Elem() Value { - switch k := v.kind(); k { - case abi.Interface: - val := v.object() - if val == js.Global.Get("$ifaceNil") { - return Value{} +//gopherjs:new This is added for export_test but is otherwise unused. +func (v Value) Field(i int) Value { + tt := v.typ.StructType() + if tt == nil { + panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) + } + if uint(i) >= uint(len(tt.Fields)) { + panic("reflect: Field index out of range") + } + + prop := v.typ.JsType().Get("fields").Index(i).Get("prop").String() + field := &tt.Fields[i] + typ := field.Typ + + fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) + if !field.Name.IsExported() { + if field.Embedded() { + fl |= flagEmbedRO + } else { + fl |= flagStickyRO } - typ := abi.ReflectType(val.Get("constructor")) - return makeValue(typ, val.Get("$val"), v.flag.ro()) + } - case abi.Pointer: - if v.IsNil() { - return Value{} + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { + if jsTag := getJsTag(tag); jsTag != "" { + for { + v = v.Field(0) + if v.typ == jsObjectPtr { + o := v.object().Get("object") + return Value{ + typ: typ, + ptr: unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), + ).Unsafe()), + flag: fl, + } + } + if v.typ.Kind() == abi.Pointer { + v = v.Elem() + } + } } - val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ)) - fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.Elem.Kind()) + } + + s := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { return Value{ - typ: tt.Elem, - ptr: unsafe.Pointer(wrapJsObject(toRType(tt.Elem), val).Unsafe()), + typ: typ, + ptr: unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), + ).Unsafe()), flag: fl, } + } + return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) +} +//gopherjs:replace +func unsafe_New(typ *abi.Type) unsafe.Pointer { + switch typ.Kind() { + case abi.Struct: + return unsafe.Pointer(typ.JsType().Get("ptr").New().Unsafe()) + case abi.Array: + return unsafe.Pointer(typ.JsType().Call("zero").Unsafe()) default: - panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) + return unsafe.Pointer(js.Global.Call("$newDataPointer", typ.JsType().Call("zero"), typ.JsPtrTo()).Unsafe()) + } +} + +//gopherjs:replace +func ValueOf(i any) Value { + if i == nil { + return Value{} } + return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) +} + +//gopherjs:new +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{ + typ: t, + ptr: unsafe.Pointer(v.Unsafe()), + flag: fl | flag(t.Kind()), + } + } + return Value{ + typ: t, + ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), + flag: fl | flag(t.Kind()) | flagIndir, + } +} + +//gopherjs:new +func (v Value) object() *js.Object { + if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { + return js.InternalObject(v.ptr) + } + if v.flag&flagIndir != 0 { + val := js.InternalObject(v.ptr).Call("$get") + jsTyp := v.typ.JsType() + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { + switch v.typ.Kind() { + case abi.Uint64, abi.Int64: + val = jsTyp.New(val.Get("$high"), val.Get("$low")) + case abi.Complex64, abi.Complex128: + val = jsTyp.New(val.Get("$real"), val.Get("$imag")) + case abi.Slice: + if val == val.Get("constructor").Get("nil") { + val = jsTyp.Get("nil") + break + } + newVal := jsTyp.New(val.Get("$array")) + newVal.Set("$offset", val.Get("$offset")) + newVal.Set("$length", val.Get("$length")) + newVal.Set("$capacity", val.Get("$capacity")) + val = newVal + } + } + return js.InternalObject(val.Unsafe()) + } + return js.InternalObject(v.ptr) +} + +//gopherjs:replace +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { + if v.flag&flagMethod != 0 { + v = makeMethodValue(context, v) + } + switch { + case directlyAssignable(dst, v.typ): + // Overwrite type so that they match. + // Same memory layout, so no harm done. + fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() + fl |= flag(dst.Kind()) + return Value{ + typ: dst, + ptr: v.ptr, + flag: fl, + } + + case implements(dst, v.typ): + if target == nil { + target = unsafe_New(dst) + } + // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement + // from upstream. ifaceE2I below does not panic, and it needs + // to run, given its custom implementation. + x := valueInterface(v) + if dst.NumMethod() == 0 { + *(*any)(target) = x + } else { + ifaceE2I(dst, x, target) + } + return Value{ + typ: dst, + ptr: target, + flag: flagIndir | flag(Interface), + } + } + + // Failed. + panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) +} + +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + js.InternalObject(dst).Call("$set", js.InternalObject(src)) +} + +//gopherjs:replace +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { + js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) } From 0662b0951bc3dc74d0dd8fdd6ec178ee42dcb17e Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 4 Feb 2026 14:29:44 -0700 Subject: [PATCH 11/48] updating reflect and abi --- compiler/natives/src/internal/abi/type.go | 130 ++++++-- .../src/internal/reflectlite/swapper.go | 1 + compiler/natives/src/reflect/reflect.go | 289 ++---------------- compiler/natives/src/reflect/swapper.go | 1 + compiler/natives/src/reflect/type.go | 3 + compiler/natives/src/reflect/value.go | 3 + 6 files changed, 137 insertions(+), 290 deletions(-) create mode 100644 compiler/natives/src/reflect/type.go create mode 100644 compiler/natives/src/reflect/value.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index d6bb33567..c9f5c8b6a 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -8,18 +8,24 @@ import ( //gopherjs:new const ( - idJsType = "_jsType" - idReflectType = "_reflectType" - idKindType = "kindType" - idRtype = "_rtype" + idJsType = `jsType` + idReflectType = `reflectType` + idKindType = `kindType` + idUncommonType = `uncommonType` ) -//gopherjs:new -var UncommonTypeMap = make(map[*Type]*UncommonType) - //gopherjs:replace func (t *Type) Uncommon() *UncommonType { - return UncommonTypeMap[t] + obj := js.InternalObject(t).Get(idUncommonType) + if obj == js.Undefined { + return nil + } + return (*UncommonType)(unsafe.Pointer(obj.Unsafe())) +} + +//gopherjs:add +func (t *Type) setUncommon(ut *UncommonType) { + js.InternalObject(t).Set(idUncommonType, js.InternalObject(ut)) } //gopherjs:replace @@ -72,6 +78,7 @@ type Name struct { tag string exported bool embedded bool + pkgPath string } //gopherjs:replace @@ -104,6 +111,14 @@ func (n Name) Data(off int) *byte //gopherjs:purge Used for byte encoding of name, not used in JS func (n Name) ReadVarint(off int) (int, int) +//gopherjs:add +func (n Name) PkgPath() string { return n.pkgPath } + +//gopherjs:add +func (n Name) SetPkgPath(pkgpath string) { + n.pkgPath = pkgpath +} + //gopherjs:replace func NewName(n, tag string, exported, embedded bool) Name { return Name{ @@ -114,34 +129,80 @@ func NewName(n, tag string, exported, embedded bool) Name { } } -//gopherjs:new -var nameOffList []Name +// NewMethodName creates name instance for a method. +// +// Input object is expected to be an entry of the "methods" list of the +// corresponding JS type. +func NewMethodName(m *js.Object) Name { + return Name{ + name: internalStr(m.Get("name")), + tag: "", + pkgPath: internalStr(m.Get("pkg")), + exported: internalStr(m.Get("pkg")) == "", + } +} + +// Instead of using this as an offset from a pointer to look up a name, +// just store the name as a pointer. +// +//gopherjs:replace +type NameOff *Name +// Added to mirror the rtype's nameOff method to keep how the nameOff is +// created and read in one spot of the code. +// //gopherjs:new func (typ *Type) NameOff(off NameOff) Name { - return nameOffList[int(off)] + return *off } +// Added to mirror the resolveReflectName method in reflect +// //gopherjs:new -func newNameOff(n Name) NameOff { - i := len(nameOffList) - nameOffList = append(nameOffList, n) - return NameOff(i) +func ResolveReflectName(n Name) NameOff { + return &n } -//gopherjs:new -var typeOffList []*Type +// Instead of using this as an offset from a pointer to look up a type, +// just store the type as a pointer. +// +//gopherjs:replace +type TypeOff *Type +// Added to mirror the rtype's typeOff method to keep how the typeOff is +// created and read in one spot of the code. +// //gopherjs:new func (typ *Type) TypeOff(off TypeOff) *Type { - return typeOffList[int(off)] + return off } +// Added to mirror the resolveReflectType method in reflect +// +//gopherjs:new +func ResolveReflectType(t *Type) TypeOff { + return t +} + +// Instead of using this as an offset from a pointer to look up a pointer, +// just store the paointer itself. +// +//gopherjs:replace +type TextOff unsafe.Pointer + +// Added to mirror the rtype's textOff method to keep how the textOff is +// created and read in one spot of the code. +// //gopherjs:new -func newTypeOff(t *Type) TypeOff { - i := len(typeOffList) - typeOffList = append(typeOffList, t) - return TypeOff(i) +func (typ *Type) TextOff(off TextOff) unsafe.Pointer { + return unsafe.Pointer(off) +} + +// Added to mirror the resolveReflectText method in reflect +// +//gopherjs:new +func ResolveReflectText(ptr unsafe.Pointer) TextOff { + return TextOff(ptr) } //gopherjs:new @@ -149,6 +210,11 @@ func (typ *Type) JsType() *js.Object { return js.InternalObject(typ).Get(idJsType) } +//gopherjs:new +func (typ *Type) setJsType(t *js.Object) { + js.InternalObject(typ).Set(idJsType, typ) +} + //gopherjs:new func (typ *Type) PtrTo() *Type { return ReflectType(js.Global.Call("$ptrType", typ.JsType())) @@ -177,7 +243,7 @@ func ReflectType(typ *js.Object) *Type { abiTyp := &Type{ Size_: uintptr(typ.Get("size").Int()), Kind_: uint8(typ.Get("kind").Int()), - Str: newNameOff(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(abiTyp).Set(idJsType, typ) typ.Set(idReflectType, js.InternalObject(abiTyp)) @@ -196,8 +262,8 @@ func ReflectType(typ *js.Object) *Type { continue } reflectMethods = append(reflectMethods, Method{ - Name: newNameOff(NewName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: newTypeOff(ReflectType(m.Get("typ"))), + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), }) } xcount := uint16(len(reflectMethods)) @@ -208,18 +274,17 @@ func ReflectType(typ *js.Object) *Type { continue } reflectMethods = append(reflectMethods, Method{ - Name: newNameOff(NewName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: newTypeOff(ReflectType(m.Get("typ"))), + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), }) } ut := &UncommonType{ - PkgPath: newNameOff(NewName(internalStr(typ.Get("pkg")), "", false, false)), + PkgPath: ResolveReflectName(NewName(internalStr(typ.Get("pkg")), "", false, false)), Mcount: uint16(methodSet.Length()), Xcount: xcount, Methods_: reflectMethods, } - UncommonTypeMap[abiTyp] = ut - js.InternalObject(ut).Set(idJsType, typ) + abiTyp.setUncommon(ut) } switch abiTyp.Kind() { @@ -270,8 +335,8 @@ func ReflectType(typ *js.Object) *Type { for i := range imethods { m := methods.Index(i) imethods[i] = Imethod{ - Name: newNameOff(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), - Typ: newTypeOff(ReflectType(m.Get("typ"))), + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), + Typ: ResolveReflectType(ReflectType(m.Get("typ"))), } } setKindType(abiTyp, &InterfaceType{ @@ -320,5 +385,4 @@ func ReflectType(typ *js.Object) *Type { //gopherjs:new func setKindType(abiTyp *Type, kindType any) { js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) - js.InternalObject(kindType).Set(idRtype, js.InternalObject(abiTyp)) } diff --git a/compiler/natives/src/internal/reflectlite/swapper.go b/compiler/natives/src/internal/reflectlite/swapper.go index b21e950ee..7f4481485 100644 --- a/compiler/natives/src/internal/reflectlite/swapper.go +++ b/compiler/natives/src/internal/reflectlite/swapper.go @@ -4,6 +4,7 @@ package reflectlite import "github.com/gopherjs/gopherjs/js" +//gopherjs:replace func Swapper(slice any) func(i, j int) { v := ValueOf(slice) if v.Kind() != Slice { diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index baa83e260..d6a6ed12d 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -9,6 +9,7 @@ import ( "internal/itoa" + "github.com/gopherjs/gopherjs/compiler/natives/src/internal/abi" "github.com/gopherjs/gopherjs/js" ) @@ -52,280 +53,54 @@ func New(typ Type) Value { return Value{pt, ptr, fl} } -func jsType(typ Type) *js.Object { - return js.InternalObject(typ).Get("jsType") +//gopherjs:new +func toAbiType(typ Type) *abi.Type { + return typ.(*rtype).common() } -func reflectType(typ *js.Object) *rtype { - if typ.Get("reflectType") == js.Undefined { - rt := &rtype{ - size: uintptr(typ.Get("size").Int()), - kind: uint8(typ.Get("kind").Int()), - str: resolveReflectName(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), - } - js.InternalObject(rt).Set("jsType", typ) - typ.Set("reflectType", js.InternalObject(rt)) - - methodSet := js.Global.Call("$methodSet", typ) - if methodSet.Length() != 0 || typ.Get("named").Bool() { - rt.tflag |= tflagUncommon - if typ.Get("named").Bool() { - rt.tflag |= tflagNamed - } - var reflectMethods []method - for i := 0; i < methodSet.Length(); i++ { // Exported methods first. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if !exported { - continue - } - reflectMethods = append(reflectMethods, method{ - name: resolveReflectName(newMethodName(m)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - xcount := uint16(len(reflectMethods)) - for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if exported { - continue - } - reflectMethods = append(reflectMethods, method{ - name: resolveReflectName(newMethodName(m)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - ut := &uncommonType{ - pkgPath: resolveReflectName(newName(internalStr(typ.Get("pkg")), "", false, false)), - mcount: uint16(methodSet.Length()), - xcount: xcount, - _methods: reflectMethods, - } - js.InternalObject(ut).Set("jsType", typ) - js.InternalObject(rt).Set("uncommonType", js.InternalObject(ut)) - } - - switch rt.Kind() { - case Array: - setKindType(rt, &arrayType{ - elem: reflectType(typ.Get("elem")), - len: uintptr(typ.Get("len").Int()), - }) - case Chan: - dir := BothDir - if typ.Get("sendOnly").Bool() { - dir = SendDir - } - if typ.Get("recvOnly").Bool() { - dir = RecvDir - } - setKindType(rt, &chanType{ - elem: reflectType(typ.Get("elem")), - dir: uintptr(dir), - }) - case Func: - params := typ.Get("params") - in := make([]*rtype, params.Length()) - for i := range in { - in[i] = reflectType(params.Index(i)) - } - results := typ.Get("results") - out := make([]*rtype, results.Length()) - for i := range out { - out[i] = reflectType(results.Index(i)) - } - outCount := uint16(results.Length()) - if typ.Get("variadic").Bool() { - outCount |= 1 << 15 - } - setKindType(rt, &funcType{ - rtype: *rt, - inCount: uint16(params.Length()), - outCount: outCount, - _in: in, - _out: out, - }) - case Interface: - methods := typ.Get("methods") - imethods := make([]imethod, methods.Length()) - for i := range imethods { - m := methods.Index(i) - imethods[i] = imethod{ - name: resolveReflectName(newMethodName(m)), - typ: newTypeOff(reflectType(m.Get("typ"))), - } - } - setKindType(rt, &interfaceType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), - methods: imethods, - }) - case Map: - setKindType(rt, &mapType{ - key: reflectType(typ.Get("key")), - elem: reflectType(typ.Get("elem")), - }) - case Ptr: - setKindType(rt, &ptrType{ - elem: reflectType(typ.Get("elem")), - }) - case Slice: - setKindType(rt, &sliceType{ - elem: reflectType(typ.Get("elem")), - }) - case Struct: - fields := typ.Get("fields") - reflectFields := make([]structField, fields.Length()) - for i := range reflectFields { - f := fields.Index(i) - reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - typ: reflectType(f.Get("typ")), - offset: uintptr(i), - } - } - setKindType(rt, &structType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), - fields: reflectFields, - }) - } - } - - return (*rtype)(unsafe.Pointer(typ.Get("reflectType").Unsafe())) -} - -func setKindType(rt *rtype, kindType any) { - js.InternalObject(rt).Set("kindType", js.InternalObject(kindType)) - js.InternalObject(kindType).Set("rtype", js.InternalObject(rt)) -} - -type uncommonType struct { - pkgPath nameOff - mcount uint16 - xcount uint16 - moff uint32 - - _methods []method -} - -func (t *uncommonType) methods() []method { - return t._methods -} - -func (t *uncommonType) exportedMethods() []method { - return t._methods[:t.xcount:t.xcount] -} - -func (t *rtype) uncommon() *uncommonType { - obj := js.InternalObject(t).Get("uncommonType") - if obj == js.Undefined { - return nil - } - return (*uncommonType)(unsafe.Pointer(obj.Unsafe())) -} - -type funcType struct { - rtype `reflect:"func"` - inCount uint16 - outCount uint16 - - _in []*rtype - _out []*rtype -} - -func (t *funcType) in() []*rtype { - return t._in -} - -func (t *funcType) out() []*rtype { - return t._out +//gopherjs:new +func jsType(typ Type) *js.Object { + return toAbiType(typ).JsType() } -type name struct { - bytes *byte -} +//gopherjs:purge +func addReflectOff(ptr unsafe.Pointer) int32 -type nameData struct { - name string - tag string - exported bool - embedded bool - pkgPath string +//gopherjs:replace +func (t *rtype) nameOff(off aNameOff) name { + return t.NameOff(off) } -var nameMap = make(map[*byte]*nameData) - -func (n name) name() (s string) { return nameMap[n.bytes].name } -func (n name) tag() (s string) { return nameMap[n.bytes].tag } -func (n name) pkgPath() string { return nameMap[n.bytes].pkgPath } -func (n name) isExported() bool { return nameMap[n.bytes].exported } -func (n name) embedded() bool { return nameMap[n.bytes].embedded } -func (n name) setPkgPath(pkgpath string) { - nameMap[n.bytes].pkgPath = pkgpath +//gopherjs:replace +func resolveReflectName(n abi.Name) aNameOff { + return abi.ResolveReflectName(n) } -func newName(n, tag string, exported, embedded bool) name { - b := new(byte) - nameMap[b] = &nameData{ - name: n, - tag: tag, - exported: exported, - embedded: embedded, - } - return name{ - bytes: b, - } +//gopherjs:replace +func (t *rtype) typeOff(off aTypeOff) *rtype { + return t.TypeOff(off) } -// newMethodName creates name instance for a method. -// -// Input object is expected to be an entry of the "methods" list of the -// corresponding JS type. -func newMethodName(m *js.Object) name { - b := new(byte) - nameMap[b] = &nameData{ - name: internalStr(m.Get("name")), - tag: "", - pkgPath: internalStr(m.Get("pkg")), - exported: internalStr(m.Get("pkg")) == "", - } - return name{bytes: b} +//gopherjs:replace +func resolveReflectType(t *abi.Type) aTypeOff { + return abi.ResolveReflectType(t) } -var nameOffList []name - -func (t *rtype) nameOff(off nameOff) name { - return nameOffList[int(off)] -} - -func resolveReflectName(n name) nameOff { - i := len(nameOffList) - nameOffList = append(nameOffList, n) - return nameOff(i) +//gopherjs:replace +func (t *rtype) textOff(off aTextOff) unsafe.Pointer { + return t.TextOff(off) } -var typeOffList []*rtype - -func (t *rtype) typeOff(off typeOff) *rtype { - return typeOffList[int(off)] +//gopherjs:replace +func resolveReflectText(ptr unsafe.Pointer) aTextOff { + return abi.ResolveReflectText(ptr) } -func newTypeOff(t *rtype) typeOff { - i := len(typeOffList) - typeOffList = append(typeOffList, t) - return typeOff(i) -} - -// addReflectOff adds a pointer to the reflection lookup map in the runtime. -// It returns a new ID that can be used as a typeOff or textOff, and will -// be resolved correctly. Implemented in the runtime package. -func addReflectOff(ptr unsafe.Pointer) int32 { - i := len(typeOffList) - typeOffList = append(typeOffList, (*rtype)(ptr)) - return int32(i) -} +// ==================================================================== +// ==================================================================== +// TODO: Continue Updating ============================================ +// ==================================================================== +// ==================================================================== func internalStr(strObj *js.Object) string { var c struct{ str string } diff --git a/compiler/natives/src/reflect/swapper.go b/compiler/natives/src/reflect/swapper.go index aca5ef371..e6b1ab27a 100644 --- a/compiler/natives/src/reflect/swapper.go +++ b/compiler/natives/src/reflect/swapper.go @@ -4,6 +4,7 @@ package reflect import "github.com/gopherjs/gopherjs/js" +//gopherjs:replace func Swapper(slice any) func(i, j int) { v := ValueOf(slice) if v.Kind() != Slice { diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go new file mode 100644 index 000000000..013396e76 --- /dev/null +++ b/compiler/natives/src/reflect/type.go @@ -0,0 +1,3 @@ +//go:build js + +package reflect diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go new file mode 100644 index 000000000..013396e76 --- /dev/null +++ b/compiler/natives/src/reflect/value.go @@ -0,0 +1,3 @@ +//go:build js + +package reflect From f28da7eff389321b97ace42ac886260bba020284 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 5 Feb 2026 09:52:28 -0700 Subject: [PATCH 12/48] trying to updated reflect --- compiler/natives/src/internal/abi/type.go | 30 +++++++ compiler/natives/src/internal/abi/utils.go | 80 +++++++++++++++++++ .../src/internal/reflectlite/reflectlite.go | 70 +--------------- .../natives/src/internal/reflectlite/utils.go | 30 ------- .../natives/src/internal/reflectlite/value.go | 21 ++--- compiler/natives/src/reflect/reflect.go | 80 ------------------- 6 files changed, 118 insertions(+), 193 deletions(-) create mode 100644 compiler/natives/src/internal/abi/utils.go delete mode 100644 compiler/natives/src/internal/reflectlite/utils.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index c9f5c8b6a..0d83eb4d2 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -1,3 +1,5 @@ +//go:build js + package abi import ( @@ -230,6 +232,25 @@ func (typ *Type) IsWrapped() bool { return typ.JsType().Get("wrapped").Bool() } +//gopherjs:new +var jsObjectPtr = ReflectType(js.Global.Get("$jsObjectPtr")) + +//gopherjs:new +func WrapJsObject(typ *Type, val *js.Object) *js.Object { + if typ == jsObjectPtr { + return jsObjectPtr.JsType().New(val) + } + return val +} + +//gopherjs:new +func UnwrapJsObject(typ *Type, val *js.Object) *js.Object { + if typ == jsObjectPtr { + return val.Get("object") + } + return val +} + //gopherjs:new func internalStr(strObj *js.Object) string { var c struct{ str string } @@ -386,3 +407,12 @@ func ReflectType(typ *js.Object) *Type { func setKindType(abiTyp *Type, kindType any) { js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) } + +//gopherjs:new +func CopyStruct(dst, src *js.Object, typ *Type) { + fields := typ.JsType().Get("fields") + for i := 0; i < fields.Length(); i++ { + prop := fields.Index(i).Get("prop").String() + dst.Set(prop, src.Get(prop)) + } +} diff --git a/compiler/natives/src/internal/abi/utils.go b/compiler/natives/src/internal/abi/utils.go new file mode 100644 index 000000000..752b60b98 --- /dev/null +++ b/compiler/natives/src/internal/abi/utils.go @@ -0,0 +1,80 @@ +//go:build js + +package abi + +// GOPHERJS: These utils are being added because they are common between +// reflect and reflectlite. + +//gopherjs:new +type errorString struct { + s string +} + +//gopherjs:new +func (e *errorString) Error() string { + return e.s +} + +//gopherjs:new +var ErrSyntax = &errorString{"invalid syntax"} + +//gopherjs:new Added to avoid a dependency on strconv.Unquote +func unquote(s string) (string, error) { + if len(s) < 2 { + return s, nil + } + if s[0] == '\'' || s[0] == '"' { + if s[len(s)-1] == s[0] { + return s[1 : len(s)-1], nil + } + return "", ErrSyntax + } + return s, nil +} + +//gopherjs:new +func GetJsTag(tag string) string { + for tag != "" { + // skip leading space + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // scan to colon. + // a space or a quote is a syntax error + i = 0 + for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { + i++ + } + if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // scan quoted string to find value + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if name == "js" { + value, _ := unquote(qvalue) + return value + } + } + return "" +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 872c0d6a4..ae695829b 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -24,8 +24,8 @@ func init() { } //gopherjs:new -func toAbiType(typ Type) *abi.Type { - return typ.(rtype).common() +func toAbiType(t Type) *abi.Type { + return t.(rtype).common() } //gopherjs:new @@ -37,69 +37,3 @@ func jsType(t Type) *js.Object { func jsPtrTo(t Type) *js.Object { return toAbiType(t).JsPtrTo() } - -//gopherjs:new -var jsObjectPtr = abi.ReflectType(js.Global.Get("$jsObjectPtr")) - -//gopherjs:new -func wrapJsObject(typ *abi.Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return jsObjectPtr.JsType().New(val) - } - return val -} - -//gopherjs:new -func unwrapJsObject(typ *abi.Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return val.Get("object") - } - return val -} - -//gopherjs:new -func getJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := unquote(qvalue) - return value - } - } - return "" -} diff --git a/compiler/natives/src/internal/reflectlite/utils.go b/compiler/natives/src/internal/reflectlite/utils.go deleted file mode 100644 index bdb345775..000000000 --- a/compiler/natives/src/internal/reflectlite/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build js - -package reflectlite - -//gopherjs:new -type errorString struct { - s string -} - -//gopherjs:new -func (e *errorString) Error() string { - return e.s -} - -//gopherjs:new -var ErrSyntax = &errorString{"invalid syntax"} - -//gopherjs:new -func unquote(s string) (string, error) { - if len(s) < 2 { - return s, nil - } - if s[0] == '\'' || s[0] == '"' { - if s[len(s)-1] == s[0] { - return s[1 : len(s)-1], nil - } - return "", ErrSyntax - } - return s, nil -} diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 1ad0c3348..8676b60ad 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -38,7 +38,7 @@ func (v Value) Elem() Value { fl |= flag(tt.Elem.Kind()) return Value{ typ: tt.Elem, - ptr: unsafe.Pointer(wrapJsObject(tt.Elem, val).Unsafe()), + ptr: unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), flag: fl, } @@ -60,7 +60,7 @@ func valueInterface(v Value) any { if v.typ.IsWrapped() { if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { cv := v.typ.JsType().Call("zero") - copyStruct(cv, v.object(), v.typ) + abi.CopyStruct(cv, v.object(), v.typ) return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) } return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) @@ -68,15 +68,6 @@ func valueInterface(v Value) any { return any(unsafe.Pointer(v.object().Unsafe())) } -//gopherjs:new -func copyStruct(dst, src *js.Object, typ *abi.Type) { - fields := typ.JsType().Get("fields") - for i := 0; i < fields.Length(); i++ { - prop := fields.Index(i).Get("prop").String() - dst.Set(prop, src.Get(prop)) - } -} - //gopherjs:new This is new but there are commented out references in the original code. func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { @@ -180,7 +171,7 @@ func (v Value) Set(x Value) { case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x))) case abi.Struct: - copyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) + abi.CopyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) default: js.InternalObject(v.ptr).Call("$set", x.object()) } @@ -239,13 +230,13 @@ func (v Value) Field(i int) Value { return Value{ typ: typ, ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), ).Unsafe()), flag: fl, } } - return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } //gopherjs:replace diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index d6a6ed12d..5a1f151fa 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -102,24 +102,6 @@ func resolveReflectText(ptr unsafe.Pointer) aTextOff { // ==================================================================== // ==================================================================== -func internalStr(strObj *js.Object) string { - var c struct{ str string } - js.InternalObject(c).Set("str", strObj) // get string without internalizing - return c.str -} - -func isWrapped(typ Type) bool { - return jsType(typ).Get("wrapped").Bool() -} - -func copyStruct(dst, src *js.Object, typ Type) { - fields := jsType(typ).Get("fields") - for i := 0; i < fields.Length(); i++ { - prop := fields.Index(i).Get("prop").String() - dst.Set(prop, src.Get(prop)) - } -} - func makeValue(t Type, v *js.Object, fl flag) Value { rt := t.common() if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { @@ -976,22 +958,6 @@ func (v Value) Cap() int { panic(&ValueError{"reflect.Value.Cap", k}) } -var jsObjectPtr = reflectType(js.Global.Get("$jsObjectPtr")) - -func wrapJsObject(typ Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return jsType(jsObjectPtr).New(val) - } - return val -} - -func unwrapJsObject(typ Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return val.Get("object") - } - return val -} - func (v Value) Elem() Value { switch k := v.kind(); k { case Interface: @@ -1067,52 +1033,6 @@ func (v Value) Field(i int) Value { return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) } -func getJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := strconv.Unquote(qvalue) - return value - } - } - return "" -} - func (v Value) UnsafePointer() unsafe.Pointer { return unsafe.Pointer(v.Pointer()) } From 4578fbad16ef97b16a7273199f58e149adbe409f Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 5 Feb 2026 15:57:41 -0700 Subject: [PATCH 13/48] slogging through reflect --- compiler/natives/src/internal/abi/type.go | 9 +- compiler/natives/src/internal/abi/utils.go | 28 + .../src/internal/reflectlite/reflectlite.go | 39 - .../natives/src/internal/reflectlite/type.go | 30 + .../natives/src/internal/reflectlite/value.go | 17 +- compiler/natives/src/reflect/reflect.go | 1159 ----------------- compiler/natives/src/reflect/type.go | 225 ++++ compiler/natives/src/reflect/value.go | 981 ++++++++++++++ 8 files changed, 1276 insertions(+), 1212 deletions(-) delete mode 100644 compiler/natives/src/internal/reflectlite/reflectlite.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 0d83eb4d2..6a4490d01 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -235,9 +235,14 @@ func (typ *Type) IsWrapped() bool { //gopherjs:new var jsObjectPtr = ReflectType(js.Global.Get("$jsObjectPtr")) +//gopherjs:new +func IsJsObjectPtr(typ *Type) bool { + return typ == jsObjectPtr +} + //gopherjs:new func WrapJsObject(typ *Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { + if IsJsObjectPtr(typ) { return jsObjectPtr.JsType().New(val) } return val @@ -245,7 +250,7 @@ func WrapJsObject(typ *Type, val *js.Object) *js.Object { //gopherjs:new func UnwrapJsObject(typ *Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { + if IsJsObjectPtr(typ) { return val.Get("object") } return val diff --git a/compiler/natives/src/internal/abi/utils.go b/compiler/natives/src/internal/abi/utils.go index 752b60b98..d0a4fff48 100644 --- a/compiler/natives/src/internal/abi/utils.go +++ b/compiler/natives/src/internal/abi/utils.go @@ -2,6 +2,12 @@ package abi +import ( + "unsafe" + + "github.com/gopherjs/gopherjs/js" +) + // GOPHERJS: These utils are being added because they are common between // reflect and reflectlite. @@ -78,3 +84,25 @@ func GetJsTag(tag string) string { } return "" } + +//gopherjs:new +func UnsafeNew(typ *Type) unsafe.Pointer { + switch typ.Kind() { + case Struct: + return unsafe.Pointer(typ.JsType().Get("ptr").New().Unsafe()) + case Array: + return unsafe.Pointer(typ.JsType().Call("zero").Unsafe()) + default: + return unsafe.Pointer(js.Global.Call("$newDataPointer", typ.JsType().Call("zero"), typ.JsPtrTo()).Unsafe()) + } +} + +//gopherjs:new +func IfaceE2I(t *Type, src any, dst unsafe.Pointer) { + js.InternalObject(dst).Call("$set", js.InternalObject(src)) +} + +//gopherjs:new +func TypedMemMove(t *Type, dst, src unsafe.Pointer) { + js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go deleted file mode 100644 index ae695829b..000000000 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build js - -package reflectlite - -import ( - "internal/abi" - - "github.com/gopherjs/gopherjs/js" -) - -func init() { - // avoid dead code elimination - used := func(i any) {} - used(rtype{}) - used(uncommonType{}) - used(arrayType{}) - used(chanType{}) - used(funcType{}) - used(interfaceType{}) - used(mapType{}) - used(ptrType{}) - used(sliceType{}) - used(structType{}) -} - -//gopherjs:new -func toAbiType(t Type) *abi.Type { - return t.(rtype).common() -} - -//gopherjs:new -func jsType(t Type) *js.Object { - return toAbiType(t).JsType() -} - -//gopherjs:new -func jsPtrTo(t Type) *js.Object { - return toAbiType(t).JsPtrTo() -} diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 106d8f18c..ba83e77c6 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -10,6 +10,36 @@ import ( "github.com/gopherjs/gopherjs/js" ) +func init() { + // avoid dead code elimination + used := func(i any) {} + used(rtype{}) + used(uncommonType{}) + used(arrayType{}) + used(chanType{}) + used(funcType{}) + used(interfaceType{}) + used(mapType{}) + used(ptrType{}) + used(sliceType{}) + used(structType{}) +} + +//gopherjs:new +func toAbiType(t Type) *abi.Type { + return t.(rtype).common() +} + +//gopherjs:new +func jsType(t Type) *js.Object { + return toAbiType(t).JsType() +} + +//gopherjs:new +func jsPtrTo(t Type) *js.Object { + return toAbiType(t).JsPtrTo() +} + // GOPHERJS: For some reason the original code left mapType and aliased the rest // to the ABI version. mapType is not used so this is an alias to override the // left over refactor cruft. diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 8676b60ad..85a9cafd4 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -204,10 +204,10 @@ func (v Value) Field(i int) Value { } if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { - if jsTag := getJsTag(tag); jsTag != "" { + if jsTag := abi.GetJsTag(tag); jsTag != "" { for { v = v.Field(0) - if v.typ == jsObjectPtr { + if abi.IsJsObjectPtr(v.typ) { o := v.object().Get("object") return Value{ typ: typ, @@ -241,14 +241,7 @@ func (v Value) Field(i int) Value { //gopherjs:replace func unsafe_New(typ *abi.Type) unsafe.Pointer { - switch typ.Kind() { - case abi.Struct: - return unsafe.Pointer(typ.JsType().Get("ptr").New().Unsafe()) - case abi.Array: - return unsafe.Pointer(typ.JsType().Call("zero").Unsafe()) - default: - return unsafe.Pointer(js.Global.Call("$newDataPointer", typ.JsType().Call("zero"), typ.JsPtrTo()).Unsafe()) - } + return abi.UnsafeNew(typ) } //gopherjs:replace @@ -350,10 +343,10 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va //gopherjs:replace func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src)) + abi.IfaceE2I(t, src, dst) } //gopherjs:replace func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) + abi.TypedMemMove(t, dst, src) } diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 5a1f151fa..bc466ba8c 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -3,535 +3,13 @@ package reflect import ( - "errors" - "strconv" "unsafe" "internal/itoa" - "github.com/gopherjs/gopherjs/compiler/natives/src/internal/abi" "github.com/gopherjs/gopherjs/js" ) -var initialized = false - -func init() { - // avoid dead code elimination - used := func(i any) {} - used(rtype{}) - used(uncommonType{}) - used(method{}) - used(arrayType{}) - used(chanType{}) - used(funcType{}) - used(interfaceType{}) - used(mapType{}) - used(ptrType{}) - used(sliceType{}) - used(structType{}) - used(imethod{}) - used(structField{}) - - initialized = true - uint8Type = TypeOf(uint8(0)).(*rtype) // set for real -} - -// New returns a Value representing a pointer to a new zero value -// for the specified type. That is, the returned Value's Type is PtrTo(typ). -// -// The upstream version includes an extra check to avoid creating types that -// are tagged as go:notinheap. This shouldn't matter in GopherJS, and tracking -// that state is over-complex, so we just skip that check. -func New(typ Type) Value { - if typ == nil { - panic("reflect: New(nil)") - } - t := typ.(*rtype) - pt := t.ptrTo() - ptr := unsafe_New(t) - fl := flag(Ptr) - return Value{pt, ptr, fl} -} - -//gopherjs:new -func toAbiType(typ Type) *abi.Type { - return typ.(*rtype).common() -} - -//gopherjs:new -func jsType(typ Type) *js.Object { - return toAbiType(typ).JsType() -} - -//gopherjs:purge -func addReflectOff(ptr unsafe.Pointer) int32 - -//gopherjs:replace -func (t *rtype) nameOff(off aNameOff) name { - return t.NameOff(off) -} - -//gopherjs:replace -func resolveReflectName(n abi.Name) aNameOff { - return abi.ResolveReflectName(n) -} - -//gopherjs:replace -func (t *rtype) typeOff(off aTypeOff) *rtype { - return t.TypeOff(off) -} - -//gopherjs:replace -func resolveReflectType(t *abi.Type) aTypeOff { - return abi.ResolveReflectType(t) -} - -//gopherjs:replace -func (t *rtype) textOff(off aTextOff) unsafe.Pointer { - return t.TextOff(off) -} - -//gopherjs:replace -func resolveReflectText(ptr unsafe.Pointer) aTextOff { - return abi.ResolveReflectText(ptr) -} - -// ==================================================================== -// ==================================================================== -// TODO: Continue Updating ============================================ -// ==================================================================== -// ==================================================================== - -func makeValue(t Type, v *js.Object, fl flag) Value { - rt := t.common() - if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { - return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} - } - return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} -} - -func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { - panic("reflect.MakeSlice of non-slice type") - } - if len < 0 { - panic("reflect.MakeSlice: negative len") - } - if cap < 0 { - panic("reflect.MakeSlice: negative cap") - } - if len > cap { - panic("reflect.MakeSlice: len > cap") - } - - return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) -} - -func TypeOf(i any) Type { - if !initialized { // avoid error of uint8Type - return &rtype{} - } - if i == nil { - return nil - } - return reflectType(js.InternalObject(i).Get("constructor")) -} - -func ValueOf(i any) Value { - if i == nil { - return Value{} - } - return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) -} - -func ArrayOf(count int, elem Type) Type { - if count < 0 { - panic("reflect: negative length passed to ArrayOf") - } - - return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) -} - -func ChanOf(dir ChanDir, t Type) Type { - return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) -} - -func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { - panic("reflect.FuncOf: last arg of variadic func must be slice") - } - - jsIn := make([]*js.Object, len(in)) - for i, v := range in { - jsIn[i] = jsType(v) - } - jsOut := make([]*js.Object, len(out)) - for i, v := range out { - jsOut[i] = jsType(v) - } - return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) -} - -func MapOf(key, elem Type) Type { - switch key.Kind() { - case Func, Map, Slice: - panic("reflect.MapOf: invalid key type " + key.String()) - } - - return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) -} - -func (t *rtype) ptrTo() *rtype { - return reflectType(js.Global.Call("$ptrType", jsType(t))) -} - -func SliceOf(t Type) Type { - return reflectType(js.Global.Call("$sliceType", jsType(t))) -} - -func StructOf(fields []StructField) Type { - var ( - jsFields = make([]*js.Object, len(fields)) - fset = map[string]struct{}{} - pkgpath string - hasGCProg bool - ) - for i, field := range fields { - if field.Name == "" { - panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no name") - } - if !isValidFieldName(field.Name) { - panic("reflect.StructOf: field " + strconv.Itoa(i) + " has invalid name") - } - if field.Type == nil { - panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") - } - f, fpkgpath := runtimeStructField(field) - ft := f.typ - if ft.kind&kindGCProg != 0 { - hasGCProg = true - } - if fpkgpath != "" { - if pkgpath == "" { - pkgpath = fpkgpath - } else if pkgpath != fpkgpath { - panic("reflect.Struct: fields with different PkgPath " + pkgpath + " and " + fpkgpath) - } - } - name := field.Name - if f.embedded() { - // Embedded field - if field.Type.Kind() == Ptr { - // Embedded ** and *interface{} are illegal - elem := field.Type.Elem() - if k := elem.Kind(); k == Ptr || k == Interface { - panic("reflect.StructOf: illegal anonymous field type " + field.Type.String()) - } - } - switch field.Type.Kind() { - case Interface: - case Ptr: - ptr := (*ptrType)(unsafe.Pointer(ft)) - if unt := ptr.uncommon(); unt != nil { - if i > 0 && unt.mcount > 0 { - // Issue 15924. - panic("reflect: embedded type with methods not implemented if type is not first field") - } - if len(fields) > 1 { - panic("reflect: embedded type with methods not implemented if there is more than one field") - } - } - default: - if unt := ft.uncommon(); unt != nil { - if i > 0 && unt.mcount > 0 { - // Issue 15924. - panic("reflect: embedded type with methods not implemented if type is not first field") - } - if len(fields) > 1 && ft.kind&kindDirectIface != 0 { - panic("reflect: embedded type with methods not implemented for non-pointer type") - } - } - } - } - - if _, dup := fset[name]; dup && name != "_" { - panic("reflect.StructOf: duplicate field " + name) - } - fset[name] = struct{}{} - // To be consistent with Compiler's behavior we need to avoid externalizing - // the "name" property. The line below is effectively an inverse of the - // internalStr() function. - jsf := js.InternalObject(struct{ name string }{name}) - // The rest is set through the js.Object() interface, which the compiler will - // externalize for us. - jsf.Set("prop", name) - jsf.Set("exported", f.name.isExported()) - jsf.Set("typ", jsType(field.Type)) - jsf.Set("tag", field.Tag) - jsf.Set("embedded", field.Anonymous) - jsFields[i] = jsf - } - _ = hasGCProg - typ := js.Global.Call("$structType", "", jsFields) - if pkgpath != "" { - typ.Set("pkgPath", pkgpath) - } - return reflectType(typ) -} - -func Zero(typ Type) Value { - return makeValue(typ, jsType(typ).Call("zero"), 0) -} - -func unsafe_New(typ *rtype) unsafe.Pointer { - switch typ.Kind() { - case Struct: - return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) - case Array: - return unsafe.Pointer(jsType(typ).Call("zero").Unsafe()) - default: - return unsafe.Pointer(js.Global.Call("$newDataPointer", jsType(typ).Call("zero"), jsType(typ.ptrTo())).Unsafe()) - } -} - -func makeInt(f flag, bits uint64, t Type) Value { - typ := t.common() - ptr := unsafe_New(typ) - switch typ.Kind() { - case Int8: - *(*int8)(ptr) = int8(bits) - case Int16: - *(*int16)(ptr) = int16(bits) - case Int, Int32: - *(*int32)(ptr) = int32(bits) - case Int64: - *(*int64)(ptr) = int64(bits) - case Uint8: - *(*uint8)(ptr) = uint8(bits) - case Uint16: - *(*uint16)(ptr) = uint16(bits) - case Uint, Uint32, Uintptr: - *(*uint32)(ptr) = uint32(bits) - case Uint64: - *(*uint64)(ptr) = uint64(bits) - } - return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} -} - -func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { - if typ.Kind() != Func { - panic("reflect: call of MakeFunc with non-Func type") - } - - t := typ.common() - ftyp := (*funcType)(unsafe.Pointer(t)) - - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - // Convert raw JS arguments into []Value the user-supplied function expects. - args := make([]Value, ftyp.NumIn()) - for i := range args { - argType := ftyp.In(i).common() - args[i] = makeValue(argType, arguments[i], 0) - } - - // Call the user-supplied function. - resultsSlice := fn(args) - - // Verify that returned value types are compatible with the function type specified by the caller. - if want, got := ftyp.NumOut(), len(resultsSlice); want != got { - panic("reflect: expected " + strconv.Itoa(want) + " return values, got " + strconv.Itoa(got)) - } - for i, rtyp := range ftyp.out() { - if !resultsSlice[i].Type().AssignableTo(rtyp) { - panic("reflect: " + strconv.Itoa(i) + " return value type is not compatible with the function declaration") - } - } - - // Rearrange return values according to the expected function signature. - switch ftyp.NumOut() { - case 0: - return nil - case 1: - return resultsSlice[0].object() - default: - results := js.Global.Get("Array").New(ftyp.NumOut()) - for i, r := range resultsSlice { - results.SetIndex(i, r.object()) - } - return results - } - }) - - return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} -} - -func typedmemmove(t *rtype, dst, src unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) -} - -func loadScalar(p unsafe.Pointer, n uintptr) uintptr { - return js.InternalObject(p).Call("$get").Unsafe() -} - -func makechan(typ *rtype, size int) (ch unsafe.Pointer) { - ctyp := (*chanType)(unsafe.Pointer(typ)) - return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.elem), size).Unsafe()) -} - -func makemap(t *rtype, cap int) (m unsafe.Pointer) { - return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) -} - -func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, *js.Object) { - kv := js.InternalObject(key) - if kv.Get("$get") != js.Undefined { - kv = kv.Call("$get") - } - k := jsType(t.Key()).Call("keyFor", kv) - return kv, k -} - -func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { - if !js.InternalObject(m).Bool() { - return nil // nil map - } - _, k := keyFor(t, key) - entry := js.InternalObject(m).Call("get", k) - if entry == js.Undefined { - return nil - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) -} - -func mapassign(t *rtype, m, key, val unsafe.Pointer) { - kv, k := keyFor(t, key) - jsVal := js.InternalObject(val).Call("$get") - et := t.Elem() - if et.Kind() == Struct { - newVal := jsType(et).Call("zero") - copyStruct(newVal, jsVal, et) - jsVal = newVal - } - entry := js.Global.Get("Object").New() - entry.Set("k", kv) - entry.Set("v", jsVal) - js.InternalObject(m).Call("set", k, entry) -} - -func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) { - _, k := keyFor(t, key) - if !js.InternalObject(m).Bool() { - return // nil map - } - js.InternalObject(m).Call("delete", k) -} - -// TODO(nevkonatkte): The following three "faststr" implementations are meant to -// perform better for the common case of string-keyed maps (see upstream: -// https://github.com/golang/go/commit/23832ba2e2fb396cda1dacf3e8afcb38ec36dcba) -// However, the stubs below will perform the same or worse because of the extra -// string-to-pointer conversion. Not sure how to fix this without significant -// code duplication, however. - -func mapaccess_faststr(t *rtype, m unsafe.Pointer, key string) (val unsafe.Pointer) { - return mapaccess(t, m, unsafe.Pointer(&key)) -} - -func mapassign_faststr(t *rtype, m unsafe.Pointer, key string, val unsafe.Pointer) { - mapassign(t, m, unsafe.Pointer(&key), val) -} - -func mapdelete_faststr(t *rtype, m unsafe.Pointer, key string) { - mapdelete(t, m, unsafe.Pointer(&key)) -} - -type hiter struct { - t Type - m *js.Object // Underlying map object. - keys *js.Object - i int - - // last is the last object the iterator indicates. If this object exists, the - // functions that return the current key or value returns this object, - // regardless of the current iterator. It is because the current iterator - // might be stale due to key deletion in a loop. - last *js.Object -} - -func (iter *hiter) skipUntilValidKey() { - for iter.i < iter.keys.Length() { - k := iter.keys.Index(iter.i) - entry := iter.m.Call("get", k) - if entry != js.Undefined { - break - } - // The key is already deleted. Move on the next item. - iter.i++ - } -} - -func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter) { - mapObj := js.InternalObject(m) - keys := js.Global.Get("Array").New() - if mapObj.Get("keys") != js.Undefined { - keysIter := mapObj.Call("keys") - if mapObj.Get("keys") != js.Undefined { - keys = js.Global.Get("Array").Call("from", keysIter) - } - } - - *it = hiter{ - t: t, - m: mapObj, - keys: keys, - i: 0, - last: nil, - } -} - -func mapiterkey(it *hiter) unsafe.Pointer { - var kv *js.Object - if it.last != nil { - kv = it.last - } else { - it.skipUntilValidKey() - if it.i == it.keys.Length() { - return nil - } - k := it.keys.Index(it.i) - kv = it.m.Call("get", k) - - // Record the key-value pair for later accesses. - it.last = kv - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(it.t.Key()))).Unsafe()) -} - -func mapiterelem(it *hiter) unsafe.Pointer { - var kv *js.Object - if it.last != nil { - kv = it.last - } else { - it.skipUntilValidKey() - if it.i == it.keys.Length() { - return nil - } - k := it.keys.Index(it.i) - kv = it.m.Call("get", k) - it.last = kv - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), jsType(PtrTo(it.t.Elem()))).Unsafe()) -} - -func mapiternext(it *hiter) { - it.last = nil - it.i++ -} - -func maplen(m unsafe.Pointer) int { - return js.InternalObject(m).Get("size").Int() -} - func cvtDirect(v Value, typ Type) Value { srcVal := v.object() if srcVal == jsType(v.typ).Get("nil") { @@ -642,39 +120,6 @@ func Copy(dst, src Value) int { return js.Global.Call("$copySlice", dstVal, srcVal).Int() } -func methodReceiver(op string, v Value, i int) (_ *rtype, t *funcType, fn unsafe.Pointer) { - var prop string - if v.typ.Kind() == Interface { - tt := (*interfaceType)(unsafe.Pointer(v.typ)) - if i < 0 || i >= len(tt.methods) { - panic("reflect: internal error: invalid method index") - } - m := &tt.methods[i] - if !tt.nameOff(m.name).isExported() { - panic("reflect: " + op + " of unexported method") - } - t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ))) - prop = tt.nameOff(m.name).name() - } else { - ms := v.typ.exportedMethods() - if uint(i) >= uint(len(ms)) { - panic("reflect: internal error: invalid method index") - } - m := ms[i] - if !v.typ.nameOff(m.name).isExported() { - panic("reflect: " + op + " of unexported method") - } - t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp))) - prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() - } - rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) - } - fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) - return -} - func valueInterface(v Value, safe bool) any { if v.flag == 0 { panic(&ValueError{"reflect.Value.Interface", 0}) @@ -697,10 +142,6 @@ func valueInterface(v Value, safe bool) any { return any(unsafe.Pointer(v.object().Unsafe())) } -func ifaceE2I(t *rtype, src any, dst unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src)) -} - func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { panic("reflect: internal error: invalid use of makePartialFunc") @@ -779,596 +220,6 @@ func (t *rtype) Method(i int) (m Method) { return m } -func (v Value) object() *js.Object { - if v.typ.Kind() == Array || v.typ.Kind() == Struct { - return js.InternalObject(v.ptr) - } - if v.flag&flagIndir != 0 { - val := js.InternalObject(v.ptr).Call("$get") - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { - switch v.typ.Kind() { - case Uint64, Int64: - val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) - case Complex64, Complex128: - val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) - case Slice: - if val == val.Get("constructor").Get("nil") { - val = jsType(v.typ).Get("nil") - break - } - newVal := jsType(v.typ).New(val.Get("$array")) - newVal.Set("$offset", val.Get("$offset")) - newVal.Set("$length", val.Get("$length")) - newVal.Set("$capacity", val.Get("$capacity")) - val = newVal - } - } - return js.InternalObject(val.Unsafe()) - } - return js.InternalObject(v.ptr) -} - -func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { - if v.flag&flagMethod != 0 { - v = makeMethodValue(context, v) - } - - switch { - case directlyAssignable(dst, v.typ): - // Overwrite type so that they match. - // Same memory layout, so no harm done. - fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() - fl |= flag(dst.Kind()) - return Value{dst, v.ptr, fl} - - case implements(dst, v.typ): - if target == nil { - target = unsafe_New(dst) - } - // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement - // from upstream. ifaceE2I below does not panic, and it needs - // to run, given its custom implementation. - x := valueInterface(v, false) - if dst.NumMethod() == 0 { - *(*any)(target) = x - } else { - ifaceE2I(dst, x, target) - } - return Value{dst, target, flagIndir | flag(Interface)} - } - - // Failed. - panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) -} - -var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) - -func (v Value) call(op string, in []Value) []Value { - var ( - t *funcType - fn unsafe.Pointer - rcvr *js.Object - ) - if v.flag&flagMethod != 0 { - _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr = v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) - } - } else { - t = (*funcType)(unsafe.Pointer(v.typ)) - fn = unsafe.Pointer(v.object().Unsafe()) - rcvr = js.Undefined - } - - if fn == nil { - panic("reflect.Value.Call: call of nil function") - } - - isSlice := op == "CallSlice" - n := t.NumIn() - if isSlice { - if !t.IsVariadic() { - panic("reflect: CallSlice of non-variadic function") - } - if len(in) < n { - panic("reflect: CallSlice with too few input arguments") - } - if len(in) > n { - panic("reflect: CallSlice with too many input arguments") - } - } else { - if t.IsVariadic() { - n-- - } - if len(in) < n { - panic("reflect: Call with too few input arguments") - } - if !t.IsVariadic() && len(in) > n { - panic("reflect: Call with too many input arguments") - } - } - for _, x := range in { - if x.Kind() == Invalid { - panic("reflect: " + op + " using zero Value argument") - } - } - for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { - panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) - } - } - if !isSlice && t.IsVariadic() { - // prepare slice for remaining values - m := len(in) - n - slice := MakeSlice(t.In(n), m, m) - elem := t.In(n).Elem() - for i := 0; i < m; i++ { - x := in[n+i] - if xt := x.Type(); !xt.AssignableTo(elem) { - panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op) - } - slice.Index(i).Set(x) - } - origIn := in - in = make([]Value, n+1) - copy(in[:n], origIn) - in[n] = slice - } - - nin := len(in) - if nin != t.NumIn() { - panic("reflect.Value.Call: wrong argument count") - } - nout := t.NumOut() - - argsArray := js.Global.Get("Array").New(t.NumIn()) - for i, arg := range in { - argsArray.SetIndex(i, unwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i).common(), nil).object())) - } - results := callHelper(js.InternalObject(fn), rcvr, argsArray) - - switch nout { - case 0: - return nil - case 1: - return []Value{makeValue(t.Out(0), wrapJsObject(t.Out(0), results), 0)} - default: - ret := make([]Value, nout) - for i := range ret { - ret[i] = makeValue(t.Out(i), wrapJsObject(t.Out(i), results.Index(i)), 0) - } - return ret - } -} - -func (v Value) Cap() int { - k := v.kind() - switch k { - case Array: - return v.typ.Len() - case Chan, Slice: - return v.object().Get("$capacity").Int() - case Ptr: - if v.typ.Elem().Kind() == Array { - return v.typ.Elem().Len() - } - panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") - } - panic(&ValueError{"reflect.Value.Cap", k}) -} - -func (v Value) Elem() Value { - switch k := v.kind(); k { - case Interface: - val := v.object() - if val == js.Global.Get("$ifaceNil") { - return Value{} - } - typ := reflectType(val.Get("constructor")) - return makeValue(typ, val.Get("$val"), v.flag.ro()) - - case Ptr: - if v.IsNil() { - return Value{} - } - val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ)) - fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.elem.Kind()) - return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} - - default: - panic(&ValueError{"reflect.Value.Elem", k}) - } -} - -func (v Value) Field(i int) Value { - if v.kind() != Struct { - panic(&ValueError{"reflect.Value.Field", v.kind()}) - } - tt := (*structType)(unsafe.Pointer(v.typ)) - if uint(i) >= uint(len(tt.fields)) { - panic("reflect: Field index out of range") - } - - prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.fields[i] - typ := field.typ - - fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.name.isExported() { - if field.embedded() { - fl |= flagEmbedRO - } else { - fl |= flagStickyRO - } - } - - if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { - if jsTag := getJsTag(tag); jsTag != "" { - for { - v = v.Field(0) - if v.typ == jsObjectPtr { - o := v.object().Get("object") - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), - ).Unsafe()), fl} - } - if v.typ.Kind() == Ptr { - v = v.Elem() - } - } - } - } - - s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) -} - -func (v Value) UnsafePointer() unsafe.Pointer { - return unsafe.Pointer(v.Pointer()) -} - -func (v Value) grow(n int) { - if n < 0 { - panic(`reflect.Value.Grow: negative len`) - } - - s := v.object() - len := s.Get(`$length`).Int() - if len+n < 0 { - panic(`reflect.Value.Grow: slice overflow`) - } - - cap := s.Get(`$capacity`).Int() - if len+n > cap { - ns := js.Global.Call("$growSlice", s, len+n) - js.InternalObject(v.ptr).Call("$set", ns) - } -} - -// extendSlice is used by native reflect.Append and reflect.AppendSlice -// Overridden to avoid the use of `unsafeheader.Slice` since GopherJS -// uses different slice implementation. -func (v Value) extendSlice(n int) Value { - v.mustBeExported() - v.mustBe(Slice) - - s := v.object() - sNil := jsType(v.typ).Get(`nil`) - fl := flagIndir | flag(Slice) - if s == sNil && n <= 0 { - return makeValue(v.typ, wrapJsObject(v.typ, sNil), fl) - } - - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", s.Get("$length")) - newSlice.Set("$capacity", s.Get("$capacity")) - - v2 := makeValue(v.typ, wrapJsObject(v.typ, newSlice), fl) - v2.grow(n) - s2 := v2.object() - s2.Set(`$length`, s2.Get(`$length`).Int()+n) - return v2 -} - -func (v Value) Index(i int) Value { - switch k := v.kind(); k { - case Array: - tt := (*arrayType)(unsafe.Pointer(v.typ)) - if i < 0 || i > int(tt.len) { - panic("reflect: array index out of range") - } - typ := tt.elem - fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) - - a := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) - - case Slice: - s := v.object() - if i < 0 || i >= s.Get("$length").Int() { - panic("reflect: slice index out of range") - } - tt := (*sliceType)(unsafe.Pointer(v.typ)) - typ := tt.elem - fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) - - i += s.Get("$offset").Int() - a := s.Get("$array") - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) - - case String: - str := *(*string)(v.ptr) - if i < 0 || i >= len(str) { - panic("reflect: string index out of range") - } - fl := v.flag.ro() | flag(Uint8) | flagIndir - c := str[i] - return Value{uint8Type, unsafe.Pointer(&c), fl} - - default: - panic(&ValueError{"reflect.Value.Index", k}) - } -} - -func (v Value) InterfaceData() [2]uintptr { - panic(errors.New("InterfaceData is not supported by GopherJS")) -} - -func (v Value) SetZero() { - v.mustBeAssignable() - v.Set(Zero(v.typ)) -} - -func (v Value) IsNil() bool { - switch k := v.kind(); k { - case Ptr, Slice: - return v.object() == jsType(v.typ).Get("nil") - case Chan: - return v.object() == js.Global.Get("$chanNil") - case Func: - return v.object() == js.Global.Get("$throwNilPointerError") - case Map: - return v.object() == js.InternalObject(false) - case Interface: - return v.object() == js.Global.Get("$ifaceNil") - case UnsafePointer: - return v.object().Unsafe() == 0 - default: - panic(&ValueError{"reflect.Value.IsNil", k}) - } -} - -func (v Value) Len() int { - switch k := v.kind(); k { - case Array, String: - return v.object().Length() - case Slice: - return v.object().Get("$length").Int() - case Chan: - return v.object().Get("$buffer").Get("length").Int() - case Map: - return v.object().Get("size").Int() - case Ptr: - if v.typ.Elem().Kind() == Array { - return v.typ.Elem().Len() - } - panic("reflect: call of reflect.Value.Len on ptr to non-array Value") - default: - panic(&ValueError{"reflect.Value.Len", k}) - } -} - -//gopherjs:purge Not used since Len() is overridden. -func (v Value) lenNonSlice() int - -func (v Value) Pointer() uintptr { - switch k := v.kind(); k { - case Chan, Map, Ptr, UnsafePointer: - if v.IsNil() { - return 0 - } - return v.object().Unsafe() - case Func: - if v.IsNil() { - return 0 - } - return 1 - case Slice: - if v.IsNil() { - return 0 - } - return v.object().Get("$array").Unsafe() - default: - panic(&ValueError{"reflect.Value.Pointer", k}) - } -} - -func (v Value) Set(x Value) { - v.mustBeAssignable() - x.mustBeExported() - x = x.assignTo("reflect.Set", v.typ, nil) - if v.flag&flagIndir != 0 { - switch v.typ.Kind() { - case Array, Struct: - jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) - case Interface: - js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) - default: - js.InternalObject(v.ptr).Call("$set", x.object()) - } - return - } - v.ptr = x.ptr -} - -func (v Value) bytesSlow() []byte { - switch v.kind() { - case Slice: - if v.typ.Elem().Kind() != Uint8 { - panic("reflect.Value.Bytes of non-byte slice") - } - return *(*[]byte)(v.ptr) - case Array: - if v.typ.Elem().Kind() != Uint8 { - panic("reflect.Value.Bytes of non-byte array") - } - if !v.CanAddr() { - panic("reflect.Value.Bytes of unaddressable byte array") - } - // Replace the following with JS to avoid using unsafe pointers. - // p := (*byte)(v.ptr) - // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) - // return unsafe.Slice(p, n) - return js.InternalObject(v.ptr).Interface().([]byte) - } - panic(&ValueError{"reflect.Value.Bytes", v.kind()}) -} - -func (v Value) SetBytes(x []byte) { - v.mustBeAssignable() - v.mustBe(Slice) - if v.typ.Elem().Kind() != Uint8 { - panic("reflect.Value.SetBytes of non-byte slice") - } - slice := js.InternalObject(x) - if v.typ.Name() != "" || v.typ.Elem().Name() != "" { - typedSlice := jsType(v.typ).New(slice.Get("$array")) - typedSlice.Set("$offset", slice.Get("$offset")) - typedSlice.Set("$length", slice.Get("$length")) - typedSlice.Set("$capacity", slice.Get("$capacity")) - slice = typedSlice - } - js.InternalObject(v.ptr).Call("$set", slice) -} - -func (v Value) SetCap(n int) { - v.mustBeAssignable() - v.mustBe(Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { - panic("reflect: slice capacity out of range in SetCap") - } - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", s.Get("$length")) - newSlice.Set("$capacity", n) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -func (v Value) SetLen(n int) { - v.mustBeAssignable() - v.mustBe(Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < 0 || n > s.Get("$capacity").Int() { - panic("reflect: slice length out of range in SetLen") - } - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", n) - newSlice.Set("$capacity", s.Get("$capacity")) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -func (v Value) Slice(i, j int) Value { - var ( - cap int - typ Type - s *js.Object - ) - switch kind := v.kind(); kind { - case Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) - - case Slice: - typ = v.typ - s = v.object() - cap = s.Get("$capacity").Int() - - case String: - str := *(*string)(v.ptr) - if i < 0 || j < i || j > len(str) { - panic("reflect.Value.Slice: string slice index out of bounds") - } - return ValueOf(str[i:j]) - - default: - panic(&ValueError{"reflect.Value.Slice", kind}) - } - - if i < 0 || j < i || j > cap { - panic("reflect.Value.Slice: slice index out of bounds") - } - - return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) -} - -func (v Value) Slice3(i, j, k int) Value { - var ( - cap int - typ Type - s *js.Object - ) - switch kind := v.kind(); kind { - case Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) - - case Slice: - typ = v.typ - s = v.object() - cap = s.Get("$capacity").Int() - - default: - panic(&ValueError{"reflect.Value.Slice3", kind}) - } - - if i < 0 || j < i || k < j || k > cap { - panic("reflect.Value.Slice3: slice index out of bounds") - } - - return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) -} - -func (v Value) Close() { - v.mustBe(Chan) - v.mustBeExported() - js.Global.Call("$close", v.object()) -} - var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { @@ -1544,13 +395,3 @@ func verifyNotInHeapPtr(p uintptr) bool { // always return true. return true } - -// typedslicecopy is implemented in prelude.js as $copySlice -// -//gopherjs:purge -func typedslicecopy(elemType *rtype, dst, src unsafeheader.Slice) int - -// growslice is implemented in prelude.js as $growSlice. -// -//gopherjs:purge -func growslice(t *rtype, old unsafeheader.Slice, num int) unsafeheader.Slice diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 013396e76..41720432c 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -1,3 +1,228 @@ //go:build js package reflect + +import ( + "strconv" + "unsafe" + + "internal/abi" + + "github.com/gopherjs/gopherjs/js" +) + +var initialized = false + +func init() { + // avoid dead code elimination + used := func(i any) {} + used(rtype{}) + used(uncommonType{}) + used(method{}) + used(arrayType{}) + used(chanType{}) + used(funcType{}) + used(interfaceType{}) + used(mapType{}) + used(ptrType{}) + used(sliceType{}) + used(structType{}) + used(imethod{}) + used(structField{}) + + initialized = true + uint8Type = TypeOf(uint8(0)).(*rtype) // set for real +} + +//gopherjd:replace +func pkgPath(n abi.Name) string { + return n.PkgPath() +} + +//gopherjs:new +func toAbiType(typ Type) *abi.Type { + return typ.(*rtype).common() +} + +//gopherjs:new +func jsType(typ Type) *js.Object { + return toAbiType(typ).JsType() +} + +func (t *rtype) ptrTo() *abi.Type { + return toAbiType(t).PtrTo() +} + +//gopherjs:purge +func addReflectOff(ptr unsafe.Pointer) int32 + +//gopherjs:replace +func (t *rtype) nameOff(off aNameOff) abi.Name { + return t.NameOff(off) +} + +//gopherjs:replace +func resolveReflectName(n abi.Name) aNameOff { + return abi.ResolveReflectName(n) +} + +//gopherjs:replace +func (t *rtype) typeOff(off aTypeOff) *abi.Type { + return t.TypeOff(off) +} + +//gopherjs:replace +func resolveReflectType(t *abi.Type) aTypeOff { + return abi.ResolveReflectType(t) +} + +//gopherjs:replace +func (t *rtype) textOff(off aTextOff) unsafe.Pointer { + return t.TextOff(off) +} + +//gopherjs:replace +func resolveReflectText(ptr unsafe.Pointer) aTextOff { + return abi.ResolveReflectText(ptr) +} + +func TypeOf(i any) Type { + if !initialized { // avoid error of uint8Type + return &rtype{} + } + if i == nil { + return nil + } + return reflectType(js.InternalObject(i).Get("constructor")) +} + +func ArrayOf(count int, elem Type) Type { + if count < 0 { + panic("reflect: negative length passed to ArrayOf") + } + + return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) +} + +func ChanOf(dir ChanDir, t Type) Type { + return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) +} + +func FuncOf(in, out []Type, variadic bool) Type { + if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { + panic("reflect.FuncOf: last arg of variadic func must be slice") + } + + jsIn := make([]*js.Object, len(in)) + for i, v := range in { + jsIn[i] = jsType(v) + } + jsOut := make([]*js.Object, len(out)) + for i, v := range out { + jsOut[i] = jsType(v) + } + return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) +} + +func MapOf(key, elem Type) Type { + switch key.Kind() { + case Func, Map, Slice: + panic("reflect.MapOf: invalid key type " + key.String()) + } + + return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) +} + +func SliceOf(t Type) Type { + return reflectType(js.Global.Call("$sliceType", jsType(t))) +} + +func StructOf(fields []StructField) Type { + var ( + jsFields = make([]*js.Object, len(fields)) + fset = map[string]struct{}{} + pkgpath string + hasGCProg bool + ) + for i, field := range fields { + if field.Name == "" { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no name") + } + if !isValidFieldName(field.Name) { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has invalid name") + } + if field.Type == nil { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") + } + f, fpkgpath := runtimeStructField(field) + ft := f.typ + if ft.kind&kindGCProg != 0 { + hasGCProg = true + } + if fpkgpath != "" { + if pkgpath == "" { + pkgpath = fpkgpath + } else if pkgpath != fpkgpath { + panic("reflect.Struct: fields with different PkgPath " + pkgpath + " and " + fpkgpath) + } + } + name := field.Name + if f.embedded() { + // Embedded field + if field.Type.Kind() == Ptr { + // Embedded ** and *interface{} are illegal + elem := field.Type.Elem() + if k := elem.Kind(); k == Ptr || k == Interface { + panic("reflect.StructOf: illegal anonymous field type " + field.Type.String()) + } + } + switch field.Type.Kind() { + case Interface: + case Ptr: + ptr := (*ptrType)(unsafe.Pointer(ft)) + if unt := ptr.uncommon(); unt != nil { + if i > 0 && unt.mcount > 0 { + // Issue 15924. + panic("reflect: embedded type with methods not implemented if type is not first field") + } + if len(fields) > 1 { + panic("reflect: embedded type with methods not implemented if there is more than one field") + } + } + default: + if unt := ft.uncommon(); unt != nil { + if i > 0 && unt.mcount > 0 { + // Issue 15924. + panic("reflect: embedded type with methods not implemented if type is not first field") + } + if len(fields) > 1 && ft.kind&kindDirectIface != 0 { + panic("reflect: embedded type with methods not implemented for non-pointer type") + } + } + } + } + + if _, dup := fset[name]; dup && name != "_" { + panic("reflect.StructOf: duplicate field " + name) + } + fset[name] = struct{}{} + // To be consistent with Compiler's behavior we need to avoid externalizing + // the "name" property. The line below is effectively an inverse of the + // internalStr() function. + jsf := js.InternalObject(struct{ name string }{name}) + // The rest is set through the js.Object() interface, which the compiler will + // externalize for us. + jsf.Set("prop", name) + jsf.Set("exported", f.name.isExported()) + jsf.Set("typ", jsType(field.Type)) + jsf.Set("tag", field.Tag) + jsf.Set("embedded", field.Anonymous) + jsFields[i] = jsf + } + _ = hasGCProg + typ := js.Global.Call("$structType", "", jsFields) + if pkgpath != "" { + typ.Set("pkgPath", pkgpath) + } + return reflectType(typ) +} diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 013396e76..2cd52d983 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -1,3 +1,984 @@ //go:build js package reflect + +import ( + "errors" + "strconv" + "unsafe" + + "internal/abi" + + "github.com/gopherjs/gopherjs/js" +) + +// New returns a Value representing a pointer to a new zero value +// for the specified type. That is, the returned Value's Type is PtrTo(typ). +// +// The upstream version includes an extra check to avoid creating types that +// are tagged as go:notinheap. This shouldn't matter in GopherJS, and tracking +// that state is over-complex, so we just skip that check. +// +//gopherjs:replace +func New(typ Type) Value { + if typ == nil { + panic("reflect: New(nil)") + } + t := typ.(*rtype) + pt := t.ptrTo() + ptr := unsafe_New(t) + fl := flag(Pointer) + return Value{typ: pt, ptr: ptr, flag: fl} +} + +//gopherjs:replace +func ValueOf(i any) Value { + if i == nil { + return Value{} + } + return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) +} + +//gopherjs:replace +func unsafe_New(typ *abi.Type) unsafe.Pointer { + return abi.UnsafeNew(typ) +} + +func makeValue(t Type, v *js.Object, fl flag) Value { + rt := t.common() + if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { + return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} + } + return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} +} + +func MakeSlice(typ Type, len, cap int) Value { + if typ.Kind() != Slice { + panic("reflect.MakeSlice of non-slice type") + } + if len < 0 { + panic("reflect.MakeSlice: negative len") + } + if cap < 0 { + panic("reflect.MakeSlice: negative cap") + } + if len > cap { + panic("reflect.MakeSlice: len > cap") + } + + return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) +} + +func Zero(typ Type) Value { + return makeValue(typ, jsType(typ).Call("zero"), 0) +} + +func makeInt(f flag, bits uint64, t Type) Value { + typ := t.common() + ptr := unsafe_New(typ) + switch typ.Kind() { + case Int8: + *(*int8)(ptr) = int8(bits) + case Int16: + *(*int16)(ptr) = int16(bits) + case Int, Int32: + *(*int32)(ptr) = int32(bits) + case Int64: + *(*int64)(ptr) = int64(bits) + case Uint8: + *(*uint8)(ptr) = uint8(bits) + case Uint16: + *(*uint16)(ptr) = uint16(bits) + case Uint, Uint32, Uintptr: + *(*uint32)(ptr) = uint32(bits) + case Uint64: + *(*uint64)(ptr) = uint64(bits) + } + return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} +} + +//gopherjs:replace +func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { + i := methodIndex + var prop string + if v.typ.Kind() == Interface { + tt := (*interfaceType)(unsafe.Pointer(v.typ)) + if i < 0 || i >= len(tt.methods) { + panic("reflect: internal error: invalid method index") + } + m := &tt.methods[i] + if !tt.nameOff(m.name).isExported() { + panic("reflect: " + op + " of unexported method") + } + rcvrtype = iface.itab.typ + t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ))) + prop = tt.nameOff(m.name).name() + } else { + rcvrtype = v.typ() + ms := v.typ.exportedMethods() + if uint(i) >= uint(len(ms)) { + panic("reflect: internal error: invalid method index") + } + m := ms[i] + if !v.typ.nameOff(m.name).isExported() { + panic("reflect: " + op + " of unexported method") + } + t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp))) + prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() + } + rcvr := v.object() + if isWrapped(v.typ) { + rcvr = jsType(v.typ).New(rcvr) + } + fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) + return +} + +func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { + if typ.Kind() != Func { + panic("reflect: call of MakeFunc with non-Func type") + } + + t := typ.common() + ftyp := (*funcType)(unsafe.Pointer(t)) + + fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { + // Convert raw JS arguments into []Value the user-supplied function expects. + args := make([]Value, ftyp.NumIn()) + for i := range args { + argType := ftyp.In(i).common() + args[i] = makeValue(argType, arguments[i], 0) + } + + // Call the user-supplied function. + resultsSlice := fn(args) + + // Verify that returned value types are compatible with the function type specified by the caller. + if want, got := ftyp.NumOut(), len(resultsSlice); want != got { + panic("reflect: expected " + strconv.Itoa(want) + " return values, got " + strconv.Itoa(got)) + } + for i, rtyp := range ftyp.out() { + if !resultsSlice[i].Type().AssignableTo(rtyp) { + panic("reflect: " + strconv.Itoa(i) + " return value type is not compatible with the function declaration") + } + } + + // Rearrange return values according to the expected function signature. + switch ftyp.NumOut() { + case 0: + return nil + case 1: + return resultsSlice[0].object() + default: + results := js.Global.Get("Array").New(ftyp.NumOut()) + for i, r := range resultsSlice { + results.SetIndex(i, r.object()) + } + return results + } + }) + + return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} +} + +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + abi.IfaceE2I(t, src, dst) +} + +//gopherjs:replace +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { + abi.TypedMemMove(t, dst, src) +} + +//gopherjs:replace +func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { + ctyp := (*abi.ChanType)(unsafe.Pointer(typ)) + return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.Elem), size).Unsafe()) +} + +//gopherjs:replace +func makemap(t *abi.Type, cap int) (m unsafe.Pointer) { + return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) +} + +func (v Value) object() *js.Object { + if v.typ.Kind() == Array || v.typ.Kind() == Struct { + return js.InternalObject(v.ptr) + } + if v.flag&flagIndir != 0 { + val := js.InternalObject(v.ptr).Call("$get") + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { + switch v.typ.Kind() { + case Uint64, Int64: + val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) + case Complex64, Complex128: + val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) + case Slice: + if val == val.Get("constructor").Get("nil") { + val = jsType(v.typ).Get("nil") + break + } + newVal := jsType(v.typ).New(val.Get("$array")) + newVal.Set("$offset", val.Get("$offset")) + newVal.Set("$length", val.Get("$length")) + newVal.Set("$capacity", val.Get("$capacity")) + val = newVal + } + } + return js.InternalObject(val.Unsafe()) + } + return js.InternalObject(v.ptr) +} + +//gopherjs:replace +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { + if v.flag&flagMethod != 0 { + v = makeMethodValue(context, v) + } + + switch { + case directlyAssignable(dst, v.typ): + // Overwrite type so that they match. + // Same memory layout, so no harm done. + fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() + fl |= flag(dst.Kind()) + return Value{dst, v.ptr, fl} + + case implements(dst, v.typ): + if target == nil { + target = unsafe_New(dst) + } + // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement + // from upstream. ifaceE2I below does not panic, and it needs + // to run, given its custom implementation. + x := valueInterface(v, false) + if dst.NumMethod() == 0 { + *(*any)(target) = x + } else { + ifaceE2I(dst, x, target) + } + return Value{dst, target, flagIndir | flag(Interface)} + } + + // Failed. + panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) +} + +var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) + +func (v Value) call(op string, in []Value) []Value { + var ( + t *funcType + fn unsafe.Pointer + rcvr *js.Object + ) + if v.flag&flagMethod != 0 { + _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) + rcvr = v.object() + if isWrapped(v.typ) { + rcvr = jsType(v.typ).New(rcvr) + } + } else { + t = (*funcType)(unsafe.Pointer(v.typ)) + fn = unsafe.Pointer(v.object().Unsafe()) + rcvr = js.Undefined + } + + if fn == nil { + panic("reflect.Value.Call: call of nil function") + } + + isSlice := op == "CallSlice" + n := t.NumIn() + if isSlice { + if !t.IsVariadic() { + panic("reflect: CallSlice of non-variadic function") + } + if len(in) < n { + panic("reflect: CallSlice with too few input arguments") + } + if len(in) > n { + panic("reflect: CallSlice with too many input arguments") + } + } else { + if t.IsVariadic() { + n-- + } + if len(in) < n { + panic("reflect: Call with too few input arguments") + } + if !t.IsVariadic() && len(in) > n { + panic("reflect: Call with too many input arguments") + } + } + for _, x := range in { + if x.Kind() == Invalid { + panic("reflect: " + op + " using zero Value argument") + } + } + for i := 0; i < n; i++ { + if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { + panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) + } + } + if !isSlice && t.IsVariadic() { + // prepare slice for remaining values + m := len(in) - n + slice := MakeSlice(t.In(n), m, m) + elem := t.In(n).Elem() + for i := 0; i < m; i++ { + x := in[n+i] + if xt := x.Type(); !xt.AssignableTo(elem) { + panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op) + } + slice.Index(i).Set(x) + } + origIn := in + in = make([]Value, n+1) + copy(in[:n], origIn) + in[n] = slice + } + + nin := len(in) + if nin != t.NumIn() { + panic("reflect.Value.Call: wrong argument count") + } + nout := t.NumOut() + + argsArray := js.Global.Get("Array").New(t.NumIn()) + for i, arg := range in { + argsArray.SetIndex(i, unwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i).common(), nil).object())) + } + results := callHelper(js.InternalObject(fn), rcvr, argsArray) + + switch nout { + case 0: + return nil + case 1: + return []Value{makeValue(t.Out(0), wrapJsObject(t.Out(0), results), 0)} + default: + ret := make([]Value, nout) + for i := range ret { + ret[i] = makeValue(t.Out(i), wrapJsObject(t.Out(i), results.Index(i)), 0) + } + return ret + } +} + +func (v Value) Cap() int { + k := v.kind() + switch k { + case Array: + return v.typ.Len() + case Chan, Slice: + return v.object().Get("$capacity").Int() + case Ptr: + if v.typ.Elem().Kind() == Array { + return v.typ.Elem().Len() + } + panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") + } + panic(&ValueError{"reflect.Value.Cap", k}) +} + +func (v Value) Elem() Value { + switch k := v.kind(); k { + case Interface: + val := v.object() + if val == js.Global.Get("$ifaceNil") { + return Value{} + } + typ := reflectType(val.Get("constructor")) + return makeValue(typ, val.Get("$val"), v.flag.ro()) + + case Ptr: + if v.IsNil() { + return Value{} + } + val := v.object() + tt := (*ptrType)(unsafe.Pointer(v.typ)) + fl := v.flag&flagRO | flagIndir | flagAddr + fl |= flag(tt.elem.Kind()) + return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} + + default: + panic(&ValueError{"reflect.Value.Elem", k}) + } +} + +func (v Value) Field(i int) Value { + if v.kind() != Struct { + panic(&ValueError{"reflect.Value.Field", v.kind()}) + } + tt := (*structType)(unsafe.Pointer(v.typ)) + if uint(i) >= uint(len(tt.fields)) { + panic("reflect: Field index out of range") + } + + prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() + field := &tt.fields[i] + typ := field.typ + + fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) + if !field.name.isExported() { + if field.embedded() { + fl |= flagEmbedRO + } else { + fl |= flagStickyRO + } + } + + if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { + if jsTag := getJsTag(tag); jsTag != "" { + for { + v = v.Field(0) + if v.typ == jsObjectPtr { + o := v.object().Get("object") + return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), + ).Unsafe()), fl} + } + if v.typ.Kind() == Ptr { + v = v.Elem() + } + } + } + } + + s := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { + return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) +} + +func (v Value) UnsafePointer() unsafe.Pointer { + return unsafe.Pointer(v.Pointer()) +} + +func (v Value) grow(n int) { + if n < 0 { + panic(`reflect.Value.Grow: negative len`) + } + + s := v.object() + len := s.Get(`$length`).Int() + if len+n < 0 { + panic(`reflect.Value.Grow: slice overflow`) + } + + cap := s.Get(`$capacity`).Int() + if len+n > cap { + ns := js.Global.Call("$growSlice", s, len+n) + js.InternalObject(v.ptr).Call("$set", ns) + } +} + +// extendSlice is used by native reflect.Append and reflect.AppendSlice +// Overridden to avoid the use of `unsafeheader.Slice` since GopherJS +// uses different slice implementation. +// +//gopherjs:replace +func (v Value) extendSlice(n int) Value { + v.mustBeExported() + v.mustBe(Slice) + + s := v.object() + sNil := jsType(v.typ).Get(`nil`) + fl := flagIndir | flag(Slice) + if s == sNil && n <= 0 { + return makeValue(v.typ, wrapJsObject(v.typ, sNil), fl) + } + + newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice.Set("$offset", s.Get("$offset")) + newSlice.Set("$length", s.Get("$length")) + newSlice.Set("$capacity", s.Get("$capacity")) + + v2 := makeValue(v.typ, wrapJsObject(v.typ, newSlice), fl) + v2.grow(n) + s2 := v2.object() + s2.Set(`$length`, s2.Get(`$length`).Int()+n) + return v2 +} + +//gopherjs:replace +func (v Value) Clear() { + switch v.Kind() { + case Slice: + sh := *(*unsafeheader.Slice)(v.ptr) + st := (*sliceType)(unsafe.Pointer(v.typ())) + typedarrayclear(st.Elem, sh.Data, sh.Len) + case Map: + mapclear(v.typ(), v.pointer()) + default: + panic(&ValueError{Method: "reflect.Value.Clear", Kind: v.Kind()}) + } +} + +func (v Value) Index(i int) Value { + switch k := v.kind(); k { + case Array: + tt := (*arrayType)(unsafe.Pointer(v.typ)) + if i < 0 || i > int(tt.len) { + panic("reflect: array index out of range") + } + typ := tt.elem + fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) + + a := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { + return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + + case Slice: + s := v.object() + if i < 0 || i >= s.Get("$length").Int() { + panic("reflect: slice index out of range") + } + tt := (*sliceType)(unsafe.Pointer(v.typ)) + typ := tt.elem + fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) + + i += s.Get("$offset").Int() + a := s.Get("$array") + if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { + return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( + js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + + case String: + str := *(*string)(v.ptr) + if i < 0 || i >= len(str) { + panic("reflect: string index out of range") + } + fl := v.flag.ro() | flag(Uint8) | flagIndir + c := str[i] + return Value{uint8Type, unsafe.Pointer(&c), fl} + + default: + panic(&ValueError{"reflect.Value.Index", k}) + } +} + +func (v Value) InterfaceData() [2]uintptr { + panic(errors.New("InterfaceData is not supported by GopherJS")) +} + +func (v Value) SetZero() { + v.mustBeAssignable() + v.Set(Zero(v.typ)) +} + +func (v Value) IsNil() bool { + switch k := v.kind(); k { + case Ptr, Slice: + return v.object() == jsType(v.typ).Get("nil") + case Chan: + return v.object() == js.Global.Get("$chanNil") + case Func: + return v.object() == js.Global.Get("$throwNilPointerError") + case Map: + return v.object() == js.InternalObject(false) + case Interface: + return v.object() == js.Global.Get("$ifaceNil") + case UnsafePointer: + return v.object().Unsafe() == 0 + default: + panic(&ValueError{"reflect.Value.IsNil", k}) + } +} + +func (v Value) Len() int { + switch k := v.kind(); k { + case Array, String: + return v.object().Length() + case Slice: + return v.object().Get("$length").Int() + case Chan: + return v.object().Get("$buffer").Get("length").Int() + case Map: + return v.object().Get("size").Int() + case Ptr: + if v.typ.Elem().Kind() == Array { + return v.typ.Elem().Len() + } + panic("reflect: call of reflect.Value.Len on ptr to non-array Value") + default: + panic(&ValueError{"reflect.Value.Len", k}) + } +} + +//gopherjs:purge Not used since Len() is overridden. +func (v Value) lenNonSlice() int + +func (v Value) Pointer() uintptr { + switch k := v.kind(); k { + case Chan, Map, Ptr, UnsafePointer: + if v.IsNil() { + return 0 + } + return v.object().Unsafe() + case Func: + if v.IsNil() { + return 0 + } + return 1 + case Slice: + if v.IsNil() { + return 0 + } + return v.object().Get("$array").Unsafe() + default: + panic(&ValueError{"reflect.Value.Pointer", k}) + } +} + +func (v Value) Set(x Value) { + v.mustBeAssignable() + x.mustBeExported() + x = x.assignTo("reflect.Set", v.typ, nil) + if v.flag&flagIndir != 0 { + switch v.typ.Kind() { + case Array, Struct: + jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) + case Interface: + js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) + default: + js.InternalObject(v.ptr).Call("$set", x.object()) + } + return + } + v.ptr = x.ptr +} + +func (v Value) bytesSlow() []byte { + switch v.kind() { + case Slice: + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.Bytes of non-byte slice") + } + return *(*[]byte)(v.ptr) + case Array: + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.Bytes of non-byte array") + } + if !v.CanAddr() { + panic("reflect.Value.Bytes of unaddressable byte array") + } + // Replace the following with JS to avoid using unsafe pointers. + // p := (*byte)(v.ptr) + // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) + // return unsafe.Slice(p, n) + return js.InternalObject(v.ptr).Interface().([]byte) + } + panic(&ValueError{"reflect.Value.Bytes", v.kind()}) +} + +func (v Value) SetBytes(x []byte) { + v.mustBeAssignable() + v.mustBe(Slice) + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.SetBytes of non-byte slice") + } + slice := js.InternalObject(x) + if v.typ.Name() != "" || v.typ.Elem().Name() != "" { + typedSlice := jsType(v.typ).New(slice.Get("$array")) + typedSlice.Set("$offset", slice.Get("$offset")) + typedSlice.Set("$length", slice.Get("$length")) + typedSlice.Set("$capacity", slice.Get("$capacity")) + slice = typedSlice + } + js.InternalObject(v.ptr).Call("$set", slice) +} + +func (v Value) SetCap(n int) { + v.mustBeAssignable() + v.mustBe(Slice) + s := js.InternalObject(v.ptr).Call("$get") + if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { + panic("reflect: slice capacity out of range in SetCap") + } + newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice.Set("$offset", s.Get("$offset")) + newSlice.Set("$length", s.Get("$length")) + newSlice.Set("$capacity", n) + js.InternalObject(v.ptr).Call("$set", newSlice) +} + +func (v Value) SetLen(n int) { + v.mustBeAssignable() + v.mustBe(Slice) + s := js.InternalObject(v.ptr).Call("$get") + if n < 0 || n > s.Get("$capacity").Int() { + panic("reflect: slice length out of range in SetLen") + } + newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice.Set("$offset", s.Get("$offset")) + newSlice.Set("$length", n) + newSlice.Set("$capacity", s.Get("$capacity")) + js.InternalObject(v.ptr).Call("$set", newSlice) +} + +func (v Value) Slice(i, j int) Value { + var ( + cap int + typ Type + s *js.Object + ) + switch kind := v.kind(); kind { + case Array: + if v.flag&flagAddr == 0 { + panic("reflect.Value.Slice: slice of unaddressable array") + } + tt := (*arrayType)(unsafe.Pointer(v.typ)) + cap = int(tt.len) + typ = SliceOf(tt.elem) + s = jsType(typ).New(v.object()) + + case Slice: + typ = v.typ + s = v.object() + cap = s.Get("$capacity").Int() + + case String: + str := *(*string)(v.ptr) + if i < 0 || j < i || j > len(str) { + panic("reflect.Value.Slice: string slice index out of bounds") + } + return ValueOf(str[i:j]) + + default: + panic(&ValueError{"reflect.Value.Slice", kind}) + } + + if i < 0 || j < i || j > cap { + panic("reflect.Value.Slice: slice index out of bounds") + } + + return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) +} + +func (v Value) Slice3(i, j, k int) Value { + var ( + cap int + typ Type + s *js.Object + ) + switch kind := v.kind(); kind { + case Array: + if v.flag&flagAddr == 0 { + panic("reflect.Value.Slice: slice of unaddressable array") + } + tt := (*arrayType)(unsafe.Pointer(v.typ)) + cap = int(tt.len) + typ = SliceOf(tt.elem) + s = jsType(typ).New(v.object()) + + case Slice: + typ = v.typ + s = v.object() + cap = s.Get("$capacity").Int() + + default: + panic(&ValueError{"reflect.Value.Slice3", kind}) + } + + if i < 0 || j < i || k < j || k > cap { + panic("reflect.Value.Slice3: slice index out of bounds") + } + + return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) +} + +func (v Value) Close() { + v.mustBe(Chan) + v.mustBeExported() + js.Global.Call("$close", v.object()) +} + +// typedslicecopy is implemented in prelude.js as $copySlice +// +//gopherjs:purge +func typedslicecopy(t *abi.Type, dst, src unsafeheader.Slice) int + +// growslice is implemented in prelude.js as $growSlice. +// +//gopherjs:purge +func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice + +//gopherjs:new +func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, *js.Object) { + kv := js.InternalObject(key) + if kv.Get("$get") != js.Undefined { + kv = kv.Call("$get") + } + k := jsType(t.Key()).Call("keyFor", kv) + return kv, k +} + +//gopherjs:replace +func mapaccess(t *abi.Type, m, key unsafe.Pointer) unsafe.Pointer { + if !js.InternalObject(m).Bool() { + return nil // nil map + } + _, k := keyFor(t, key) + entry := js.InternalObject(m).Call("get", k) + if entry == js.Undefined { + return nil + } + return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) +} + +//gopherjs:replace +func mapassign(t *abi.Type, m, key, val unsafe.Pointer) { + kv, k := keyFor(t, key) + jsVal := js.InternalObject(val).Call("$get") + et := t.Elem() + if et.Kind() == Struct { + newVal := jsType(et).Call("zero") + copyStruct(newVal, jsVal, et) + jsVal = newVal + } + entry := js.Global.Get("Object").New() + entry.Set("k", kv) + entry.Set("v", jsVal) + js.InternalObject(m).Call("set", k, entry) +} + +//gopherjs:replace +func mapdelete(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer) { + _, k := keyFor(t, key) + if !js.InternalObject(m).Bool() { + return // nil map + } + js.InternalObject(m).Call("delete", k) +} + +// TODO(nevkonatkte): The following three "faststr" implementations are meant to +// perform better for the common case of string-keyed maps (see upstream: +// https://github.com/golang/go/commit/23832ba2e2fb396cda1dacf3e8afcb38ec36dcba) +// However, the stubs below will perform the same or worse because of the extra +// string-to-pointer conversion. Not sure how to fix this without significant +// code duplication, however. + +//gopherjs:replace +func mapaccess_faststr(t *abi.Type, m unsafe.Pointer, key string) (val unsafe.Pointer) { + return mapaccess(t, m, unsafe.Pointer(&key)) +} + +//gopherjs:replace +func mapassign_faststr(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer) { + mapassign(t, m, unsafe.Pointer(&key), val) +} + +//gopherjs:replace +func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string) { + mapdelete(t, m, unsafe.Pointer(&key)) +} + +//gopherjs:replace +type hiter struct { + t Type + m *js.Object // Underlying map object. + keys *js.Object + i int + + // last is the last object the iterator indicates. If this object exists, the + // functions that return the current key or value returns this object, + // regardless of the current iterator. It is because the current iterator + // might be stale due to key deletion in a loop. + last *js.Object +} + +//gopherjs:new +func (iter *hiter) skipUntilValidKey() { + for iter.i < iter.keys.Length() { + k := iter.keys.Index(iter.i) + entry := iter.m.Call("get", k) + if entry != js.Undefined { + break + } + // The key is already deleted. Move on the next item. + iter.i++ + } +} + +//gopherjs:replace +func mapiterinit(t *abi.Type, m unsafe.Pointer, it *hiter) { + mapObj := js.InternalObject(m) + keys := js.Global.Get("Array").New() + if mapObj.Get("keys") != js.Undefined { + keysIter := mapObj.Call("keys") + if mapObj.Get("keys") != js.Undefined { + keys = js.Global.Get("Array").Call("from", keysIter) + } + } + + *it = hiter{ + t: t, + m: mapObj, + keys: keys, + i: 0, + last: nil, + } +} + +//gopherjs:replace +func mapiterkey(it *hiter) unsafe.Pointer { + var kv *js.Object + if it.last != nil { + kv = it.last + } else { + it.skipUntilValidKey() + if it.i == it.keys.Length() { + return nil + } + k := it.keys.Index(it.i) + kv = it.m.Call("get", k) + + // Record the key-value pair for later accesses. + it.last = kv + } + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(it.t.Key()))).Unsafe()) +} + +//gopherjs:replace +func mapiterelem(it *hiter) unsafe.Pointer { + var kv *js.Object + if it.last != nil { + kv = it.last + } else { + it.skipUntilValidKey() + if it.i == it.keys.Length() { + return nil + } + k := it.keys.Index(it.i) + kv = it.m.Call("get", k) + it.last = kv + } + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), jsType(PtrTo(it.t.Elem()))).Unsafe()) +} + +//gopherjs:replace +func mapiternext(it *hiter) { + it.last = nil + it.i++ +} + +//gopherjs:replace +func maplen(m unsafe.Pointer) int { + return js.InternalObject(m).Get("size").Int() +} From 203c1d06823515a79fd96ca49b7198ef1f585794 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 9 Feb 2026 10:39:27 -0700 Subject: [PATCH 14/48] Working on reflect --- compiler/natives/src/internal/abi/type.go | 8 ++ .../natives/src/internal/reflectlite/value.go | 10 +- compiler/natives/src/reflect/makefunc.go | 25 +++++ compiler/natives/src/reflect/reflect.go | 16 ---- compiler/natives/src/reflect/type.go | 10 +- compiler/natives/src/reflect/value.go | 92 ++++++++++++------- 6 files changed, 102 insertions(+), 59 deletions(-) create mode 100644 compiler/natives/src/reflect/makefunc.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 6a4490d01..a231c28ff 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -30,6 +30,14 @@ func (t *Type) setUncommon(ut *UncommonType) { js.InternalObject(t).Set(idUncommonType, js.InternalObject(ut)) } +//gopherjs:add This is the same as ArrayType(), MapType(), etc but they didn't have one for ChanType. +func (t *Type) ChanType() *ChanType { + if t.Kind() != Chan { + return nil + } + return (*ChanType)(unsafe.Pointer(t)) +} + //gopherjs:replace type UncommonType struct { PkgPath NameOff // import path diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 85a9cafd4..a431ca0aa 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -68,13 +68,13 @@ func valueInterface(v Value) any { return any(unsafe.Pointer(v.object().Unsafe())) } -//gopherjs:new This is new but there are commented out references in the original code. +//gopherjs:new This is new to reflectlite but there are commented out references in the native code and a copy in reflect. func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { panic("reflect: internal error: invalid use of makePartialFunc") } - _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) + fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() if v.typ.IsWrapped() { rcvr = v.typ.JsType().New(rcvr) @@ -89,8 +89,8 @@ func makeMethodValue(op string, v Value) Value { } } -//gopherjs:new -func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe.Pointer) { +//gopherjs:new This is a simplified copy of the version in reflect. +func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { var prop string if v.typ.Kind() == abi.Interface { tt := v.typ.InterfaceType() @@ -101,7 +101,6 @@ func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe. if !tt.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = tt.TypeOff(m.Typ).FuncType() prop = tt.NameOff(m.Name).Name() } else { ms := v.typ.ExportedMethods() @@ -112,7 +111,6 @@ func methodReceiver(op string, v Value, i int) (_ rtype, t *funcType, fn unsafe. if !v.typ.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = v.typ.TypeOff(m.Mtyp).FuncType() prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() } rcvr := v.object() diff --git a/compiler/natives/src/reflect/makefunc.go b/compiler/natives/src/reflect/makefunc.go new file mode 100644 index 000000000..19cb4b72e --- /dev/null +++ b/compiler/natives/src/reflect/makefunc.go @@ -0,0 +1,25 @@ +//go:build js + +package reflect + +import ( + "unsafe" + + "github.com/gopherjs/gopherjs/js" +) + +func makeMethodValue(op string, v Value) Value { + if v.flag&flagMethod == 0 { + panic("reflect: internal error: invalid use of makePartialFunc") + } + + _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) + rcvr := v.object() + if isWrapped(v.typ) { + rcvr = jsType(v.typ).New(rcvr) + } + fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { + return js.InternalObject(fn).Call("apply", rcvr, arguments) + }) + return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} +} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index bc466ba8c..5d20b4e0e 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -142,22 +142,6 @@ func valueInterface(v Value, safe bool) any { return any(unsafe.Pointer(v.object().Unsafe())) } -func makeMethodValue(op string, v Value) Value { - if v.flag&flagMethod == 0 { - panic("reflect: internal error: invalid use of makePartialFunc") - } - - _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) - } - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - return js.InternalObject(fn).Call("apply", rcvr, arguments) - }) - return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} -} - func (t *rtype) pointers() bool { switch t.Kind() { case Ptr, Map, Chan, Func, Struct, Array: diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 41720432c..88840de14 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -34,11 +34,6 @@ func init() { uint8Type = TypeOf(uint8(0)).(*rtype) // set for real } -//gopherjd:replace -func pkgPath(n abi.Name) string { - return n.PkgPath() -} - //gopherjs:new func toAbiType(typ Type) *abi.Type { return typ.(*rtype).common() @@ -86,6 +81,11 @@ func resolveReflectText(ptr unsafe.Pointer) aTextOff { return abi.ResolveReflectText(ptr) } +//gopherjd:replace +func pkgPath(n abi.Name) string { + return n.PkgPath() +} + func TypeOf(i any) Type { if !initialized { // avoid error of uint8Type return &rtype{} diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 2cd52d983..c27575111 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -12,6 +12,9 @@ import ( "github.com/gopherjs/gopherjs/js" ) +//gopherjs:purge This is the header for an interface value with methods and not needed for GopherJS. +type nonEmptyInterface struct{} + // New returns a Value representing a pointer to a new zero value // for the specified type. That is, the returned Value's Type is PtrTo(typ). // @@ -24,11 +27,11 @@ func New(typ Type) Value { if typ == nil { panic("reflect: New(nil)") } - t := typ.(*rtype) - pt := t.ptrTo() + t := toAbiType(typ) + pt := t.PtrTo() ptr := unsafe_New(t) fl := flag(Pointer) - return Value{typ: pt, ptr: ptr, flag: fl} + return Value{typ_: pt, ptr: ptr, flag: fl} } //gopherjs:replace @@ -44,12 +47,20 @@ func unsafe_New(typ *abi.Type) unsafe.Pointer { return abi.UnsafeNew(typ) } -func makeValue(t Type, v *js.Object, fl flag) Value { - rt := t.common() - if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { - return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{ + typ_: t, + ptr: unsafe.Pointer(v.Unsafe()), + flag: fl | flag(t.Kind()), + } + } + return Value{ + typ_: t, + ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), + flag: fl | flag(t.Kind()) | flagIndir, } - return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} } func MakeSlice(typ Type, len, cap int) Value { @@ -66,74 +77,91 @@ func MakeSlice(typ Type, len, cap int) Value { panic("reflect.MakeSlice: len > cap") } - return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) + return makeValue(toAbiType(typ), js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) } func Zero(typ Type) Value { - return makeValue(typ, jsType(typ).Call("zero"), 0) + return makeValue(toAbiType(typ), jsType(typ).Call("zero"), 0) } func makeInt(f flag, bits uint64, t Type) Value { typ := t.common() ptr := unsafe_New(typ) switch typ.Kind() { - case Int8: + case abi.Int8: *(*int8)(ptr) = int8(bits) - case Int16: + case abi.Int16: *(*int16)(ptr) = int16(bits) - case Int, Int32: + case abi.Int, abi.Int32: *(*int32)(ptr) = int32(bits) - case Int64: + case abi.Int64: *(*int64)(ptr) = int64(bits) - case Uint8: + case abi.Uint8: *(*uint8)(ptr) = uint8(bits) - case Uint16: + case abi.Uint16: *(*uint16)(ptr) = uint16(bits) - case Uint, Uint32, Uintptr: + case abi.Uint, abi.Uint32, abi.Uintptr: *(*uint32)(ptr) = uint32(bits) - case Uint64: + case abi.Uint64: *(*uint64)(ptr) = uint64(bits) } - return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} + return Value{ + typ_: typ, + ptr: ptr, + flag: f | flagIndir | flag(typ.Kind()), + } } //gopherjs:replace func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { i := methodIndex var prop string - if v.typ.Kind() == Interface { - tt := (*interfaceType)(unsafe.Pointer(v.typ)) - if i < 0 || i >= len(tt.methods) { + if tt := v.typ().InterfaceType(); tt != nil { + if i < 0 || i >= len(tt.Methods) { panic("reflect: internal error: invalid method index") } - m := &tt.methods[i] - if !tt.nameOff(m.name).isExported() { + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - rcvrtype = iface.itab.typ - t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ))) - prop = tt.nameOff(m.name).name() + // TODO(grantnelson-wf): Set rcvrtype to the type the interface is holding onto. + t = tt.TypeOff(m.typ).FuncType() + prop = tt.NameOff(m.Name).Name() } else { rcvrtype = v.typ() - ms := v.typ.exportedMethods() + ms := v.typ().ExportedMethods() if uint(i) >= uint(len(ms)) { panic("reflect: internal error: invalid method index") } m := ms[i] - if !v.typ.nameOff(m.name).isExported() { + if !v.typ().NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp))) + t = v.typ().TypeOff(m.mtyp).FuncType() prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() } rcvr := v.object() - if isWrapped(v.typ) { + if v.typ().IsWrapped() { rcvr = jsType(v.typ).New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) return } +//gopherjs:replace +func storeRcvr(v Value, p unsafe.Pointer) { + t := v.typ() + if t.Kind() == abi.Interface { + // the interface data word becomes the receiver word + iface := (*nonEmptyInterface)(v.ptr) + *(*unsafe.Pointer)(p) = iface.word + } else if v.flag&flagIndir != 0 && !ifaceIndir(t) { + *(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(v.ptr) + } else { + *(*unsafe.Pointer)(p) = v.ptr + } +} + func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { if typ.Kind() != Func { panic("reflect: call of MakeFunc with non-Func type") @@ -193,7 +221,7 @@ func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { //gopherjs:replace func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { - ctyp := (*abi.ChanType)(unsafe.Pointer(typ)) + ctyp := typ.ChanType() return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.Elem), size).Unsafe()) } From da027267fdb7b8d14f2e31358f8e0bacb89c26d7 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 10 Feb 2026 09:04:38 -0700 Subject: [PATCH 15/48] working on reflect --- compiler/natives/src/internal/abi/type.go | 25 ++ compiler/natives/src/reflect/makefunc.go | 4 + compiler/natives/src/reflect/type.go | 6 + compiler/natives/src/reflect/value.go | 283 +++++++++++++--------- 4 files changed, 199 insertions(+), 119 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index a231c28ff..bccd9f2ff 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -38,6 +38,31 @@ func (t *Type) ChanType() *ChanType { return (*ChanType)(unsafe.Pointer(t)) } +//gopherjs:add This is the same as ArrayType(), MapType(), etc but they didn't have one for PtrType. +func (t *Type) PtrType() *PtrType { + if t.Kind() != Pointer { + return nil + } + return (*PtrType)(unsafe.Pointer(t)) +} + +//gopherjs:add This is the same as ArrayType(), MapType(), etc but they didn't have one for SliceType. +func (t *Type) SliceType() *SliceType { + if t.Kind() != Slice { + return nil + } + return (*SliceType)(unsafe.Pointer(t)) +} + +//gopherjs:add Shared by reflect and reflectlite rtypes +func (t *Type) String() string { + s := t.NameOff(t.Str).Name() + if t.TFlag&TFlagExtraStar != 0 { + return s[1:] + } + return s +} + //gopherjs:replace type UncommonType struct { PkgPath NameOff // import path diff --git a/compiler/natives/src/reflect/makefunc.go b/compiler/natives/src/reflect/makefunc.go index 19cb4b72e..97a41d65c 100644 --- a/compiler/natives/src/reflect/makefunc.go +++ b/compiler/natives/src/reflect/makefunc.go @@ -8,6 +8,7 @@ import ( "github.com/gopherjs/gopherjs/js" ) +//gopherjs:replace func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { panic("reflect: internal error: invalid use of makePartialFunc") @@ -23,3 +24,6 @@ func makeMethodValue(op string, v Value) Value { }) return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} } + +//gopherjs:purge +func makeFuncStub() diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 88840de14..0fba3d170 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -86,6 +86,7 @@ func pkgPath(n abi.Name) string { return n.PkgPath() } +//gopherjs:replace func TypeOf(i any) Type { if !initialized { // avoid error of uint8Type return &rtype{} @@ -96,6 +97,11 @@ func TypeOf(i any) Type { return reflectType(js.InternalObject(i).Get("constructor")) } +//gopherjs:replace +func rtypeOf(i any) *abi.Type { + return abi.ReflectType(js.InternalObject(i).Get("constructor")) +} + func ArrayOf(count int, elem Type) Type { if count < 0 { panic("reflect: negative length passed to ArrayOf") diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index c27575111..ea2a6695a 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -12,9 +12,18 @@ import ( "github.com/gopherjs/gopherjs/js" ) -//gopherjs:purge This is the header for an interface value with methods and not needed for GopherJS. +//gopherjs:purge This is the header for an any interface and invalid for GopherJS. +type emptyInterface struct{} + +//gopherjs:purge This is the header for an interface value with methods and invalid for GopherJS. type nonEmptyInterface struct{} +//gopherjs:purge +func packEface(v Value) any + +//gopherjs:purge +func unpackEface(i any) Value + // New returns a Value representing a pointer to a new zero value // for the specified type. That is, the returned Value's Type is PtrTo(typ). // @@ -125,7 +134,7 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t panic("reflect: " + op + " of unexported method") } // TODO(grantnelson-wf): Set rcvrtype to the type the interface is holding onto. - t = tt.TypeOff(m.typ).FuncType() + t = tt.TypeOff(m.Typ).FuncType() prop = tt.NameOff(m.Name).Name() } else { rcvrtype = v.typ() @@ -137,30 +146,22 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t if !v.typ().NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = v.typ().TypeOff(m.mtyp).FuncType() - prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() + t = v.typ().TypeOff(m.Mtyp).FuncType() + prop = js.Global.Call("$methodSet", v.typ().JsType()).Index(i).Get("prop").String() } rcvr := v.object() if v.typ().IsWrapped() { - rcvr = jsType(v.typ).New(rcvr) + rcvr = v.typ().JsType().New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) return } -//gopherjs:replace -func storeRcvr(v Value, p unsafe.Pointer) { - t := v.typ() - if t.Kind() == abi.Interface { - // the interface data word becomes the receiver word - iface := (*nonEmptyInterface)(v.ptr) - *(*unsafe.Pointer)(p) = iface.word - } else if v.flag&flagIndir != 0 && !ifaceIndir(t) { - *(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(v.ptr) - } else { - *(*unsafe.Pointer)(p) = v.ptr - } -} +//gopherjs:purge +func storeRcvr(v Value, p unsafe.Pointer) + +//gopherjs:purge +func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { if typ.Kind() != Func { @@ -168,13 +169,13 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { } t := typ.common() - ftyp := (*funcType)(unsafe.Pointer(t)) + ftyp := t.FuncType() fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { // Convert raw JS arguments into []Value the user-supplied function expects. args := make([]Value, ftyp.NumIn()) for i := range args { - argType := ftyp.In(i).common() + argType := ftyp.In(i) args[i] = makeValue(argType, arguments[i], 0) } @@ -185,8 +186,8 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { if want, got := ftyp.NumOut(), len(resultsSlice); want != got { panic("reflect: expected " + strconv.Itoa(want) + " return values, got " + strconv.Itoa(got)) } - for i, rtyp := range ftyp.out() { - if !resultsSlice[i].Type().AssignableTo(rtyp) { + for i, rtyp := range ftyp.OutSlice() { + if !resultsSlice[i].Type().AssignableTo(toRType(rtyp)) { panic("reflect: " + strconv.Itoa(i) + " return value type is not compatible with the function declaration") } } @@ -222,7 +223,7 @@ func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { //gopherjs:replace func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { ctyp := typ.ChanType() - return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.Elem), size).Unsafe()) + return unsafe.Pointer(js.Global.Get("$Chan").New(ctyp.Elem.JsType(), size).Unsafe()) } //gopherjs:replace @@ -231,23 +232,24 @@ func makemap(t *abi.Type, cap int) (m unsafe.Pointer) { } func (v Value) object() *js.Object { - if v.typ.Kind() == Array || v.typ.Kind() == Struct { + if v.typ().Kind() == abi.Array || v.typ().Kind() == abi.Struct { return js.InternalObject(v.ptr) } + jsTyp := v.typ().JsType() if v.flag&flagIndir != 0 { val := js.InternalObject(v.ptr).Call("$get") - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { - switch v.typ.Kind() { - case Uint64, Int64: - val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) - case Complex64, Complex128: - val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) - case Slice: + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { + switch v.typ().Kind() { + case abi.Uint64, abi.Int64: + val = jsTyp.New(val.Get("$high"), val.Get("$low")) + case abi.Complex64, abi.Complex128: + val = jsTyp.New(val.Get("$real"), val.Get("$imag")) + case abi.Slice: if val == val.Get("constructor").Get("nil") { - val = jsType(v.typ).Get("nil") + val = jsTyp.Get("nil") break } - newVal := jsType(v.typ).New(val.Get("$array")) + newVal := jsTyp.New(val.Get("$array")) newVal.Set("$offset", val.Get("$offset")) newVal.Set("$length", val.Get("$length")) newVal.Set("$capacity", val.Get("$capacity")) @@ -266,14 +268,14 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } switch { - case directlyAssignable(dst, v.typ): + case directlyAssignable(dst, v.typ()): // Overwrite type so that they match. // Same memory layout, so no harm done. fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) return Value{dst, v.ptr, fl} - case implements(dst, v.typ): + case implements(dst, v.typ()): if target == nil { target = unsafe_New(dst) } @@ -290,7 +292,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } // Failed. - panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) + panic(context + ": value of type " + v.typ().String() + " is not assignable to type " + dst.String()) } var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) @@ -304,11 +306,11 @@ func (v Value) call(op string, in []Value) []Value { if v.flag&flagMethod != 0 { _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr = v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) } } else { - t = (*funcType)(unsafe.Pointer(v.typ)) + t = v.typ().FuncType() fn = unsafe.Pointer(v.object().Unsafe()) rcvr = js.Undefined } @@ -346,15 +348,15 @@ func (v Value) call(op string, in []Value) []Value { } } for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { + if xt, targ := in[i].Type(), toRType(t.In(i)); !xt.AssignableTo(targ) { panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) } } if !isSlice && t.IsVariadic() { // prepare slice for remaining values m := len(in) - n - slice := MakeSlice(t.In(n), m, m) - elem := t.In(n).Elem() + slice := MakeSlice(toRType(t.In(n)), m, m) + elem := toRType(t.In(n).Elem()) for i := 0; i < m; i++ { x := in[n+i] if xt := x.Type(); !xt.AssignableTo(elem) { @@ -376,7 +378,7 @@ func (v Value) call(op string, in []Value) []Value { argsArray := js.Global.Get("Array").New(t.NumIn()) for i, arg := range in { - argsArray.SetIndex(i, unwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i).common(), nil).object())) + argsArray.SetIndex(i, abi.UnwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i), nil).object())) } results := callHelper(js.InternalObject(fn), rcvr, argsArray) @@ -384,11 +386,11 @@ func (v Value) call(op string, in []Value) []Value { case 0: return nil case 1: - return []Value{makeValue(t.Out(0), wrapJsObject(t.Out(0), results), 0)} + return []Value{makeValue(t.Out(0), abi.WrapJsObject(t.Out(0), results), 0)} default: ret := make([]Value, nout) for i := range ret { - ret[i] = makeValue(t.Out(i), wrapJsObject(t.Out(i), results.Index(i)), 0) + ret[i] = makeValue(t.Out(i), abi.WrapJsObject(t.Out(i), results.Index(i)), 0) } return ret } @@ -398,16 +400,16 @@ func (v Value) Cap() int { k := v.kind() switch k { case Array: - return v.typ.Len() + return v.typ().Len() case Chan, Slice: return v.object().Get("$capacity").Int() case Ptr: - if v.typ.Elem().Kind() == Array { - return v.typ.Elem().Len() + if v.typ().Elem().Kind() == abi.Array { + return v.typ().Elem().Len() } panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") } - panic(&ValueError{"reflect.Value.Cap", k}) + panic(&ValueError{Method: "reflect.Value.Cap", Kind: k}) } func (v Value) Elem() Value { @@ -417,7 +419,7 @@ func (v Value) Elem() Value { if val == js.Global.Get("$ifaceNil") { return Value{} } - typ := reflectType(val.Get("constructor")) + typ := abi.ReflectType(val.Get("constructor")) return makeValue(typ, val.Get("$val"), v.flag.ro()) case Ptr: @@ -425,50 +427,58 @@ func (v Value) Elem() Value { return Value{} } val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ)) + tt := v.typ().PtrType() fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.elem.Kind()) - return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} + fl |= flag(tt.Elem.Kind()) + return Value{ + typ_: tt.Elem, + ptr: unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), + flag: fl, + } default: - panic(&ValueError{"reflect.Value.Elem", k}) + panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) } } func (v Value) Field(i int) Value { - if v.kind() != Struct { - panic(&ValueError{"reflect.Value.Field", v.kind()}) + tt := v.typ().StructType() + if tt == nil { + panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) } - tt := (*structType)(unsafe.Pointer(v.typ)) - if uint(i) >= uint(len(tt.fields)) { + if uint(i) >= uint(len(tt.Fields)) { panic("reflect: Field index out of range") } - prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.fields[i] - typ := field.typ + prop := v.typ().JsType().Get("fields").Index(i).Get("prop").String() + field := &tt.Fields[i] + typ := field.Typ fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.name.isExported() { - if field.embedded() { + if !field.Name.IsExported() { + if field.Embedded() { fl |= flagEmbedRO } else { fl |= flagStickyRO } } - if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { - if jsTag := getJsTag(tag); jsTag != "" { + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { + if jsTag := abi.GetJsTag(tag); jsTag != "" { for { v = v.Field(0) - if v.typ == jsObjectPtr { + if abi.IsJsObjectPtr(v.typ()) { o := v.object().Get("object") - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), - ).Unsafe()), fl} + return Value{ + typ_: typ, + ptr: unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), + ).Unsafe()), + flag: fl, + } } - if v.typ.Kind() == Ptr { + if v.typ().Kind() == abi.Pointer { v = v.Elem() } } @@ -476,13 +486,17 @@ func (v Value) Field(i int) Value { } s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{ + typ_: typ, + ptr: unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), + flag: fl, + } } - return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } func (v Value) UnsafePointer() unsafe.Pointer { @@ -517,33 +531,47 @@ func (v Value) extendSlice(n int) Value { v.mustBe(Slice) s := v.object() - sNil := jsType(v.typ).Get(`nil`) + sNil := v.typ().JsType().Get(`nil`) fl := flagIndir | flag(Slice) if s == sNil && n <= 0 { - return makeValue(v.typ, wrapJsObject(v.typ, sNil), fl) + return makeValue(v.typ(), abi.WrapJsObject(v.typ(), sNil), fl) } - newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice := v.typ().JsType().New(s.Get("$array")) newSlice.Set("$offset", s.Get("$offset")) newSlice.Set("$length", s.Get("$length")) newSlice.Set("$capacity", s.Get("$capacity")) - v2 := makeValue(v.typ, wrapJsObject(v.typ, newSlice), fl) + v2 := makeValue(v.typ(), abi.WrapJsObject(v.typ(), newSlice), fl) v2.grow(n) s2 := v2.object() s2.Set(`$length`, s2.Get(`$length`).Int()+n) return v2 } +//gopherjs:purge +func mapclear(t *abi.Type, m unsafe.Pointer) + +//gopherjs:purge +func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) + +// TODO(grantnelson-wf): Make sure this is tested since it is new. +// //gopherjs:replace func (v Value) Clear() { switch v.Kind() { case Slice: - sh := *(*unsafeheader.Slice)(v.ptr) - st := (*sliceType)(unsafe.Pointer(v.typ())) - typedarrayclear(st.Elem, sh.Data, sh.Len) - case Map: - mapclear(v.typ(), v.pointer()) + elem := v.typ().SliceType().Elem + zeroFn := elem.JsType().Get("zero") + a := js.InternalObject(v.ptr) + offset := a.Get("$offset").Int() + length := a.Get("$length").Int() + for i := 0; i < length; i++ { + a.SetIndex(i+offset, zeroFn.Invoke()) + } + // case Map: + // TODO(grantnelson-wf): Finish implementing + // mapclear(v.typ(), v.pointer()) default: panic(&ValueError{Method: "reflect.Value.Clear", Kind: v.Kind()}) } @@ -552,40 +580,48 @@ func (v Value) Clear() { func (v Value) Index(i int) Value { switch k := v.kind(); k { case Array: - tt := (*arrayType)(unsafe.Pointer(v.typ)) - if i < 0 || i > int(tt.len) { + tt := v.typ().ArrayType() + if i < 0 || i > int(tt.Len) { panic("reflect: array index out of range") } - typ := tt.elem + typ := tt.Elem fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) a := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{ + typ_: typ, + ptr: unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), + flag: fl, + } } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) case Slice: s := v.object() if i < 0 || i >= s.Get("$length").Int() { panic("reflect: slice index out of range") } - tt := (*sliceType)(unsafe.Pointer(v.typ)) - typ := tt.elem + tt := v.typ().SliceType() + typ := tt.Elem fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) i += s.Get("$offset").Int() a := s.Get("$array") - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{ + typ_: typ, + ptr: unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), + flag: fl, + } } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) case String: str := *(*string)(v.ptr) @@ -594,10 +630,14 @@ func (v Value) Index(i int) Value { } fl := v.flag.ro() | flag(Uint8) | flagIndir c := str[i] - return Value{uint8Type, unsafe.Pointer(&c), fl} + return Value{ + typ_: uint8Type, + ptr: unsafe.Pointer(&c), + flag: fl, + } default: - panic(&ValueError{"reflect.Value.Index", k}) + panic(&ValueError{Method: "reflect.Value.Index", Kind: k}) } } @@ -607,13 +647,13 @@ func (v Value) InterfaceData() [2]uintptr { func (v Value) SetZero() { v.mustBeAssignable() - v.Set(Zero(v.typ)) + v.Set(Zero(toRType(v.typ()))) } func (v Value) IsNil() bool { switch k := v.kind(); k { case Ptr, Slice: - return v.object() == jsType(v.typ).Get("nil") + return v.object() == v.typ().JsType().Get("nil") case Chan: return v.object() == js.Global.Get("$chanNil") case Func: @@ -640,8 +680,8 @@ func (v Value) Len() int { case Map: return v.object().Get("size").Int() case Ptr: - if v.typ.Elem().Kind() == Array { - return v.typ.Elem().Len() + if elem := v.typ().Elem(); elem.Kind() == abi.Array { + return elem.Len() } panic("reflect: call of reflect.Value.Len on ptr to non-array Value") default: @@ -677,12 +717,12 @@ func (v Value) Pointer() uintptr { func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() - x = x.assignTo("reflect.Set", v.typ, nil) + x = x.assignTo("reflect.Set", v.typ(), nil) if v.flag&flagIndir != 0 { - switch v.typ.Kind() { - case Array, Struct: - jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) - case Interface: + switch v.typ().Kind() { + case abi.Array, abi.Struct: + v.typ().JsType().Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) + case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) default: js.InternalObject(v.ptr).Call("$set", x.object()) @@ -695,12 +735,12 @@ func (v Value) Set(x Value) { func (v Value) bytesSlow() []byte { switch v.kind() { case Slice: - if v.typ.Elem().Kind() != Uint8 { + if v.typ().Elem().Kind() != abi.Uint8 { panic("reflect.Value.Bytes of non-byte slice") } return *(*[]byte)(v.ptr) case Array: - if v.typ.Elem().Kind() != Uint8 { + if v.typ().Elem().Kind() != abi.Uint8 { panic("reflect.Value.Bytes of non-byte array") } if !v.CanAddr() { @@ -718,12 +758,12 @@ func (v Value) bytesSlow() []byte { func (v Value) SetBytes(x []byte) { v.mustBeAssignable() v.mustBe(Slice) - if v.typ.Elem().Kind() != Uint8 { + if v.typ().Elem().Kind() != abi.Uint8 { panic("reflect.Value.SetBytes of non-byte slice") } slice := js.InternalObject(x) - if v.typ.Name() != "" || v.typ.Elem().Name() != "" { - typedSlice := jsType(v.typ).New(slice.Get("$array")) + if toRType(v.typ()).Name() != "" || toRType(v.typ()).Elem().Name() != "" { + typedSlice := v.typ().JsType().New(slice.Get("$array")) typedSlice.Set("$offset", slice.Get("$offset")) typedSlice.Set("$length", slice.Get("$length")) typedSlice.Set("$capacity", slice.Get("$capacity")) @@ -1010,3 +1050,8 @@ func mapiternext(it *hiter) { func maplen(m unsafe.Pointer) int { return js.InternalObject(m).Get("size").Int() } + +// gopherjs:replace +func noescape(p unsafe.Pointer) unsafe.Pointer { + return p +} From 1f96b274608044c226f0b81e1d47e2dfeac55d17 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 10 Feb 2026 13:50:26 -0700 Subject: [PATCH 16/48] Got reflect value compiling, now onto type --- compiler/natives/src/internal/abi/type.go | 6 +-- compiler/natives/src/reflect/value.go | 50 +++++++++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index bccd9f2ff..73f09006a 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -30,7 +30,7 @@ func (t *Type) setUncommon(ut *UncommonType) { js.InternalObject(t).Set(idUncommonType, js.InternalObject(ut)) } -//gopherjs:add This is the same as ArrayType(), MapType(), etc but they didn't have one for ChanType. +//gopherjs:add This is the same as ArrayType(), MapType(), etc but for ChanType. func (t *Type) ChanType() *ChanType { if t.Kind() != Chan { return nil @@ -38,7 +38,7 @@ func (t *Type) ChanType() *ChanType { return (*ChanType)(unsafe.Pointer(t)) } -//gopherjs:add This is the same as ArrayType(), MapType(), etc but they didn't have one for PtrType. +//gopherjs:add This is the same as ArrayType(), MapType(), etc but for PtrType. func (t *Type) PtrType() *PtrType { if t.Kind() != Pointer { return nil @@ -46,7 +46,7 @@ func (t *Type) PtrType() *PtrType { return (*PtrType)(unsafe.Pointer(t)) } -//gopherjs:add This is the same as ArrayType(), MapType(), etc but they didn't have one for SliceType. +//gopherjs:add This is the same as ArrayType(), MapType(), etc but for SliceType. func (t *Type) SliceType() *SliceType { if t.Kind() != Slice { return nil diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index ea2a6695a..7f3897f91 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -779,7 +779,7 @@ func (v Value) SetCap(n int) { if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { panic("reflect: slice capacity out of range in SetCap") } - newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice := v.typ().JsType().New(s.Get("$array")) newSlice.Set("$offset", s.Get("$offset")) newSlice.Set("$length", s.Get("$length")) newSlice.Set("$capacity", n) @@ -793,7 +793,7 @@ func (v Value) SetLen(n int) { if n < 0 || n > s.Get("$capacity").Int() { panic("reflect: slice length out of range in SetLen") } - newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice := v.typ().JsType().New(s.Get("$array")) newSlice.Set("$offset", s.Get("$offset")) newSlice.Set("$length", n) newSlice.Set("$capacity", s.Get("$capacity")) @@ -803,7 +803,7 @@ func (v Value) SetLen(n int) { func (v Value) Slice(i, j int) Value { var ( cap int - typ Type + typ *abi.Type s *js.Object ) switch kind := v.kind(); kind { @@ -811,13 +811,13 @@ func (v Value) Slice(i, j int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) + tt := v.typ().ArrayType() + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)).common() + s = typ.JsType().New(v.object()) case Slice: - typ = v.typ + typ = v.typ() s = v.object() cap = s.Get("$capacity").Int() @@ -829,7 +829,7 @@ func (v Value) Slice(i, j int) Value { return ValueOf(str[i:j]) default: - panic(&ValueError{"reflect.Value.Slice", kind}) + panic(&ValueError{Method: "reflect.Value.Slice", Kind: kind}) } if i < 0 || j < i || j > cap { @@ -842,7 +842,7 @@ func (v Value) Slice(i, j int) Value { func (v Value) Slice3(i, j, k int) Value { var ( cap int - typ Type + typ *abi.Type s *js.Object ) switch kind := v.kind(); kind { @@ -850,18 +850,18 @@ func (v Value) Slice3(i, j, k int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) + tt := v.typ().ArrayType() + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)).common() + s = typ.JsType().New(v.object()) case Slice: - typ = v.typ + typ = v.typ() s = v.object() cap = s.Get("$capacity").Int() default: - panic(&ValueError{"reflect.Value.Slice3", kind}) + panic(&ValueError{Method: "reflect.Value.Slice3", Kind: kind}) } if i < 0 || j < i || k < j || k > cap { @@ -888,12 +888,12 @@ func typedslicecopy(t *abi.Type, dst, src unsafeheader.Slice) int func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice //gopherjs:new -func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, *js.Object) { +func keyFor(t *abi.Type, key unsafe.Pointer) (*js.Object, *js.Object) { kv := js.InternalObject(key) if kv.Get("$get") != js.Undefined { kv = kv.Call("$get") } - k := jsType(t.Key()).Call("keyFor", kv) + k := t.Key().JsType().Call("keyFor", kv) return kv, k } @@ -907,7 +907,7 @@ func mapaccess(t *abi.Type, m, key unsafe.Pointer) unsafe.Pointer { if entry == js.Undefined { return nil } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), t.Elem().JsPtrTo()).Unsafe()) } //gopherjs:replace @@ -915,9 +915,9 @@ func mapassign(t *abi.Type, m, key, val unsafe.Pointer) { kv, k := keyFor(t, key) jsVal := js.InternalObject(val).Call("$get") et := t.Elem() - if et.Kind() == Struct { - newVal := jsType(et).Call("zero") - copyStruct(newVal, jsVal, et) + if et.Kind() == abi.Struct { + newVal := et.JsType().Call("zero") + abi.CopyStruct(newVal, jsVal, et) jsVal = newVal } entry := js.Global.Get("Object").New() @@ -959,7 +959,7 @@ func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string) { //gopherjs:replace type hiter struct { - t Type + t *abi.Type m *js.Object // Underlying map object. keys *js.Object i int @@ -1020,7 +1020,7 @@ func mapiterkey(it *hiter) unsafe.Pointer { // Record the key-value pair for later accesses. it.last = kv } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(it.t.Key()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), it.t.Key().JsPtrTo()).Unsafe()) } //gopherjs:replace @@ -1037,7 +1037,7 @@ func mapiterelem(it *hiter) unsafe.Pointer { kv = it.m.Call("get", k) it.last = kv } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), jsType(PtrTo(it.t.Elem()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), it.t.Elem().JsPtrTo()).Unsafe()) } //gopherjs:replace From e7aa99f394cc0e236ad8b9cd25f39514b55f39fa Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 10 Feb 2026 15:46:37 -0700 Subject: [PATCH 17/48] Finish reflect, now fixing rwmutex, hopefully soon I'll be able to start running tests --- compiler/natives/src/internal/abi/type.go | 20 +++- .../natives/src/internal/reflectlite/value.go | 2 +- compiler/natives/src/reflect/makefunc.go | 10 +- compiler/natives/src/reflect/reflect.go | 105 ++++++++++++------ compiler/natives/src/reflect/type.go | 57 +++++----- compiler/natives/src/reflect/value.go | 12 +- 6 files changed, 128 insertions(+), 78 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 73f09006a..252413044 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -30,7 +30,7 @@ func (t *Type) setUncommon(ut *UncommonType) { js.InternalObject(t).Set(idUncommonType, js.InternalObject(ut)) } -//gopherjs:add This is the same as ArrayType(), MapType(), etc but for ChanType. +//gopherjs:add Same as ArrayType(), MapType(), etc but for ChanType. func (t *Type) ChanType() *ChanType { if t.Kind() != Chan { return nil @@ -38,7 +38,7 @@ func (t *Type) ChanType() *ChanType { return (*ChanType)(unsafe.Pointer(t)) } -//gopherjs:add This is the same as ArrayType(), MapType(), etc but for PtrType. +//gopherjs:add Same as ArrayType(), MapType(), etc but for PtrType. func (t *Type) PtrType() *PtrType { if t.Kind() != Pointer { return nil @@ -46,7 +46,7 @@ func (t *Type) PtrType() *PtrType { return (*PtrType)(unsafe.Pointer(t)) } -//gopherjs:add This is the same as ArrayType(), MapType(), etc but for SliceType. +//gopherjs:add Same as ArrayType(), MapType(), etc but for SliceType func (t *Type) SliceType() *SliceType { if t.Kind() != Slice { return nil @@ -54,6 +54,14 @@ func (t *Type) SliceType() *SliceType { return (*SliceType)(unsafe.Pointer(t)) } +//gopherjs:add Same as ArrayType(), MapType(), etc but for InterfaceType +func (t *Type) InterfaceType() *InterfaceType { + if t.Kind() != Interface { + return nil + } + return (*InterfaceType)(unsafe.Pointer(t)) +} + //gopherjs:add Shared by reflect and reflectlite rtypes func (t *Type) String() string { s := t.NameOff(t.Str).Name() @@ -266,17 +274,17 @@ func (typ *Type) IsWrapped() bool { } //gopherjs:new -var jsObjectPtr = ReflectType(js.Global.Get("$jsObjectPtr")) +var JsObjectPtr = ReflectType(js.Global.Get("$jsObjectPtr")) //gopherjs:new func IsJsObjectPtr(typ *Type) bool { - return typ == jsObjectPtr + return typ == JsObjectPtr } //gopherjs:new func WrapJsObject(typ *Type, val *js.Object) *js.Object { if IsJsObjectPtr(typ) { - return jsObjectPtr.JsType().New(val) + return JsObjectPtr.JsType().New(val) } return val } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index a431ca0aa..1adc3d359 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -33,7 +33,7 @@ func (v Value) Elem() Value { return Value{} } val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ)) + tt := v.typ.PtrType() fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.Elem.Kind()) return Value{ diff --git a/compiler/natives/src/reflect/makefunc.go b/compiler/natives/src/reflect/makefunc.go index 97a41d65c..222d10fae 100644 --- a/compiler/natives/src/reflect/makefunc.go +++ b/compiler/natives/src/reflect/makefunc.go @@ -16,13 +16,17 @@ func makeMethodValue(op string, v Value) Value { _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) } fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { return js.InternalObject(fn).Call("apply", rcvr, arguments) }) - return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} + return Value{ + typ_: v.Type().common(), + ptr: unsafe.Pointer(fv.Unsafe()), + flag: v.flag.ro() | flag(Func), + } } //gopherjs:purge diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 5d20b4e0e..23683cb10 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -5,15 +5,17 @@ package reflect import ( "unsafe" + "internal/abi" "internal/itoa" "github.com/gopherjs/gopherjs/js" ) +//gopherjs:add func cvtDirect(v Value, typ Type) Value { srcVal := v.object() - if srcVal == jsType(v.typ).Get("nil") { - return makeValue(typ, jsType(typ).Get("nil"), v.flag) + if srcVal == v.typ().JsType().Get("nil") { + return makeValue(toAbiType(typ), jsType(typ).Get("nil"), v.flag) } var val *js.Object @@ -27,12 +29,12 @@ func cvtDirect(v Value, typ Type) Value { case Ptr: switch typ.Elem().Kind() { case Struct: - if typ.Elem() == v.typ.Elem() { + if toAbiType(typ.Elem()) == v.typ().Elem() { val = srcVal break } val = jsType(typ).New() - copyStruct(val, srcVal, typ.Elem()) + abi.CopyStruct(val, srcVal, toAbiType(typ.Elem())) case Array: // Unlike other pointers, array pointers are "wrapped" types (see // isWrapped() in the compiler package), and are represented by a native @@ -43,16 +45,22 @@ func cvtDirect(v Value, typ Type) Value { } case Struct: val = jsType(typ).Get("ptr").New() - copyStruct(val, srcVal, typ) + abi.CopyStruct(val, srcVal, toAbiType(typ)) case Array, Bool, Chan, Func, Interface, Map, String, UnsafePointer: val = js.InternalObject(v.ptr) default: - panic(&ValueError{"reflect.Convert", k}) + panic(&ValueError{Method: "reflect.Convert", Kind: k}) + } + return Value{ + typ_: typ.common(), + ptr: unsafe.Pointer(val.Unsafe()), + flag: v.flag.ro() | v.flag&flagIndir | flag(typ.Kind()), } - return Value{typ.common(), unsafe.Pointer(val.Unsafe()), v.flag.ro() | v.flag&flagIndir | flag(typ.Kind())} } // convertOp: []T -> *[N]T +// +//gopherjs:add func cvtSliceArrayPtr(v Value, t Type) Value { slice := v.object() @@ -62,10 +70,16 @@ func cvtSliceArrayPtr(v Value, t Type) Value { panic("reflect: cannot convert slice with length " + itoa.Itoa(slen) + " to pointer to array with length " + itoa.Itoa(alen)) } array := js.Global.Call("$sliceToGoArray", slice, jsType(t)) - return Value{t.common(), unsafe.Pointer(array.Unsafe()), v.flag&^(flagIndir|flagAddr|flagKindMask) | flag(Ptr)} + return Value{ + typ_: t.common(), + ptr: unsafe.Pointer(array.Unsafe()), + flag: v.flag&^(flagIndir|flagAddr|flagKindMask) | flag(Ptr), + } } // convertOp: []T -> [N]T +// +//gopherjs:add func cvtSliceArray(v Value, t Type) Value { n := t.Len() if n > v.Len() { @@ -77,13 +91,18 @@ func cvtSliceArray(v Value, t Type) Value { js.Global.Call("$copySlice", dst, slice) arr := dst.Get("$array") - return Value{t.common(), unsafe.Pointer(arr.Unsafe()), v.flag&^(flagAddr|flagKindMask) | flag(Array)} + return Value{ + typ_: t.common(), + ptr: unsafe.Pointer(arr.Unsafe()), + flag: v.flag&^(flagAddr|flagKindMask) | flag(Array), + } } +//gopherjs:replace func Copy(dst, src Value) int { dk := dst.kind() if dk != Array && dk != Slice { - panic(&ValueError{"reflect.Copy", dk}) + panic(&ValueError{Method: "reflect.Copy", Kind: dk}) } if dk == Array { dst.mustBeAssignable() @@ -93,25 +112,25 @@ func Copy(dst, src Value) int { sk := src.kind() var stringCopy bool if sk != Array && sk != Slice { - stringCopy = sk == String && dst.typ.Elem().Kind() == Uint8 + stringCopy = sk == String && dst.typ().Elem().Kind() == abi.Uint8 if !stringCopy { - panic(&ValueError{"reflect.Copy", sk}) + panic(&ValueError{Method: "reflect.Copy", Kind: sk}) } } src.mustBeExported() if !stringCopy { - typesMustMatch("reflect.Copy", dst.typ.Elem(), src.typ.Elem()) + typesMustMatch("reflect.Copy", toRType(dst.typ().Elem()), toRType(src.typ().Elem())) } dstVal := dst.object() if dk == Array { - dstVal = jsType(SliceOf(dst.typ.Elem())).New(dstVal) + dstVal = jsType(SliceOf(toRType(dst.typ().Elem()))).New(dstVal) } srcVal := src.object() if sk == Array { - srcVal = jsType(SliceOf(src.typ.Elem())).New(srcVal) + srcVal = jsType(SliceOf(toRType(src.typ().Elem()))).New(srcVal) } if stringCopy { @@ -120,9 +139,10 @@ func Copy(dst, src Value) int { return js.Global.Call("$copySlice", dstVal, srcVal).Int() } +//gopherjs:add func valueInterface(v Value, safe bool) any { if v.flag == 0 { - panic(&ValueError{"reflect.Value.Interface", 0}) + panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) } if safe && v.flag&flagRO != 0 { panic("reflect.Value.Interface: cannot return value obtained from unexported field or method") @@ -131,17 +151,19 @@ func valueInterface(v Value, safe bool) any { v = makeMethodValue("Interface", v) } - if isWrapped(v.typ) { + if v.typ().IsWrapped() { + jsTyp := v.typ().JsType() if v.flag&flagIndir != 0 && v.Kind() == Struct { - cv := jsType(v.typ).Call("zero") - copyStruct(cv, v.object(), v.typ) - return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) + cv := jsTyp.Call("zero") + abi.CopyStruct(cv, v.object(), v.typ()) + return any(unsafe.Pointer(jsTyp.New(cv).Unsafe())) } - return any(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) + return any(unsafe.Pointer(jsTyp.New(v.object()).Unsafe())) } return any(unsafe.Pointer(v.object().Unsafe())) } +//gopherjs:add func (t *rtype) pointers() bool { switch t.Kind() { case Ptr, Map, Chan, Func, Struct, Array: @@ -151,6 +173,7 @@ func (t *rtype) pointers() bool { } } +//gopherjs:replace func (t *rtype) Comparable() bool { switch t.Kind() { case Func, Slice, Map: @@ -167,6 +190,7 @@ func (t *rtype) Comparable() bool { return true } +//gopherjs:replace func (t *rtype) Method(i int) (m Method) { if t.Kind() == Interface { tt := (*interfaceType)(unsafe.Pointer(t)) @@ -177,19 +201,19 @@ func (t *rtype) Method(i int) (m Method) { panic("reflect: Method index out of range") } p := methods[i] - pname := t.nameOff(p.name) - m.Name = pname.name() + pname := t.nameOff(p.Name) + m.Name = pname.Name() fl := flag(Func) - mtyp := t.typeOff(p.mtyp) - ft := (*funcType)(unsafe.Pointer(mtyp)) - in := make([]Type, 0, 1+len(ft.in())) + mtyp := t.typeOff(p.Mtyp) + ft := mtyp.FuncType() + in := make([]Type, 0, 1+ft.NumIn()) in = append(in, t) - for _, arg := range ft.in() { - in = append(in, arg) + for _, arg := range ft.InSlice() { + in = append(in, toRType(arg)) } - out := make([]Type, 0, len(ft.out())) - for _, ret := range ft.out() { - out = append(out, ret) + out := make([]Type, 0, ft.NumOut()) + for _, ret := range ft.OutSlice() { + out = append(out, toRType(ret)) } mt := FuncOf(in, out, ft.IsVariadic()) m.Type = mt @@ -198,14 +222,20 @@ func (t *rtype) Method(i int) (m Method) { rcvr := arguments[0] return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) }) - m.Func = Value{mt.(*rtype), unsafe.Pointer(fn.Unsafe()), fl} + m.Func = Value{ + typ_: toAbiType(mt), + ptr: unsafe.Pointer(fn.Unsafe()), + flag: fl, + } m.Index = i return m } +//gopherjs:add var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) +//gopherjs:add func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { comms := [][]*js.Object{{js.InternalObject(ch)}} if nb { @@ -220,6 +250,7 @@ func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, receive return true, recvRes.Index(1).Bool() } +//gopherjs:add func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { comms := [][]*js.Object{{js.InternalObject(ch), js.InternalObject(val).Call("$get")}} if nb { @@ -232,6 +263,7 @@ func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { return true } +//gopherjs:add func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { comms := make([][]*js.Object, len(rselects)) for i, s := range rselects { @@ -264,6 +296,7 @@ func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { return c, false } +//gopherjs:replace func DeepEqual(a1, a2 any) bool { i1 := js.InternalObject(a1) i2 := js.InternalObject(a2) @@ -276,6 +309,7 @@ func DeepEqual(a1, a2 any) bool { return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) } +//gopherjs:add func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if !v1.IsValid() || !v2.IsValid() { return !v1.IsValid() && !v2.IsValid() @@ -283,8 +317,8 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if v1.Type() != v2.Type() { return false } - if v1.Type() == jsObjectPtr { - return unwrapJsObject(jsObjectPtr, v1.object()) == unwrapJsObject(jsObjectPtr, v2.object()) + if abi.IsJsObjectPtr(v1.typ()) { + return abi.UnwrapJsObject(abi.JsObjectPtr, v1.object()) == abi.UnwrapJsObject(abi.JsObjectPtr, v2.object()) } switch v1.Kind() { @@ -360,6 +394,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1, false)), js.InternalObject(valueInterface(v2, false))).Bool() } +//gopherjs:add func stringsLastIndex(s string, c byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == c { @@ -369,10 +404,12 @@ func stringsLastIndex(s string, c byte) int { return -1 } +//gopherjs:add func stringsHasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } +//gopherjs:add func verifyNotInHeapPtr(p uintptr) bool { // Go runtime uses this method to make sure that a uintptr won't crash GC if // interpreted as a heap pointer. This is not relevant for GopherJS, so we can diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 0fba3d170..0d3b52eb0 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -11,14 +11,11 @@ import ( "github.com/gopherjs/gopherjs/js" ) -var initialized = false - func init() { // avoid dead code elimination used := func(i any) {} used(rtype{}) used(uncommonType{}) - used(method{}) used(arrayType{}) used(chanType{}) used(funcType{}) @@ -27,11 +24,7 @@ func init() { used(ptrType{}) used(sliceType{}) used(structType{}) - used(imethod{}) used(structField{}) - - initialized = true - uint8Type = TypeOf(uint8(0)).(*rtype) // set for real } //gopherjs:new @@ -44,6 +37,7 @@ func jsType(typ Type) *js.Object { return toAbiType(typ).JsType() } +//gopherjs:replace func (t *rtype) ptrTo() *abi.Type { return toAbiType(t).PtrTo() } @@ -53,7 +47,7 @@ func addReflectOff(ptr unsafe.Pointer) int32 //gopherjs:replace func (t *rtype) nameOff(off aNameOff) abi.Name { - return t.NameOff(off) + return toAbiType(t).NameOff(off) } //gopherjs:replace @@ -63,7 +57,7 @@ func resolveReflectName(n abi.Name) aNameOff { //gopherjs:replace func (t *rtype) typeOff(off aTypeOff) *abi.Type { - return t.TypeOff(off) + return toAbiType(t).TypeOff(off) } //gopherjs:replace @@ -73,7 +67,7 @@ func resolveReflectType(t *abi.Type) aTypeOff { //gopherjs:replace func (t *rtype) textOff(off aTextOff) unsafe.Pointer { - return t.TextOff(off) + return toAbiType(t).TextOff(off) } //gopherjs:replace @@ -88,13 +82,10 @@ func pkgPath(n abi.Name) string { //gopherjs:replace func TypeOf(i any) Type { - if !initialized { // avoid error of uint8Type - return &rtype{} - } if i == nil { return nil } - return reflectType(js.InternalObject(i).Get("constructor")) + return toRType(rtypeOf(i)) } //gopherjs:replace @@ -102,18 +93,21 @@ func rtypeOf(i any) *abi.Type { return abi.ReflectType(js.InternalObject(i).Get("constructor")) } +//gopherjs:replace func ArrayOf(count int, elem Type) Type { if count < 0 { panic("reflect: negative length passed to ArrayOf") } - return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) + return toRType(abi.ReflectType(js.Global.Call("$arrayType", jsType(elem), count))) } +//gopherjs:replace func ChanOf(dir ChanDir, t Type) Type { - return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) + return toRType(abi.ReflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir))) } +//gopherjs:replace func FuncOf(in, out []Type, variadic bool) Type { if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { panic("reflect.FuncOf: last arg of variadic func must be slice") @@ -127,22 +121,25 @@ func FuncOf(in, out []Type, variadic bool) Type { for i, v := range out { jsOut[i] = jsType(v) } - return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) + return toRType(abi.ReflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic))) } +//gopherjs:replace func MapOf(key, elem Type) Type { switch key.Kind() { case Func, Map, Slice: panic("reflect.MapOf: invalid key type " + key.String()) } - return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) + return toRType(abi.ReflectType(js.Global.Call("$mapType", jsType(key), jsType(elem)))) } +//gopherjs:replace func SliceOf(t Type) Type { - return reflectType(js.Global.Call("$sliceType", jsType(t))) + return toRType(abi.ReflectType(js.Global.Call("$sliceType", jsType(t)))) } +//gopherjs:replace func StructOf(fields []StructField) Type { var ( jsFields = make([]*js.Object, len(fields)) @@ -161,8 +158,8 @@ func StructOf(fields []StructField) Type { panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") } f, fpkgpath := runtimeStructField(field) - ft := f.typ - if ft.kind&kindGCProg != 0 { + ft := f.Typ + if ft.Kind()&kindGCProg != 0 { hasGCProg = true } if fpkgpath != "" { @@ -173,7 +170,7 @@ func StructOf(fields []StructField) Type { } } name := field.Name - if f.embedded() { + if f.Embedded() { // Embedded field if field.Type.Kind() == Ptr { // Embedded ** and *interface{} are illegal @@ -185,9 +182,9 @@ func StructOf(fields []StructField) Type { switch field.Type.Kind() { case Interface: case Ptr: - ptr := (*ptrType)(unsafe.Pointer(ft)) - if unt := ptr.uncommon(); unt != nil { - if i > 0 && unt.mcount > 0 { + ptr := ft.PtrType() + if unt := ptr.Uncommon(); unt != nil { + if i > 0 && unt.Mcount > 0 { // Issue 15924. panic("reflect: embedded type with methods not implemented if type is not first field") } @@ -196,12 +193,12 @@ func StructOf(fields []StructField) Type { } } default: - if unt := ft.uncommon(); unt != nil { - if i > 0 && unt.mcount > 0 { + if unt := ft.Uncommon(); unt != nil { + if i > 0 && unt.Mcount > 0 { // Issue 15924. panic("reflect: embedded type with methods not implemented if type is not first field") } - if len(fields) > 1 && ft.kind&kindDirectIface != 0 { + if len(fields) > 1 && ft.Kind()&kindDirectIface != 0 { panic("reflect: embedded type with methods not implemented for non-pointer type") } } @@ -219,7 +216,7 @@ func StructOf(fields []StructField) Type { // The rest is set through the js.Object() interface, which the compiler will // externalize for us. jsf.Set("prop", name) - jsf.Set("exported", f.name.isExported()) + jsf.Set("exported", f.Name.IsExported()) jsf.Set("typ", jsType(field.Type)) jsf.Set("tag", field.Tag) jsf.Set("embedded", field.Anonymous) @@ -230,5 +227,5 @@ func StructOf(fields []StructField) Type { if pkgpath != "" { typ.Set("pkgPath", pkgpath) } - return reflectType(typ) + return toRType(abi.ReflectType(typ)) } diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 7f3897f91..ef6e3cdbd 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -207,7 +207,11 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { } }) - return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} + return Value{ + typ_: t, + ptr: unsafe.Pointer(fv.Unsafe()), + flag: flag(Func), + } } //gopherjs:replace @@ -273,7 +277,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va // Same memory layout, so no harm done. fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) - return Value{dst, v.ptr, fl} + return Value{typ_: dst, ptr: v.ptr, flag: fl} case implements(dst, v.typ()): if target == nil { @@ -288,7 +292,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } else { ifaceE2I(dst, x, target) } - return Value{dst, target, flagIndir | flag(Interface)} + return Value{typ_: dst, ptr: target, flag: flagIndir | flag(Interface)} } // Failed. @@ -752,7 +756,7 @@ func (v Value) bytesSlow() []byte { // return unsafe.Slice(p, n) return js.InternalObject(v.ptr).Interface().([]byte) } - panic(&ValueError{"reflect.Value.Bytes", v.kind()}) + panic(&ValueError{Method: "reflect.Value.Bytes", Kind: v.kind()}) } func (v Value) SetBytes(x []byte) { From e54c491281776074af757d87b708877e36ea9021 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 10 Feb 2026 16:40:40 -0700 Subject: [PATCH 18/48] Got it compiling for abi tests, still panicked but getting there --- compiler/natives/src/internal/abi/type.go | 9 +++++++++ compiler/natives/src/internal/abi/utils.go | 5 ++++- compiler/natives/src/reflect/type.go | 12 ++++++++++++ compiler/natives/src/sync/sync.go | 10 ++++++++++ compiler/natives/src/syscall/syscall_js_wasm.go | 9 +++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 252413044..af1bf42cd 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -462,3 +462,12 @@ func CopyStruct(dst, src *js.Object, typ *Type) { dst.Set(prop, src.Get(prop)) } } + +//gopherjs:purge Uses unsafeSliceFor +func (t *Type) GcSlice(begin, end uintptr) []byte + +//gopherjs:purge Uses unsafe.String or stringHeader +func unsafeStringFor(b *byte, l int) string + +//gopherjs:purge Uses unsafe.Slice or sliceHeader +func unsafeSliceFor(b *byte, l int) []byte diff --git a/compiler/natives/src/internal/abi/utils.go b/compiler/natives/src/internal/abi/utils.go index d0a4fff48..2d391ff63 100644 --- a/compiler/natives/src/internal/abi/utils.go +++ b/compiler/natives/src/internal/abi/utils.go @@ -9,7 +9,10 @@ import ( ) // GOPHERJS: These utils are being added because they are common between -// reflect and reflectlite. +// reflect and reflectlite. The [Go proverb](https://go-proverbs.github.io/), +// "A little copying is better than a little dependency," isn't applicable +// when both reflect and reflectlite already depend on ABI. We can reduce +// our native overrides in both locations by putting common code here. //gopherjs:new type errorString struct { diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 0d3b52eb0..a3cb18bb2 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -229,3 +229,15 @@ func StructOf(fields []StructField) Type { } return toRType(abi.ReflectType(typ)) } + +//gopherjs:purge Used in original MapOf and not used in override MapOf by GopherJS +func bucketOf(ktyp, etyp *abi.Type) *abi.Type + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func (t *rtype) gcSlice(begin, end uintptr) []byte + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func appendGCProg(dst []byte, typ *abi.Type) []byte diff --git a/compiler/natives/src/sync/sync.go b/compiler/natives/src/sync/sync.go index 1aa1c7adb..6605b513d 100644 --- a/compiler/natives/src/sync/sync.go +++ b/compiler/natives/src/sync/sync.go @@ -87,3 +87,13 @@ func runtime_nanotime() int64 func throw(s string) { js.Global.Call("$throwRuntimeError", s) } + +// GOPHERJS: This is identical to the original but without the go:linkname +// that can not be handled right now, "can not insert local implementation..." +// TODO(grantnelson-wf): Remove once linking works both directions. +// +//gopherjs:replace +func syscall_hasWaitingReaders(rw *RWMutex) bool { + r := rw.readerCount.Load() + return r < 0 && r+rwmutexMaxReaders > 0 +} diff --git a/compiler/natives/src/syscall/syscall_js_wasm.go b/compiler/natives/src/syscall/syscall_js_wasm.go index 346da22d4..4281421c4 100644 --- a/compiler/natives/src/syscall/syscall_js_wasm.go +++ b/compiler/natives/src/syscall/syscall_js_wasm.go @@ -1,6 +1,7 @@ package syscall import ( + "sync" "syscall/js" _ "unsafe" // go:linkname ) @@ -52,6 +53,14 @@ func unsetenv_c(k string) { //go:linkname godebug_notify runtime.godebug_notify func godebug_notify(key, value string) +// GOPHERJS: This was replaced so that we could add the go:linkname since +// sync.syscall_hasWaitingReaders had a link we can't handle right now, +// "can not insert local implementation..." +// TODO(grantnelson-wf): Remove once linking works both directions. +// +//go:linkname hasWaitingReaders sync.syscall_hasWaitingReaders +func hasWaitingReaders(rw *sync.RWMutex) bool + func setStat(st *Stat_t, jsSt js.Value) { // This method is an almost-exact copy of upstream, except for 4 places where // time stamps are obtained as floats in lieu of int64. Upstream wasm emulates From 59e6e1e05a3cc0b95a8251e4216cc064df3f18fb Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 12:05:15 -0700 Subject: [PATCH 19/48] debugging tests --- compiler/natives/src/internal/abi/abi_test.go | 15 ++++++++++ .../natives/src/internal/godebug/godebug.go | 3 ++ .../src/internal/reflectlite/all_test.go | 29 +++++++++++++++++++ .../src/internal/reflectlite/export_test.go | 15 +--------- .../natives/src/internal/reflectlite/value.go | 1 + compiler/natives/src/runtime/runtime.go | 5 ++++ 6 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 compiler/natives/src/internal/abi/abi_test.go create mode 100644 compiler/natives/src/internal/reflectlite/all_test.go diff --git a/compiler/natives/src/internal/abi/abi_test.go b/compiler/natives/src/internal/abi/abi_test.go new file mode 100644 index 000000000..892d6cc8d --- /dev/null +++ b/compiler/natives/src/internal/abi/abi_test.go @@ -0,0 +1,15 @@ +//go:build js + +package abi_test + +import "testing" + +//gopherjs:replace +func TestFuncPC(t *testing.T) { + t.Skip(`test involes checking the PC (program counter)`) +} + +//gopherjs:replace +func TestFuncPCCompileError(t *testing.T) { + t.Skip(`test involes checking the PC (program counter)`) +} diff --git a/compiler/natives/src/internal/godebug/godebug.go b/compiler/natives/src/internal/godebug/godebug.go index 3d7d719eb..40007a918 100644 --- a/compiler/natives/src/internal/godebug/godebug.go +++ b/compiler/natives/src/internal/godebug/godebug.go @@ -6,3 +6,6 @@ import _ "unsafe" // go:linkname //go:linkname setUpdate runtime.godebug_setUpdate func setUpdate(update func(def, env string)) + +//go:linkname setNewIncNonDefault runtime.godebug_setNewIncNonDefault +func setNewIncNonDefault(newIncNonDefault func(string) func()) diff --git a/compiler/natives/src/internal/reflectlite/all_test.go b/compiler/natives/src/internal/reflectlite/all_test.go new file mode 100644 index 000000000..fa1aeab24 --- /dev/null +++ b/compiler/natives/src/internal/reflectlite/all_test.go @@ -0,0 +1,29 @@ +//go:build js + +package reflectlite_test + +import ( + "testing" + + . "internal/reflectlite" +) + +// TODO: REMOVE (only added to break down the steps of the test) +func TestTypes(t *testing.T) { + for i, tt := range typeTests { + println(`>> i >>`, i) + println(`>> tt.i >>`, tt.i) + println(`>> tt.s >>`, tt.s) + + v := ValueOf(tt.i) + println(`>> v >>`, v) + + f := Field(v, 0) + println(`>> f >>`, f) + + tx := f.Type() + println(`>> tx >>`, tx) + + testReflectType(t, i, tx, tt.s) + } +} diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index bb5b86e4a..1da33e623 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -2,18 +2,8 @@ package reflectlite -import "internal/abi" - -// Field returns the i'th field of the struct v. -// It panics if v's Kind is not Struct or i is out of range. -// //gopherjs:replace -func Field(v Value, i int) Value { - if v.kind() != abi.Struct { - panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) - } - return v.Field(i) -} +func Field(v Value, i int) Value { return v.Field(i) } //gopherjs:purge Used in FirstMethodNameBytes type EmbedWithUnexpMeth struct{} @@ -26,6 +16,3 @@ var pinUnexpMethI pinUnexpMeth //gopherjs:purge Uses pointer arithmetic for names func FirstMethodNameBytes(t Type) *byte - -//gopherjs:purge Was unused -type Buffer struct{} diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 1adc3d359..69d63d38d 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -184,6 +184,7 @@ func (v Value) Field(i int) Value { if tt == nil { panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) } + if uint(i) >= uint(len(tt.Fields)) { panic("reflect: Field index out of range") } diff --git a/compiler/natives/src/runtime/runtime.go b/compiler/natives/src/runtime/runtime.go index 25b15c221..0984ba941 100644 --- a/compiler/natives/src/runtime/runtime.go +++ b/compiler/natives/src/runtime/runtime.go @@ -501,6 +501,11 @@ func godebug_setUpdate(update func(def, env string)) { godebug_notify(godebugEnvKey, godebugEnv) } +// godebug_setUpdate implements the setNewIncNonDefault in src/internal/godebug/godebug.go +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { + // TODO(grantnelson-wf): Figure out what to do for this method. +} + func getEnvString(key string) string { process := js.Global.Get(`process`) if process == js.Undefined { From a8f4b600702b2de4189f44b15b71e5883a4dc767 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 13:43:29 -0700 Subject: [PATCH 20/48] cleaning up some changes --- compiler/natives/src/internal/abi/type.go | 443 +++++++++--------- compiler/natives/src/internal/abi/utils.go | 15 + .../src/internal/reflectlite/export_test.go | 12 +- .../src/internal/reflectlite/reflectlite.go | 192 ++++++++ .../natives/src/internal/reflectlite/type.go | 64 --- .../natives/src/internal/reflectlite/value.go | 123 +---- compiler/natives/src/reflect/reflect.go | 28 +- compiler/natives/src/reflect/value.go | 2 +- 8 files changed, 445 insertions(+), 434 deletions(-) create mode 100644 compiler/natives/src/internal/reflectlite/reflectlite.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index af1bf42cd..6104c5291 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -16,6 +16,181 @@ const ( idUncommonType = `uncommonType` ) +//gopherjs:new +func ReflectType(typ *js.Object) *Type { + if typ.Get(idReflectType) == js.Undefined { + abiTyp := &Type{ + Size_: uintptr(typ.Get("size").Int()), + Kind_: uint8(typ.Get("kind").Int()), + Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + } + js.InternalObject(abiTyp).Set(idJsType, typ) + typ.Set(idReflectType, js.InternalObject(abiTyp)) + + methodSet := js.Global.Call("$methodSet", typ) + if methodSet.Length() != 0 || typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagUncommon + if typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagNamed + } + var reflectMethods []Method + for i := 0; i < methodSet.Length(); i++ { // Exported methods first. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if !exported { + continue + } + reflectMethods = append(reflectMethods, Method{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), + }) + } + xcount := uint16(len(reflectMethods)) + for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if exported { + continue + } + reflectMethods = append(reflectMethods, Method{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), + }) + } + ut := &UncommonType{ + PkgPath: ResolveReflectName(NewName(internalStr(typ.Get("pkg")), "", false, false)), + Mcount: uint16(methodSet.Length()), + Xcount: xcount, + Methods_: reflectMethods, + } + abiTyp.setUncommon(ut) + } + + switch abiTyp.Kind() { + case Array: + setKindType(abiTyp, &ArrayType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + Len: uintptr(typ.Get("len").Int()), + }) + case Chan: + dir := BothDir + if typ.Get("sendOnly").Bool() { + dir = SendDir + } + if typ.Get("recvOnly").Bool() { + dir = RecvDir + } + setKindType(abiTyp, &ChanType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + Dir: dir, + }) + case Func: + params := typ.Get("params") + in := make([]*Type, params.Length()) + for i := range in { + in[i] = ReflectType(params.Index(i)) + } + results := typ.Get("results") + out := make([]*Type, results.Length()) + for i := range out { + out[i] = ReflectType(results.Index(i)) + } + outCount := uint16(results.Length()) + if typ.Get("variadic").Bool() { + outCount |= 1 << 15 + } + setKindType(abiTyp, &FuncType{ + Type: *abiTyp, + InCount: uint16(params.Length()), + OutCount: outCount, + In_: in, + Out_: out, + }) + case Interface: + methods := typ.Get("methods") + imethods := make([]Imethod, methods.Length()) + for i := range imethods { + m := methods.Index(i) + imethods[i] = Imethod{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), + Typ: ResolveReflectType(ReflectType(m.Get("typ"))), + } + } + setKindType(abiTyp, &InterfaceType{ + Type: *abiTyp, + PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), + Methods: imethods, + }) + case Map: + setKindType(abiTyp, &MapType{ + Type: *abiTyp, + Key: ReflectType(typ.Get("key")), + Elem: ReflectType(typ.Get("elem")), + }) + case Pointer: + setKindType(abiTyp, &PtrType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + }) + case Slice: + setKindType(abiTyp, &SliceType{ + Type: *abiTyp, + Elem: ReflectType(typ.Get("elem")), + }) + case Struct: + fields := typ.Get("fields") + reflectFields := make([]StructField, fields.Length()) + for i := range reflectFields { + f := fields.Index(i) + reflectFields[i] = StructField{ + Name: NewName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + Typ: ReflectType(f.Get("typ")), + Offset: uintptr(i), + } + } + setKindType(abiTyp, &StructType{ + Type: *abiTyp, + PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), + Fields: reflectFields, + }) + } + } + + return (*Type)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) +} + +//gopherjs:new +func setKindType(abiTyp *Type, kindType any) { + js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) +} + +//gopherjs:new +func (t *Type) KindType() *Type { + return (*Type)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) +} + +//gopherjs:replace +type UncommonType struct { + PkgPath NameOff // import path + Mcount uint16 // method count + Xcount uint16 // exported method count + + // GOPHERJS: Added access to methods + Methods_ []Method +} + +//gopherjs:replace +func (t *UncommonType) Methods() []Method { + return t.Methods_ +} + +//gopherjs:replace +func (t *UncommonType) ExportedMethods() []Method { + return t.Methods_[:t.Xcount:t.Xcount] +} + //gopherjs:replace func (t *Type) Uncommon() *UncommonType { obj := js.InternalObject(t).Get(idUncommonType) @@ -25,12 +200,32 @@ func (t *Type) Uncommon() *UncommonType { return (*UncommonType)(unsafe.Pointer(obj.Unsafe())) } -//gopherjs:add +//gopherjs:new func (t *Type) setUncommon(ut *UncommonType) { js.InternalObject(t).Set(idUncommonType, js.InternalObject(ut)) } -//gopherjs:add Same as ArrayType(), MapType(), etc but for ChanType. +//gopherjs:new +func (typ *Type) JsType() *js.Object { + return js.InternalObject(typ).Get(idJsType) +} + +//gopherjs:new +func (typ *Type) setJsType(t *js.Object) { + js.InternalObject(typ).Set(idJsType, typ) +} + +//gopherjs:new +func (typ *Type) PtrTo() *Type { + return ReflectType(js.Global.Call("$ptrType", typ.JsType())) +} + +//gopherjs:new +func (typ *Type) JsPtrTo() *js.Object { + return typ.PtrTo().JsType() +} + +//gopherjs:new Same as ArrayType(), MapType(), etc but for ChanType. func (t *Type) ChanType() *ChanType { if t.Kind() != Chan { return nil @@ -38,7 +233,7 @@ func (t *Type) ChanType() *ChanType { return (*ChanType)(unsafe.Pointer(t)) } -//gopherjs:add Same as ArrayType(), MapType(), etc but for PtrType. +//gopherjs:new Same as ArrayType(), MapType(), etc but for PtrType. func (t *Type) PtrType() *PtrType { if t.Kind() != Pointer { return nil @@ -46,7 +241,7 @@ func (t *Type) PtrType() *PtrType { return (*PtrType)(unsafe.Pointer(t)) } -//gopherjs:add Same as ArrayType(), MapType(), etc but for SliceType +//gopherjs:new Same as ArrayType(), MapType(), etc but for SliceType func (t *Type) SliceType() *SliceType { if t.Kind() != Slice { return nil @@ -54,15 +249,7 @@ func (t *Type) SliceType() *SliceType { return (*SliceType)(unsafe.Pointer(t)) } -//gopherjs:add Same as ArrayType(), MapType(), etc but for InterfaceType -func (t *Type) InterfaceType() *InterfaceType { - if t.Kind() != Interface { - return nil - } - return (*InterfaceType)(unsafe.Pointer(t)) -} - -//gopherjs:add Shared by reflect and reflectlite rtypes +//gopherjs:new Shared by reflect and reflectlite rtypes func (t *Type) String() string { s := t.NameOff(t.Str).Name() if t.TFlag&TFlagExtraStar != 0 { @@ -71,29 +258,6 @@ func (t *Type) String() string { return s } -//gopherjs:replace -type UncommonType struct { - PkgPath NameOff // import path - Mcount uint16 // method count - Xcount uint16 // exported method count - - // GOPHERJS: Added access to methods - Methods_ []Method -} - -//gopherjs:purge Used for pointer arthmatic -func addChecked(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer - -//gopherjs:replace -func (t *UncommonType) Methods() []Method { - return t.Methods_ -} - -//gopherjs:replace -func (t *UncommonType) ExportedMethods() []Method { - return t.Methods_[:t.Xcount:t.Xcount] -} - //gopherjs:replace type FuncType struct { Type `reflect:"func"` @@ -154,10 +318,10 @@ func (n Name) Data(off int) *byte //gopherjs:purge Used for byte encoding of name, not used in JS func (n Name) ReadVarint(off int) (int, int) -//gopherjs:add +//gopherjs:new func (n Name) PkgPath() string { return n.pkgPath } -//gopherjs:add +//gopherjs:new func (n Name) SetPkgPath(pkgpath string) { n.pkgPath = pkgpath } @@ -172,19 +336,6 @@ func NewName(n, tag string, exported, embedded bool) Name { } } -// NewMethodName creates name instance for a method. -// -// Input object is expected to be an entry of the "methods" list of the -// corresponding JS type. -func NewMethodName(m *js.Object) Name { - return Name{ - name: internalStr(m.Get("name")), - tag: "", - pkgPath: internalStr(m.Get("pkg")), - exported: internalStr(m.Get("pkg")) == "", - } -} - // Instead of using this as an offset from a pointer to look up a name, // just store the name as a pointer. // @@ -249,23 +400,10 @@ func ResolveReflectText(ptr unsafe.Pointer) TextOff { } //gopherjs:new -func (typ *Type) JsType() *js.Object { - return js.InternalObject(typ).Get(idJsType) -} - -//gopherjs:new -func (typ *Type) setJsType(t *js.Object) { - js.InternalObject(typ).Set(idJsType, typ) -} - -//gopherjs:new -func (typ *Type) PtrTo() *Type { - return ReflectType(js.Global.Call("$ptrType", typ.JsType())) -} - -//gopherjs:new -func (typ *Type) JsPtrTo() *js.Object { - return typ.PtrTo().JsType() +func internalStr(strObj *js.Object) string { + var c struct{ str string } + js.InternalObject(c).Set("str", strObj) // get string without internalizing + return c.str } //gopherjs:new @@ -276,14 +414,9 @@ func (typ *Type) IsWrapped() bool { //gopherjs:new var JsObjectPtr = ReflectType(js.Global.Get("$jsObjectPtr")) -//gopherjs:new -func IsJsObjectPtr(typ *Type) bool { - return typ == JsObjectPtr -} - //gopherjs:new func WrapJsObject(typ *Type, val *js.Object) *js.Object { - if IsJsObjectPtr(typ) { + if typ == JsObjectPtr { return JsObjectPtr.JsType().New(val) } return val @@ -291,169 +424,12 @@ func WrapJsObject(typ *Type, val *js.Object) *js.Object { //gopherjs:new func UnwrapJsObject(typ *Type, val *js.Object) *js.Object { - if IsJsObjectPtr(typ) { + if typ == JsObjectPtr { return val.Get("object") } return val } -//gopherjs:new -func internalStr(strObj *js.Object) string { - var c struct{ str string } - js.InternalObject(c).Set("str", strObj) // get string without internalizing - return c.str -} - -//gopherjs:new -func ReflectType(typ *js.Object) *Type { - if typ.Get(idReflectType) == js.Undefined { - abiTyp := &Type{ - Size_: uintptr(typ.Get("size").Int()), - Kind_: uint8(typ.Get("kind").Int()), - Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), - } - js.InternalObject(abiTyp).Set(idJsType, typ) - typ.Set(idReflectType, js.InternalObject(abiTyp)) - - methodSet := js.Global.Call("$methodSet", typ) - if methodSet.Length() != 0 || typ.Get("named").Bool() { - abiTyp.TFlag |= TFlagUncommon - if typ.Get("named").Bool() { - abiTyp.TFlag |= TFlagNamed - } - var reflectMethods []Method - for i := 0; i < methodSet.Length(); i++ { // Exported methods first. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if !exported { - continue - } - reflectMethods = append(reflectMethods, Method{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), - }) - } - xcount := uint16(len(reflectMethods)) - for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if exported { - continue - } - reflectMethods = append(reflectMethods, Method{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), - }) - } - ut := &UncommonType{ - PkgPath: ResolveReflectName(NewName(internalStr(typ.Get("pkg")), "", false, false)), - Mcount: uint16(methodSet.Length()), - Xcount: xcount, - Methods_: reflectMethods, - } - abiTyp.setUncommon(ut) - } - - switch abiTyp.Kind() { - case Array: - setKindType(abiTyp, &ArrayType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - Len: uintptr(typ.Get("len").Int()), - }) - case Chan: - dir := BothDir - if typ.Get("sendOnly").Bool() { - dir = SendDir - } - if typ.Get("recvOnly").Bool() { - dir = RecvDir - } - setKindType(abiTyp, &ChanType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - Dir: dir, - }) - case Func: - params := typ.Get("params") - in := make([]*Type, params.Length()) - for i := range in { - in[i] = ReflectType(params.Index(i)) - } - results := typ.Get("results") - out := make([]*Type, results.Length()) - for i := range out { - out[i] = ReflectType(results.Index(i)) - } - outCount := uint16(results.Length()) - if typ.Get("variadic").Bool() { - outCount |= 1 << 15 - } - setKindType(abiTyp, &FuncType{ - Type: *abiTyp, - InCount: uint16(params.Length()), - OutCount: outCount, - In_: in, - Out_: out, - }) - case Interface: - methods := typ.Get("methods") - imethods := make([]Imethod, methods.Length()) - for i := range imethods { - m := methods.Index(i) - imethods[i] = Imethod{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), - Typ: ResolveReflectType(ReflectType(m.Get("typ"))), - } - } - setKindType(abiTyp, &InterfaceType{ - Type: *abiTyp, - PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), - Methods: imethods, - }) - case Map: - setKindType(abiTyp, &MapType{ - Type: *abiTyp, - Key: ReflectType(typ.Get("key")), - Elem: ReflectType(typ.Get("elem")), - }) - case Pointer: - setKindType(abiTyp, &PtrType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - }) - case Slice: - setKindType(abiTyp, &SliceType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - }) - case Struct: - fields := typ.Get("fields") - reflectFields := make([]StructField, fields.Length()) - for i := range reflectFields { - f := fields.Index(i) - reflectFields[i] = StructField{ - Name: NewName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - Typ: ReflectType(f.Get("typ")), - Offset: uintptr(i), - } - } - setKindType(abiTyp, &StructType{ - Type: *abiTyp, - PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), - Fields: reflectFields, - }) - } - } - - return (*Type)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) -} - -//gopherjs:new -func setKindType(abiTyp *Type, kindType any) { - js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) -} - //gopherjs:new func CopyStruct(dst, src *js.Object, typ *Type) { fields := typ.JsType().Get("fields") @@ -463,6 +439,9 @@ func CopyStruct(dst, src *js.Object, typ *Type) { } } +//gopherjs:purge Used for pointer arthmatic +func addChecked(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer + //gopherjs:purge Uses unsafeSliceFor func (t *Type) GcSlice(begin, end uintptr) []byte diff --git a/compiler/natives/src/internal/abi/utils.go b/compiler/natives/src/internal/abi/utils.go index 2d391ff63..ba26d92e2 100644 --- a/compiler/natives/src/internal/abi/utils.go +++ b/compiler/natives/src/internal/abi/utils.go @@ -88,6 +88,21 @@ func GetJsTag(tag string) string { return "" } +// NewMethodName creates name instance for a method. +// +// Input object is expected to be an entry of the "methods" list of the +// corresponding JS type. +// +//gopherjs:new +func NewMethodName(m *js.Object) Name { + return Name{ + name: internalStr(m.Get("name")), + tag: "", + pkgPath: internalStr(m.Get("pkg")), + exported: internalStr(m.Get("pkg")) == "", + } +} + //gopherjs:new func UnsafeNew(typ *Type) unsafe.Pointer { switch typ.Kind() { diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index 1da33e623..9aa7dbd11 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -2,8 +2,18 @@ package reflectlite +import "internal/abi" + +// Field returns the i'th field of the struct v. +// It panics if v's Kind is not Struct or i is out of range. +// //gopherjs:replace -func Field(v Value, i int) Value { return v.Field(i) } +func Field(v Value, i int) Value { + if v.kind() != abi.Struct { + panic(&ValueError{"reflect.Value.Field", v.kind()}) + } + return v.Field(i) +} //gopherjs:purge Used in FirstMethodNameBytes type EmbedWithUnexpMeth struct{} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go new file mode 100644 index 000000000..0014f6f8d --- /dev/null +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -0,0 +1,192 @@ +//go:build js + +package reflectlite + +import ( + "unsafe" + + "internal/abi" + + "github.com/gopherjs/gopherjs/js" +) + +func init() { + // avoid dead code elimination + used := func(i any) {} + used(rtype{}) + used(uncommonType{}) + used(arrayType{}) + used(chanType{}) + used(funcType{}) + used(interfaceType{}) + used(mapType{}) + used(ptrType{}) + used(sliceType{}) + used(structType{}) +} + +//gopherjs:new +func jsType(t Type) *js.Object { + return toAbiType(t).JsType() +} + +//gopherjs:new +func toAbiType(t Type) *abi.Type { + return t.(rtype).common() +} + +//gopherjs:new +func jsPtrTo(t Type) *js.Object { + return toAbiType(t).JsPtrTo() +} + +//gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. +type name struct{} + +//gopherjs:replace +func pkgPath(n abi.Name) string { return "" } + +//gopherjs:purge Unused function because of nameOffList in internal/abi overrides +func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer + +//gopherjs:purge Unused function because of typeOffList in internal/abi overrides +func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer + +//gopherjs:replace +func (t rtype) nameOff(off nameOff) abi.Name { + return t.NameOff(off) +} + +//gopherjs:replace +func (t rtype) typeOff(off typeOff) *abi.Type { + return t.TypeOff(off) +} + +//gopherjs:new +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{ + typ: t, + ptr: unsafe.Pointer(v.Unsafe()), + flag: fl | flag(t.Kind()), + } + } + return Value{ + typ: t, + ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), + flag: fl | flag(t.Kind()) | flagIndir, + } +} + +//gopherjs:replace +func TypeOf(i any) Type { + if i == nil { + return nil + } + return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) +} + +//gopherjs:replace +func ValueOf(i any) Value { + if i == nil { + return Value{} + } + return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) +} + +//gopherjs:replace +func unsafe_New(typ *abi.Type) unsafe.Pointer { + return abi.UnsafeNew(typ) +} + +//gopherjs:replace +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { + abi.TypedMemMove(t, dst, src) +} + +//gopherjs:new This is a simplified copy of the version in reflect. +func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { + var prop string + if v.typ.Kind() == abi.Interface { + tt := v.typ.InterfaceType() + if i < 0 || i >= len(tt.Methods) { + panic("reflect: internal error: invalid method index") + } + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { + panic("reflect: " + op + " of unexported method") + } + prop = tt.NameOff(m.Name).Name() + } else { + ms := v.typ.ExportedMethods() + if uint(i) >= uint(len(ms)) { + panic("reflect: internal error: invalid method index") + } + m := ms[i] + if !v.typ.NameOff(m.Name).IsExported() { + panic("reflect: " + op + " of unexported method") + } + prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() + } + rcvr := v.object() + if v.typ.IsWrapped() { + rcvr = v.typ.JsType().New(rcvr) + } + fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) + return +} + +//gopherjs:replace +func valueInterface(v Value) any { + if v.flag == 0 { + panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) + } + + if v.flag&flagMethod != 0 { + v = makeMethodValue("Interface", v) + } + + if v.typ.IsWrapped() { + if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { + cv := v.typ.JsType().Call("zero") + abi.CopyStruct(cv, v.object(), v.typ) + return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) + } + return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) + } + return any(unsafe.Pointer(v.object().Unsafe())) +} + +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + abi.IfaceE2I(t, src, dst) +} + +//gopherjs:replace +func methodName() string { + // TODO(grantnelson-wf): methodName returns the name of the calling method, + // assumed to be two stack frames above. + return "?FIXME?" +} + +//gopherjs:new This is new to reflectlite but there are commented out references in the native code and a copy in reflect. +func makeMethodValue(op string, v Value) Value { + if v.flag&flagMethod == 0 { + panic("reflect: internal error: invalid use of makePartialFunc") + } + + fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) + rcvr := v.object() + if v.typ.IsWrapped() { + rcvr = v.typ.JsType().New(rcvr) + } + fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { + return js.InternalObject(fn).Call("apply", rcvr, arguments) + }) + return Value{ + typ: v.Type().common(), + ptr: unsafe.Pointer(fv.Unsafe()), + flag: v.flag.ro() | flag(abi.Func), + } +} diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index ba83e77c6..b1116776c 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -3,43 +3,9 @@ package reflectlite import ( - "unsafe" - "internal/abi" - - "github.com/gopherjs/gopherjs/js" ) -func init() { - // avoid dead code elimination - used := func(i any) {} - used(rtype{}) - used(uncommonType{}) - used(arrayType{}) - used(chanType{}) - used(funcType{}) - used(interfaceType{}) - used(mapType{}) - used(ptrType{}) - used(sliceType{}) - used(structType{}) -} - -//gopherjs:new -func toAbiType(t Type) *abi.Type { - return t.(rtype).common() -} - -//gopherjs:new -func jsType(t Type) *js.Object { - return toAbiType(t).JsType() -} - -//gopherjs:new -func jsPtrTo(t Type) *js.Object { - return toAbiType(t).JsPtrTo() -} - // GOPHERJS: For some reason the original code left mapType and aliased the rest // to the ABI version. mapType is not used so this is an alias to override the // left over refactor cruft. @@ -47,36 +13,6 @@ func jsPtrTo(t Type) *js.Object { //gopherjs:replace type mapType = abi.MapType -//gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. -type name struct{} - -//gopherjs:replace -func pkgPath(n abi.Name) string { return "" } - -//gopherjs:purge Unused function because of nameOffList in internal/abi overrides -func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer - -//gopherjs:purge Unused function because of typeOffList in internal/abi overrides -func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer - -//gopherjs:replace -func (t rtype) nameOff(off nameOff) abi.Name { - return t.NameOff(off) -} - -//gopherjs:replace -func (t rtype) typeOff(off typeOff) *abi.Type { - return t.TypeOff(off) -} - -//gopherjs:replace -func TypeOf(i any) Type { - if i == nil { - return nil - } - return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) -} - //gopherjs:replace func (t rtype) Comparable() bool { switch t.Kind() { diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 69d63d38d..e035c1633 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -10,13 +10,6 @@ import ( "github.com/gopherjs/gopherjs/js" ) -//gopherjs:replace -func methodName() string { - // TODO(grantnelson-wf): methodName returns the name of the calling method, - // assumed to be two stack frames above. - return "?FIXME?" -} - //gopherjs:replace func (v Value) Elem() Value { switch k := v.kind(); k { @@ -47,80 +40,6 @@ func (v Value) Elem() Value { } } -//gopherjs:replace -func valueInterface(v Value) any { - if v.flag == 0 { - panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) - } - - if v.flag&flagMethod != 0 { - v = makeMethodValue("Interface", v) - } - - if v.typ.IsWrapped() { - if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { - cv := v.typ.JsType().Call("zero") - abi.CopyStruct(cv, v.object(), v.typ) - return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) - } - return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) - } - return any(unsafe.Pointer(v.object().Unsafe())) -} - -//gopherjs:new This is new to reflectlite but there are commented out references in the native code and a copy in reflect. -func makeMethodValue(op string, v Value) Value { - if v.flag&flagMethod == 0 { - panic("reflect: internal error: invalid use of makePartialFunc") - } - - fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr := v.object() - if v.typ.IsWrapped() { - rcvr = v.typ.JsType().New(rcvr) - } - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - return js.InternalObject(fn).Call("apply", rcvr, arguments) - }) - return Value{ - typ: v.Type().common(), - ptr: unsafe.Pointer(fv.Unsafe()), - flag: v.flag.ro() | flag(abi.Func), - } -} - -//gopherjs:new This is a simplified copy of the version in reflect. -func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { - var prop string - if v.typ.Kind() == abi.Interface { - tt := v.typ.InterfaceType() - if i < 0 || i >= len(tt.Methods) { - panic("reflect: internal error: invalid method index") - } - m := &tt.Methods[i] - if !tt.NameOff(m.Name).IsExported() { - panic("reflect: " + op + " of unexported method") - } - prop = tt.NameOff(m.Name).Name() - } else { - ms := v.typ.ExportedMethods() - if uint(i) >= uint(len(ms)) { - panic("reflect: internal error: invalid method index") - } - m := ms[i] - if !v.typ.NameOff(m.Name).IsExported() { - panic("reflect: " + op + " of unexported method") - } - prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() - } - rcvr := v.object() - if v.typ.IsWrapped() { - rcvr = v.typ.JsType().New(rcvr) - } - fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) - return -} - //gopherjs:replace func (v Value) IsNil() bool { switch k := v.kind(); k { @@ -206,7 +125,7 @@ func (v Value) Field(i int) Value { if jsTag := abi.GetJsTag(tag); jsTag != "" { for { v = v.Field(0) - if abi.IsJsObjectPtr(v.typ) { + if v.typ == abi.JsObjectPtr { o := v.object().Get("object") return Value{ typ: typ, @@ -238,36 +157,6 @@ func (v Value) Field(i int) Value { return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } -//gopherjs:replace -func unsafe_New(typ *abi.Type) unsafe.Pointer { - return abi.UnsafeNew(typ) -} - -//gopherjs:replace -func ValueOf(i any) Value { - if i == nil { - return Value{} - } - return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) -} - -//gopherjs:new -func makeValue(t *abi.Type, v *js.Object, fl flag) Value { - switch t.Kind() { - case abi.Array, abi.Struct, abi.Pointer: - return Value{ - typ: t, - ptr: unsafe.Pointer(v.Unsafe()), - flag: fl | flag(t.Kind()), - } - } - return Value{ - typ: t, - ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), - flag: fl | flag(t.Kind()) | flagIndir, - } -} - //gopherjs:new func (v Value) object() *js.Object { if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { @@ -339,13 +228,3 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va // Failed. panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) } - -//gopherjs:replace -func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { - abi.IfaceE2I(t, src, dst) -} - -//gopherjs:replace -func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { - abi.TypedMemMove(t, dst, src) -} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 23683cb10..9bfe63640 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -11,7 +11,7 @@ import ( "github.com/gopherjs/gopherjs/js" ) -//gopherjs:add +//gopherjs:replace func cvtDirect(v Value, typ Type) Value { srcVal := v.object() if srcVal == v.typ().JsType().Get("nil") { @@ -60,7 +60,7 @@ func cvtDirect(v Value, typ Type) Value { // convertOp: []T -> *[N]T // -//gopherjs:add +//gopherjs:replace func cvtSliceArrayPtr(v Value, t Type) Value { slice := v.object() @@ -79,7 +79,7 @@ func cvtSliceArrayPtr(v Value, t Type) Value { // convertOp: []T -> [N]T // -//gopherjs:add +//gopherjs:replace func cvtSliceArray(v Value, t Type) Value { n := t.Len() if n > v.Len() { @@ -139,7 +139,7 @@ func Copy(dst, src Value) int { return js.Global.Call("$copySlice", dstVal, srcVal).Int() } -//gopherjs:add +//gopherjs:replace func valueInterface(v Value, safe bool) any { if v.flag == 0 { panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) @@ -163,7 +163,7 @@ func valueInterface(v Value, safe bool) any { return any(unsafe.Pointer(v.object().Unsafe())) } -//gopherjs:add +//gopherjs:new func (t *rtype) pointers() bool { switch t.Kind() { case Ptr, Map, Chan, Func, Struct, Array: @@ -232,10 +232,10 @@ func (t *rtype) Method(i int) (m Method) { return m } -//gopherjs:add +//gopherjs:new var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) -//gopherjs:add +//gopherjs:replace func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { comms := [][]*js.Object{{js.InternalObject(ch)}} if nb { @@ -250,7 +250,7 @@ func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, receive return true, recvRes.Index(1).Bool() } -//gopherjs:add +//gopherjs:replace func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { comms := [][]*js.Object{{js.InternalObject(ch), js.InternalObject(val).Call("$get")}} if nb { @@ -263,7 +263,7 @@ func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { return true } -//gopherjs:add +//gopherjs:replace func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { comms := make([][]*js.Object, len(rselects)) for i, s := range rselects { @@ -309,7 +309,7 @@ func DeepEqual(a1, a2 any) bool { return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) } -//gopherjs:add +//gopherjs:new func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if !v1.IsValid() || !v2.IsValid() { return !v1.IsValid() && !v2.IsValid() @@ -317,7 +317,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if v1.Type() != v2.Type() { return false } - if abi.IsJsObjectPtr(v1.typ()) { + if v1.typ() == abi.JsObjectPtr { return abi.UnwrapJsObject(abi.JsObjectPtr, v1.object()) == abi.UnwrapJsObject(abi.JsObjectPtr, v2.object()) } @@ -394,7 +394,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1, false)), js.InternalObject(valueInterface(v2, false))).Bool() } -//gopherjs:add +//gopherjs:new func stringsLastIndex(s string, c byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == c { @@ -404,12 +404,12 @@ func stringsLastIndex(s string, c byte) int { return -1 } -//gopherjs:add +//gopherjs:new func stringsHasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } -//gopherjs:add +//gopherjs:replace func verifyNotInHeapPtr(p uintptr) bool { // Go runtime uses this method to make sure that a uintptr won't crash GC if // interpreted as a heap pointer. This is not relevant for GopherJS, so we can diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index ef6e3cdbd..c22e4d222 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -471,7 +471,7 @@ func (v Value) Field(i int) Value { if jsTag := abi.GetJsTag(tag); jsTag != "" { for { v = v.Field(0) - if abi.IsJsObjectPtr(v.typ()) { + if v.typ() == abi.JsObjectPtr { o := v.object().Get("object") return Value{ typ_: typ, From a6e18e23e76fae81adca39b7d07ed74a58fb6387 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 14:08:54 -0700 Subject: [PATCH 21/48] cleaning up some changes --- .../src/internal/reflectlite/reflectlite.go | 2 +- .../natives/src/internal/reflectlite/type.go | 3 +- .../natives/src/internal/reflectlite/value.go | 192 +++++++++--------- 3 files changed, 99 insertions(+), 98 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 0014f6f8d..a3f53a0bc 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -140,7 +140,7 @@ func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { //gopherjs:replace func valueInterface(v Value) any { if v.flag == 0 { - panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) + panic(&ValueError{"reflect.Value.Interface", 0}) } if v.flag&flagMethod != 0 { diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index b1116776c..b6f1eb761 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -23,7 +23,8 @@ func (t rtype) Comparable() bool { case abi.Struct: st := t.StructType() for i := 0; i < len(st.Fields); i++ { - if !toRType(st.Fields[i].Typ).Comparable() { + ft := st.Fields[i] + if !toRType(ft.Typ).Comparable() { return false } } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index e035c1633..b004a76c4 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -10,34 +10,76 @@ import ( "github.com/gopherjs/gopherjs/js" ) -//gopherjs:replace -func (v Value) Elem() Value { - switch k := v.kind(); k { - case abi.Interface: - val := v.object() - if val == js.Global.Get("$ifaceNil") { - return Value{} +//gopherjs:new +func (v Value) object() *js.Object { + if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { + return js.InternalObject(v.ptr) + } + if v.flag&flagIndir != 0 { + val := js.InternalObject(v.ptr).Call("$get") + jsTyp := v.typ.JsType() + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { + switch v.typ.Kind() { + case abi.Uint64, abi.Int64: + val = jsTyp.New(val.Get("$high"), val.Get("$low")) + case abi.Complex64, abi.Complex128: + val = jsTyp.New(val.Get("$real"), val.Get("$imag")) + case abi.Slice: + if val == val.Get("constructor").Get("nil") { + val = jsTyp.Get("nil") + break + } + newVal := jsTyp.New(val.Get("$array")) + newVal.Set("$offset", val.Get("$offset")) + newVal.Set("$length", val.Get("$length")) + newVal.Set("$capacity", val.Get("$capacity")) + val = newVal + } } - typ := abi.ReflectType(val.Get("constructor")) - return makeValue(typ, val.Get("$val"), v.flag.ro()) + return js.InternalObject(val.Unsafe()) + } + return js.InternalObject(v.ptr) +} - case abi.Pointer: - if v.IsNil() { - return Value{} - } - val := v.object() - tt := v.typ.PtrType() - fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.Elem.Kind()) +//gopherjs:replace +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { + if v.flag&flagMethod != 0 { + v = makeMethodValue(context, v) + } + switch { + case directlyAssignable(dst, v.typ): + // Overwrite type so that they match. + // Same memory layout, so no harm done. + fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() + fl |= flag(dst.Kind()) return Value{ - typ: tt.Elem, - ptr: unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), + typ: dst, + ptr: v.ptr, flag: fl, } - default: - panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) + case implements(dst, v.typ): + if target == nil { + target = unsafe_New(dst) + } + // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement + // from upstream. ifaceE2I below does not panic, and it needs + // to run, given its custom implementation. + x := valueInterface(v) + if dst.NumMethod() == 0 { + *(*any)(target) = x + } else { + ifaceE2I(dst, x, target) + } + return Value{ + typ: dst, + ptr: target, + flag: flagIndir | flag(Interface), + } } + + // Failed. + panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) } //gopherjs:replace @@ -56,7 +98,7 @@ func (v Value) IsNil() bool { case abi.UnsafePointer: return v.object().Unsafe() == 0 default: - panic(&ValueError{Method: "reflect.Value.IsNil", Kind: k}) + panic(&ValueError{"reflect.Value.IsNil", k}) } } @@ -72,7 +114,7 @@ func (v Value) Len() int { case abi.Map: return v.object().Get("size").Int() default: - panic(&ValueError{Method: "reflect.Value.Len", Kind: k}) + panic(&ValueError{"reflect.Value.Len", k}) } } @@ -97,11 +139,41 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } +//gopherjs:replace +func (v Value) Elem() Value { + switch k := v.kind(); k { + case abi.Interface: + val := v.object() + if val == js.Global.Get("$ifaceNil") { + return Value{} + } + typ := abi.ReflectType(val.Get("constructor")) + return makeValue(typ, val.Get("$val"), v.flag.ro()) + + case abi.Pointer: + if v.IsNil() { + return Value{} + } + val := v.object() + tt := v.typ.PtrType() + fl := v.flag&flagRO | flagIndir | flagAddr + fl |= flag(tt.Elem.Kind()) + return Value{ + typ: tt.Elem, + ptr: unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), + flag: fl, + } + + default: + panic(&ValueError{"reflect.Value.Elem", k}) + } +} + //gopherjs:new This is added for export_test but is otherwise unused. func (v Value) Field(i int) Value { tt := v.typ.StructType() if tt == nil { - panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) + panic(&ValueError{"reflect.Value.Field", v.kind()}) } if uint(i) >= uint(len(tt.Fields)) { @@ -156,75 +228,3 @@ func (v Value) Field(i int) Value { } return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } - -//gopherjs:new -func (v Value) object() *js.Object { - if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { - return js.InternalObject(v.ptr) - } - if v.flag&flagIndir != 0 { - val := js.InternalObject(v.ptr).Call("$get") - jsTyp := v.typ.JsType() - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { - switch v.typ.Kind() { - case abi.Uint64, abi.Int64: - val = jsTyp.New(val.Get("$high"), val.Get("$low")) - case abi.Complex64, abi.Complex128: - val = jsTyp.New(val.Get("$real"), val.Get("$imag")) - case abi.Slice: - if val == val.Get("constructor").Get("nil") { - val = jsTyp.Get("nil") - break - } - newVal := jsTyp.New(val.Get("$array")) - newVal.Set("$offset", val.Get("$offset")) - newVal.Set("$length", val.Get("$length")) - newVal.Set("$capacity", val.Get("$capacity")) - val = newVal - } - } - return js.InternalObject(val.Unsafe()) - } - return js.InternalObject(v.ptr) -} - -//gopherjs:replace -func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { - if v.flag&flagMethod != 0 { - v = makeMethodValue(context, v) - } - switch { - case directlyAssignable(dst, v.typ): - // Overwrite type so that they match. - // Same memory layout, so no harm done. - fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() - fl |= flag(dst.Kind()) - return Value{ - typ: dst, - ptr: v.ptr, - flag: fl, - } - - case implements(dst, v.typ): - if target == nil { - target = unsafe_New(dst) - } - // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement - // from upstream. ifaceE2I below does not panic, and it needs - // to run, given its custom implementation. - x := valueInterface(v) - if dst.NumMethod() == 0 { - *(*any)(target) = x - } else { - ifaceE2I(dst, x, target) - } - return Value{ - typ: dst, - ptr: target, - flag: flagIndir | flag(Interface), - } - } - - // Failed. - panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) -} From 718124ee0cb8f3d3d90636648c3ef9d0ca6df8c8 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 15:04:02 -0700 Subject: [PATCH 22/48] cleaning up some changes --- .../src/internal/reflectlite/export_test.go | 4 +- .../src/internal/reflectlite/reflectlite.go | 70 ++++++++++--------- .../natives/src/internal/reflectlite/value.go | 44 ++++-------- 3 files changed, 53 insertions(+), 65 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index 9aa7dbd11..91bffa275 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -2,14 +2,12 @@ package reflectlite -import "internal/abi" - // Field returns the i'th field of the struct v. // It panics if v's Kind is not Struct or i is out of range. // //gopherjs:replace func Field(v Value, i int) Value { - if v.kind() != abi.Struct { + if v.kind() != Struct { panic(&ValueError{"reflect.Value.Field", v.kind()}) } return v.Field(i) diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index a3f53a0bc..09f83b632 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -25,9 +25,9 @@ func init() { used(structType{}) } -//gopherjs:new -func jsType(t Type) *js.Object { - return toAbiType(t).JsType() +// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. +func jsType(typ *abi.Type) *js.Object { + return typ.JsType() } //gopherjs:new @@ -40,8 +40,15 @@ func jsPtrTo(t Type) *js.Object { return toAbiType(t).JsPtrTo() } +// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. +func reflectType(typ *js.Object) rtype { + return toRType(abi.ReflectType(typ)) +} + //gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. -type name struct{} +type name struct { + bytes *byte +} //gopherjs:replace func pkgPath(n abi.Name) string { return "" } @@ -62,21 +69,24 @@ func (t rtype) typeOff(off typeOff) *abi.Type { return t.TypeOff(off) } +// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. +func isWrapped(typ *abi.Type) bool { + return typ.IsWrapped() +} + +// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. +func copyStruct(dst, src *js.Object, typ *abi.Type) { + abi.CopyStruct(dst, src, typ) +} + //gopherjs:new -func makeValue(t *abi.Type, v *js.Object, fl flag) Value { +func makeValue(t Type, v *js.Object, fl flag) Value { + rt := t.common() switch t.Kind() { case abi.Array, abi.Struct, abi.Pointer: - return Value{ - typ: t, - ptr: unsafe.Pointer(v.Unsafe()), - flag: fl | flag(t.Kind()), - } - } - return Value{ - typ: t, - ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), - flag: fl | flag(t.Kind()) | flagIndir, + return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} } + return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, rt.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} } //gopherjs:replace @@ -84,7 +94,7 @@ func TypeOf(i any) Type { if i == nil { return nil } - return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) + return reflectType(js.InternalObject(i).Get("constructor")) } //gopherjs:replace @@ -92,7 +102,7 @@ func ValueOf(i any) Value { if i == nil { return Value{} } - return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) + return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) } //gopherjs:replace @@ -127,11 +137,11 @@ func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { if !v.typ.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - prop = js.Global.Call("$methodSet", v.typ.JsType()).Index(i).Get("prop").String() + prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() } rcvr := v.object() - if v.typ.IsWrapped() { - rcvr = v.typ.JsType().New(rcvr) + if isWrapped(v.typ) { + rcvr = jsType(v.typ).New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) return @@ -147,13 +157,13 @@ func valueInterface(v Value) any { v = makeMethodValue("Interface", v) } - if v.typ.IsWrapped() { + if isWrapped(v.typ) { if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { - cv := v.typ.JsType().Call("zero") - abi.CopyStruct(cv, v.object(), v.typ) - return any(unsafe.Pointer(v.typ.JsType().New(cv).Unsafe())) + cv := jsType(v.typ).Call("zero") + copyStruct(cv, v.object(), v.typ) + return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) } - return any(unsafe.Pointer(v.typ.JsType().New(v.object()).Unsafe())) + return any(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) } return any(unsafe.Pointer(v.object().Unsafe())) } @@ -178,15 +188,11 @@ func makeMethodValue(op string, v Value) Value { fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() - if v.typ.IsWrapped() { - rcvr = v.typ.JsType().New(rcvr) + if isWrapped(v.typ) { + rcvr = jsType(v.typ).New(rcvr) } fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { return js.InternalObject(fn).Call("apply", rcvr, arguments) }) - return Value{ - typ: v.Type().common(), - ptr: unsafe.Pointer(fv.Unsafe()), - flag: v.flag.ro() | flag(abi.Func), - } + return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(abi.Func)} } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index b004a76c4..3ca68337b 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -52,11 +52,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va // Same memory layout, so no harm done. fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) - return Value{ - typ: dst, - ptr: v.ptr, - flag: fl, - } + return Value{dst, v.ptr, fl} case implements(dst, v.typ): if target == nil { @@ -71,11 +67,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } else { ifaceE2I(dst, x, target) } - return Value{ - typ: dst, - ptr: target, - flag: flagIndir | flag(Interface), - } + return Value{dst, target, flagIndir | flag(Interface)} } // Failed. @@ -130,7 +122,7 @@ func (v Value) Set(x Value) { case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x))) case abi.Struct: - abi.CopyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) + copyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) default: js.InternalObject(v.ptr).Call("$set", x.object()) } @@ -147,7 +139,7 @@ func (v Value) Elem() Value { if val == js.Global.Get("$ifaceNil") { return Value{} } - typ := abi.ReflectType(val.Get("constructor")) + typ := reflectType(val.Get("constructor")) return makeValue(typ, val.Get("$val"), v.flag.ro()) case abi.Pointer: @@ -180,7 +172,7 @@ func (v Value) Field(i int) Value { panic("reflect: Field index out of range") } - prop := v.typ.JsType().Get("fields").Index(i).Get("prop").String() + prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() field := &tt.Fields[i] typ := field.Typ @@ -199,14 +191,10 @@ func (v Value) Field(i int) Value { v = v.Field(0) if v.typ == abi.JsObjectPtr { o := v.object().Get("object") - return Value{ - typ: typ, - ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), - ).Unsafe()), - flag: fl, - } + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), + ).Unsafe()), fl} } if v.typ.Kind() == abi.Pointer { v = v.Elem() @@ -217,14 +205,10 @@ func (v Value) Field(i int) Value { s := js.InternalObject(v.ptr) if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ: typ, - ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), - flag: fl, - } + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} } - return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) + return makeValue(toRType(typ), abi.WrapJsObject(typ, s.Get(prop)), fl) } From f0230f777f84b93cd4b7c71763b74011959f243c Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 16:19:38 -0700 Subject: [PATCH 23/48] cleaning up some changes --- compiler/natives/src/internal/abi/type.go | 19 ++++++++++++++++++ .../natives/src/internal/reflectlite/type.go | 20 ++----------------- .../natives/src/internal/reflectlite/value.go | 13 ++++++------ compiler/natives/src/reflect/reflect.go | 14 +------------ 4 files changed, 28 insertions(+), 38 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 6104c5291..6547d512f 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -439,6 +439,25 @@ func CopyStruct(dst, src *js.Object, typ *Type) { } } +//gopherjs:new +func (t *Type) Comparable() bool { + switch t.Kind() { + case Func, Slice, Map: + return false + case Array: + return t.Elem().Comparable() + case Struct: + st := t.StructType() + for i := 0; i < len(st.Fields); i++ { + ft := st.Fields[i] + if !ft.Typ.Comparable() { + return false + } + } + } + return true +} + //gopherjs:purge Used for pointer arthmatic func addChecked(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index b6f1eb761..1175ed951 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -2,9 +2,7 @@ package reflectlite -import ( - "internal/abi" -) +import "internal/abi" // GOPHERJS: For some reason the original code left mapType and aliased the rest // to the ABI version. mapType is not used so this is an alias to override the @@ -15,19 +13,5 @@ type mapType = abi.MapType //gopherjs:replace func (t rtype) Comparable() bool { - switch t.Kind() { - case abi.Func, abi.Slice, abi.Map: - return false - case abi.Array: - return t.Elem().Comparable() - case abi.Struct: - st := t.StructType() - for i := 0; i < len(st.Fields); i++ { - ft := st.Fields[i] - if !toRType(ft.Typ).Comparable() { - return false - } - } - } - return true + return toAbiType(t).Comparable() } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 3ca68337b..0dcfe45ff 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -17,19 +17,18 @@ func (v Value) object() *js.Object { } if v.flag&flagIndir != 0 { val := js.InternalObject(v.ptr).Call("$get") - jsTyp := v.typ.JsType() - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { switch v.typ.Kind() { case abi.Uint64, abi.Int64: - val = jsTyp.New(val.Get("$high"), val.Get("$low")) + val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) case abi.Complex64, abi.Complex128: - val = jsTyp.New(val.Get("$real"), val.Get("$imag")) + val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) case abi.Slice: if val == val.Get("constructor").Get("nil") { - val = jsTyp.Get("nil") + val = jsType(v.typ).Get("nil") break } - newVal := jsTyp.New(val.Get("$array")) + newVal := jsType(v.typ).New(val.Get("$array")) newVal.Set("$offset", val.Get("$offset")) newVal.Set("$length", val.Get("$length")) newVal.Set("$capacity", val.Get("$capacity")) @@ -71,7 +70,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } // Failed. - panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String()) + panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) } //gopherjs:replace diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 9bfe63640..34974556e 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -175,19 +175,7 @@ func (t *rtype) pointers() bool { //gopherjs:replace func (t *rtype) Comparable() bool { - switch t.Kind() { - case Func, Slice, Map: - return false - case Array: - return t.Elem().Comparable() - case Struct: - for i := 0; i < t.NumField(); i++ { - if !t.Field(i).Type.Comparable() { - return false - } - } - } - return true + return toAbiType(t).Comparable() } //gopherjs:replace From e08d727b42ffdeeba1ac5dec31bfe17a21cc7e99 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 16:46:38 -0700 Subject: [PATCH 24/48] cleaning up some changes --- .../src/internal/reflectlite/reflectlite.go | 42 +++++++------------ .../natives/src/internal/reflectlite/value.go | 20 ++++----- 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index 09f83b632..f12ff5b60 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -25,7 +25,7 @@ func init() { used(structType{}) } -// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. +// TODO(grantnelson-wf): This is just to minimize diffs. After this is merged into the go1.21 branch, remove it. func jsType(typ *abi.Type) *js.Object { return typ.JsType() } @@ -40,11 +40,6 @@ func jsPtrTo(t Type) *js.Object { return toAbiType(t).JsPtrTo() } -// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. -func reflectType(typ *js.Object) rtype { - return toRType(abi.ReflectType(typ)) -} - //gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. type name struct { bytes *byte @@ -69,24 +64,13 @@ func (t rtype) typeOff(off typeOff) *abi.Type { return t.TypeOff(off) } -// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. -func isWrapped(typ *abi.Type) bool { - return typ.IsWrapped() -} - -// TODO(grantnelson-wf): After this is in the go1.21 branch, remove it, this is just to minimize diffs. -func copyStruct(dst, src *js.Object, typ *abi.Type) { - abi.CopyStruct(dst, src, typ) -} - //gopherjs:new -func makeValue(t Type, v *js.Object, fl flag) Value { - rt := t.common() +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { switch t.Kind() { case abi.Array, abi.Struct, abi.Pointer: - return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} + return Value{t, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} } - return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, rt.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} + return Value{t, unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} } //gopherjs:replace @@ -94,7 +78,7 @@ func TypeOf(i any) Type { if i == nil { return nil } - return reflectType(js.InternalObject(i).Get("constructor")) + return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) } //gopherjs:replace @@ -102,7 +86,7 @@ func ValueOf(i any) Value { if i == nil { return Value{} } - return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) + return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) } //gopherjs:replace @@ -140,7 +124,7 @@ func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() } rcvr := v.object() - if isWrapped(v.typ) { + if v.typ.IsWrapped() { rcvr = jsType(v.typ).New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) @@ -157,10 +141,10 @@ func valueInterface(v Value) any { v = makeMethodValue("Interface", v) } - if isWrapped(v.typ) { + if v.typ.IsWrapped() { if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { cv := jsType(v.typ).Call("zero") - copyStruct(cv, v.object(), v.typ) + abi.CopyStruct(cv, v.object(), v.typ) return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) } return any(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) @@ -173,10 +157,12 @@ func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { abi.IfaceE2I(t, src, dst) } +// TODO(grantnelson-wf): methodName returns the name of the calling method, +// assumed to be two stack frames above. Determine if we can get this value now +// and if methodName is needed +// //gopherjs:replace func methodName() string { - // TODO(grantnelson-wf): methodName returns the name of the calling method, - // assumed to be two stack frames above. return "?FIXME?" } @@ -188,7 +174,7 @@ func makeMethodValue(op string, v Value) Value { fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() - if isWrapped(v.typ) { + if v.typ.IsWrapped() { rcvr = jsType(v.typ).New(rcvr) } fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 0dcfe45ff..365b6f935 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -77,7 +77,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va func (v Value) IsNil() bool { switch k := v.kind(); k { case abi.Pointer, abi.Slice: - return v.object() == v.typ.JsType().Get("nil") + return v.object() == jsType(v.typ).Get("nil") case abi.Chan: return v.object() == js.Global.Get("$chanNil") case abi.Func: @@ -112,16 +112,16 @@ func (v Value) Len() int { //gopherjs:replace func (v Value) Set(x Value) { v.mustBeAssignable() - x.mustBeExported() + x.mustBeExported() // TODO(grantnelson-wf): This uses the unimplemented `methodName()`. When methodName is fixed we can use Set to test. x = x.assignTo("reflect.Set", v.typ, nil) if v.flag&flagIndir != 0 { switch v.typ.Kind() { case abi.Array: - v.typ.JsType().Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) + jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x))) case abi.Struct: - copyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) + abi.CopyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) default: js.InternalObject(v.ptr).Call("$set", x.object()) } @@ -138,7 +138,7 @@ func (v Value) Elem() Value { if val == js.Global.Get("$ifaceNil") { return Value{} } - typ := reflectType(val.Get("constructor")) + typ := abi.ReflectType(val.Get("constructor")) return makeValue(typ, val.Get("$val"), v.flag.ro()) case abi.Pointer: @@ -149,18 +149,14 @@ func (v Value) Elem() Value { tt := v.typ.PtrType() fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.Elem.Kind()) - return Value{ - typ: tt.Elem, - ptr: unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), - flag: fl, - } + return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} default: panic(&ValueError{"reflect.Value.Elem", k}) } } -//gopherjs:new This is added for export_test but is otherwise unused. +// TODO(grantnelson-wf): Move this to export_test since it is defined there in the original code and only used for tests. func (v Value) Field(i int) Value { tt := v.typ.StructType() if tt == nil { @@ -209,5 +205,5 @@ func (v Value) Field(i int) Value { js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), ).Unsafe()), fl} } - return makeValue(toRType(typ), abi.WrapJsObject(typ, s.Get(prop)), fl) + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } From 13c232a607e36349e0c6233ecdb50b9d1ec9eb70 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 17:05:57 -0700 Subject: [PATCH 25/48] fixing issues in ABI --- compiler/natives/src/internal/abi/type.go | 56 +++++++++++++++++------ 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 6547d512f..f7717ebdd 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -166,9 +166,16 @@ func setKindType(abiTyp *Type, kindType any) { js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) } +// getKindType will get the type specific to the kind if this type. +// For example `getKindType[StructType](Struct, t)` returns t cast to a +// [*StructType], or nil if the type's kind is not a [Struct]. +// //gopherjs:new -func (t *Type) KindType() *Type { - return (*Type)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) +func getKindType[T any](kind Kind, t *Type) *T { + if t.Kind() != kind { + return nil + } + return (*T)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) } //gopherjs:replace @@ -225,28 +232,49 @@ func (typ *Type) JsPtrTo() *js.Object { return typ.PtrTo().JsType() } +//================================================================================= +// TODO(grantnelson-wf): Test out overriding the cast of pointer types to Work for typeKinds. +// If the override works, find all unsafe pointer casts still being done and override them, e.g. Elem(). +//================================================================================= + +//gophejs:replace +func (t *Type) StructType() *StructType { + return getKindType[StructType](Struct, t) +} + +//gophejs:replace +func (t *Type) MapType() *MapType { + return getKindType[MapType](Map, t) +} + +//gophejs:replace +func (t *Type) ArrayType() *ArrayType { + return getKindType[ArrayType](Array, t) +} + +//gophejs:replace +func (t *Type) FuncType() *FuncType { + return getKindType[FuncType](Func, t) +} + +//gophejs:replace +func (t *Type) InterfaceType() *InterfaceType { + return getKindType[InterfaceType](Interface, t) +} + //gopherjs:new Same as ArrayType(), MapType(), etc but for ChanType. func (t *Type) ChanType() *ChanType { - if t.Kind() != Chan { - return nil - } - return (*ChanType)(unsafe.Pointer(t)) + return getKindType[ChanType](Chan, t) } //gopherjs:new Same as ArrayType(), MapType(), etc but for PtrType. func (t *Type) PtrType() *PtrType { - if t.Kind() != Pointer { - return nil - } - return (*PtrType)(unsafe.Pointer(t)) + return getKindType[PtrType](Pointer, t) } //gopherjs:new Same as ArrayType(), MapType(), etc but for SliceType func (t *Type) SliceType() *SliceType { - if t.Kind() != Slice { - return nil - } - return (*SliceType)(unsafe.Pointer(t)) + return getKindType[SliceType](Slice, t) } //gopherjs:new Shared by reflect and reflectlite rtypes From f547d49f19e61558e9918338e1867ccce47b2e12 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 11 Feb 2026 17:09:53 -0700 Subject: [PATCH 26/48] fixing issues in ABI --- compiler/natives/src/internal/abi/type.go | 17 +++++++++++ .../src/internal/reflectlite/all_test.go | 29 ------------------- 2 files changed, 17 insertions(+), 29 deletions(-) delete mode 100644 compiler/natives/src/internal/reflectlite/all_test.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index f7717ebdd..3d89d29ca 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -237,6 +237,23 @@ func (typ *Type) JsPtrTo() *js.Object { // If the override works, find all unsafe pointer casts still being done and override them, e.g. Elem(). //================================================================================= +//gophejs:replace +func (t *Type) Elem() *Type { + switch t.Kind() { + case Array: + return t.ArrayType().Elem + case Chan: + return t.ChanType().Elem + case Map: + return t.MapType().Elem + case Pointer: + return t.PtrType().Elem + case Slice: + return t.SliceType().Elem + } + return nil +} + //gophejs:replace func (t *Type) StructType() *StructType { return getKindType[StructType](Struct, t) diff --git a/compiler/natives/src/internal/reflectlite/all_test.go b/compiler/natives/src/internal/reflectlite/all_test.go deleted file mode 100644 index fa1aeab24..000000000 --- a/compiler/natives/src/internal/reflectlite/all_test.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build js - -package reflectlite_test - -import ( - "testing" - - . "internal/reflectlite" -) - -// TODO: REMOVE (only added to break down the steps of the test) -func TestTypes(t *testing.T) { - for i, tt := range typeTests { - println(`>> i >>`, i) - println(`>> tt.i >>`, tt.i) - println(`>> tt.s >>`, tt.s) - - v := ValueOf(tt.i) - println(`>> v >>`, v) - - f := Field(v, 0) - println(`>> f >>`, f) - - tx := f.Type() - println(`>> tx >>`, tx) - - testReflectType(t, i, tx, tt.s) - } -} From 692e72a02bdaba151c113acd529f5b5214d0985d Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 12 Feb 2026 15:39:37 -0700 Subject: [PATCH 27/48] fixing up the tests --- compiler/natives/src/internal/abi/type.go | 34 ++++ .../src/internal/reflectlite/export_test.go | 9 ++ .../src/internal/reflectlite/tostring_test.go | 12 ++ .../natives/src/internal/reflectlite/type.go | 5 + compiler/natives/src/reflect/reflect.go | 16 +- compiler/natives/src/reflect/type.go | 39 +++++ compiler/natives/src/reflect/value.go | 148 +++++++++--------- 7 files changed, 184 insertions(+), 79 deletions(-) create mode 100644 compiler/natives/src/internal/reflectlite/tostring_test.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 3d89d29ca..a4540f994 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -294,6 +294,40 @@ func (t *Type) SliceType() *SliceType { return getKindType[SliceType](Slice, t) } +// Len returns the length of t if t is an array type, otherwise 0 +// +//gopherjs:replace Used a pointer cast to get the array kind type. +func (t *Type) Len() int { + if arr := t.ArrayType(); arr != nil { + return int(arr.Len) + } + return 0 +} + +//gopherjs:replace Used a pointer cast to get the chan kind type. +func (t *Type) ChanDir() ChanDir { + if ch := t.ChanType(); ch != nil { + return ch.Dir + } + return InvalidDir +} + +//gopherjs:replace Used a pointer cast to get the interface kind type. +func (t *Type) NumMethod() int { + if tt := t.InterfaceType(); tt != nil { + return tt.NumMethod() + } + return len(t.ExportedMethods()) +} + +//gopherjs:replace Used a pointer cast to get the map kind type. +func (t *Type) Key() *Type { + if mt := t.MapType(); mt != nil { + return mt.Key + } + return nil +} + //gopherjs:new Shared by reflect and reflectlite rtypes func (t *Type) String() string { s := t.NameOff(t.Str).Name() diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index 91bffa275..d9a3dce3e 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -13,6 +13,15 @@ func Field(v Value, i int) Value { return v.Field(i) } +//gopherjs:replace Used a pointer cast for the struct kind type. +func TField(typ Type, i int) Type { + tt := toAbiType(typ).StructType() + if tt == nil { + panic("reflect: Field of non-struct type") + } + return StructFieldType(tt, i) +} + //gopherjs:purge Used in FirstMethodNameBytes type EmbedWithUnexpMeth struct{} diff --git a/compiler/natives/src/internal/reflectlite/tostring_test.go b/compiler/natives/src/internal/reflectlite/tostring_test.go new file mode 100644 index 000000000..0648a9749 --- /dev/null +++ b/compiler/natives/src/internal/reflectlite/tostring_test.go @@ -0,0 +1,12 @@ +//go:build js + +package reflectlite_test + +import "reflect" + +// TODO: REMOVE +// +//gopherjs:keep-original +func valueToStringImpl(val reflect.Value) string { + return _gopherjs_original_valueToStringImpl(val) +} diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 1175ed951..896a3e149 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -15,3 +15,8 @@ type mapType = abi.MapType func (t rtype) Comparable() bool { return toAbiType(t).Comparable() } + +//gopherjs:replace +func (t rtype) String() string { + return toAbiType(t).String() +} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 34974556e..1fd2abc48 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -178,10 +178,18 @@ func (t *rtype) Comparable() bool { return toAbiType(t).Comparable() } +//gopherjs:replace Used pointer cast to interface kind type. +func (t *rtype) NumMethod() int { + if tt := t.common().InterfaceType(); tt != nil { + return tt.NumMethod() + } + return len(t.exportedMethods()) +} + //gopherjs:replace func (t *rtype) Method(i int) (m Method) { if t.Kind() == Interface { - tt := (*interfaceType)(unsafe.Pointer(t)) + tt := toInterfaceType(t.common()) return tt.Method(i) } methods := t.exportedMethods() @@ -210,11 +218,7 @@ func (t *rtype) Method(i int) (m Method) { rcvr := arguments[0] return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) }) - m.Func = Value{ - typ_: toAbiType(mt), - ptr: unsafe.Pointer(fn.Unsafe()), - flag: fl, - } + m.Func = Value{toAbiType(mt), unsafe.Pointer(fn.Unsafe()), fl} m.Index = i return m diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index a3cb18bb2..29fe3b0d6 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -27,6 +27,22 @@ func init() { used(structField{}) } +// GOPHERJS: In Go the rtype and ABI Type share a memory footprint so a pointer +// to one is a pointer to the other. This doesn't work in JS so instead we construct +// a new rtype with a `*abi.Type` inside of it but that means the pointers are +// different and multiple `*rtypes` may point to the same ABI type. +// (reflectlite does this better by having the rtype not be a pointer.) +// +//gopherjs:replace +type rtype struct { + t *abi.Type +} + +//gopherjs:replace +func (t *rtype) common() *abi.Type { + return t.t +} + //gopherjs:new func toAbiType(typ Type) *abi.Type { return typ.(*rtype).common() @@ -42,6 +58,16 @@ func (t *rtype) ptrTo() *abi.Type { return toAbiType(t).PtrTo() } +//gopherjs:replace +func toRType(t *abi.Type) *rtype { + return &rtype{t: t} +} + +//gopherjs:replace +func (t *rtype) String() string { + return toAbiType(t).String() +} + //gopherjs:purge func addReflectOff(ptr unsafe.Pointer) int32 @@ -93,6 +119,19 @@ func rtypeOf(i any) *abi.Type { return abi.ReflectType(js.InternalObject(i).Get("constructor")) } +// GOPHERJS: reflect defined instance of interface type has the same issue +// that rtype has with assuming the pointers can be the same for. +// +//gopherjs:replace +type interfaceType struct { + *abi.InterfaceType +} + +//gopherjs:new +func toInterfaceType(typ *abi.Type) *interfaceType { + return &interfaceType{InterfaceType: typ.InterfaceType()} +} + //gopherjs:replace func ArrayOf(count int, elem Type) Type { if count < 0 { diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index c22e4d222..bdbc91d59 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -40,7 +40,7 @@ func New(typ Type) Value { pt := t.PtrTo() ptr := unsafe_New(t) fl := flag(Pointer) - return Value{typ_: pt, ptr: ptr, flag: fl} + return Value{pt, ptr, fl} } //gopherjs:replace @@ -56,22 +56,16 @@ func unsafe_New(typ *abi.Type) unsafe.Pointer { return abi.UnsafeNew(typ) } +//gopherjs:new func makeValue(t *abi.Type, v *js.Object, fl flag) Value { switch t.Kind() { case abi.Array, abi.Struct, abi.Pointer: - return Value{ - typ_: t, - ptr: unsafe.Pointer(v.Unsafe()), - flag: fl | flag(t.Kind()), - } - } - return Value{ - typ_: t, - ptr: unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), - flag: fl | flag(t.Kind()) | flagIndir, + return Value{t, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} } + return Value{t, unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} } +//gopherjs:replace func MakeSlice(typ Type, len, cap int) Value { if typ.Kind() != Slice { panic("reflect.MakeSlice of non-slice type") @@ -89,10 +83,12 @@ func MakeSlice(typ Type, len, cap int) Value { return makeValue(toAbiType(typ), js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) } +//gopherjs:replace func Zero(typ Type) Value { return makeValue(toAbiType(typ), jsType(typ).Call("zero"), 0) } +//gopherjs:replace func makeInt(f flag, bits uint64, t Type) Value { typ := t.common() ptr := unsafe_New(typ) @@ -114,11 +110,7 @@ func makeInt(f flag, bits uint64, t Type) Value { case abi.Uint64: *(*uint64)(ptr) = uint64(bits) } - return Value{ - typ_: typ, - ptr: ptr, - flag: f | flagIndir | flag(typ.Kind()), - } + return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} } //gopherjs:replace @@ -163,6 +155,7 @@ func storeRcvr(v Value, p unsafe.Pointer) //gopherjs:purge func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) +//gopherjs:replace func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { if typ.Kind() != Func { panic("reflect: call of MakeFunc with non-Func type") @@ -207,11 +200,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { } }) - return Value{ - typ_: t, - ptr: unsafe.Pointer(fv.Unsafe()), - flag: flag(Func), - } + return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} } //gopherjs:replace @@ -235,6 +224,7 @@ func makemap(t *abi.Type, cap int) (m unsafe.Pointer) { return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) } +//gopherjs:new func (v Value) object() *js.Object { if v.typ().Kind() == abi.Array || v.typ().Kind() == abi.Struct { return js.InternalObject(v.ptr) @@ -277,7 +267,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va // Same memory layout, so no harm done. fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) - return Value{typ_: dst, ptr: v.ptr, flag: fl} + return Value{dst, v.ptr, fl} case implements(dst, v.typ()): if target == nil { @@ -292,15 +282,17 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va } else { ifaceE2I(dst, x, target) } - return Value{typ_: dst, ptr: target, flag: flagIndir | flag(Interface)} + return Value{dst, target, flagIndir | flag(Interface)} } // Failed. panic(context + ": value of type " + v.typ().String() + " is not assignable to type " + dst.String()) } +//gopherjs:new var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) +//gopherjs:replace func (v Value) call(op string, in []Value) []Value { var ( t *funcType @@ -400,6 +392,15 @@ func (v Value) call(op string, in []Value) []Value { } } +//gopherjs:replace Used a pointer cast to get *rtype that doesn't work in JS. +func (v Value) Type() Type { + if v.flag != 0 && v.flag&flagMethod == 0 { + return toRType(v.typ_) + } + return v.typeSlow() +} + +//gopherjs:replace func (v Value) Cap() int { k := v.kind() switch k { @@ -413,9 +414,10 @@ func (v Value) Cap() int { } panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") } - panic(&ValueError{Method: "reflect.Value.Cap", Kind: k}) + panic(&ValueError{"reflect.Value.Cap", k}) } +//gopherjs:replace func (v Value) Elem() Value { switch k := v.kind(); k { case Interface: @@ -434,21 +436,25 @@ func (v Value) Elem() Value { tt := v.typ().PtrType() fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.Elem.Kind()) - return Value{ - typ_: tt.Elem, - ptr: unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), - flag: fl, - } + return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} default: - panic(&ValueError{Method: "reflect.Value.Elem", Kind: k}) + panic(&ValueError{"reflect.Value.Elem", k}) } } +//gopherjs:replace +func (v Value) NumField() int { + v.mustBe(Struct) + tt := v.typ().StructType() + return len(tt.Fields) +} + +//gopherjs:replace func (v Value) Field(i int) Value { tt := v.typ().StructType() if tt == nil { - panic(&ValueError{Method: "reflect.Value.Field", Kind: v.kind()}) + panic(&ValueError{"reflect.Value.Field", v.kind()}) } if uint(i) >= uint(len(tt.Fields)) { panic("reflect: Field index out of range") @@ -473,14 +479,10 @@ func (v Value) Field(i int) Value { v = v.Field(0) if v.typ() == abi.JsObjectPtr { o := v.object().Get("object") - return Value{ - typ_: typ, - ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), - ).Unsafe()), - flag: fl, - } + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), + ).Unsafe()), fl} } if v.typ().Kind() == abi.Pointer { v = v.Elem() @@ -491,22 +493,20 @@ func (v Value) Field(i int) Value { s := js.InternalObject(v.ptr) if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ_: typ, - ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), - flag: fl, - } + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} } return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } +//gopherjs:replace func (v Value) UnsafePointer() unsafe.Pointer { return unsafe.Pointer(v.Pointer()) } +//gopherjs:replace func (v Value) grow(n int) { if n < 0 { panic(`reflect.Value.Grow: negative len`) @@ -577,10 +577,11 @@ func (v Value) Clear() { // TODO(grantnelson-wf): Finish implementing // mapclear(v.typ(), v.pointer()) default: - panic(&ValueError{Method: "reflect.Value.Clear", Kind: v.Kind()}) + panic(&ValueError{"reflect.Value.Clear", v.Kind()}) } } +//gopherjs:replace func (v Value) Index(i int) Value { switch k := v.kind(); k { case Array: @@ -593,14 +594,10 @@ func (v Value) Index(i int) Value { a := js.InternalObject(v.ptr) if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ_: typ, - ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), - flag: fl, - } + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} } return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) @@ -616,14 +613,10 @@ func (v Value) Index(i int) Value { i += s.Get("$offset").Int() a := s.Get("$array") if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{ - typ_: typ, - ptr: unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), - flag: fl, - } + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} } return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) @@ -634,26 +627,25 @@ func (v Value) Index(i int) Value { } fl := v.flag.ro() | flag(Uint8) | flagIndir c := str[i] - return Value{ - typ_: uint8Type, - ptr: unsafe.Pointer(&c), - flag: fl, - } + return Value{uint8Type, unsafe.Pointer(&c), fl} default: - panic(&ValueError{Method: "reflect.Value.Index", Kind: k}) + panic(&ValueError{"reflect.Value.Index", k}) } } +//gopherjs:replace func (v Value) InterfaceData() [2]uintptr { panic(errors.New("InterfaceData is not supported by GopherJS")) } +//gopherjs:replace func (v Value) SetZero() { v.mustBeAssignable() v.Set(Zero(toRType(v.typ()))) } +//gopherjs:replace func (v Value) IsNil() bool { switch k := v.kind(); k { case Ptr, Slice: @@ -673,6 +665,7 @@ func (v Value) IsNil() bool { } } +//gopherjs:replace func (v Value) Len() int { switch k := v.kind(); k { case Array, String: @@ -696,6 +689,7 @@ func (v Value) Len() int { //gopherjs:purge Not used since Len() is overridden. func (v Value) lenNonSlice() int +//gopherjs:replace func (v Value) Pointer() uintptr { switch k := v.kind(); k { case Chan, Map, Ptr, UnsafePointer: @@ -718,6 +712,7 @@ func (v Value) Pointer() uintptr { } } +//gopherjs:replace func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() @@ -736,6 +731,7 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } +//gopherjs:replace func (v Value) bytesSlow() []byte { switch v.kind() { case Slice: @@ -756,9 +752,10 @@ func (v Value) bytesSlow() []byte { // return unsafe.Slice(p, n) return js.InternalObject(v.ptr).Interface().([]byte) } - panic(&ValueError{Method: "reflect.Value.Bytes", Kind: v.kind()}) + panic(&ValueError{"reflect.Value.Bytes", v.kind()}) } +//gopherjs:replace func (v Value) SetBytes(x []byte) { v.mustBeAssignable() v.mustBe(Slice) @@ -776,6 +773,7 @@ func (v Value) SetBytes(x []byte) { js.InternalObject(v.ptr).Call("$set", slice) } +//gopherjs:replace func (v Value) SetCap(n int) { v.mustBeAssignable() v.mustBe(Slice) @@ -790,6 +788,7 @@ func (v Value) SetCap(n int) { js.InternalObject(v.ptr).Call("$set", newSlice) } +//gopherjs:replace func (v Value) SetLen(n int) { v.mustBeAssignable() v.mustBe(Slice) @@ -804,6 +803,7 @@ func (v Value) SetLen(n int) { js.InternalObject(v.ptr).Call("$set", newSlice) } +//gopherjs:replace func (v Value) Slice(i, j int) Value { var ( cap int @@ -833,7 +833,7 @@ func (v Value) Slice(i, j int) Value { return ValueOf(str[i:j]) default: - panic(&ValueError{Method: "reflect.Value.Slice", Kind: kind}) + panic(&ValueError{"reflect.Value.Slice", kind}) } if i < 0 || j < i || j > cap { @@ -843,6 +843,7 @@ func (v Value) Slice(i, j int) Value { return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) } +//gopherjs:replace func (v Value) Slice3(i, j, k int) Value { var ( cap int @@ -865,7 +866,7 @@ func (v Value) Slice3(i, j, k int) Value { cap = s.Get("$capacity").Int() default: - panic(&ValueError{Method: "reflect.Value.Slice3", Kind: kind}) + panic(&ValueError{"reflect.Value.Slice3", kind}) } if i < 0 || j < i || k < j || k > cap { @@ -875,6 +876,7 @@ func (v Value) Slice3(i, j, k int) Value { return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) } +//gopherjs:replace func (v Value) Close() { v.mustBe(Chan) v.mustBeExported() From 163d51beeaacbddff37ffe135b766d00b9dd8db1 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 12 Feb 2026 16:19:08 -0700 Subject: [PATCH 28/48] fixing up the tests --- compiler/natives/src/reflect/reflect.go | 8 -- compiler/natives/src/reflect/type.go | 176 ++++++++++++++++++++++-- compiler/natives/src/reflect/value.go | 12 +- 3 files changed, 174 insertions(+), 22 deletions(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 1fd2abc48..f664db3f4 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -178,14 +178,6 @@ func (t *rtype) Comparable() bool { return toAbiType(t).Comparable() } -//gopherjs:replace Used pointer cast to interface kind type. -func (t *rtype) NumMethod() int { - if tt := t.common().InterfaceType(); tt != nil { - return tt.NumMethod() - } - return len(t.exportedMethods()) -} - //gopherjs:replace func (t *rtype) Method(i int) (m Method) { if t.Kind() == Interface { diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 29fe3b0d6..6f0416193 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -29,8 +29,9 @@ func init() { // GOPHERJS: In Go the rtype and ABI Type share a memory footprint so a pointer // to one is a pointer to the other. This doesn't work in JS so instead we construct -// a new rtype with a `*abi.Type` inside of it but that means the pointers are -// different and multiple `*rtypes` may point to the same ABI type. +// a new rtype with a `*abi.Type` inside of it. However, that means the pointers are +// different and multiple `*rtypes` may point to the same ABI type, so we have to +// take that into account when overriding code or leaving the original code. // (reflectlite does this better by having the rtype not be a pointer.) // //gopherjs:replace @@ -119,17 +120,72 @@ func rtypeOf(i any) *abi.Type { return abi.ReflectType(js.InternalObject(i).Get("constructor")) } -// GOPHERJS: reflect defined instance of interface type has the same issue -// that rtype has with assuming the pointers can be the same for. -// -//gopherjs:replace +//gopherjs:purge Unused type +type common struct{} + +//gopherjs:replace Same issue as rtype type interfaceType struct { *abi.InterfaceType } //gopherjs:new func toInterfaceType(typ *abi.Type) *interfaceType { - return &interfaceType{InterfaceType: typ.InterfaceType()} + if tt := typ.InterfaceType(); tt != nil { + return &interfaceType{InterfaceType: tt} + } + return nil +} + +//gopherjs:replace Same issue as rtype +type mapType struct { + *abi.MapType +} + +//gopherjs:new +func toMapType(typ *abi.Type) *mapType { + if tt := typ.MapType(); tt != nil { + return &mapType{MapType: tt} + } + return nil +} + +//gopherjs:replace Same issue as rtype +type ptrType struct { + *abi.PtrType +} + +//gopherjs:new +func toPtrType(typ *abi.Type) *ptrType { + if tt := typ.PtrType(); tt != nil { + return &ptrType{PtrType: tt} + } + return nil +} + +//gopherjs:replace Same issue as rtype +type sliceType struct { + *abi.SliceType +} + +//gopherjs:new +func toSliceType(typ *abi.Type) *sliceType { + if tt := typ.SliceType(); tt != nil { + return &sliceType{SliceType: tt} + } + return nil +} + +//gopherjs:replace Same issue as rtype +type structType struct { + *abi.StructType +} + +//gopherjs:new +func toStructType(typ *abi.Type) *structType { + if tt := typ.StructType(); tt != nil { + return &structType{StructType: tt} + } + return nil } //gopherjs:replace @@ -221,7 +277,7 @@ func StructOf(fields []StructField) Type { switch field.Type.Kind() { case Interface: case Ptr: - ptr := ft.PtrType() + ptr := toPtrType(ft) if unt := ptr.Uncommon(); unt != nil { if i > 0 && unt.Mcount > 0 { // Issue 15924. @@ -280,3 +336,107 @@ func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) //gopherjs:purge Relates to GC programs not valid for GopherJS func appendGCProg(dst []byte, typ *abi.Type) []byte + +//gopherjs:replace Used pointer cast to interface kind type. +func (t *rtype) NumMethod() int { + if tt := toInterfaceType(t.common()); tt != nil { + return tt.NumMethod() + } + return len(t.exportedMethods()) +} + +//gopherjs:replace Used pointer cast to struct kind type. +func (t *rtype) Field(i int) StructField { + if tt := toStructType(t.common()); tt != nil { + return tt.Field(i) + } + panic("reflect: Field of non-struct type " + t.String()) +} + +//gopherjs:replace Used pointer cast to struct kind type. +func (t *rtype) FieldByIndex(index []int) StructField { + if tt := toStructType(t.common()); tt != nil { + return tt.FieldByIndex(index) + } + panic("reflect: FieldByIndex of non-struct type " + t.String()) +} + +//gopherjs:replace Used pointer cast to struct kind type. +func (t *rtype) FieldByName(name string) (StructField, bool) { + if tt := toStructType(t.common()); tt != nil { + return tt.FieldByName(name) + } + panic("reflect: FieldByName of non-struct type " + t.String()) +} + +//gopherjs:replace Used pointer cast to struct kind type. +func (t *rtype) FieldByNameFunc(match func(string) bool) (StructField, bool) { + if tt := toStructType(t.common()); tt != nil { + return tt.FieldByNameFunc(match) + } + panic("reflect: FieldByNameFunc of non-struct type " + t.String()) +} + +//gopherjs:replace Used pointer cast to map kind type. +func (t *rtype) Key() Type { + if tt := toMapType(t.common()); tt != nil { + return toType(tt.Key) + } + panic("reflect: Key of non-map type " + t.String()) +} + +//gopherjs:replace Used pointer cast to array kind type. +func (t *rtype) Len() int { + if tt := t.common().ArrayType(); tt != nil { + return int(tt.Len) + } + panic("reflect: Len of non-array type " + t.String()) +} + +//gopherjs:replace Used pointer cast to struct kind type. +func (t *rtype) NumField() int { + if tt := toStructType(t.common()); tt != nil { + return len(tt.Fields) + } + panic("reflect: NumField of non-struct type " + t.String()) +} + +//gopherjs:replace Used pointer cast to func kind type. +func (t *rtype) In(i int) Type { + if tt := t.common().FuncType(); tt != nil { + return toType(tt.InSlice()[i]) + } + panic("reflect: In of non-func type " + t.String()) +} + +//gopherjs:replace Used pointer cast to fun kind type. +func (t *rtype) NumIn() int { + if tt := t.common().FuncType(); tt != nil { + return tt.NumIn() + } + panic("reflect: NumIn of non-func type " + t.String()) +} + +//gopherjs:replace Used pointer cast to func kind type. +func (t *rtype) NumOut() int { + if tt := t.common().FuncType(); tt != nil { + return tt.NumOut() + } + panic("reflect: NumOut of non-func type " + t.String()) +} + +//gopherjs:replace Used pointer cast to func kind type. +func (t *rtype) Out(i int) Type { + if tt := t.common().FuncType(); tt != nil { + return toType(tt.OutSlice()[i]) + } + panic("reflect: Out of non-func type " + t.String()) +} + +//gopherjs:replace Used pointer cast to func kind type. +func (t *rtype) IsVariadic() bool { + if tt := t.common().FuncType(); tt != nil { + return tt.IsVariadic() + } + panic("reflect: IsVariadic of non-func type " + t.String()) +} diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index bdbc91d59..608d1cc74 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -117,7 +117,7 @@ func makeInt(f flag, bits uint64, t Type) Value { func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { i := methodIndex var prop string - if tt := v.typ().InterfaceType(); tt != nil { + if tt := toInterfaceType(v.typ()); tt != nil { if i < 0 || i >= len(tt.Methods) { panic("reflect: internal error: invalid method index") } @@ -433,7 +433,7 @@ func (v Value) Elem() Value { return Value{} } val := v.object() - tt := v.typ().PtrType() + tt := toPtrType(v.typ()) fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.Elem.Kind()) return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} @@ -446,13 +446,13 @@ func (v Value) Elem() Value { //gopherjs:replace func (v Value) NumField() int { v.mustBe(Struct) - tt := v.typ().StructType() + tt := toStructType(v.typ()) return len(tt.Fields) } //gopherjs:replace func (v Value) Field(i int) Value { - tt := v.typ().StructType() + tt := toStructType(v.typ()) if tt == nil { panic(&ValueError{"reflect.Value.Field", v.kind()}) } @@ -565,7 +565,7 @@ func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) func (v Value) Clear() { switch v.Kind() { case Slice: - elem := v.typ().SliceType().Elem + elem := toSliceType(v.typ()).Elem zeroFn := elem.JsType().Get("zero") a := js.InternalObject(v.ptr) offset := a.Get("$offset").Int() @@ -606,7 +606,7 @@ func (v Value) Index(i int) Value { if i < 0 || i >= s.Get("$length").Int() { panic("reflect: slice index out of range") } - tt := v.typ().SliceType() + tt := toSliceType(v.typ()) typ := tt.Elem fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) From c81e52adcf0aa35efbbe16a2d697e39a9bd5d9db Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Fri, 13 Feb 2026 15:04:14 -0700 Subject: [PATCH 29/48] Started fixing my mistake --- compiler/expressions.go | 41 +- compiler/natives/src/internal/abi/type.go | 420 +++++++----------- .../src/internal/reflectlite/tostring_test.go | 12 - .../natives/src/internal/reflectlite/value.go | 2 +- 4 files changed, 191 insertions(+), 284 deletions(-) delete mode 100644 compiler/natives/src/internal/reflectlite/tostring_test.go diff --git a/compiler/expressions.go b/compiler/expressions.go index 3a28661b2..3a3bf896b 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1104,20 +1104,53 @@ func (fc *funcContext) translateExprSlice(exprs []ast.Expr, desiredType types.Ty return parts } +// packageAllowsKindTypeConversion determines if the current package should +// be checked for a special type of casts, `kindType` conversions. +func (fc *funcContext) packageAllowsKindTypeConversion() bool { + switch fc.pkgCtx.Pkg.Path() { + case `reflect`, `internal/reflectlite`, `internal/abi`: + return true + } + return false +} + +// typeAllowsKindTypeConversion determines if the given object ID for a desired type of an unsafe pointer conversion +// is a type we know should have been added as a `kindType` field to the type pointed at by the unsafe pointer. +func typeAllowsKindTypeConversion(desiredTypeID string) bool { + fmt.Printf("!!BANG!! %q\n", desiredTypeID) // TODO(grantnelson-wf): Remove + + switch desiredTypeID { + case "arrayType", "chanType", "funcType", "interfaceType", "mapType", "ptrType", "sliceType", "structType": + return true + } + return false +} + func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type) *expression { exprType := fc.typeOf(expr) if types.Identical(exprType, desiredType) { return fc.translateExpr(expr) } - if fc.pkgCtx.Pkg.Path() == "reflect" || fc.pkgCtx.Pkg.Path() == "internal/reflectlite" { + // For some specific packages, e.g. reflect, the Go code performs casts between different sized memory footprints + // and leverages that the pointer to the first field is the same as the pointer to the full struct. These conversions + // are normally not allowed by GopherJS. However, in specific packages, the original code does this kind of cast + // so often, that to avoid them would cause massive amounts of native overrides. To simplify the native overrides + // for these specific packages we will allow casts between specific types by looking up the `kindType` that is + // assigned when creating them. + // + // The structures are `type K struct{T; additional fields}`. In Go the untyped pointer to `K` is also the untypes + // pointer to the first field, i.e. `T`. These packages will hold onto `t *T` then cast to the kind type like + // `k = (*K)unsafe.Pointer(t)`. Normally this isn't allowed in JS because `K` is larger with additional fields, + // but when we created `t` in the native overrides, we assign `k` as the `t.kindType` and translate those specific + // casts to get that `kindType`, thus greatly reducing the amount of overrides we have to add to those packages. + if fc.packageAllowsKindTypeConversion() { if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(fc.typeOf(call.Fun), types.Typ[types.UnsafePointer]) { if ptr, isPtr := desiredType.(*types.Pointer); isPtr { if named, isNamed := ptr.Elem().(*types.Named); isNamed { - switch named.Obj().Name() { - case "arrayType", "chanType", "funcType", "interfaceType", "mapType", "ptrType", "sliceType", "structType": + if typeAllowsKindTypeConversion(named.Obj().Id()) { return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion - default: + } else { return fc.translateExpr(expr) } } diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index a4540f994..54a657c73 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -18,164 +18,153 @@ const ( //gopherjs:new func ReflectType(typ *js.Object) *Type { - if typ.Get(idReflectType) == js.Undefined { - abiTyp := &Type{ - Size_: uintptr(typ.Get("size").Int()), - Kind_: uint8(typ.Get("kind").Int()), - Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), - } - js.InternalObject(abiTyp).Set(idJsType, typ) - typ.Set(idReflectType, js.InternalObject(abiTyp)) - - methodSet := js.Global.Call("$methodSet", typ) - if methodSet.Length() != 0 || typ.Get("named").Bool() { - abiTyp.TFlag |= TFlagUncommon - if typ.Get("named").Bool() { - abiTyp.TFlag |= TFlagNamed - } - var reflectMethods []Method - for i := 0; i < methodSet.Length(); i++ { // Exported methods first. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if !exported { - continue - } - reflectMethods = append(reflectMethods, Method{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), - }) - } - xcount := uint16(len(reflectMethods)) - for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if exported { - continue - } - reflectMethods = append(reflectMethods, Method{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), - Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), - }) - } - ut := &UncommonType{ - PkgPath: ResolveReflectName(NewName(internalStr(typ.Get("pkg")), "", false, false)), - Mcount: uint16(methodSet.Length()), - Xcount: xcount, - Methods_: reflectMethods, - } - abiTyp.setUncommon(ut) - } + // If the object already had the reflect type determined, return it. + if typ.Get(idReflectType) != js.Undefined { + return (*Type)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) + } - switch abiTyp.Kind() { - case Array: - setKindType(abiTyp, &ArrayType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - Len: uintptr(typ.Get("len").Int()), - }) - case Chan: - dir := BothDir - if typ.Get("sendOnly").Bool() { - dir = SendDir - } - if typ.Get("recvOnly").Bool() { - dir = RecvDir + // Create new ABI type. + abiTyp := &Type{ + Size_: uintptr(typ.Get("size").Int()), + Kind_: uint8(typ.Get("kind").Int()), + Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + } + js.InternalObject(abiTyp).Set(idJsType, typ) + typ.Set(idReflectType, js.InternalObject(abiTyp)) + + // Add the UncommonType to ABI type if the type has methods. + methodSet := js.Global.Call("$methodSet", typ) + if methodSet.Length() != 0 || typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagUncommon + if typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagNamed + } + var reflectMethods []Method + for i := 0; i < methodSet.Length(); i++ { // Exported methods first. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if !exported { + continue } - setKindType(abiTyp, &ChanType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - Dir: dir, + reflectMethods = append(reflectMethods, Method{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), }) - case Func: - params := typ.Get("params") - in := make([]*Type, params.Length()) - for i := range in { - in[i] = ReflectType(params.Index(i)) - } - results := typ.Get("results") - out := make([]*Type, results.Length()) - for i := range out { - out[i] = ReflectType(results.Index(i)) - } - outCount := uint16(results.Length()) - if typ.Get("variadic").Bool() { - outCount |= 1 << 15 + } + xcount := uint16(len(reflectMethods)) + for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if exported { + continue } - setKindType(abiTyp, &FuncType{ - Type: *abiTyp, - InCount: uint16(params.Length()), - OutCount: outCount, - In_: in, - Out_: out, + reflectMethods = append(reflectMethods, Method{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), }) - case Interface: - methods := typ.Get("methods") - imethods := make([]Imethod, methods.Length()) - for i := range imethods { - m := methods.Index(i) - imethods[i] = Imethod{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), - Typ: ResolveReflectType(ReflectType(m.Get("typ"))), - } + } + ut := &UncommonType{ + PkgPath: ResolveReflectName(NewName(internalStr(typ.Get("pkg")), "", false, false)), + Mcount: uint16(methodSet.Length()), + Xcount: xcount, + Methods_: reflectMethods, + } + js.InternalObject(abiTyp).Set(idUncommonType, js.InternalObject(ut)) + } + + // Create the kind type for the ABI type if the kind has additional information. + switch abiTyp.Kind() { + case Array: + abiTyp.kindType = &ArrayType{ + Type: abiTyp, + Elem: ReflectType(typ.Get("elem")), + Len: uintptr(typ.Get("len").Int()), + } + case Chan: + dir := BothDir + if typ.Get("sendOnly").Bool() { + dir = SendDir + } + if typ.Get("recvOnly").Bool() { + dir = RecvDir + } + abiTyp.kindType = &ChanType{ + Type: abiTyp, + Elem: ReflectType(typ.Get("elem")), + Dir: dir, + } + case Func: + params := typ.Get("params") + in := make([]*Type, params.Length()) + for i := range in { + in[i] = ReflectType(params.Index(i)) + } + results := typ.Get("results") + out := make([]*Type, results.Length()) + for i := range out { + out[i] = ReflectType(results.Index(i)) + } + outCount := uint16(results.Length()) + if typ.Get("variadic").Bool() { + outCount |= 1 << 15 + } + abiTyp.kindType = &FuncType{ + Type: abiTyp, + InCount: uint16(params.Length()), + OutCount: outCount, + In_: in, + Out_: out, + } + case Interface: + methods := typ.Get("methods") + imethods := make([]Imethod, methods.Length()) + for i := range imethods { + m := methods.Index(i) + imethods[i] = Imethod{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), + Typ: ResolveReflectType(ReflectType(m.Get("typ"))), } - setKindType(abiTyp, &InterfaceType{ - Type: *abiTyp, - PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), - Methods: imethods, - }) - case Map: - setKindType(abiTyp, &MapType{ - Type: *abiTyp, - Key: ReflectType(typ.Get("key")), - Elem: ReflectType(typ.Get("elem")), - }) - case Pointer: - setKindType(abiTyp, &PtrType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - }) - case Slice: - setKindType(abiTyp, &SliceType{ - Type: *abiTyp, - Elem: ReflectType(typ.Get("elem")), - }) - case Struct: - fields := typ.Get("fields") - reflectFields := make([]StructField, fields.Length()) - for i := range reflectFields { - f := fields.Index(i) - reflectFields[i] = StructField{ - Name: NewName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - Typ: ReflectType(f.Get("typ")), - Offset: uintptr(i), - } + } + abiTyp.kindType = &InterfaceType{ + Type: abiTyp, + PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), + Methods: imethods, + } + case Map: + abiTyp.kindType = &MapType{ + Type: abiTyp, + Key: ReflectType(typ.Get("key")), + Elem: ReflectType(typ.Get("elem")), + } + case Pointer: + abiTyp.kindType = &PtrType{ + Type: abiTyp, + Elem: ReflectType(typ.Get("elem")), + } + case Slice: + abiTyp.kindType = &SliceType{ + Type: abiTyp, + Elem: ReflectType(typ.Get("elem")), + } + case Struct: + fields := typ.Get("fields") + reflectFields := make([]StructField, fields.Length()) + for i := range reflectFields { + f := fields.Index(i) + reflectFields[i] = StructField{ + Name: NewName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + Typ: ReflectType(f.Get("typ")), + Offset: uintptr(i), } - setKindType(abiTyp, &StructType{ - Type: *abiTyp, - PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), - Fields: reflectFields, - }) + } + abiTyp.kindType = &StructType{ + Type: abiTyp, + PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), + Fields: reflectFields, } } - return (*Type)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) -} - -//gopherjs:new -func setKindType(abiTyp *Type, kindType any) { - js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) -} - -// getKindType will get the type specific to the kind if this type. -// For example `getKindType[StructType](Struct, t)` returns t cast to a -// [*StructType], or nil if the type's kind is not a [Struct]. -// -//gopherjs:new -func getKindType[T any](kind Kind, t *Type) *T { - if t.Kind() != kind { - return nil - } - return (*T)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) + return abiTyp } //gopherjs:replace @@ -207,21 +196,11 @@ func (t *Type) Uncommon() *UncommonType { return (*UncommonType)(unsafe.Pointer(obj.Unsafe())) } -//gopherjs:new -func (t *Type) setUncommon(ut *UncommonType) { - js.InternalObject(t).Set(idUncommonType, js.InternalObject(ut)) -} - //gopherjs:new func (typ *Type) JsType() *js.Object { return js.InternalObject(typ).Get(idJsType) } -//gopherjs:new -func (typ *Type) setJsType(t *js.Object) { - js.InternalObject(typ).Set(idJsType, typ) -} - //gopherjs:new func (typ *Type) PtrTo() *Type { return ReflectType(js.Global.Call("$ptrType", typ.JsType())) @@ -232,102 +211,6 @@ func (typ *Type) JsPtrTo() *js.Object { return typ.PtrTo().JsType() } -//================================================================================= -// TODO(grantnelson-wf): Test out overriding the cast of pointer types to Work for typeKinds. -// If the override works, find all unsafe pointer casts still being done and override them, e.g. Elem(). -//================================================================================= - -//gophejs:replace -func (t *Type) Elem() *Type { - switch t.Kind() { - case Array: - return t.ArrayType().Elem - case Chan: - return t.ChanType().Elem - case Map: - return t.MapType().Elem - case Pointer: - return t.PtrType().Elem - case Slice: - return t.SliceType().Elem - } - return nil -} - -//gophejs:replace -func (t *Type) StructType() *StructType { - return getKindType[StructType](Struct, t) -} - -//gophejs:replace -func (t *Type) MapType() *MapType { - return getKindType[MapType](Map, t) -} - -//gophejs:replace -func (t *Type) ArrayType() *ArrayType { - return getKindType[ArrayType](Array, t) -} - -//gophejs:replace -func (t *Type) FuncType() *FuncType { - return getKindType[FuncType](Func, t) -} - -//gophejs:replace -func (t *Type) InterfaceType() *InterfaceType { - return getKindType[InterfaceType](Interface, t) -} - -//gopherjs:new Same as ArrayType(), MapType(), etc but for ChanType. -func (t *Type) ChanType() *ChanType { - return getKindType[ChanType](Chan, t) -} - -//gopherjs:new Same as ArrayType(), MapType(), etc but for PtrType. -func (t *Type) PtrType() *PtrType { - return getKindType[PtrType](Pointer, t) -} - -//gopherjs:new Same as ArrayType(), MapType(), etc but for SliceType -func (t *Type) SliceType() *SliceType { - return getKindType[SliceType](Slice, t) -} - -// Len returns the length of t if t is an array type, otherwise 0 -// -//gopherjs:replace Used a pointer cast to get the array kind type. -func (t *Type) Len() int { - if arr := t.ArrayType(); arr != nil { - return int(arr.Len) - } - return 0 -} - -//gopherjs:replace Used a pointer cast to get the chan kind type. -func (t *Type) ChanDir() ChanDir { - if ch := t.ChanType(); ch != nil { - return ch.Dir - } - return InvalidDir -} - -//gopherjs:replace Used a pointer cast to get the interface kind type. -func (t *Type) NumMethod() int { - if tt := t.InterfaceType(); tt != nil { - return tt.NumMethod() - } - return len(t.ExportedMethods()) -} - -//gopherjs:replace Used a pointer cast to get the map kind type. -func (t *Type) Key() *Type { - if mt := t.MapType(); mt != nil { - return mt.Key - } - return nil -} - //gopherjs:new Shared by reflect and reflectlite rtypes func (t *Type) String() string { s := t.NameOff(t.Str).Name() @@ -337,9 +220,12 @@ func (t *Type) String() string { return s } +//gopherjs:purge Uncommon types are stored differently. +type structTypeUncommon struct{} + //gopherjs:replace type FuncType struct { - Type `reflect:"func"` + Type InCount uint16 OutCount uint16 @@ -415,63 +301,63 @@ func NewName(n, tag string, exported, embedded bool) Name { } } -// Instead of using this as an offset from a pointer to look up a name, +// GOPHERJS: Instead of using this as an offset from a pointer to look up a name, // just store the name as a pointer. // //gopherjs:replace type NameOff *Name -// Added to mirror the rtype's nameOff method to keep how the nameOff is -// created and read in one spot of the code. +// GOPHERJS: Added to mirror the rtype's nameOff method to keep how +// the nameOff is created and read in one spot of the code. // //gopherjs:new func (typ *Type) NameOff(off NameOff) Name { return *off } -// Added to mirror the resolveReflectName method in reflect +// GOPHERJS: Added to mirror the resolveReflectName method in reflect // //gopherjs:new func ResolveReflectName(n Name) NameOff { return &n } -// Instead of using this as an offset from a pointer to look up a type, +// GOPHERJS: Instead of using this as an offset from a pointer to look up a type, // just store the type as a pointer. // //gopherjs:replace type TypeOff *Type -// Added to mirror the rtype's typeOff method to keep how the typeOff is -// created and read in one spot of the code. +// GOPHERJS: Added to mirror the rtype's typeOff method to keep how +// the typeOff is created and read in one spot of the code. // //gopherjs:new func (typ *Type) TypeOff(off TypeOff) *Type { return off } -// Added to mirror the resolveReflectType method in reflect +// GOPHERJS: Added to mirror the resolveReflectType method in reflect // //gopherjs:new func ResolveReflectType(t *Type) TypeOff { return t } -// Instead of using this as an offset from a pointer to look up a pointer, +// GOPHERJS: Instead of using this as an offset from a pointer to look up a pointer, // just store the paointer itself. // //gopherjs:replace type TextOff unsafe.Pointer -// Added to mirror the rtype's textOff method to keep how the textOff is -// created and read in one spot of the code. +// GOPHERJS: Added to mirror the rtype's textOff method to keep how +// the textOff is created and read in one spot of the code. // //gopherjs:new func (typ *Type) TextOff(off TextOff) unsafe.Pointer { return unsafe.Pointer(off) } -// Added to mirror the resolveReflectText method in reflect +// GOPHERJS: Added to mirror the resolveReflectText method in reflect // //gopherjs:new func ResolveReflectText(ptr unsafe.Pointer) TextOff { diff --git a/compiler/natives/src/internal/reflectlite/tostring_test.go b/compiler/natives/src/internal/reflectlite/tostring_test.go deleted file mode 100644 index 0648a9749..000000000 --- a/compiler/natives/src/internal/reflectlite/tostring_test.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build js - -package reflectlite_test - -import "reflect" - -// TODO: REMOVE -// -//gopherjs:keep-original -func valueToStringImpl(val reflect.Value) string { - return _gopherjs_original_valueToStringImpl(val) -} diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 365b6f935..b3e9d9e9b 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -146,7 +146,7 @@ func (v Value) Elem() Value { return Value{} } val := v.object() - tt := v.typ.PtrType() + tt := (*abi.PtrType)(unsafe.Pointer(v.typ)) fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.Elem.Kind()) return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} From decbee6587f3087ceb3dbca3ba59f9fa546c1c28 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 17 Feb 2026 10:22:46 -0700 Subject: [PATCH 30/48] Started fixing my mistake --- compiler/expressions.go | 23 +- compiler/natives/src/internal/abi/type.go | 57 ++--- .../src/internal/reflectlite/reflectlite.go | 9 + compiler/natives/src/reflect/reflect.go | 2 +- compiler/natives/src/reflect/type.go | 198 +----------------- compiler/natives/src/reflect/value.go | 31 ++- 6 files changed, 80 insertions(+), 240 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 3a3bf896b..231e823dd 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1108,7 +1108,7 @@ func (fc *funcContext) translateExprSlice(exprs []ast.Expr, desiredType types.Ty // be checked for a special type of casts, `kindType` conversions. func (fc *funcContext) packageAllowsKindTypeConversion() bool { switch fc.pkgCtx.Pkg.Path() { - case `reflect`, `internal/reflectlite`, `internal/abi`: + case `internal/abi`, `internal/reflectlite`, `reflect`: return true } return false @@ -1116,12 +1116,19 @@ func (fc *funcContext) packageAllowsKindTypeConversion() bool { // typeAllowsKindTypeConversion determines if the given object ID for a desired type of an unsafe pointer conversion // is a type we know should have been added as a `kindType` field to the type pointed at by the unsafe pointer. -func typeAllowsKindTypeConversion(desiredTypeID string) bool { - fmt.Printf("!!BANG!! %q\n", desiredTypeID) // TODO(grantnelson-wf): Remove - - switch desiredTypeID { - case "arrayType", "chanType", "funcType", "interfaceType", "mapType", "ptrType", "sliceType", "structType": - return true +func typeAllowsKindTypeConversion(desiredPkgPath, desiredName string) bool { + switch desiredPkgPath { + case `internal/abi`: + switch desiredName { + case `ArrayType`, `ChanType`, `FuncType`, `InterfaceType`, `MapType`, `PtrType`, `SliceType`, `StructType`: + return true + } + case `reflect`: + switch desiredName { + // The following are extensions of the ABI equivalent type to add more methods. + case `interfaceType`, `mapType`, `ptrType`, `sliceType`, `structType`: + return true + } } return false } @@ -1148,7 +1155,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(fc.typeOf(call.Fun), types.Typ[types.UnsafePointer]) { if ptr, isPtr := desiredType.(*types.Pointer); isPtr { if named, isNamed := ptr.Elem().(*types.Named); isNamed { - if typeAllowsKindTypeConversion(named.Obj().Id()) { + if typeAllowsKindTypeConversion(named.Obj().Pkg().Path(), named.Obj().Name()) { return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion } else { return fc.translateExpr(expr) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 54a657c73..8bd272b3e 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -75,11 +75,10 @@ func ReflectType(typ *js.Object) *Type { // Create the kind type for the ABI type if the kind has additional information. switch abiTyp.Kind() { case Array: - abiTyp.kindType = &ArrayType{ - Type: abiTyp, + setKindType(abiTyp, &ArrayType{ Elem: ReflectType(typ.Get("elem")), Len: uintptr(typ.Get("len").Int()), - } + }) case Chan: dir := BothDir if typ.Get("sendOnly").Bool() { @@ -88,11 +87,10 @@ func ReflectType(typ *js.Object) *Type { if typ.Get("recvOnly").Bool() { dir = RecvDir } - abiTyp.kindType = &ChanType{ - Type: abiTyp, + setKindType(abiTyp, &ChanType{ Elem: ReflectType(typ.Get("elem")), Dir: dir, - } + }) case Func: params := typ.Get("params") in := make([]*Type, params.Length()) @@ -108,13 +106,12 @@ func ReflectType(typ *js.Object) *Type { if typ.Get("variadic").Bool() { outCount |= 1 << 15 } - abiTyp.kindType = &FuncType{ - Type: abiTyp, + setKindType(abiTyp, &FuncType{ InCount: uint16(params.Length()), OutCount: outCount, In_: in, Out_: out, - } + }) case Interface: methods := typ.Get("methods") imethods := make([]Imethod, methods.Length()) @@ -125,27 +122,23 @@ func ReflectType(typ *js.Object) *Type { Typ: ResolveReflectType(ReflectType(m.Get("typ"))), } } - abiTyp.kindType = &InterfaceType{ - Type: abiTyp, + setKindType(abiTyp, &InterfaceType{ PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), Methods: imethods, - } + }) case Map: - abiTyp.kindType = &MapType{ - Type: abiTyp, + setKindType(abiTyp, &MapType{ Key: ReflectType(typ.Get("key")), Elem: ReflectType(typ.Get("elem")), - } + }) case Pointer: - abiTyp.kindType = &PtrType{ - Type: abiTyp, + setKindType(abiTyp, &PtrType{ Elem: ReflectType(typ.Get("elem")), - } + }) case Slice: - abiTyp.kindType = &SliceType{ - Type: abiTyp, + setKindType(abiTyp, &SliceType{ Elem: ReflectType(typ.Get("elem")), - } + }) case Struct: fields := typ.Get("fields") reflectFields := make([]StructField, fields.Length()) @@ -157,16 +150,32 @@ func ReflectType(typ *js.Object) *Type { Offset: uintptr(i), } } - abiTyp.kindType = &StructType{ - Type: abiTyp, + setKindType(abiTyp, &StructType{ PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), Fields: reflectFields, - } + }) } return abiTyp } +//gopherjs:new +func setKindType(abiTyp *Type, kindType any) { + // Add the kindType to the abiTyp object so that when a cast is performed + // from the abiTyp into the kind type, a custom cast in `translateConversion` + // in compiler/expressions.go will use this kindType to cast. + // This makes it so we don't have to override all of the kind type casts, + // e.g. `tt := (*chanType)(unsafe.Pointer(typ))`, and the compiler + // will replace that automatically with `typ.kindType`. + js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) + + // Assign Type to the abiType. The abiType is a `*Type` and the Type + // field on the kind types are `Type`. However, this is valid because of how + // pointers and references work in JS. We set this so that the Type + // isn't a copy but the actual abiType object. + js.InternalObject(kindType).Set("Type", js.InternalObject(abiTyp)) +} + //gopherjs:replace type UncommonType struct { PkgPath NameOff // import path diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index f12ff5b60..b25511e20 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -182,3 +182,12 @@ func makeMethodValue(op string, v Value) Value { }) return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(abi.Func)} } + +//gopherjs:purge +type emptyInterface struct{} + +//gopherjs:purge +func unpackEface(i any) Value + +//gopherjs:purge +func packEface(v Value) any diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index f664db3f4..b610a4cdd 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -181,7 +181,7 @@ func (t *rtype) Comparable() bool { //gopherjs:replace func (t *rtype) Method(i int) (m Method) { if t.Kind() == Interface { - tt := toInterfaceType(t.common()) + tt := (*interfaceType)(unsafe.Pointer(t)) return tt.Method(i) } methods := t.exportedMethods() diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 6f0416193..979e27e0c 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -27,23 +27,6 @@ func init() { used(structField{}) } -// GOPHERJS: In Go the rtype and ABI Type share a memory footprint so a pointer -// to one is a pointer to the other. This doesn't work in JS so instead we construct -// a new rtype with a `*abi.Type` inside of it. However, that means the pointers are -// different and multiple `*rtypes` may point to the same ABI type, so we have to -// take that into account when overriding code or leaving the original code. -// (reflectlite does this better by having the rtype not be a pointer.) -// -//gopherjs:replace -type rtype struct { - t *abi.Type -} - -//gopherjs:replace -func (t *rtype) common() *abi.Type { - return t.t -} - //gopherjs:new func toAbiType(typ Type) *abi.Type { return typ.(*rtype).common() @@ -56,12 +39,18 @@ func jsType(typ Type) *js.Object { //gopherjs:replace func (t *rtype) ptrTo() *abi.Type { - return toAbiType(t).PtrTo() + return abi.ReflectType(js.Global.Call("$ptrType", jsType(t))) } //gopherjs:replace func toRType(t *abi.Type) *rtype { - return &rtype{t: t} + rtyp := &rtype{} + // Assign t to the abiType. The abiType is a `*Type` and the t + // field on `rtype` is `Type`. However, this is valid because of how + // pointers and references work in JS. We set this so that the t + // isn't a copy but the actual abiType object. + js.InternalObject(rtyp).Set("t", js.InternalObject(t)) + return rtyp } //gopherjs:replace @@ -123,71 +112,6 @@ func rtypeOf(i any) *abi.Type { //gopherjs:purge Unused type type common struct{} -//gopherjs:replace Same issue as rtype -type interfaceType struct { - *abi.InterfaceType -} - -//gopherjs:new -func toInterfaceType(typ *abi.Type) *interfaceType { - if tt := typ.InterfaceType(); tt != nil { - return &interfaceType{InterfaceType: tt} - } - return nil -} - -//gopherjs:replace Same issue as rtype -type mapType struct { - *abi.MapType -} - -//gopherjs:new -func toMapType(typ *abi.Type) *mapType { - if tt := typ.MapType(); tt != nil { - return &mapType{MapType: tt} - } - return nil -} - -//gopherjs:replace Same issue as rtype -type ptrType struct { - *abi.PtrType -} - -//gopherjs:new -func toPtrType(typ *abi.Type) *ptrType { - if tt := typ.PtrType(); tt != nil { - return &ptrType{PtrType: tt} - } - return nil -} - -//gopherjs:replace Same issue as rtype -type sliceType struct { - *abi.SliceType -} - -//gopherjs:new -func toSliceType(typ *abi.Type) *sliceType { - if tt := typ.SliceType(); tt != nil { - return &sliceType{SliceType: tt} - } - return nil -} - -//gopherjs:replace Same issue as rtype -type structType struct { - *abi.StructType -} - -//gopherjs:new -func toStructType(typ *abi.Type) *structType { - if tt := typ.StructType(); tt != nil { - return &structType{StructType: tt} - } - return nil -} - //gopherjs:replace func ArrayOf(count int, elem Type) Type { if count < 0 { @@ -277,7 +201,7 @@ func StructOf(fields []StructField) Type { switch field.Type.Kind() { case Interface: case Ptr: - ptr := toPtrType(ft) + ptr := (*ptrType)(unsafe.Pointer(ft)) if unt := ptr.Uncommon(); unt != nil { if i > 0 && unt.Mcount > 0 { // Issue 15924. @@ -336,107 +260,3 @@ func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) //gopherjs:purge Relates to GC programs not valid for GopherJS func appendGCProg(dst []byte, typ *abi.Type) []byte - -//gopherjs:replace Used pointer cast to interface kind type. -func (t *rtype) NumMethod() int { - if tt := toInterfaceType(t.common()); tt != nil { - return tt.NumMethod() - } - return len(t.exportedMethods()) -} - -//gopherjs:replace Used pointer cast to struct kind type. -func (t *rtype) Field(i int) StructField { - if tt := toStructType(t.common()); tt != nil { - return tt.Field(i) - } - panic("reflect: Field of non-struct type " + t.String()) -} - -//gopherjs:replace Used pointer cast to struct kind type. -func (t *rtype) FieldByIndex(index []int) StructField { - if tt := toStructType(t.common()); tt != nil { - return tt.FieldByIndex(index) - } - panic("reflect: FieldByIndex of non-struct type " + t.String()) -} - -//gopherjs:replace Used pointer cast to struct kind type. -func (t *rtype) FieldByName(name string) (StructField, bool) { - if tt := toStructType(t.common()); tt != nil { - return tt.FieldByName(name) - } - panic("reflect: FieldByName of non-struct type " + t.String()) -} - -//gopherjs:replace Used pointer cast to struct kind type. -func (t *rtype) FieldByNameFunc(match func(string) bool) (StructField, bool) { - if tt := toStructType(t.common()); tt != nil { - return tt.FieldByNameFunc(match) - } - panic("reflect: FieldByNameFunc of non-struct type " + t.String()) -} - -//gopherjs:replace Used pointer cast to map kind type. -func (t *rtype) Key() Type { - if tt := toMapType(t.common()); tt != nil { - return toType(tt.Key) - } - panic("reflect: Key of non-map type " + t.String()) -} - -//gopherjs:replace Used pointer cast to array kind type. -func (t *rtype) Len() int { - if tt := t.common().ArrayType(); tt != nil { - return int(tt.Len) - } - panic("reflect: Len of non-array type " + t.String()) -} - -//gopherjs:replace Used pointer cast to struct kind type. -func (t *rtype) NumField() int { - if tt := toStructType(t.common()); tt != nil { - return len(tt.Fields) - } - panic("reflect: NumField of non-struct type " + t.String()) -} - -//gopherjs:replace Used pointer cast to func kind type. -func (t *rtype) In(i int) Type { - if tt := t.common().FuncType(); tt != nil { - return toType(tt.InSlice()[i]) - } - panic("reflect: In of non-func type " + t.String()) -} - -//gopherjs:replace Used pointer cast to fun kind type. -func (t *rtype) NumIn() int { - if tt := t.common().FuncType(); tt != nil { - return tt.NumIn() - } - panic("reflect: NumIn of non-func type " + t.String()) -} - -//gopherjs:replace Used pointer cast to func kind type. -func (t *rtype) NumOut() int { - if tt := t.common().FuncType(); tt != nil { - return tt.NumOut() - } - panic("reflect: NumOut of non-func type " + t.String()) -} - -//gopherjs:replace Used pointer cast to func kind type. -func (t *rtype) Out(i int) Type { - if tt := t.common().FuncType(); tt != nil { - return toType(tt.OutSlice()[i]) - } - panic("reflect: Out of non-func type " + t.String()) -} - -//gopherjs:replace Used pointer cast to func kind type. -func (t *rtype) IsVariadic() bool { - if tt := t.common().FuncType(); tt != nil { - return tt.IsVariadic() - } - panic("reflect: IsVariadic of non-func type " + t.String()) -} diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 608d1cc74..4c76f8f68 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -9,6 +9,7 @@ import ( "internal/abi" + "github.com/gopherjs/gopherjs/compiler/natives/src/internal/unsafeheader" "github.com/gopherjs/gopherjs/js" ) @@ -117,7 +118,8 @@ func makeInt(f flag, bits uint64, t Type) Value { func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { i := methodIndex var prop string - if tt := toInterfaceType(v.typ()); tt != nil { + if v.typ().Kind() == abi.Interface { + tt := (*interfaceType)(unsafe.Pointer(v.typ())) if i < 0 || i >= len(tt.Methods) { panic("reflect: internal error: invalid method index") } @@ -125,8 +127,7 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t if !tt.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - // TODO(grantnelson-wf): Set rcvrtype to the type the interface is holding onto. - t = tt.TypeOff(m.Typ).FuncType() + t = (*funcType)(unsafe.Pointer(tt.typeOff(m.Typ))) prop = tt.NameOff(m.Name).Name() } else { rcvrtype = v.typ() @@ -138,7 +139,7 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t if !v.typ().NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = v.typ().TypeOff(m.Mtyp).FuncType() + t = (*funcType)(unsafe.Pointer(v.typ().TypeOff(m.Mtyp))) prop = js.Global.Call("$methodSet", v.typ().JsType()).Index(i).Get("prop").String() } rcvr := v.object() @@ -215,7 +216,7 @@ func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { //gopherjs:replace func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { - ctyp := typ.ChanType() + ctyp := (*chanType)(unsafe.Pointer(typ)) return unsafe.Pointer(js.Global.Get("$Chan").New(ctyp.Elem.JsType(), size).Unsafe()) } @@ -433,7 +434,7 @@ func (v Value) Elem() Value { return Value{} } val := v.object() - tt := toPtrType(v.typ()) + tt := (*ptrType)(unsafe.Pointer(v.typ())) fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(tt.Elem.Kind()) return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} @@ -443,19 +444,12 @@ func (v Value) Elem() Value { } } -//gopherjs:replace -func (v Value) NumField() int { - v.mustBe(Struct) - tt := toStructType(v.typ()) - return len(tt.Fields) -} - //gopherjs:replace func (v Value) Field(i int) Value { - tt := toStructType(v.typ()) - if tt == nil { + if v.kind() != Struct { panic(&ValueError{"reflect.Value.Field", v.kind()}) } + tt := (*structType)(unsafe.Pointer(v.typ())) if uint(i) >= uint(len(tt.Fields)) { panic("reflect: Field index out of range") } @@ -565,7 +559,8 @@ func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) func (v Value) Clear() { switch v.Kind() { case Slice: - elem := toSliceType(v.typ()).Elem + st := (*sliceType)(unsafe.Pointer(v.typ())) + elem := st.Elem zeroFn := elem.JsType().Get("zero") a := js.InternalObject(v.ptr) offset := a.Get("$offset").Int() @@ -606,7 +601,7 @@ func (v Value) Index(i int) Value { if i < 0 || i >= s.Get("$length").Int() { panic("reflect: slice index out of range") } - tt := toSliceType(v.typ()) + tt := (*sliceType)(unsafe.Pointer(v.typ())) typ := tt.Elem fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) @@ -746,7 +741,7 @@ func (v Value) bytesSlow() []byte { if !v.CanAddr() { panic("reflect.Value.Bytes of unaddressable byte array") } - // Replace the following with JS to avoid using unsafe pointers. + // GOPHERJS: Replace the following with JS to avoid using unsafe pointers. // p := (*byte)(v.ptr) // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) // return unsafe.Slice(p, n) From 3e427039e3cef76412f3449d64e19343ac82402c Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 17 Feb 2026 10:54:30 -0700 Subject: [PATCH 31/48] Finished fixing mistake, continue with reflectlite tests --- .../src/internal/reflectlite/export_test.go | 9 ------ .../src/internal/reflectlite/reflectlite.go | 32 +++---------------- .../natives/src/internal/reflectlite/type.go | 24 ++++++++------ .../natives/src/internal/reflectlite/value.go | 13 ++++++-- 4 files changed, 31 insertions(+), 47 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index d9a3dce3e..91bffa275 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -13,15 +13,6 @@ func Field(v Value, i int) Value { return v.Field(i) } -//gopherjs:replace Used a pointer cast for the struct kind type. -func TField(typ Type, i int) Type { - tt := toAbiType(typ).StructType() - if tt == nil { - panic("reflect: Field of non-struct type") - } - return StructFieldType(tt, i) -} - //gopherjs:purge Used in FirstMethodNameBytes type EmbedWithUnexpMeth struct{} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index b25511e20..abf01b6e6 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -15,13 +15,8 @@ func init() { used := func(i any) {} used(rtype{}) used(uncommonType{}) - used(arrayType{}) - used(chanType{}) used(funcType{}) used(interfaceType{}) - used(mapType{}) - used(ptrType{}) - used(sliceType{}) used(structType{}) } @@ -30,23 +25,15 @@ func jsType(typ *abi.Type) *js.Object { return typ.JsType() } -//gopherjs:new -func toAbiType(t Type) *abi.Type { - return t.(rtype).common() -} - -//gopherjs:new -func jsPtrTo(t Type) *js.Object { - return toAbiType(t).JsPtrTo() -} - -//gopherjs:purge The name type is mostly unused, replaced by abi.Name, except in pkgPath which we don't implement. +//gopherjs:purge Unused, replaced by abi.Name. type name struct { bytes *byte } //gopherjs:replace -func pkgPath(n abi.Name) string { return "" } +func pkgPath(n abi.Name) string { + return n.PkgPath() +} //gopherjs:purge Unused function because of nameOffList in internal/abi overrides func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer @@ -166,7 +153,7 @@ func methodName() string { return "?FIXME?" } -//gopherjs:new This is new to reflectlite but there are commented out references in the native code and a copy in reflect. +//gopherjs:new func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { panic("reflect: internal error: invalid use of makePartialFunc") @@ -182,12 +169,3 @@ func makeMethodValue(op string, v Value) Value { }) return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(abi.Func)} } - -//gopherjs:purge -type emptyInterface struct{} - -//gopherjs:purge -func unpackEface(i any) Value - -//gopherjs:purge -func packEface(v Value) any diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 896a3e149..6d7d281a7 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -2,21 +2,27 @@ package reflectlite -import "internal/abi" +//gopherjs:purge Unused in reflectlite, abi.ArrayType is used instead +type arrayType struct{} -// GOPHERJS: For some reason the original code left mapType and aliased the rest -// to the ABI version. mapType is not used so this is an alias to override the -// left over refactor cruft. -// -//gopherjs:replace -type mapType = abi.MapType +//gopherjs:purge Unused in reflectlite, abi.ChanType is used instead +type chanType struct{} + +//gopherjs:purge Unused in reflectlite, abi.MapType is used instead +type mapType struct{} + +//gopherjs:purge Unused in reflectlite, abi.PtrType is used instead +type ptrType struct{} + +//gopherjs:purge Unused in reflectlite, abi.SliceType is used instead +type sliceType struct{} //gopherjs:replace func (t rtype) Comparable() bool { - return toAbiType(t).Comparable() + return t.common().Comparable() } //gopherjs:replace func (t rtype) String() string { - return toAbiType(t).String() + return t.common().String() } diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index b3e9d9e9b..07e954986 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -112,7 +112,7 @@ func (v Value) Len() int { //gopherjs:replace func (v Value) Set(x Value) { v.mustBeAssignable() - x.mustBeExported() // TODO(grantnelson-wf): This uses the unimplemented `methodName()`. When methodName is fixed we can use Set to test. + x.mustBeExported() x = x.assignTo("reflect.Set", v.typ, nil) if v.flag&flagIndir != 0 { switch v.typ.Kind() { @@ -156,7 +156,7 @@ func (v Value) Elem() Value { } } -// TODO(grantnelson-wf): Move this to export_test since it is defined there in the original code and only used for tests. +// TODO(grantnelson-wf): To minimize diffs, this was not moved to export_test. After this is merged into the go1.21 branch, move it. func (v Value) Field(i int) Value { tt := v.typ.StructType() if tt == nil { @@ -207,3 +207,12 @@ func (v Value) Field(i int) Value { } return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } + +//gopherjs:purge Unused type +type emptyInterface struct{} + +//gopherjs:purge Unused method for emptyInterface +func unpackEface(i any) Value + +//gopherjs:purge Unused method for emptyInterface +func packEface(v Value) any From 2d4d8ca1186df34af560e1d52e21e9250ddf1909 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 09:41:03 -0700 Subject: [PATCH 32/48] Trying to use kindTypeExt --- compiler/expressions.go | 18 +++++++++-------- compiler/natives/src/internal/abi/type.go | 20 ++++++++++++++++++- .../src/internal/reflectlite/export_test.go | 2 +- compiler/natives/src/reflect/type.go | 16 +++++++++++++++ compiler/natives/src/reflect/value.go | 2 +- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 231e823dd..cf92637f6 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1105,7 +1105,7 @@ func (fc *funcContext) translateExprSlice(exprs []ast.Expr, desiredType types.Ty } // packageAllowsKindTypeConversion determines if the current package should -// be checked for a special type of casts, `kindType` conversions. +// be checked for a special type of casts, `kindType` or `kindTypeExt` conversions. func (fc *funcContext) packageAllowsKindTypeConversion() bool { switch fc.pkgCtx.Pkg.Path() { case `internal/abi`, `internal/reflectlite`, `reflect`: @@ -1115,22 +1115,23 @@ func (fc *funcContext) packageAllowsKindTypeConversion() bool { } // typeAllowsKindTypeConversion determines if the given object ID for a desired type of an unsafe pointer conversion -// is a type we know should have been added as a `kindType` field to the type pointed at by the unsafe pointer. -func typeAllowsKindTypeConversion(desiredPkgPath, desiredName string) bool { +// is a type we know should have been added as a `kindType` or `kindTypeExt` field to the type pointed at by the unsafe pointer. +func typeAllowsKindTypeConversion(desiredPkgPath, desiredName string) (bool, string) { switch desiredPkgPath { case `internal/abi`: switch desiredName { case `ArrayType`, `ChanType`, `FuncType`, `InterfaceType`, `MapType`, `PtrType`, `SliceType`, `StructType`: - return true + return true, `kindType` } case `reflect`: switch desiredName { // The following are extensions of the ABI equivalent type to add more methods. + // e.g. `type structType struct { abi.StructType }` case `interfaceType`, `mapType`, `ptrType`, `sliceType`, `structType`: - return true + return true, `kindTypeExt` } } - return false + return false, `` } func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type) *expression { @@ -1155,8 +1156,9 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(fc.typeOf(call.Fun), types.Typ[types.UnsafePointer]) { if ptr, isPtr := desiredType.(*types.Pointer); isPtr { if named, isNamed := ptr.Elem().(*types.Named); isNamed { - if typeAllowsKindTypeConversion(named.Obj().Pkg().Path(), named.Obj().Name()) { - return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion + doKindTypeCast, fieldName := typeAllowsKindTypeConversion(named.Obj().Pkg().Path(), named.Obj().Name()) + if doKindTypeCast { + return fc.formatExpr("%e.%s", call.Args[0], fieldName) // unsafe conversion } else { return fc.translateExpr(expr) } diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 8bd272b3e..200c93112 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -13,6 +13,7 @@ const ( idJsType = `jsType` idReflectType = `reflectType` idKindType = `kindType` + idKindTypeExt = `kindTypeExt` idUncommonType = `uncommonType` ) @@ -126,19 +127,23 @@ func ReflectType(typ *js.Object) *Type { PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), Methods: imethods, }) + setKindTypeExt(abiTyp, `InterfaceType`, &struct{ InterfaceType }{}) case Map: setKindType(abiTyp, &MapType{ Key: ReflectType(typ.Get("key")), Elem: ReflectType(typ.Get("elem")), }) + setKindTypeExt(abiTyp, `MapType`, &struct{ MapType }{}) case Pointer: setKindType(abiTyp, &PtrType{ Elem: ReflectType(typ.Get("elem")), }) + setKindTypeExt(abiTyp, `PtrType`, &struct{ PtrType }{}) case Slice: setKindType(abiTyp, &SliceType{ Elem: ReflectType(typ.Get("elem")), }) + setKindTypeExt(abiTyp, `SliceType`, &struct{ SliceType }{}) case Struct: fields := typ.Get("fields") reflectFields := make([]StructField, fields.Length()) @@ -154,6 +159,7 @@ func ReflectType(typ *js.Object) *Type { PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), Fields: reflectFields, }) + setKindTypeExt(abiTyp, `StructType`, &struct{ StructType }{}) } return abiTyp @@ -173,7 +179,19 @@ func setKindType(abiTyp *Type, kindType any) { // field on the kind types are `Type`. However, this is valid because of how // pointers and references work in JS. We set this so that the Type // isn't a copy but the actual abiType object. - js.InternalObject(kindType).Set("Type", js.InternalObject(abiTyp)) + js.InternalObject(kindType).Set(`Type`, js.InternalObject(abiTyp)) +} + +//gopherjs:new +func setKindTypeExt(abiTyp *Type, kindTypeName string, kindTypeExt any) { + kindType := js.InternalObject(abiTyp).Get(idKindType) + js.InternalObject(kindTypeExt).Set(kindTypeName, kindType) + + // Add the kindTypeExt to the abiTyp object. The type extensions in + // reflect embed a kindType into an object that can have additional methods + // added to it. To handle those kinds we automatically change the cast to + // those types to look up the kindTypeExt value. + js.InternalObject(abiTyp).Set(idKindTypeExt, js.InternalObject(kindTypeExt)) } //gopherjs:replace diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index 91bffa275..20864b71b 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -22,5 +22,5 @@ type pinUnexpMeth interface{} //gopherjs:purge Used in FirstMethodNameBytes var pinUnexpMethI pinUnexpMeth -//gopherjs:purge Uses pointer arithmetic for names +//gopherjs:purge Unused method that uses pointer arithmetic for names func FirstMethodNameBytes(t Type) *byte diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 979e27e0c..0b735f308 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -260,3 +260,19 @@ func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) //gopherjs:purge Relates to GC programs not valid for GopherJS func appendGCProg(dst []byte, typ *abi.Type) []byte + +// =========================================================================== +// =========================================================================== +// TODO(grantnelson-wf): REMOVE +// =========================================================================== +// =========================================================================== +// +//gopherjs:replace +func (v Value) NumField() int { + v.mustBe(Struct) + + println(`>> v.typ >>`, v.typ()) + + tt := (*structType)(unsafe.Pointer(v.typ())) + return len(tt.Fields) +} diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 4c76f8f68..5180b63b8 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -49,7 +49,7 @@ func ValueOf(i any) Value { if i == nil { return Value{} } - return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) + return makeValue(rtypeOf(i), js.InternalObject(i).Get("$val"), 0) } //gopherjs:replace From 4f2e9bb33be8d2e649c85ac40e6c0a5e62e74d42 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 13:19:15 -0700 Subject: [PATCH 33/48] Fixed the kindTypeExt problem --- compiler/expressions.go | 39 +++++--------- compiler/natives/src/internal/abi/type.go | 18 ------- compiler/natives/src/reflect/type.go | 64 +++++++++++++++++++---- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index cf92637f6..6360101d6 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1114,26 +1114,6 @@ func (fc *funcContext) packageAllowsKindTypeConversion() bool { return false } -// typeAllowsKindTypeConversion determines if the given object ID for a desired type of an unsafe pointer conversion -// is a type we know should have been added as a `kindType` or `kindTypeExt` field to the type pointed at by the unsafe pointer. -func typeAllowsKindTypeConversion(desiredPkgPath, desiredName string) (bool, string) { - switch desiredPkgPath { - case `internal/abi`: - switch desiredName { - case `ArrayType`, `ChanType`, `FuncType`, `InterfaceType`, `MapType`, `PtrType`, `SliceType`, `StructType`: - return true, `kindType` - } - case `reflect`: - switch desiredName { - // The following are extensions of the ABI equivalent type to add more methods. - // e.g. `type structType struct { abi.StructType }` - case `interfaceType`, `mapType`, `ptrType`, `sliceType`, `structType`: - return true, `kindTypeExt` - } - } - return false, `` -} - func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type) *expression { exprType := fc.typeOf(expr) if types.Identical(exprType, desiredType) { @@ -1156,12 +1136,21 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(fc.typeOf(call.Fun), types.Typ[types.UnsafePointer]) { if ptr, isPtr := desiredType.(*types.Pointer); isPtr { if named, isNamed := ptr.Elem().(*types.Named); isNamed { - doKindTypeCast, fieldName := typeAllowsKindTypeConversion(named.Obj().Pkg().Path(), named.Obj().Name()) - if doKindTypeCast { - return fc.formatExpr("%e.%s", call.Args[0], fieldName) // unsafe conversion - } else { - return fc.translateExpr(expr) + switch named.Obj().Pkg().Path() { + case `internal/abi`: + switch named.Obj().Name() { + case `ArrayType`, `ChanType`, `FuncType`, `InterfaceType`, `MapType`, `PtrType`, `SliceType`, `StructType`: + return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion + } + case `reflect`: + switch named.Obj().Name() { + // The following are extensions of the ABI equivalent type to add more methods. + // e.g. `type structType struct { abi.StructType }` + case `interfaceType`, `mapType`, `ptrType`, `sliceType`, `structType`: + return fc.formatExpr("toKindTypeExt(%e)", call.Args[0]) // unsafe conversion + } } + return fc.translateExpr(expr) } } } diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 200c93112..8995fb303 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -13,7 +13,6 @@ const ( idJsType = `jsType` idReflectType = `reflectType` idKindType = `kindType` - idKindTypeExt = `kindTypeExt` idUncommonType = `uncommonType` ) @@ -127,23 +126,19 @@ func ReflectType(typ *js.Object) *Type { PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), Methods: imethods, }) - setKindTypeExt(abiTyp, `InterfaceType`, &struct{ InterfaceType }{}) case Map: setKindType(abiTyp, &MapType{ Key: ReflectType(typ.Get("key")), Elem: ReflectType(typ.Get("elem")), }) - setKindTypeExt(abiTyp, `MapType`, &struct{ MapType }{}) case Pointer: setKindType(abiTyp, &PtrType{ Elem: ReflectType(typ.Get("elem")), }) - setKindTypeExt(abiTyp, `PtrType`, &struct{ PtrType }{}) case Slice: setKindType(abiTyp, &SliceType{ Elem: ReflectType(typ.Get("elem")), }) - setKindTypeExt(abiTyp, `SliceType`, &struct{ SliceType }{}) case Struct: fields := typ.Get("fields") reflectFields := make([]StructField, fields.Length()) @@ -159,7 +154,6 @@ func ReflectType(typ *js.Object) *Type { PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), Fields: reflectFields, }) - setKindTypeExt(abiTyp, `StructType`, &struct{ StructType }{}) } return abiTyp @@ -182,18 +176,6 @@ func setKindType(abiTyp *Type, kindType any) { js.InternalObject(kindType).Set(`Type`, js.InternalObject(abiTyp)) } -//gopherjs:new -func setKindTypeExt(abiTyp *Type, kindTypeName string, kindTypeExt any) { - kindType := js.InternalObject(abiTyp).Get(idKindType) - js.InternalObject(kindTypeExt).Set(kindTypeName, kindType) - - // Add the kindTypeExt to the abiTyp object. The type extensions in - // reflect embed a kindType into an object that can have additional methods - // added to it. To handle those kinds we automatically change the cast to - // those types to look up the kindTypeExt value. - js.InternalObject(abiTyp).Set(idKindTypeExt, js.InternalObject(kindTypeExt)) -} - //gopherjs:replace type UncommonType struct { PkgPath NameOff // import path diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 0b735f308..3c6e4ef86 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -25,6 +25,7 @@ func init() { used(sliceType{}) used(structType{}) used(structField{}) + used(toKindTypeExt) } //gopherjs:new @@ -261,18 +262,59 @@ func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) //gopherjs:purge Relates to GC programs not valid for GopherJS func appendGCProg(dst []byte, typ *abi.Type) []byte -// =========================================================================== -// =========================================================================== -// TODO(grantnelson-wf): REMOVE -// =========================================================================== -// =========================================================================== +// toKindTypeExt will be automatically called when a cast to one of the +// extended kind types is performed. // -//gopherjs:replace -func (v Value) NumField() int { - v.mustBe(Struct) +// This is similar to `kindType` except that the reflect package has +// extended several of the kind types to have additional methods added to them. +// To get access to those methods, the `kindTypeExt` is checked or created. +// The automatic cast is handled in compiler/expressions.go +// +// gopherjs:new +func toKindTypeExt(src any) *js.Object { + var abiTyp *abi.Type + switch t := src.(type) { + case *rtype: + abiTyp = t.common() + case Type: + abiTyp = toAbiType(t) + case *abi.Type: + abiTyp = t + default: + panic(`unexpected type in toKindTypeExt`) + } - println(`>> v.typ >>`, v.typ()) + const ( + idKindType = `kindType` + idKindTypeExt = `kindTypeExt` + ) + // Check if a kindTypeExt has already been created for this type. + ext := js.InternalObject(abiTyp).Get(idKindTypeExt) + if ext != js.Undefined { + return ext + } - tt := (*structType)(unsafe.Pointer(v.typ())) - return len(tt.Fields) + // Constructe a new kindTypeExt for this type. + kindType := js.InternalObject(abiTyp).Get(idKindType) + switch abiTyp.Kind() { + case abi.Interface: + ext = js.InternalObject(&interfaceType{}) + ext.Set(`InterfaceType`, js.InternalObject(kindType)) + case abi.Map: + ext = js.InternalObject(&mapType{}) + ext.Set(`MapType`, js.InternalObject(kindType)) + case abi.Pointer: + ext = js.InternalObject(&ptrType{}) + ext.Set(`PtrType`, js.InternalObject(kindType)) + case abi.Slice: + ext = js.InternalObject(&sliceType{}) + ext.Set(`SliceType`, js.InternalObject(kindType)) + case abi.Struct: + ext = js.InternalObject(&structType{}) + ext.Set(`StructType`, js.InternalObject(kindType)) + default: + panic(`unexpected kind in toKindTypeExt`) + } + js.InternalObject(abiTyp).Set(idKindTypeExt, ext) + return ext } From 63ff450ffa7aa925478342eb134603f0d9ee7072 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 13:37:39 -0700 Subject: [PATCH 34/48] Cleaning up reflect --- compiler/natives/src/reflect/reflect.go | 549 ++++++++++++++++++++++++ compiler/natives/src/reflect/type.go | 229 ---------- compiler/natives/src/reflect/value.go | 323 -------------- 3 files changed, 549 insertions(+), 552 deletions(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index b610a4cdd..6c682c3d0 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -3,6 +3,7 @@ package reflect import ( + "strconv" "unsafe" "internal/abi" @@ -11,6 +12,554 @@ import ( "github.com/gopherjs/gopherjs/js" ) +func init() { + // avoid dead code elimination + used := func(i any) {} + used(rtype{}) + used(uncommonType{}) + used(arrayType{}) + used(chanType{}) + used(funcType{}) + used(interfaceType{}) + used(mapType{}) + used(ptrType{}) + used(sliceType{}) + used(structType{}) + used(structField{}) + used(toKindTypeExt) +} + +// New returns a Value representing a pointer to a new zero value +// for the specified type. That is, the returned Value's Type is PtrTo(typ). +// +// The upstream version includes an extra check to avoid creating types that +// are tagged as go:notinheap. This shouldn't matter in GopherJS, and tracking +// that state is over-complex, so we just skip that check. +// +//gopherjs:replace +func New(typ Type) Value { + if typ == nil { + panic("reflect: New(nil)") + } + t := toAbiType(typ) + pt := t.PtrTo() + ptr := unsafe_New(t) + fl := flag(Pointer) + return Value{pt, ptr, fl} +} + +//gopherjs:new +func toAbiType(typ Type) *abi.Type { + return typ.(*rtype).common() +} + +//gopherjs:new +func jsType(typ Type) *js.Object { + return toAbiType(typ).JsType() +} + +//gopherjs:replace +func toRType(t *abi.Type) *rtype { + rtyp := &rtype{} + // Assign t to the abiType. The abiType is a `*Type` and the t + // field on `rtype` is `Type`. However, this is valid because of how + // pointers and references work in JS. We set this so that the t + // isn't a copy but the actual abiType object. + js.InternalObject(rtyp).Set("t", js.InternalObject(t)) + return rtyp +} + +//gopherjs:purge +func addReflectOff(ptr unsafe.Pointer) int32 + +//gopherjs:replace +func (t *rtype) nameOff(off aNameOff) abi.Name { + return toAbiType(t).NameOff(off) +} + +//gopherjs:replace +func resolveReflectName(n abi.Name) aNameOff { + return abi.ResolveReflectName(n) +} + +//gopherjs:replace +func (t *rtype) typeOff(off aTypeOff) *abi.Type { + return toAbiType(t).TypeOff(off) +} + +//gopherjs:replace +func resolveReflectType(t *abi.Type) aTypeOff { + return abi.ResolveReflectType(t) +} + +//gopherjs:replace +func (t *rtype) textOff(off aTextOff) unsafe.Pointer { + return toAbiType(t).TextOff(off) +} + +//gopherjs:replace +func resolveReflectText(ptr unsafe.Pointer) aTextOff { + return abi.ResolveReflectText(ptr) +} + +//gopherjd:replace +func pkgPath(n abi.Name) string { + return n.PkgPath() +} + +//gopherjs:new +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{t, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} + } + return Value{t, unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} +} + +//gopherjs:replace +func MakeSlice(typ Type, len, cap int) Value { + if typ.Kind() != Slice { + panic("reflect.MakeSlice of non-slice type") + } + if len < 0 { + panic("reflect.MakeSlice: negative len") + } + if cap < 0 { + panic("reflect.MakeSlice: negative cap") + } + if len > cap { + panic("reflect.MakeSlice: len > cap") + } + + return makeValue(toAbiType(typ), js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) +} + +//gopherjs:replace +func TypeOf(i any) Type { + if i == nil { + return nil + } + return toRType(rtypeOf(i)) +} + +//gopherjs:replace +func ValueOf(i any) Value { + if i == nil { + return Value{} + } + return makeValue(rtypeOf(i), js.InternalObject(i).Get("$val"), 0) +} + +//gopherjs:replace +func ArrayOf(count int, elem Type) Type { + if count < 0 { + panic("reflect: negative length passed to ArrayOf") + } + + return toRType(abi.ReflectType(js.Global.Call("$arrayType", jsType(elem), count))) +} + +//gopherjs:replace +func ChanOf(dir ChanDir, t Type) Type { + return toRType(abi.ReflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir))) +} + +//gopherjs:replace +func FuncOf(in, out []Type, variadic bool) Type { + if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { + panic("reflect.FuncOf: last arg of variadic func must be slice") + } + + jsIn := make([]*js.Object, len(in)) + for i, v := range in { + jsIn[i] = jsType(v) + } + jsOut := make([]*js.Object, len(out)) + for i, v := range out { + jsOut[i] = jsType(v) + } + return toRType(abi.ReflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic))) +} + +//gopherjs:replace +func MapOf(key, elem Type) Type { + switch key.Kind() { + case Func, Map, Slice: + panic("reflect.MapOf: invalid key type " + key.String()) + } + + return toRType(abi.ReflectType(js.Global.Call("$mapType", jsType(key), jsType(elem)))) +} + +//gopherjs:replace +func (t *rtype) ptrTo() *abi.Type { + return abi.ReflectType(js.Global.Call("$ptrType", jsType(t))) +} + +//gopherjs:replace +func SliceOf(t Type) Type { + return toRType(abi.ReflectType(js.Global.Call("$sliceType", jsType(t)))) +} + +//gopherjs:replace +func StructOf(fields []StructField) Type { + var ( + jsFields = make([]*js.Object, len(fields)) + fset = map[string]struct{}{} + pkgpath string + hasGCProg bool + ) + for i, field := range fields { + if field.Name == "" { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no name") + } + if !isValidFieldName(field.Name) { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has invalid name") + } + if field.Type == nil { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") + } + f, fpkgpath := runtimeStructField(field) + ft := f.Typ + if ft.Kind()&kindGCProg != 0 { + hasGCProg = true + } + if fpkgpath != "" { + if pkgpath == "" { + pkgpath = fpkgpath + } else if pkgpath != fpkgpath { + panic("reflect.Struct: fields with different PkgPath " + pkgpath + " and " + fpkgpath) + } + } + name := field.Name + if f.Embedded() { + // Embedded field + if field.Type.Kind() == Ptr { + // Embedded ** and *interface{} are illegal + elem := field.Type.Elem() + if k := elem.Kind(); k == Ptr || k == Interface { + panic("reflect.StructOf: illegal anonymous field type " + field.Type.String()) + } + } + switch field.Type.Kind() { + case Interface: + case Ptr: + ptr := (*ptrType)(unsafe.Pointer(ft)) + if unt := ptr.Uncommon(); unt != nil { + if i > 0 && unt.Mcount > 0 { + // Issue 15924. + panic("reflect: embedded type with methods not implemented if type is not first field") + } + if len(fields) > 1 { + panic("reflect: embedded type with methods not implemented if there is more than one field") + } + } + default: + if unt := ft.Uncommon(); unt != nil { + if i > 0 && unt.Mcount > 0 { + // Issue 15924. + panic("reflect: embedded type with methods not implemented if type is not first field") + } + if len(fields) > 1 && ft.Kind()&kindDirectIface != 0 { + panic("reflect: embedded type with methods not implemented for non-pointer type") + } + } + } + } + + if _, dup := fset[name]; dup && name != "_" { + panic("reflect.StructOf: duplicate field " + name) + } + fset[name] = struct{}{} + // To be consistent with Compiler's behavior we need to avoid externalizing + // the "name" property. The line below is effectively an inverse of the + // internalStr() function. + jsf := js.InternalObject(struct{ name string }{name}) + // The rest is set through the js.Object() interface, which the compiler will + // externalize for us. + jsf.Set("prop", name) + jsf.Set("exported", f.Name.IsExported()) + jsf.Set("typ", jsType(field.Type)) + jsf.Set("tag", field.Tag) + jsf.Set("embedded", field.Anonymous) + jsFields[i] = jsf + } + _ = hasGCProg + typ := js.Global.Call("$structType", "", jsFields) + if pkgpath != "" { + typ.Set("pkgPath", pkgpath) + } + return toRType(abi.ReflectType(typ)) +} + +//gopherjs:replace +func Zero(typ Type) Value { + return makeValue(toAbiType(typ), jsType(typ).Call("zero"), 0) +} + +//gopherjs:replace +func unsafe_New(typ *abi.Type) unsafe.Pointer { + return abi.UnsafeNew(typ) +} + +//gopherjs:replace +func makeInt(f flag, bits uint64, t Type) Value { + typ := t.common() + ptr := unsafe_New(typ) + switch typ.Kind() { + case abi.Int8: + *(*int8)(ptr) = int8(bits) + case abi.Int16: + *(*int16)(ptr) = int16(bits) + case abi.Int, abi.Int32: + *(*int32)(ptr) = int32(bits) + case abi.Int64: + *(*int64)(ptr) = int64(bits) + case abi.Uint8: + *(*uint8)(ptr) = uint8(bits) + case abi.Uint16: + *(*uint16)(ptr) = uint16(bits) + case abi.Uint, abi.Uint32, abi.Uintptr: + *(*uint32)(ptr) = uint32(bits) + case abi.Uint64: + *(*uint64)(ptr) = uint64(bits) + } + return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} +} + +//gopherjs:replace +func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { + if typ.Kind() != Func { + panic("reflect: call of MakeFunc with non-Func type") + } + + t := typ.common() + ftyp := t.FuncType() + + fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { + // Convert raw JS arguments into []Value the user-supplied function expects. + args := make([]Value, ftyp.NumIn()) + for i := range args { + argType := ftyp.In(i) + args[i] = makeValue(argType, arguments[i], 0) + } + + // Call the user-supplied function. + resultsSlice := fn(args) + + // Verify that returned value types are compatible with the function type specified by the caller. + if want, got := ftyp.NumOut(), len(resultsSlice); want != got { + panic("reflect: expected " + strconv.Itoa(want) + " return values, got " + strconv.Itoa(got)) + } + for i, rtyp := range ftyp.OutSlice() { + if !resultsSlice[i].Type().AssignableTo(toRType(rtyp)) { + panic("reflect: " + strconv.Itoa(i) + " return value type is not compatible with the function declaration") + } + } + + // Rearrange return values according to the expected function signature. + switch ftyp.NumOut() { + case 0: + return nil + case 1: + return resultsSlice[0].object() + default: + results := js.Global.Get("Array").New(ftyp.NumOut()) + for i, r := range resultsSlice { + results.SetIndex(i, r.object()) + } + return results + } + }) + + return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} +} + +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + abi.IfaceE2I(t, src, dst) +} + +//gopherjs:replace +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { + abi.TypedMemMove(t, dst, src) +} + +//gopherjs:replace +func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { + ctyp := (*chanType)(unsafe.Pointer(typ)) + return unsafe.Pointer(js.Global.Get("$Chan").New(ctyp.Elem.JsType(), size).Unsafe()) +} + +//gopherjs:replace +func makemap(t *abi.Type, cap int) (m unsafe.Pointer) { + return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) +} + +//gopherjs:new +func keyFor(t *abi.Type, key unsafe.Pointer) (*js.Object, *js.Object) { + kv := js.InternalObject(key) + if kv.Get("$get") != js.Undefined { + kv = kv.Call("$get") + } + k := t.Key().JsType().Call("keyFor", kv) + return kv, k +} + +//gopherjs:replace +func mapaccess(t *abi.Type, m, key unsafe.Pointer) unsafe.Pointer { + if !js.InternalObject(m).Bool() { + return nil // nil map + } + _, k := keyFor(t, key) + entry := js.InternalObject(m).Call("get", k) + if entry == js.Undefined { + return nil + } + return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), t.Elem().JsPtrTo()).Unsafe()) +} + +//gopherjs:replace +func mapassign(t *abi.Type, m, key, val unsafe.Pointer) { + kv, k := keyFor(t, key) + jsVal := js.InternalObject(val).Call("$get") + et := t.Elem() + if et.Kind() == abi.Struct { + newVal := et.JsType().Call("zero") + abi.CopyStruct(newVal, jsVal, et) + jsVal = newVal + } + entry := js.Global.Get("Object").New() + entry.Set("k", kv) + entry.Set("v", jsVal) + js.InternalObject(m).Call("set", k, entry) +} + +//gopherjs:replace +func mapdelete(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer) { + _, k := keyFor(t, key) + if !js.InternalObject(m).Bool() { + return // nil map + } + js.InternalObject(m).Call("delete", k) +} + +// TODO(nevkonatkte): The following three "faststr" implementations are meant to +// perform better for the common case of string-keyed maps (see upstream: +// https://github.com/golang/go/commit/23832ba2e2fb396cda1dacf3e8afcb38ec36dcba) +// However, the stubs below will perform the same or worse because of the extra +// string-to-pointer conversion. Not sure how to fix this without significant +// code duplication, however. + +//gopherjs:replace +func mapaccess_faststr(t *abi.Type, m unsafe.Pointer, key string) (val unsafe.Pointer) { + return mapaccess(t, m, unsafe.Pointer(&key)) +} + +//gopherjs:replace +func mapassign_faststr(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer) { + mapassign(t, m, unsafe.Pointer(&key), val) +} + +//gopherjs:replace +func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string) { + mapdelete(t, m, unsafe.Pointer(&key)) +} + +//gopherjs:replace +type hiter struct { + t *abi.Type + m *js.Object // Underlying map object. + keys *js.Object + i int + + // last is the last object the iterator indicates. If this object exists, the + // functions that return the current key or value returns this object, + // regardless of the current iterator. It is because the current iterator + // might be stale due to key deletion in a loop. + last *js.Object +} + +//gopherjs:new +func (iter *hiter) skipUntilValidKey() { + for iter.i < iter.keys.Length() { + k := iter.keys.Index(iter.i) + entry := iter.m.Call("get", k) + if entry != js.Undefined { + break + } + // The key is already deleted. Move on the next item. + iter.i++ + } +} + +//gopherjs:replace +func mapiterinit(t *abi.Type, m unsafe.Pointer, it *hiter) { + mapObj := js.InternalObject(m) + keys := js.Global.Get("Array").New() + if mapObj.Get("keys") != js.Undefined { + keysIter := mapObj.Call("keys") + if mapObj.Get("keys") != js.Undefined { + keys = js.Global.Get("Array").Call("from", keysIter) + } + } + + *it = hiter{ + t: t, + m: mapObj, + keys: keys, + i: 0, + last: nil, + } +} + +//gopherjs:replace +func mapiterkey(it *hiter) unsafe.Pointer { + var kv *js.Object + if it.last != nil { + kv = it.last + } else { + it.skipUntilValidKey() + if it.i == it.keys.Length() { + return nil + } + k := it.keys.Index(it.i) + kv = it.m.Call("get", k) + + // Record the key-value pair for later accesses. + it.last = kv + } + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), it.t.Key().JsPtrTo()).Unsafe()) +} + +//gopherjs:replace +func mapiterelem(it *hiter) unsafe.Pointer { + var kv *js.Object + if it.last != nil { + kv = it.last + } else { + it.skipUntilValidKey() + if it.i == it.keys.Length() { + return nil + } + k := it.keys.Index(it.i) + kv = it.m.Call("get", k) + it.last = kv + } + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), it.t.Elem().JsPtrTo()).Unsafe()) +} + +//gopherjs:replace +func mapiternext(it *hiter) { + it.last = nil + it.i++ +} + +//gopherjs:replace +func maplen(m unsafe.Pointer) int { + return js.InternalObject(m).Get("size").Int() +} + //gopherjs:replace func cvtDirect(v Value, typ Type) Value { srcVal := v.object() diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go index 3c6e4ef86..09f657b60 100644 --- a/compiler/natives/src/reflect/type.go +++ b/compiler/natives/src/reflect/type.go @@ -3,108 +3,16 @@ package reflect import ( - "strconv" - "unsafe" - "internal/abi" "github.com/gopherjs/gopherjs/js" ) -func init() { - // avoid dead code elimination - used := func(i any) {} - used(rtype{}) - used(uncommonType{}) - used(arrayType{}) - used(chanType{}) - used(funcType{}) - used(interfaceType{}) - used(mapType{}) - used(ptrType{}) - used(sliceType{}) - used(structType{}) - used(structField{}) - used(toKindTypeExt) -} - -//gopherjs:new -func toAbiType(typ Type) *abi.Type { - return typ.(*rtype).common() -} - -//gopherjs:new -func jsType(typ Type) *js.Object { - return toAbiType(typ).JsType() -} - -//gopherjs:replace -func (t *rtype) ptrTo() *abi.Type { - return abi.ReflectType(js.Global.Call("$ptrType", jsType(t))) -} - -//gopherjs:replace -func toRType(t *abi.Type) *rtype { - rtyp := &rtype{} - // Assign t to the abiType. The abiType is a `*Type` and the t - // field on `rtype` is `Type`. However, this is valid because of how - // pointers and references work in JS. We set this so that the t - // isn't a copy but the actual abiType object. - js.InternalObject(rtyp).Set("t", js.InternalObject(t)) - return rtyp -} - //gopherjs:replace func (t *rtype) String() string { return toAbiType(t).String() } -//gopherjs:purge -func addReflectOff(ptr unsafe.Pointer) int32 - -//gopherjs:replace -func (t *rtype) nameOff(off aNameOff) abi.Name { - return toAbiType(t).NameOff(off) -} - -//gopherjs:replace -func resolveReflectName(n abi.Name) aNameOff { - return abi.ResolveReflectName(n) -} - -//gopherjs:replace -func (t *rtype) typeOff(off aTypeOff) *abi.Type { - return toAbiType(t).TypeOff(off) -} - -//gopherjs:replace -func resolveReflectType(t *abi.Type) aTypeOff { - return abi.ResolveReflectType(t) -} - -//gopherjs:replace -func (t *rtype) textOff(off aTextOff) unsafe.Pointer { - return toAbiType(t).TextOff(off) -} - -//gopherjs:replace -func resolveReflectText(ptr unsafe.Pointer) aTextOff { - return abi.ResolveReflectText(ptr) -} - -//gopherjd:replace -func pkgPath(n abi.Name) string { - return n.PkgPath() -} - -//gopherjs:replace -func TypeOf(i any) Type { - if i == nil { - return nil - } - return toRType(rtypeOf(i)) -} - //gopherjs:replace func rtypeOf(i any) *abi.Type { return abi.ReflectType(js.InternalObject(i).Get("constructor")) @@ -113,143 +21,6 @@ func rtypeOf(i any) *abi.Type { //gopherjs:purge Unused type type common struct{} -//gopherjs:replace -func ArrayOf(count int, elem Type) Type { - if count < 0 { - panic("reflect: negative length passed to ArrayOf") - } - - return toRType(abi.ReflectType(js.Global.Call("$arrayType", jsType(elem), count))) -} - -//gopherjs:replace -func ChanOf(dir ChanDir, t Type) Type { - return toRType(abi.ReflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir))) -} - -//gopherjs:replace -func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { - panic("reflect.FuncOf: last arg of variadic func must be slice") - } - - jsIn := make([]*js.Object, len(in)) - for i, v := range in { - jsIn[i] = jsType(v) - } - jsOut := make([]*js.Object, len(out)) - for i, v := range out { - jsOut[i] = jsType(v) - } - return toRType(abi.ReflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic))) -} - -//gopherjs:replace -func MapOf(key, elem Type) Type { - switch key.Kind() { - case Func, Map, Slice: - panic("reflect.MapOf: invalid key type " + key.String()) - } - - return toRType(abi.ReflectType(js.Global.Call("$mapType", jsType(key), jsType(elem)))) -} - -//gopherjs:replace -func SliceOf(t Type) Type { - return toRType(abi.ReflectType(js.Global.Call("$sliceType", jsType(t)))) -} - -//gopherjs:replace -func StructOf(fields []StructField) Type { - var ( - jsFields = make([]*js.Object, len(fields)) - fset = map[string]struct{}{} - pkgpath string - hasGCProg bool - ) - for i, field := range fields { - if field.Name == "" { - panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no name") - } - if !isValidFieldName(field.Name) { - panic("reflect.StructOf: field " + strconv.Itoa(i) + " has invalid name") - } - if field.Type == nil { - panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") - } - f, fpkgpath := runtimeStructField(field) - ft := f.Typ - if ft.Kind()&kindGCProg != 0 { - hasGCProg = true - } - if fpkgpath != "" { - if pkgpath == "" { - pkgpath = fpkgpath - } else if pkgpath != fpkgpath { - panic("reflect.Struct: fields with different PkgPath " + pkgpath + " and " + fpkgpath) - } - } - name := field.Name - if f.Embedded() { - // Embedded field - if field.Type.Kind() == Ptr { - // Embedded ** and *interface{} are illegal - elem := field.Type.Elem() - if k := elem.Kind(); k == Ptr || k == Interface { - panic("reflect.StructOf: illegal anonymous field type " + field.Type.String()) - } - } - switch field.Type.Kind() { - case Interface: - case Ptr: - ptr := (*ptrType)(unsafe.Pointer(ft)) - if unt := ptr.Uncommon(); unt != nil { - if i > 0 && unt.Mcount > 0 { - // Issue 15924. - panic("reflect: embedded type with methods not implemented if type is not first field") - } - if len(fields) > 1 { - panic("reflect: embedded type with methods not implemented if there is more than one field") - } - } - default: - if unt := ft.Uncommon(); unt != nil { - if i > 0 && unt.Mcount > 0 { - // Issue 15924. - panic("reflect: embedded type with methods not implemented if type is not first field") - } - if len(fields) > 1 && ft.Kind()&kindDirectIface != 0 { - panic("reflect: embedded type with methods not implemented for non-pointer type") - } - } - } - } - - if _, dup := fset[name]; dup && name != "_" { - panic("reflect.StructOf: duplicate field " + name) - } - fset[name] = struct{}{} - // To be consistent with Compiler's behavior we need to avoid externalizing - // the "name" property. The line below is effectively an inverse of the - // internalStr() function. - jsf := js.InternalObject(struct{ name string }{name}) - // The rest is set through the js.Object() interface, which the compiler will - // externalize for us. - jsf.Set("prop", name) - jsf.Set("exported", f.Name.IsExported()) - jsf.Set("typ", jsType(field.Type)) - jsf.Set("tag", field.Tag) - jsf.Set("embedded", field.Anonymous) - jsFields[i] = jsf - } - _ = hasGCProg - typ := js.Global.Call("$structType", "", jsFields) - if pkgpath != "" { - typ.Set("pkgPath", pkgpath) - } - return toRType(abi.ReflectType(typ)) -} - //gopherjs:purge Used in original MapOf and not used in override MapOf by GopherJS func bucketOf(ktyp, etyp *abi.Type) *abi.Type diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 5180b63b8..946f81f82 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -4,7 +4,6 @@ package reflect import ( "errors" - "strconv" "unsafe" "internal/abi" @@ -25,95 +24,6 @@ func packEface(v Value) any //gopherjs:purge func unpackEface(i any) Value -// New returns a Value representing a pointer to a new zero value -// for the specified type. That is, the returned Value's Type is PtrTo(typ). -// -// The upstream version includes an extra check to avoid creating types that -// are tagged as go:notinheap. This shouldn't matter in GopherJS, and tracking -// that state is over-complex, so we just skip that check. -// -//gopherjs:replace -func New(typ Type) Value { - if typ == nil { - panic("reflect: New(nil)") - } - t := toAbiType(typ) - pt := t.PtrTo() - ptr := unsafe_New(t) - fl := flag(Pointer) - return Value{pt, ptr, fl} -} - -//gopherjs:replace -func ValueOf(i any) Value { - if i == nil { - return Value{} - } - return makeValue(rtypeOf(i), js.InternalObject(i).Get("$val"), 0) -} - -//gopherjs:replace -func unsafe_New(typ *abi.Type) unsafe.Pointer { - return abi.UnsafeNew(typ) -} - -//gopherjs:new -func makeValue(t *abi.Type, v *js.Object, fl flag) Value { - switch t.Kind() { - case abi.Array, abi.Struct, abi.Pointer: - return Value{t, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} - } - return Value{t, unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} -} - -//gopherjs:replace -func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { - panic("reflect.MakeSlice of non-slice type") - } - if len < 0 { - panic("reflect.MakeSlice: negative len") - } - if cap < 0 { - panic("reflect.MakeSlice: negative cap") - } - if len > cap { - panic("reflect.MakeSlice: len > cap") - } - - return makeValue(toAbiType(typ), js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) -} - -//gopherjs:replace -func Zero(typ Type) Value { - return makeValue(toAbiType(typ), jsType(typ).Call("zero"), 0) -} - -//gopherjs:replace -func makeInt(f flag, bits uint64, t Type) Value { - typ := t.common() - ptr := unsafe_New(typ) - switch typ.Kind() { - case abi.Int8: - *(*int8)(ptr) = int8(bits) - case abi.Int16: - *(*int16)(ptr) = int16(bits) - case abi.Int, abi.Int32: - *(*int32)(ptr) = int32(bits) - case abi.Int64: - *(*int64)(ptr) = int64(bits) - case abi.Uint8: - *(*uint8)(ptr) = uint8(bits) - case abi.Uint16: - *(*uint16)(ptr) = uint16(bits) - case abi.Uint, abi.Uint32, abi.Uintptr: - *(*uint32)(ptr) = uint32(bits) - case abi.Uint64: - *(*uint64)(ptr) = uint64(bits) - } - return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} -} - //gopherjs:replace func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { i := methodIndex @@ -156,75 +66,6 @@ func storeRcvr(v Value, p unsafe.Pointer) //gopherjs:purge func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) -//gopherjs:replace -func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { - if typ.Kind() != Func { - panic("reflect: call of MakeFunc with non-Func type") - } - - t := typ.common() - ftyp := t.FuncType() - - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - // Convert raw JS arguments into []Value the user-supplied function expects. - args := make([]Value, ftyp.NumIn()) - for i := range args { - argType := ftyp.In(i) - args[i] = makeValue(argType, arguments[i], 0) - } - - // Call the user-supplied function. - resultsSlice := fn(args) - - // Verify that returned value types are compatible with the function type specified by the caller. - if want, got := ftyp.NumOut(), len(resultsSlice); want != got { - panic("reflect: expected " + strconv.Itoa(want) + " return values, got " + strconv.Itoa(got)) - } - for i, rtyp := range ftyp.OutSlice() { - if !resultsSlice[i].Type().AssignableTo(toRType(rtyp)) { - panic("reflect: " + strconv.Itoa(i) + " return value type is not compatible with the function declaration") - } - } - - // Rearrange return values according to the expected function signature. - switch ftyp.NumOut() { - case 0: - return nil - case 1: - return resultsSlice[0].object() - default: - results := js.Global.Get("Array").New(ftyp.NumOut()) - for i, r := range resultsSlice { - results.SetIndex(i, r.object()) - } - return results - } - }) - - return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} -} - -//gopherjs:replace -func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { - abi.IfaceE2I(t, src, dst) -} - -//gopherjs:replace -func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { - abi.TypedMemMove(t, dst, src) -} - -//gopherjs:replace -func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { - ctyp := (*chanType)(unsafe.Pointer(typ)) - return unsafe.Pointer(js.Global.Get("$Chan").New(ctyp.Elem.JsType(), size).Unsafe()) -} - -//gopherjs:replace -func makemap(t *abi.Type, cap int) (m unsafe.Pointer) { - return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) -} - //gopherjs:new func (v Value) object() *js.Object { if v.typ().Kind() == abi.Array || v.typ().Kind() == abi.Struct { @@ -888,170 +729,6 @@ func typedslicecopy(t *abi.Type, dst, src unsafeheader.Slice) int //gopherjs:purge func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice -//gopherjs:new -func keyFor(t *abi.Type, key unsafe.Pointer) (*js.Object, *js.Object) { - kv := js.InternalObject(key) - if kv.Get("$get") != js.Undefined { - kv = kv.Call("$get") - } - k := t.Key().JsType().Call("keyFor", kv) - return kv, k -} - -//gopherjs:replace -func mapaccess(t *abi.Type, m, key unsafe.Pointer) unsafe.Pointer { - if !js.InternalObject(m).Bool() { - return nil // nil map - } - _, k := keyFor(t, key) - entry := js.InternalObject(m).Call("get", k) - if entry == js.Undefined { - return nil - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), t.Elem().JsPtrTo()).Unsafe()) -} - -//gopherjs:replace -func mapassign(t *abi.Type, m, key, val unsafe.Pointer) { - kv, k := keyFor(t, key) - jsVal := js.InternalObject(val).Call("$get") - et := t.Elem() - if et.Kind() == abi.Struct { - newVal := et.JsType().Call("zero") - abi.CopyStruct(newVal, jsVal, et) - jsVal = newVal - } - entry := js.Global.Get("Object").New() - entry.Set("k", kv) - entry.Set("v", jsVal) - js.InternalObject(m).Call("set", k, entry) -} - -//gopherjs:replace -func mapdelete(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer) { - _, k := keyFor(t, key) - if !js.InternalObject(m).Bool() { - return // nil map - } - js.InternalObject(m).Call("delete", k) -} - -// TODO(nevkonatkte): The following three "faststr" implementations are meant to -// perform better for the common case of string-keyed maps (see upstream: -// https://github.com/golang/go/commit/23832ba2e2fb396cda1dacf3e8afcb38ec36dcba) -// However, the stubs below will perform the same or worse because of the extra -// string-to-pointer conversion. Not sure how to fix this without significant -// code duplication, however. - -//gopherjs:replace -func mapaccess_faststr(t *abi.Type, m unsafe.Pointer, key string) (val unsafe.Pointer) { - return mapaccess(t, m, unsafe.Pointer(&key)) -} - -//gopherjs:replace -func mapassign_faststr(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer) { - mapassign(t, m, unsafe.Pointer(&key), val) -} - -//gopherjs:replace -func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string) { - mapdelete(t, m, unsafe.Pointer(&key)) -} - -//gopherjs:replace -type hiter struct { - t *abi.Type - m *js.Object // Underlying map object. - keys *js.Object - i int - - // last is the last object the iterator indicates. If this object exists, the - // functions that return the current key or value returns this object, - // regardless of the current iterator. It is because the current iterator - // might be stale due to key deletion in a loop. - last *js.Object -} - -//gopherjs:new -func (iter *hiter) skipUntilValidKey() { - for iter.i < iter.keys.Length() { - k := iter.keys.Index(iter.i) - entry := iter.m.Call("get", k) - if entry != js.Undefined { - break - } - // The key is already deleted. Move on the next item. - iter.i++ - } -} - -//gopherjs:replace -func mapiterinit(t *abi.Type, m unsafe.Pointer, it *hiter) { - mapObj := js.InternalObject(m) - keys := js.Global.Get("Array").New() - if mapObj.Get("keys") != js.Undefined { - keysIter := mapObj.Call("keys") - if mapObj.Get("keys") != js.Undefined { - keys = js.Global.Get("Array").Call("from", keysIter) - } - } - - *it = hiter{ - t: t, - m: mapObj, - keys: keys, - i: 0, - last: nil, - } -} - -//gopherjs:replace -func mapiterkey(it *hiter) unsafe.Pointer { - var kv *js.Object - if it.last != nil { - kv = it.last - } else { - it.skipUntilValidKey() - if it.i == it.keys.Length() { - return nil - } - k := it.keys.Index(it.i) - kv = it.m.Call("get", k) - - // Record the key-value pair for later accesses. - it.last = kv - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), it.t.Key().JsPtrTo()).Unsafe()) -} - -//gopherjs:replace -func mapiterelem(it *hiter) unsafe.Pointer { - var kv *js.Object - if it.last != nil { - kv = it.last - } else { - it.skipUntilValidKey() - if it.i == it.keys.Length() { - return nil - } - k := it.keys.Index(it.i) - kv = it.m.Call("get", k) - it.last = kv - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), it.t.Elem().JsPtrTo()).Unsafe()) -} - -//gopherjs:replace -func mapiternext(it *hiter) { - it.last = nil - it.i++ -} - -//gopherjs:replace -func maplen(m unsafe.Pointer) int { - return js.InternalObject(m).Get("size").Int() -} - // gopherjs:replace func noescape(p unsafe.Pointer) unsafe.Pointer { return p From c71da24deebea37cc4334f2a1c0e6996b85b0b2b Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 14:21:15 -0700 Subject: [PATCH 35/48] Cleaning up reflect --- .../src/internal/reflectlite/reflectlite.go | 2 +- compiler/natives/src/reflect/reflect.go | 36 +++++++------------ 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index abf01b6e6..5995f66ae 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -20,7 +20,7 @@ func init() { used(structType{}) } -// TODO(grantnelson-wf): This is just to minimize diffs. After this is merged into the go1.21 branch, remove it. +// gopherjs:new func jsType(typ *abi.Type) *js.Object { return typ.JsType() } diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 6c682c3d0..4b61f74a2 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -49,13 +49,13 @@ func New(typ Type) Value { } //gopherjs:new -func toAbiType(typ Type) *abi.Type { - return typ.(*rtype).common() +func jsType(typ Type) *js.Object { + return toAbiType(typ).JsType() } //gopherjs:new -func jsType(typ Type) *js.Object { - return toAbiType(typ).JsType() +func toAbiType(typ Type) *abi.Type { + return typ.(*rtype).common() } //gopherjs:replace @@ -334,7 +334,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { } t := typ.common() - ftyp := t.FuncType() + ftyp := (*funcType)(unsafe.Pointer(t)) fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { // Convert raw JS arguments into []Value the user-supplied function expects. @@ -598,13 +598,9 @@ func cvtDirect(v Value, typ Type) Value { case Array, Bool, Chan, Func, Interface, Map, String, UnsafePointer: val = js.InternalObject(v.ptr) default: - panic(&ValueError{Method: "reflect.Convert", Kind: k}) - } - return Value{ - typ_: typ.common(), - ptr: unsafe.Pointer(val.Unsafe()), - flag: v.flag.ro() | v.flag&flagIndir | flag(typ.Kind()), + panic(&ValueError{"reflect.Convert", k}) } + return Value{typ.common(), unsafe.Pointer(val.Unsafe()), v.flag.ro() | v.flag&flagIndir | flag(typ.Kind())} } // convertOp: []T -> *[N]T @@ -619,11 +615,7 @@ func cvtSliceArrayPtr(v Value, t Type) Value { panic("reflect: cannot convert slice with length " + itoa.Itoa(slen) + " to pointer to array with length " + itoa.Itoa(alen)) } array := js.Global.Call("$sliceToGoArray", slice, jsType(t)) - return Value{ - typ_: t.common(), - ptr: unsafe.Pointer(array.Unsafe()), - flag: v.flag&^(flagIndir|flagAddr|flagKindMask) | flag(Ptr), - } + return Value{t.common(), unsafe.Pointer(array.Unsafe()), v.flag&^(flagIndir|flagAddr|flagKindMask) | flag(Ptr)} } // convertOp: []T -> [N]T @@ -640,18 +632,14 @@ func cvtSliceArray(v Value, t Type) Value { js.Global.Call("$copySlice", dst, slice) arr := dst.Get("$array") - return Value{ - typ_: t.common(), - ptr: unsafe.Pointer(arr.Unsafe()), - flag: v.flag&^(flagAddr|flagKindMask) | flag(Array), - } + return Value{t.common(), unsafe.Pointer(arr.Unsafe()), v.flag&^(flagAddr|flagKindMask) | flag(Array)} } //gopherjs:replace func Copy(dst, src Value) int { dk := dst.kind() if dk != Array && dk != Slice { - panic(&ValueError{Method: "reflect.Copy", Kind: dk}) + panic(&ValueError{"reflect.Copy", dk}) } if dk == Array { dst.mustBeAssignable() @@ -663,7 +651,7 @@ func Copy(dst, src Value) int { if sk != Array && sk != Slice { stringCopy = sk == String && dst.typ().Elem().Kind() == abi.Uint8 if !stringCopy { - panic(&ValueError{Method: "reflect.Copy", Kind: sk}) + panic(&ValueError{"reflect.Copy", sk}) } } src.mustBeExported() @@ -691,7 +679,7 @@ func Copy(dst, src Value) int { //gopherjs:replace func valueInterface(v Value, safe bool) any { if v.flag == 0 { - panic(&ValueError{Method: "reflect.Value.Interface", Kind: 0}) + panic(&ValueError{"reflect.Value.Interface", 0}) } if safe && v.flag&flagRO != 0 { panic("reflect.Value.Interface: cannot return value obtained from unexported field or method") From 9e2fe959814d2ec4e3f6174899fab81cf5941ef6 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 14:26:03 -0700 Subject: [PATCH 36/48] Cleaning up reflect --- compiler/natives/src/reflect/makefunc.go | 33 ------------ compiler/natives/src/reflect/reflect.go | 67 ++++++++++++++++++++++-- compiler/natives/src/reflect/value.go | 39 ++------------ 3 files changed, 65 insertions(+), 74 deletions(-) delete mode 100644 compiler/natives/src/reflect/makefunc.go diff --git a/compiler/natives/src/reflect/makefunc.go b/compiler/natives/src/reflect/makefunc.go deleted file mode 100644 index 222d10fae..000000000 --- a/compiler/natives/src/reflect/makefunc.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build js - -package reflect - -import ( - "unsafe" - - "github.com/gopherjs/gopherjs/js" -) - -//gopherjs:replace -func makeMethodValue(op string, v Value) Value { - if v.flag&flagMethod == 0 { - panic("reflect: internal error: invalid use of makePartialFunc") - } - - _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr := v.object() - if v.typ().IsWrapped() { - rcvr = v.typ().JsType().New(rcvr) - } - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - return js.InternalObject(fn).Call("apply", rcvr, arguments) - }) - return Value{ - typ_: v.Type().common(), - ptr: unsafe.Pointer(fv.Unsafe()), - flag: v.flag.ro() | flag(Func), - } -} - -//gopherjs:purge -func makeFuncStub() diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 4b61f74a2..d5b37caf5 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -375,11 +375,6 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} } -//gopherjs:replace -func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { - abi.IfaceE2I(t, src, dst) -} - //gopherjs:replace func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { abi.TypedMemMove(t, dst, src) @@ -676,6 +671,68 @@ func Copy(dst, src Value) int { return js.Global.Call("$copySlice", dstVal, srcVal).Int() } +//gopherjs:replace +func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { + i := methodIndex + var prop string + if v.typ().Kind() == abi.Interface { + tt := (*interfaceType)(unsafe.Pointer(v.typ())) + if i < 0 || i >= len(tt.Methods) { + panic("reflect: internal error: invalid method index") + } + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { + panic("reflect: " + op + " of unexported method") + } + t = (*funcType)(unsafe.Pointer(tt.typeOff(m.Typ))) + prop = tt.NameOff(m.Name).Name() + } else { + rcvrtype = v.typ() + ms := v.typ().ExportedMethods() + if uint(i) >= uint(len(ms)) { + panic("reflect: internal error: invalid method index") + } + m := ms[i] + if !v.typ().NameOff(m.Name).IsExported() { + panic("reflect: " + op + " of unexported method") + } + t = (*funcType)(unsafe.Pointer(v.typ().TypeOff(m.Mtyp))) + prop = js.Global.Call("$methodSet", v.typ().JsType()).Index(i).Get("prop").String() + } + rcvr := v.object() + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) + } + fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) + return +} + +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + abi.IfaceE2I(t, src, dst) +} + +//gopherjs:replace +func makeMethodValue(op string, v Value) Value { + if v.flag&flagMethod == 0 { + panic("reflect: internal error: invalid use of makePartialFunc") + } + + _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) + rcvr := v.object() + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) + } + fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { + return js.InternalObject(fn).Call("apply", rcvr, arguments) + }) + return Value{ + typ_: v.Type().common(), + ptr: unsafe.Pointer(fv.Unsafe()), + flag: v.flag.ro() | flag(Func), + } +} + //gopherjs:replace func valueInterface(v Value, safe bool) any { if v.flag == 0 { diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index 946f81f82..fec451f42 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -24,42 +24,6 @@ func packEface(v Value) any //gopherjs:purge func unpackEface(i any) Value -//gopherjs:replace -func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { - i := methodIndex - var prop string - if v.typ().Kind() == abi.Interface { - tt := (*interfaceType)(unsafe.Pointer(v.typ())) - if i < 0 || i >= len(tt.Methods) { - panic("reflect: internal error: invalid method index") - } - m := &tt.Methods[i] - if !tt.NameOff(m.Name).IsExported() { - panic("reflect: " + op + " of unexported method") - } - t = (*funcType)(unsafe.Pointer(tt.typeOff(m.Typ))) - prop = tt.NameOff(m.Name).Name() - } else { - rcvrtype = v.typ() - ms := v.typ().ExportedMethods() - if uint(i) >= uint(len(ms)) { - panic("reflect: internal error: invalid method index") - } - m := ms[i] - if !v.typ().NameOff(m.Name).IsExported() { - panic("reflect: " + op + " of unexported method") - } - t = (*funcType)(unsafe.Pointer(v.typ().TypeOff(m.Mtyp))) - prop = js.Global.Call("$methodSet", v.typ().JsType()).Index(i).Get("prop").String() - } - rcvr := v.object() - if v.typ().IsWrapped() { - rcvr = v.typ().JsType().New(rcvr) - } - fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) - return -} - //gopherjs:purge func storeRcvr(v Value, p unsafe.Pointer) @@ -733,3 +697,6 @@ func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice func noescape(p unsafe.Pointer) unsafe.Pointer { return p } + +//gopherjs:purge +func makeFuncStub() From 5f5dd3bb64eaac2d16a58869755ec8e2c40e6e8c Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 14:30:16 -0700 Subject: [PATCH 37/48] Cleaning up reflect --- compiler/natives/src/reflect/reflect.go | 48 ++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index d5b37caf5..30d9d1b0a 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -707,6 +707,30 @@ func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t return } +//gopherjs:replace +func valueInterface(v Value, safe bool) any { + if v.flag == 0 { + panic(&ValueError{"reflect.Value.Interface", 0}) + } + if safe && v.flag&flagRO != 0 { + panic("reflect.Value.Interface: cannot return value obtained from unexported field or method") + } + if v.flag&flagMethod != 0 { + v = makeMethodValue("Interface", v) + } + + if v.typ().IsWrapped() { + jsTyp := v.typ().JsType() + if v.flag&flagIndir != 0 && v.Kind() == Struct { + cv := jsTyp.Call("zero") + abi.CopyStruct(cv, v.object(), v.typ()) + return any(unsafe.Pointer(jsTyp.New(cv).Unsafe())) + } + return any(unsafe.Pointer(jsTyp.New(v.object()).Unsafe())) + } + return any(unsafe.Pointer(v.object().Unsafe())) +} + //gopherjs:replace func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { abi.IfaceE2I(t, src, dst) @@ -733,30 +757,6 @@ func makeMethodValue(op string, v Value) Value { } } -//gopherjs:replace -func valueInterface(v Value, safe bool) any { - if v.flag == 0 { - panic(&ValueError{"reflect.Value.Interface", 0}) - } - if safe && v.flag&flagRO != 0 { - panic("reflect.Value.Interface: cannot return value obtained from unexported field or method") - } - if v.flag&flagMethod != 0 { - v = makeMethodValue("Interface", v) - } - - if v.typ().IsWrapped() { - jsTyp := v.typ().JsType() - if v.flag&flagIndir != 0 && v.Kind() == Struct { - cv := jsTyp.Call("zero") - abi.CopyStruct(cv, v.object(), v.typ()) - return any(unsafe.Pointer(jsTyp.New(cv).Unsafe())) - } - return any(unsafe.Pointer(jsTyp.New(v.object()).Unsafe())) - } - return any(unsafe.Pointer(v.object().Unsafe())) -} - //gopherjs:new func (t *rtype) pointers() bool { switch t.Kind() { From 79892af852e1f6d831ea2d920c2184a5fbcf3df8 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 14:42:48 -0700 Subject: [PATCH 38/48] Cleaning up reflect --- compiler/natives/src/reflect/reflect.go | 654 +++++++++++++++++++++++ compiler/natives/src/reflect/value.go | 655 ------------------------ 2 files changed, 654 insertions(+), 655 deletions(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 30d9d1b0a..7c3eb2064 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -3,6 +3,7 @@ package reflect import ( + "errors" "strconv" "unsafe" @@ -810,6 +811,659 @@ func (t *rtype) Method(i int) (m Method) { return m } +//gopherjs:new +func (v Value) object() *js.Object { + if v.typ().Kind() == abi.Array || v.typ().Kind() == abi.Struct { + return js.InternalObject(v.ptr) + } + jsTyp := v.typ().JsType() + if v.flag&flagIndir != 0 { + val := js.InternalObject(v.ptr).Call("$get") + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { + switch v.typ().Kind() { + case abi.Uint64, abi.Int64: + val = jsTyp.New(val.Get("$high"), val.Get("$low")) + case abi.Complex64, abi.Complex128: + val = jsTyp.New(val.Get("$real"), val.Get("$imag")) + case abi.Slice: + if val == val.Get("constructor").Get("nil") { + val = jsTyp.Get("nil") + break + } + newVal := jsTyp.New(val.Get("$array")) + newVal.Set("$offset", val.Get("$offset")) + newVal.Set("$length", val.Get("$length")) + newVal.Set("$capacity", val.Get("$capacity")) + val = newVal + } + } + return js.InternalObject(val.Unsafe()) + } + return js.InternalObject(v.ptr) +} + +//gopherjs:replace +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { + if v.flag&flagMethod != 0 { + v = makeMethodValue(context, v) + } + + switch { + case directlyAssignable(dst, v.typ()): + // Overwrite type so that they match. + // Same memory layout, so no harm done. + fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() + fl |= flag(dst.Kind()) + return Value{dst, v.ptr, fl} + + case implements(dst, v.typ()): + if target == nil { + target = unsafe_New(dst) + } + // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement + // from upstream. ifaceE2I below does not panic, and it needs + // to run, given its custom implementation. + x := valueInterface(v, false) + if dst.NumMethod() == 0 { + *(*any)(target) = x + } else { + ifaceE2I(dst, x, target) + } + return Value{dst, target, flagIndir | flag(Interface)} + } + + // Failed. + panic(context + ": value of type " + v.typ().String() + " is not assignable to type " + dst.String()) +} + +//gopherjs:new +var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) + +//gopherjs:replace +func (v Value) call(op string, in []Value) []Value { + var ( + t *funcType + fn unsafe.Pointer + rcvr *js.Object + ) + if v.flag&flagMethod != 0 { + _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) + rcvr = v.object() + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) + } + } else { + t = v.typ().FuncType() + fn = unsafe.Pointer(v.object().Unsafe()) + rcvr = js.Undefined + } + + if fn == nil { + panic("reflect.Value.Call: call of nil function") + } + + isSlice := op == "CallSlice" + n := t.NumIn() + if isSlice { + if !t.IsVariadic() { + panic("reflect: CallSlice of non-variadic function") + } + if len(in) < n { + panic("reflect: CallSlice with too few input arguments") + } + if len(in) > n { + panic("reflect: CallSlice with too many input arguments") + } + } else { + if t.IsVariadic() { + n-- + } + if len(in) < n { + panic("reflect: Call with too few input arguments") + } + if !t.IsVariadic() && len(in) > n { + panic("reflect: Call with too many input arguments") + } + } + for _, x := range in { + if x.Kind() == Invalid { + panic("reflect: " + op + " using zero Value argument") + } + } + for i := 0; i < n; i++ { + if xt, targ := in[i].Type(), toRType(t.In(i)); !xt.AssignableTo(targ) { + panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) + } + } + if !isSlice && t.IsVariadic() { + // prepare slice for remaining values + m := len(in) - n + slice := MakeSlice(toRType(t.In(n)), m, m) + elem := toRType(t.In(n).Elem()) + for i := 0; i < m; i++ { + x := in[n+i] + if xt := x.Type(); !xt.AssignableTo(elem) { + panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op) + } + slice.Index(i).Set(x) + } + origIn := in + in = make([]Value, n+1) + copy(in[:n], origIn) + in[n] = slice + } + + nin := len(in) + if nin != t.NumIn() { + panic("reflect.Value.Call: wrong argument count") + } + nout := t.NumOut() + + argsArray := js.Global.Get("Array").New(t.NumIn()) + for i, arg := range in { + argsArray.SetIndex(i, abi.UnwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i), nil).object())) + } + results := callHelper(js.InternalObject(fn), rcvr, argsArray) + + switch nout { + case 0: + return nil + case 1: + return []Value{makeValue(t.Out(0), abi.WrapJsObject(t.Out(0), results), 0)} + default: + ret := make([]Value, nout) + for i := range ret { + ret[i] = makeValue(t.Out(i), abi.WrapJsObject(t.Out(i), results.Index(i)), 0) + } + return ret + } +} + +//gopherjs:replace Used a pointer cast to get *rtype that doesn't work in JS. +func (v Value) Type() Type { + if v.flag != 0 && v.flag&flagMethod == 0 { + return toRType(v.typ_) + } + return v.typeSlow() +} + +//gopherjs:replace +func (v Value) Cap() int { + k := v.kind() + switch k { + case Array: + return v.typ().Len() + case Chan, Slice: + return v.object().Get("$capacity").Int() + case Ptr: + if v.typ().Elem().Kind() == abi.Array { + return v.typ().Elem().Len() + } + panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") + } + panic(&ValueError{"reflect.Value.Cap", k}) +} + +//gopherjs:replace +func (v Value) Elem() Value { + switch k := v.kind(); k { + case Interface: + val := v.object() + if val == js.Global.Get("$ifaceNil") { + return Value{} + } + typ := abi.ReflectType(val.Get("constructor")) + return makeValue(typ, val.Get("$val"), v.flag.ro()) + + case Ptr: + if v.IsNil() { + return Value{} + } + val := v.object() + tt := (*ptrType)(unsafe.Pointer(v.typ())) + fl := v.flag&flagRO | flagIndir | flagAddr + fl |= flag(tt.Elem.Kind()) + return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} + + default: + panic(&ValueError{"reflect.Value.Elem", k}) + } +} + +//gopherjs:replace +func (v Value) Field(i int) Value { + if v.kind() != Struct { + panic(&ValueError{"reflect.Value.Field", v.kind()}) + } + tt := (*structType)(unsafe.Pointer(v.typ())) + if uint(i) >= uint(len(tt.Fields)) { + panic("reflect: Field index out of range") + } + + prop := v.typ().JsType().Get("fields").Index(i).Get("prop").String() + field := &tt.Fields[i] + typ := field.Typ + + fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) + if !field.Name.IsExported() { + if field.Embedded() { + fl |= flagEmbedRO + } else { + fl |= flagStickyRO + } + } + + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { + if jsTag := abi.GetJsTag(tag); jsTag != "" { + for { + v = v.Field(0) + if v.typ() == abi.JsObjectPtr { + o := v.object().Get("object") + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), + ).Unsafe()), fl} + } + if v.typ().Kind() == abi.Pointer { + v = v.Elem() + } + } + } + } + + s := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) +} + +//gopherjs:replace +func (v Value) UnsafePointer() unsafe.Pointer { + return unsafe.Pointer(v.Pointer()) +} + +//gopherjs:replace +func (v Value) grow(n int) { + if n < 0 { + panic(`reflect.Value.Grow: negative len`) + } + + s := v.object() + len := s.Get(`$length`).Int() + if len+n < 0 { + panic(`reflect.Value.Grow: slice overflow`) + } + + cap := s.Get(`$capacity`).Int() + if len+n > cap { + ns := js.Global.Call("$growSlice", s, len+n) + js.InternalObject(v.ptr).Call("$set", ns) + } +} + +// extendSlice is used by native reflect.Append and reflect.AppendSlice +// Overridden to avoid the use of `unsafeheader.Slice` since GopherJS +// uses different slice implementation. +// +//gopherjs:replace +func (v Value) extendSlice(n int) Value { + v.mustBeExported() + v.mustBe(Slice) + + s := v.object() + sNil := v.typ().JsType().Get(`nil`) + fl := flagIndir | flag(Slice) + if s == sNil && n <= 0 { + return makeValue(v.typ(), abi.WrapJsObject(v.typ(), sNil), fl) + } + + newSlice := v.typ().JsType().New(s.Get("$array")) + newSlice.Set("$offset", s.Get("$offset")) + newSlice.Set("$length", s.Get("$length")) + newSlice.Set("$capacity", s.Get("$capacity")) + + v2 := makeValue(v.typ(), abi.WrapJsObject(v.typ(), newSlice), fl) + v2.grow(n) + s2 := v2.object() + s2.Set(`$length`, s2.Get(`$length`).Int()+n) + return v2 +} + +//gopherjs:purge +func mapclear(t *abi.Type, m unsafe.Pointer) + +//gopherjs:purge +func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) + +// TODO(grantnelson-wf): Make sure this is tested since it is new. +// +//gopherjs:replace +func (v Value) Clear() { + switch v.Kind() { + case Slice: + st := (*sliceType)(unsafe.Pointer(v.typ())) + elem := st.Elem + zeroFn := elem.JsType().Get("zero") + a := js.InternalObject(v.ptr) + offset := a.Get("$offset").Int() + length := a.Get("$length").Int() + for i := 0; i < length; i++ { + a.SetIndex(i+offset, zeroFn.Invoke()) + } + // case Map: + // TODO(grantnelson-wf): Finish implementing + // mapclear(v.typ(), v.pointer()) + default: + panic(&ValueError{"reflect.Value.Clear", v.Kind()}) + } +} + +//gopherjs:replace +func (v Value) Index(i int) Value { + switch k := v.kind(); k { + case Array: + tt := v.typ().ArrayType() + if i < 0 || i > int(tt.Len) { + panic("reflect: array index out of range") + } + typ := tt.Elem + fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) + + a := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) + + case Slice: + s := v.object() + if i < 0 || i >= s.Get("$length").Int() { + panic("reflect: slice index out of range") + } + tt := (*sliceType)(unsafe.Pointer(v.typ())) + typ := tt.Elem + fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) + + i += s.Get("$offset").Int() + a := s.Get("$array") + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) + + case String: + str := *(*string)(v.ptr) + if i < 0 || i >= len(str) { + panic("reflect: string index out of range") + } + fl := v.flag.ro() | flag(Uint8) | flagIndir + c := str[i] + return Value{uint8Type, unsafe.Pointer(&c), fl} + + default: + panic(&ValueError{"reflect.Value.Index", k}) + } +} + +//gopherjs:replace +func (v Value) InterfaceData() [2]uintptr { + panic(errors.New("InterfaceData is not supported by GopherJS")) +} + +//gopherjs:replace +func (v Value) SetZero() { + v.mustBeAssignable() + v.Set(Zero(toRType(v.typ()))) +} + +//gopherjs:replace +func (v Value) IsNil() bool { + switch k := v.kind(); k { + case Ptr, Slice: + return v.object() == v.typ().JsType().Get("nil") + case Chan: + return v.object() == js.Global.Get("$chanNil") + case Func: + return v.object() == js.Global.Get("$throwNilPointerError") + case Map: + return v.object() == js.InternalObject(false) + case Interface: + return v.object() == js.Global.Get("$ifaceNil") + case UnsafePointer: + return v.object().Unsafe() == 0 + default: + panic(&ValueError{"reflect.Value.IsNil", k}) + } +} + +//gopherjs:replace +func (v Value) Len() int { + switch k := v.kind(); k { + case Array, String: + return v.object().Length() + case Slice: + return v.object().Get("$length").Int() + case Chan: + return v.object().Get("$buffer").Get("length").Int() + case Map: + return v.object().Get("size").Int() + case Ptr: + if elem := v.typ().Elem(); elem.Kind() == abi.Array { + return elem.Len() + } + panic("reflect: call of reflect.Value.Len on ptr to non-array Value") + default: + panic(&ValueError{"reflect.Value.Len", k}) + } +} + +//gopherjs:purge Not used since Len() is overridden. +func (v Value) lenNonSlice() int + +//gopherjs:replace +func (v Value) Pointer() uintptr { + switch k := v.kind(); k { + case Chan, Map, Ptr, UnsafePointer: + if v.IsNil() { + return 0 + } + return v.object().Unsafe() + case Func: + if v.IsNil() { + return 0 + } + return 1 + case Slice: + if v.IsNil() { + return 0 + } + return v.object().Get("$array").Unsafe() + default: + panic(&ValueError{"reflect.Value.Pointer", k}) + } +} + +//gopherjs:replace +func (v Value) Set(x Value) { + v.mustBeAssignable() + x.mustBeExported() + x = x.assignTo("reflect.Set", v.typ(), nil) + if v.flag&flagIndir != 0 { + switch v.typ().Kind() { + case abi.Array, abi.Struct: + v.typ().JsType().Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) + case abi.Interface: + js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) + default: + js.InternalObject(v.ptr).Call("$set", x.object()) + } + return + } + v.ptr = x.ptr +} + +//gopherjs:replace +func (v Value) bytesSlow() []byte { + switch v.kind() { + case Slice: + if v.typ().Elem().Kind() != abi.Uint8 { + panic("reflect.Value.Bytes of non-byte slice") + } + return *(*[]byte)(v.ptr) + case Array: + if v.typ().Elem().Kind() != abi.Uint8 { + panic("reflect.Value.Bytes of non-byte array") + } + if !v.CanAddr() { + panic("reflect.Value.Bytes of unaddressable byte array") + } + // GOPHERJS: Replace the following with JS to avoid using unsafe pointers. + // p := (*byte)(v.ptr) + // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) + // return unsafe.Slice(p, n) + return js.InternalObject(v.ptr).Interface().([]byte) + } + panic(&ValueError{"reflect.Value.Bytes", v.kind()}) +} + +//gopherjs:replace +func (v Value) SetBytes(x []byte) { + v.mustBeAssignable() + v.mustBe(Slice) + if v.typ().Elem().Kind() != abi.Uint8 { + panic("reflect.Value.SetBytes of non-byte slice") + } + slice := js.InternalObject(x) + if toRType(v.typ()).Name() != "" || toRType(v.typ()).Elem().Name() != "" { + typedSlice := v.typ().JsType().New(slice.Get("$array")) + typedSlice.Set("$offset", slice.Get("$offset")) + typedSlice.Set("$length", slice.Get("$length")) + typedSlice.Set("$capacity", slice.Get("$capacity")) + slice = typedSlice + } + js.InternalObject(v.ptr).Call("$set", slice) +} + +//gopherjs:replace +func (v Value) SetCap(n int) { + v.mustBeAssignable() + v.mustBe(Slice) + s := js.InternalObject(v.ptr).Call("$get") + if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { + panic("reflect: slice capacity out of range in SetCap") + } + newSlice := v.typ().JsType().New(s.Get("$array")) + newSlice.Set("$offset", s.Get("$offset")) + newSlice.Set("$length", s.Get("$length")) + newSlice.Set("$capacity", n) + js.InternalObject(v.ptr).Call("$set", newSlice) +} + +//gopherjs:replace +func (v Value) SetLen(n int) { + v.mustBeAssignable() + v.mustBe(Slice) + s := js.InternalObject(v.ptr).Call("$get") + if n < 0 || n > s.Get("$capacity").Int() { + panic("reflect: slice length out of range in SetLen") + } + newSlice := v.typ().JsType().New(s.Get("$array")) + newSlice.Set("$offset", s.Get("$offset")) + newSlice.Set("$length", n) + newSlice.Set("$capacity", s.Get("$capacity")) + js.InternalObject(v.ptr).Call("$set", newSlice) +} + +//gopherjs:replace +func (v Value) Slice(i, j int) Value { + var ( + cap int + typ *abi.Type + s *js.Object + ) + switch kind := v.kind(); kind { + case Array: + if v.flag&flagAddr == 0 { + panic("reflect.Value.Slice: slice of unaddressable array") + } + tt := v.typ().ArrayType() + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)).common() + s = typ.JsType().New(v.object()) + + case Slice: + typ = v.typ() + s = v.object() + cap = s.Get("$capacity").Int() + + case String: + str := *(*string)(v.ptr) + if i < 0 || j < i || j > len(str) { + panic("reflect.Value.Slice: string slice index out of bounds") + } + return ValueOf(str[i:j]) + + default: + panic(&ValueError{"reflect.Value.Slice", kind}) + } + + if i < 0 || j < i || j > cap { + panic("reflect.Value.Slice: slice index out of bounds") + } + + return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) +} + +//gopherjs:replace +func (v Value) Slice3(i, j, k int) Value { + var ( + cap int + typ *abi.Type + s *js.Object + ) + switch kind := v.kind(); kind { + case Array: + if v.flag&flagAddr == 0 { + panic("reflect.Value.Slice: slice of unaddressable array") + } + tt := v.typ().ArrayType() + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)).common() + s = typ.JsType().New(v.object()) + + case Slice: + typ = v.typ() + s = v.object() + cap = s.Get("$capacity").Int() + + default: + panic(&ValueError{"reflect.Value.Slice3", kind}) + } + + if i < 0 || j < i || k < j || k > cap { + panic("reflect.Value.Slice3: slice index out of bounds") + } + + return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) +} + +//gopherjs:replace +func (v Value) Close() { + v.mustBe(Chan) + v.mustBeExported() + js.Global.Call("$close", v.object()) +} + //gopherjs:new var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go index fec451f42..5e92af8da 100644 --- a/compiler/natives/src/reflect/value.go +++ b/compiler/natives/src/reflect/value.go @@ -3,13 +3,11 @@ package reflect import ( - "errors" "unsafe" "internal/abi" "github.com/gopherjs/gopherjs/compiler/natives/src/internal/unsafeheader" - "github.com/gopherjs/gopherjs/js" ) //gopherjs:purge This is the header for an any interface and invalid for GopherJS. @@ -30,659 +28,6 @@ func storeRcvr(v Value, p unsafe.Pointer) //gopherjs:purge func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) -//gopherjs:new -func (v Value) object() *js.Object { - if v.typ().Kind() == abi.Array || v.typ().Kind() == abi.Struct { - return js.InternalObject(v.ptr) - } - jsTyp := v.typ().JsType() - if v.flag&flagIndir != 0 { - val := js.InternalObject(v.ptr).Call("$get") - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { - switch v.typ().Kind() { - case abi.Uint64, abi.Int64: - val = jsTyp.New(val.Get("$high"), val.Get("$low")) - case abi.Complex64, abi.Complex128: - val = jsTyp.New(val.Get("$real"), val.Get("$imag")) - case abi.Slice: - if val == val.Get("constructor").Get("nil") { - val = jsTyp.Get("nil") - break - } - newVal := jsTyp.New(val.Get("$array")) - newVal.Set("$offset", val.Get("$offset")) - newVal.Set("$length", val.Get("$length")) - newVal.Set("$capacity", val.Get("$capacity")) - val = newVal - } - } - return js.InternalObject(val.Unsafe()) - } - return js.InternalObject(v.ptr) -} - -//gopherjs:replace -func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { - if v.flag&flagMethod != 0 { - v = makeMethodValue(context, v) - } - - switch { - case directlyAssignable(dst, v.typ()): - // Overwrite type so that they match. - // Same memory layout, so no harm done. - fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() - fl |= flag(dst.Kind()) - return Value{dst, v.ptr, fl} - - case implements(dst, v.typ()): - if target == nil { - target = unsafe_New(dst) - } - // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement - // from upstream. ifaceE2I below does not panic, and it needs - // to run, given its custom implementation. - x := valueInterface(v, false) - if dst.NumMethod() == 0 { - *(*any)(target) = x - } else { - ifaceE2I(dst, x, target) - } - return Value{dst, target, flagIndir | flag(Interface)} - } - - // Failed. - panic(context + ": value of type " + v.typ().String() + " is not assignable to type " + dst.String()) -} - -//gopherjs:new -var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) - -//gopherjs:replace -func (v Value) call(op string, in []Value) []Value { - var ( - t *funcType - fn unsafe.Pointer - rcvr *js.Object - ) - if v.flag&flagMethod != 0 { - _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr = v.object() - if v.typ().IsWrapped() { - rcvr = v.typ().JsType().New(rcvr) - } - } else { - t = v.typ().FuncType() - fn = unsafe.Pointer(v.object().Unsafe()) - rcvr = js.Undefined - } - - if fn == nil { - panic("reflect.Value.Call: call of nil function") - } - - isSlice := op == "CallSlice" - n := t.NumIn() - if isSlice { - if !t.IsVariadic() { - panic("reflect: CallSlice of non-variadic function") - } - if len(in) < n { - panic("reflect: CallSlice with too few input arguments") - } - if len(in) > n { - panic("reflect: CallSlice with too many input arguments") - } - } else { - if t.IsVariadic() { - n-- - } - if len(in) < n { - panic("reflect: Call with too few input arguments") - } - if !t.IsVariadic() && len(in) > n { - panic("reflect: Call with too many input arguments") - } - } - for _, x := range in { - if x.Kind() == Invalid { - panic("reflect: " + op + " using zero Value argument") - } - } - for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), toRType(t.In(i)); !xt.AssignableTo(targ) { - panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) - } - } - if !isSlice && t.IsVariadic() { - // prepare slice for remaining values - m := len(in) - n - slice := MakeSlice(toRType(t.In(n)), m, m) - elem := toRType(t.In(n).Elem()) - for i := 0; i < m; i++ { - x := in[n+i] - if xt := x.Type(); !xt.AssignableTo(elem) { - panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op) - } - slice.Index(i).Set(x) - } - origIn := in - in = make([]Value, n+1) - copy(in[:n], origIn) - in[n] = slice - } - - nin := len(in) - if nin != t.NumIn() { - panic("reflect.Value.Call: wrong argument count") - } - nout := t.NumOut() - - argsArray := js.Global.Get("Array").New(t.NumIn()) - for i, arg := range in { - argsArray.SetIndex(i, abi.UnwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i), nil).object())) - } - results := callHelper(js.InternalObject(fn), rcvr, argsArray) - - switch nout { - case 0: - return nil - case 1: - return []Value{makeValue(t.Out(0), abi.WrapJsObject(t.Out(0), results), 0)} - default: - ret := make([]Value, nout) - for i := range ret { - ret[i] = makeValue(t.Out(i), abi.WrapJsObject(t.Out(i), results.Index(i)), 0) - } - return ret - } -} - -//gopherjs:replace Used a pointer cast to get *rtype that doesn't work in JS. -func (v Value) Type() Type { - if v.flag != 0 && v.flag&flagMethod == 0 { - return toRType(v.typ_) - } - return v.typeSlow() -} - -//gopherjs:replace -func (v Value) Cap() int { - k := v.kind() - switch k { - case Array: - return v.typ().Len() - case Chan, Slice: - return v.object().Get("$capacity").Int() - case Ptr: - if v.typ().Elem().Kind() == abi.Array { - return v.typ().Elem().Len() - } - panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") - } - panic(&ValueError{"reflect.Value.Cap", k}) -} - -//gopherjs:replace -func (v Value) Elem() Value { - switch k := v.kind(); k { - case Interface: - val := v.object() - if val == js.Global.Get("$ifaceNil") { - return Value{} - } - typ := abi.ReflectType(val.Get("constructor")) - return makeValue(typ, val.Get("$val"), v.flag.ro()) - - case Ptr: - if v.IsNil() { - return Value{} - } - val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ())) - fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.Elem.Kind()) - return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} - - default: - panic(&ValueError{"reflect.Value.Elem", k}) - } -} - -//gopherjs:replace -func (v Value) Field(i int) Value { - if v.kind() != Struct { - panic(&ValueError{"reflect.Value.Field", v.kind()}) - } - tt := (*structType)(unsafe.Pointer(v.typ())) - if uint(i) >= uint(len(tt.Fields)) { - panic("reflect: Field index out of range") - } - - prop := v.typ().JsType().Get("fields").Index(i).Get("prop").String() - field := &tt.Fields[i] - typ := field.Typ - - fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.Name.IsExported() { - if field.Embedded() { - fl |= flagEmbedRO - } else { - fl |= flagStickyRO - } - } - - if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { - if jsTag := abi.GetJsTag(tag); jsTag != "" { - for { - v = v.Field(0) - if v.typ() == abi.JsObjectPtr { - o := v.object().Get("object") - return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), - ).Unsafe()), fl} - } - if v.typ().Kind() == abi.Pointer { - v = v.Elem() - } - } - } - } - - s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) -} - -//gopherjs:replace -func (v Value) UnsafePointer() unsafe.Pointer { - return unsafe.Pointer(v.Pointer()) -} - -//gopherjs:replace -func (v Value) grow(n int) { - if n < 0 { - panic(`reflect.Value.Grow: negative len`) - } - - s := v.object() - len := s.Get(`$length`).Int() - if len+n < 0 { - panic(`reflect.Value.Grow: slice overflow`) - } - - cap := s.Get(`$capacity`).Int() - if len+n > cap { - ns := js.Global.Call("$growSlice", s, len+n) - js.InternalObject(v.ptr).Call("$set", ns) - } -} - -// extendSlice is used by native reflect.Append and reflect.AppendSlice -// Overridden to avoid the use of `unsafeheader.Slice` since GopherJS -// uses different slice implementation. -// -//gopherjs:replace -func (v Value) extendSlice(n int) Value { - v.mustBeExported() - v.mustBe(Slice) - - s := v.object() - sNil := v.typ().JsType().Get(`nil`) - fl := flagIndir | flag(Slice) - if s == sNil && n <= 0 { - return makeValue(v.typ(), abi.WrapJsObject(v.typ(), sNil), fl) - } - - newSlice := v.typ().JsType().New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", s.Get("$length")) - newSlice.Set("$capacity", s.Get("$capacity")) - - v2 := makeValue(v.typ(), abi.WrapJsObject(v.typ(), newSlice), fl) - v2.grow(n) - s2 := v2.object() - s2.Set(`$length`, s2.Get(`$length`).Int()+n) - return v2 -} - -//gopherjs:purge -func mapclear(t *abi.Type, m unsafe.Pointer) - -//gopherjs:purge -func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) - -// TODO(grantnelson-wf): Make sure this is tested since it is new. -// -//gopherjs:replace -func (v Value) Clear() { - switch v.Kind() { - case Slice: - st := (*sliceType)(unsafe.Pointer(v.typ())) - elem := st.Elem - zeroFn := elem.JsType().Get("zero") - a := js.InternalObject(v.ptr) - offset := a.Get("$offset").Int() - length := a.Get("$length").Int() - for i := 0; i < length; i++ { - a.SetIndex(i+offset, zeroFn.Invoke()) - } - // case Map: - // TODO(grantnelson-wf): Finish implementing - // mapclear(v.typ(), v.pointer()) - default: - panic(&ValueError{"reflect.Value.Clear", v.Kind()}) - } -} - -//gopherjs:replace -func (v Value) Index(i int) Value { - switch k := v.kind(); k { - case Array: - tt := v.typ().ArrayType() - if i < 0 || i > int(tt.Len) { - panic("reflect: array index out of range") - } - typ := tt.Elem - fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) - - a := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) - - case Slice: - s := v.object() - if i < 0 || i >= s.Get("$length").Int() { - panic("reflect: slice index out of range") - } - tt := (*sliceType)(unsafe.Pointer(v.typ())) - typ := tt.Elem - fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) - - i += s.Get("$offset").Int() - a := s.Get("$array") - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) - - case String: - str := *(*string)(v.ptr) - if i < 0 || i >= len(str) { - panic("reflect: string index out of range") - } - fl := v.flag.ro() | flag(Uint8) | flagIndir - c := str[i] - return Value{uint8Type, unsafe.Pointer(&c), fl} - - default: - panic(&ValueError{"reflect.Value.Index", k}) - } -} - -//gopherjs:replace -func (v Value) InterfaceData() [2]uintptr { - panic(errors.New("InterfaceData is not supported by GopherJS")) -} - -//gopherjs:replace -func (v Value) SetZero() { - v.mustBeAssignable() - v.Set(Zero(toRType(v.typ()))) -} - -//gopherjs:replace -func (v Value) IsNil() bool { - switch k := v.kind(); k { - case Ptr, Slice: - return v.object() == v.typ().JsType().Get("nil") - case Chan: - return v.object() == js.Global.Get("$chanNil") - case Func: - return v.object() == js.Global.Get("$throwNilPointerError") - case Map: - return v.object() == js.InternalObject(false) - case Interface: - return v.object() == js.Global.Get("$ifaceNil") - case UnsafePointer: - return v.object().Unsafe() == 0 - default: - panic(&ValueError{"reflect.Value.IsNil", k}) - } -} - -//gopherjs:replace -func (v Value) Len() int { - switch k := v.kind(); k { - case Array, String: - return v.object().Length() - case Slice: - return v.object().Get("$length").Int() - case Chan: - return v.object().Get("$buffer").Get("length").Int() - case Map: - return v.object().Get("size").Int() - case Ptr: - if elem := v.typ().Elem(); elem.Kind() == abi.Array { - return elem.Len() - } - panic("reflect: call of reflect.Value.Len on ptr to non-array Value") - default: - panic(&ValueError{"reflect.Value.Len", k}) - } -} - -//gopherjs:purge Not used since Len() is overridden. -func (v Value) lenNonSlice() int - -//gopherjs:replace -func (v Value) Pointer() uintptr { - switch k := v.kind(); k { - case Chan, Map, Ptr, UnsafePointer: - if v.IsNil() { - return 0 - } - return v.object().Unsafe() - case Func: - if v.IsNil() { - return 0 - } - return 1 - case Slice: - if v.IsNil() { - return 0 - } - return v.object().Get("$array").Unsafe() - default: - panic(&ValueError{"reflect.Value.Pointer", k}) - } -} - -//gopherjs:replace -func (v Value) Set(x Value) { - v.mustBeAssignable() - x.mustBeExported() - x = x.assignTo("reflect.Set", v.typ(), nil) - if v.flag&flagIndir != 0 { - switch v.typ().Kind() { - case abi.Array, abi.Struct: - v.typ().JsType().Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) - case abi.Interface: - js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) - default: - js.InternalObject(v.ptr).Call("$set", x.object()) - } - return - } - v.ptr = x.ptr -} - -//gopherjs:replace -func (v Value) bytesSlow() []byte { - switch v.kind() { - case Slice: - if v.typ().Elem().Kind() != abi.Uint8 { - panic("reflect.Value.Bytes of non-byte slice") - } - return *(*[]byte)(v.ptr) - case Array: - if v.typ().Elem().Kind() != abi.Uint8 { - panic("reflect.Value.Bytes of non-byte array") - } - if !v.CanAddr() { - panic("reflect.Value.Bytes of unaddressable byte array") - } - // GOPHERJS: Replace the following with JS to avoid using unsafe pointers. - // p := (*byte)(v.ptr) - // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) - // return unsafe.Slice(p, n) - return js.InternalObject(v.ptr).Interface().([]byte) - } - panic(&ValueError{"reflect.Value.Bytes", v.kind()}) -} - -//gopherjs:replace -func (v Value) SetBytes(x []byte) { - v.mustBeAssignable() - v.mustBe(Slice) - if v.typ().Elem().Kind() != abi.Uint8 { - panic("reflect.Value.SetBytes of non-byte slice") - } - slice := js.InternalObject(x) - if toRType(v.typ()).Name() != "" || toRType(v.typ()).Elem().Name() != "" { - typedSlice := v.typ().JsType().New(slice.Get("$array")) - typedSlice.Set("$offset", slice.Get("$offset")) - typedSlice.Set("$length", slice.Get("$length")) - typedSlice.Set("$capacity", slice.Get("$capacity")) - slice = typedSlice - } - js.InternalObject(v.ptr).Call("$set", slice) -} - -//gopherjs:replace -func (v Value) SetCap(n int) { - v.mustBeAssignable() - v.mustBe(Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { - panic("reflect: slice capacity out of range in SetCap") - } - newSlice := v.typ().JsType().New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", s.Get("$length")) - newSlice.Set("$capacity", n) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -//gopherjs:replace -func (v Value) SetLen(n int) { - v.mustBeAssignable() - v.mustBe(Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < 0 || n > s.Get("$capacity").Int() { - panic("reflect: slice length out of range in SetLen") - } - newSlice := v.typ().JsType().New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", n) - newSlice.Set("$capacity", s.Get("$capacity")) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -//gopherjs:replace -func (v Value) Slice(i, j int) Value { - var ( - cap int - typ *abi.Type - s *js.Object - ) - switch kind := v.kind(); kind { - case Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := v.typ().ArrayType() - cap = int(tt.Len) - typ = SliceOf(toRType(tt.Elem)).common() - s = typ.JsType().New(v.object()) - - case Slice: - typ = v.typ() - s = v.object() - cap = s.Get("$capacity").Int() - - case String: - str := *(*string)(v.ptr) - if i < 0 || j < i || j > len(str) { - panic("reflect.Value.Slice: string slice index out of bounds") - } - return ValueOf(str[i:j]) - - default: - panic(&ValueError{"reflect.Value.Slice", kind}) - } - - if i < 0 || j < i || j > cap { - panic("reflect.Value.Slice: slice index out of bounds") - } - - return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) -} - -//gopherjs:replace -func (v Value) Slice3(i, j, k int) Value { - var ( - cap int - typ *abi.Type - s *js.Object - ) - switch kind := v.kind(); kind { - case Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := v.typ().ArrayType() - cap = int(tt.Len) - typ = SliceOf(toRType(tt.Elem)).common() - s = typ.JsType().New(v.object()) - - case Slice: - typ = v.typ() - s = v.object() - cap = s.Get("$capacity").Int() - - default: - panic(&ValueError{"reflect.Value.Slice3", kind}) - } - - if i < 0 || j < i || k < j || k > cap { - panic("reflect.Value.Slice3: slice index out of bounds") - } - - return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) -} - -//gopherjs:replace -func (v Value) Close() { - v.mustBe(Chan) - v.mustBeExported() - js.Global.Call("$close", v.object()) -} - // typedslicecopy is implemented in prelude.js as $copySlice // //gopherjs:purge From ad1e462a5280ca64850a0a4131da8b44ec222174 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 18 Feb 2026 14:55:53 -0700 Subject: [PATCH 39/48] Cleaning up reflect --- compiler/natives/src/reflect/reflect.go | 131 ++++++++++++++++++++++-- compiler/natives/src/reflect/type.go | 91 ---------------- compiler/natives/src/reflect/value.go | 47 --------- 3 files changed, 125 insertions(+), 144 deletions(-) delete mode 100644 compiler/natives/src/reflect/type.go delete mode 100644 compiler/natives/src/reflect/value.go diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 7c3eb2064..caba2f127 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -10,6 +10,7 @@ import ( "internal/abi" "internal/itoa" + "github.com/gopherjs/gopherjs/compiler/natives/src/internal/unsafeheader" "github.com/gopherjs/gopherjs/js" ) @@ -70,6 +71,16 @@ func toRType(t *abi.Type) *rtype { return rtyp } +//gopherjs:replace +func (t *rtype) String() string { + return toAbiType(t).String() +} + +//gopherjs:replace +func rtypeOf(i any) *abi.Type { + return abi.ReflectType(js.InternalObject(i).Get("constructor")) +} + //gopherjs:purge func addReflectOff(ptr unsafe.Pointer) int32 @@ -788,7 +799,7 @@ func (t *rtype) Method(i int) (m Method) { m.Name = pname.Name() fl := flag(Func) mtyp := t.typeOff(p.Mtyp) - ft := mtyp.FuncType() + ft := (*funcType)(unsafe.Pointer(mtyp)) in := make([]Type, 0, 1+ft.NumIn()) in = append(in, t) for _, arg := range ft.InSlice() { @@ -893,7 +904,7 @@ func (v Value) call(op string, in []Value) []Value { rcvr = v.typ().JsType().New(rcvr) } } else { - t = v.typ().FuncType() + t = (*funcType)(unsafe.Pointer(v.typ)) fn = unsafe.Pointer(v.object().Unsafe()) rcvr = js.Undefined } @@ -979,7 +990,7 @@ func (v Value) call(op string, in []Value) []Value { } } -//gopherjs:replace Used a pointer cast to get *rtype that doesn't work in JS. +//gopherjs:replace func (v Value) Type() Type { if v.flag != 0 && v.flag&flagMethod == 0 { return toRType(v.typ_) @@ -1327,7 +1338,7 @@ func (v Value) bytesSlow() []byte { if !v.CanAddr() { panic("reflect.Value.Bytes of unaddressable byte array") } - // GOPHERJS: Replace the following with JS to avoid using unsafe pointers. + // Replace the following with JS to avoid using unsafe pointers. // p := (*byte)(v.ptr) // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) // return unsafe.Slice(p, n) @@ -1396,7 +1407,7 @@ func (v Value) Slice(i, j int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := v.typ().ArrayType() + tt := (*arrayType)(unsafe.Pointer(v.typ)) cap = int(tt.Len) typ = SliceOf(toRType(tt.Elem)).common() s = typ.JsType().New(v.object()) @@ -1436,7 +1447,7 @@ func (v Value) Slice3(i, j, k int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := v.typ().ArrayType() + tt := (*arrayType)(unsafe.Pointer(v.typ)) cap = int(tt.Len) typ = SliceOf(toRType(tt.Elem)).common() s = typ.JsType().New(v.object()) @@ -1648,3 +1659,111 @@ func verifyNotInHeapPtr(p uintptr) bool { // always return true. return true } + +// typedslicecopy is implemented in prelude.js as $copySlice +// +//gopherjs:purge +func typedslicecopy(t *abi.Type, dst, src unsafeheader.Slice) int + +// growslice is implemented in prelude.js as $growSlice. +// +//gopherjs:purge +func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice + +//gopherjs:purge This is the header for an any interface and invalid for GopherJS. +type emptyInterface struct{} + +//gopherjs:purge This is the header for an interface value with methods and invalid for GopherJS. +type nonEmptyInterface struct{} + +//gopherjs:purge +func packEface(v Value) any + +//gopherjs:purge +func unpackEface(i any) Value + +//gopherjs:purge +func storeRcvr(v Value, p unsafe.Pointer) + +//gopherjs:purge +func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) + +// gopherjs:replace +func noescape(p unsafe.Pointer) unsafe.Pointer { + return p +} + +//gopherjs:purge +func makeFuncStub() + +//gopherjs:purge Unused type +type common struct{} + +//gopherjs:purge Used in original MapOf and not used in override MapOf by GopherJS +func bucketOf(ktyp, etyp *abi.Type) *abi.Type + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func (t *rtype) gcSlice(begin, end uintptr) []byte + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func appendGCProg(dst []byte, typ *abi.Type) []byte + +// toKindTypeExt will be automatically called when a cast to one of the +// extended kind types is performed. +// +// This is similar to `kindType` except that the reflect package has +// extended several of the kind types to have additional methods added to them. +// To get access to those methods, the `kindTypeExt` is checked or created. +// The automatic cast is handled in compiler/expressions.go +// +// gopherjs:new +func toKindTypeExt(src any) *js.Object { + var abiTyp *abi.Type + switch t := src.(type) { + case *rtype: + abiTyp = t.common() + case Type: + abiTyp = toAbiType(t) + case *abi.Type: + abiTyp = t + default: + panic(`unexpected type in toKindTypeExt`) + } + + const ( + idKindType = `kindType` + idKindTypeExt = `kindTypeExt` + ) + // Check if a kindTypeExt has already been created for this type. + ext := js.InternalObject(abiTyp).Get(idKindTypeExt) + if ext != js.Undefined { + return ext + } + + // Constructe a new kindTypeExt for this type. + kindType := js.InternalObject(abiTyp).Get(idKindType) + switch abiTyp.Kind() { + case abi.Interface: + ext = js.InternalObject(&interfaceType{}) + ext.Set(`InterfaceType`, js.InternalObject(kindType)) + case abi.Map: + ext = js.InternalObject(&mapType{}) + ext.Set(`MapType`, js.InternalObject(kindType)) + case abi.Pointer: + ext = js.InternalObject(&ptrType{}) + ext.Set(`PtrType`, js.InternalObject(kindType)) + case abi.Slice: + ext = js.InternalObject(&sliceType{}) + ext.Set(`SliceType`, js.InternalObject(kindType)) + case abi.Struct: + ext = js.InternalObject(&structType{}) + ext.Set(`StructType`, js.InternalObject(kindType)) + default: + panic(`unexpected kind in toKindTypeExt`) + } + js.InternalObject(abiTyp).Set(idKindTypeExt, ext) + return ext +} diff --git a/compiler/natives/src/reflect/type.go b/compiler/natives/src/reflect/type.go deleted file mode 100644 index 09f657b60..000000000 --- a/compiler/natives/src/reflect/type.go +++ /dev/null @@ -1,91 +0,0 @@ -//go:build js - -package reflect - -import ( - "internal/abi" - - "github.com/gopherjs/gopherjs/js" -) - -//gopherjs:replace -func (t *rtype) String() string { - return toAbiType(t).String() -} - -//gopherjs:replace -func rtypeOf(i any) *abi.Type { - return abi.ReflectType(js.InternalObject(i).Get("constructor")) -} - -//gopherjs:purge Unused type -type common struct{} - -//gopherjs:purge Used in original MapOf and not used in override MapOf by GopherJS -func bucketOf(ktyp, etyp *abi.Type) *abi.Type - -//gopherjs:purge Relates to GC programs not valid for GopherJS -func (t *rtype) gcSlice(begin, end uintptr) []byte - -//gopherjs:purge Relates to GC programs not valid for GopherJS -func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) - -//gopherjs:purge Relates to GC programs not valid for GopherJS -func appendGCProg(dst []byte, typ *abi.Type) []byte - -// toKindTypeExt will be automatically called when a cast to one of the -// extended kind types is performed. -// -// This is similar to `kindType` except that the reflect package has -// extended several of the kind types to have additional methods added to them. -// To get access to those methods, the `kindTypeExt` is checked or created. -// The automatic cast is handled in compiler/expressions.go -// -// gopherjs:new -func toKindTypeExt(src any) *js.Object { - var abiTyp *abi.Type - switch t := src.(type) { - case *rtype: - abiTyp = t.common() - case Type: - abiTyp = toAbiType(t) - case *abi.Type: - abiTyp = t - default: - panic(`unexpected type in toKindTypeExt`) - } - - const ( - idKindType = `kindType` - idKindTypeExt = `kindTypeExt` - ) - // Check if a kindTypeExt has already been created for this type. - ext := js.InternalObject(abiTyp).Get(idKindTypeExt) - if ext != js.Undefined { - return ext - } - - // Constructe a new kindTypeExt for this type. - kindType := js.InternalObject(abiTyp).Get(idKindType) - switch abiTyp.Kind() { - case abi.Interface: - ext = js.InternalObject(&interfaceType{}) - ext.Set(`InterfaceType`, js.InternalObject(kindType)) - case abi.Map: - ext = js.InternalObject(&mapType{}) - ext.Set(`MapType`, js.InternalObject(kindType)) - case abi.Pointer: - ext = js.InternalObject(&ptrType{}) - ext.Set(`PtrType`, js.InternalObject(kindType)) - case abi.Slice: - ext = js.InternalObject(&sliceType{}) - ext.Set(`SliceType`, js.InternalObject(kindType)) - case abi.Struct: - ext = js.InternalObject(&structType{}) - ext.Set(`StructType`, js.InternalObject(kindType)) - default: - panic(`unexpected kind in toKindTypeExt`) - } - js.InternalObject(abiTyp).Set(idKindTypeExt, ext) - return ext -} diff --git a/compiler/natives/src/reflect/value.go b/compiler/natives/src/reflect/value.go deleted file mode 100644 index 5e92af8da..000000000 --- a/compiler/natives/src/reflect/value.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build js - -package reflect - -import ( - "unsafe" - - "internal/abi" - - "github.com/gopherjs/gopherjs/compiler/natives/src/internal/unsafeheader" -) - -//gopherjs:purge This is the header for an any interface and invalid for GopherJS. -type emptyInterface struct{} - -//gopherjs:purge This is the header for an interface value with methods and invalid for GopherJS. -type nonEmptyInterface struct{} - -//gopherjs:purge -func packEface(v Value) any - -//gopherjs:purge -func unpackEface(i any) Value - -//gopherjs:purge -func storeRcvr(v Value, p unsafe.Pointer) - -//gopherjs:purge -func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) - -// typedslicecopy is implemented in prelude.js as $copySlice -// -//gopherjs:purge -func typedslicecopy(t *abi.Type, dst, src unsafeheader.Slice) int - -// growslice is implemented in prelude.js as $growSlice. -// -//gopherjs:purge -func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice - -// gopherjs:replace -func noescape(p unsafe.Pointer) unsafe.Pointer { - return p -} - -//gopherjs:purge -func makeFuncStub() From 21c87b75fddc334e0c41ad52f7ab9623b0967a91 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 19 Feb 2026 09:40:35 -0700 Subject: [PATCH 40/48] Cleaning up reflect --- compiler/natives/src/internal/abi/type.go | 8 ++++++++ compiler/natives/src/reflect/export_test.go | 13 +++++++++++++ compiler/natives/src/reflect/reflect.go | 9 ++++++--- 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 compiler/natives/src/reflect/export_test.go diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 8995fb303..143764ab6 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -73,6 +73,7 @@ func ReflectType(typ *js.Object) *Type { } // Create the kind type for the ABI type if the kind has additional information. + // Also set `PtrBytes` to a non-zero number for types with Pointer() values. switch abiTyp.Kind() { case Array: setKindType(abiTyp, &ArrayType{ @@ -87,6 +88,7 @@ func ReflectType(typ *js.Object) *Type { if typ.Get("recvOnly").Bool() { dir = RecvDir } + abiTyp.PtrBytes = 4 setKindType(abiTyp, &ChanType{ Elem: ReflectType(typ.Get("elem")), Dir: dir, @@ -106,6 +108,7 @@ func ReflectType(typ *js.Object) *Type { if typ.Get("variadic").Bool() { outCount |= 1 << 15 } + abiTyp.PtrBytes = 4 setKindType(abiTyp, &FuncType{ InCount: uint16(params.Length()), OutCount: outCount, @@ -127,15 +130,18 @@ func ReflectType(typ *js.Object) *Type { Methods: imethods, }) case Map: + abiTyp.PtrBytes = 4 setKindType(abiTyp, &MapType{ Key: ReflectType(typ.Get("key")), Elem: ReflectType(typ.Get("elem")), }) case Pointer: + abiTyp.PtrBytes = 4 setKindType(abiTyp, &PtrType{ Elem: ReflectType(typ.Get("elem")), }) case Slice: + abiTyp.PtrBytes = 4 setKindType(abiTyp, &SliceType{ Elem: ReflectType(typ.Get("elem")), }) @@ -154,6 +160,8 @@ func ReflectType(typ *js.Object) *Type { PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), Fields: reflectFields, }) + case UnsafePointer: + abiTyp.PtrBytes = 4 } return abiTyp diff --git a/compiler/natives/src/reflect/export_test.go b/compiler/natives/src/reflect/export_test.go new file mode 100644 index 000000000..dc98ce923 --- /dev/null +++ b/compiler/natives/src/reflect/export_test.go @@ -0,0 +1,13 @@ +package reflect + +//gopherjs:purge Uses GC, stack, and funcLayout +func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr, stack, gc, inReg, outReg []byte, ptrs bool) + +//gopherjs:purge Uses internal information from map implementsion +func MapBucketOf(x, y Type) Type + +//gopherjs:purge Uses internal information from map implementsion +func CachedBucketOf(m Type) Type + +//gopherjs:purge Uses the byte name resolution +func FirstMethodNameBytes(t Type) *byte diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index caba2f127..ebcd4763a 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -904,7 +904,7 @@ func (v Value) call(op string, in []Value) []Value { rcvr = v.typ().JsType().New(rcvr) } } else { - t = (*funcType)(unsafe.Pointer(v.typ)) + t = (*funcType)(unsafe.Pointer(v.typ())) fn = unsafe.Pointer(v.object().Unsafe()) rcvr = js.Undefined } @@ -1407,7 +1407,7 @@ func (v Value) Slice(i, j int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := (*arrayType)(unsafe.Pointer(v.typ)) + tt := (*arrayType)(unsafe.Pointer(v.typ())) cap = int(tt.Len) typ = SliceOf(toRType(tt.Elem)).common() s = typ.JsType().New(v.object()) @@ -1447,7 +1447,7 @@ func (v Value) Slice3(i, j, k int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := (*arrayType)(unsafe.Pointer(v.typ)) + tt := (*arrayType)(unsafe.Pointer(v.typ())) cap = int(tt.Len) typ = SliceOf(toRType(tt.Elem)).common() s = typ.JsType().New(v.object()) @@ -1702,6 +1702,9 @@ type common struct{} //gopherjs:purge Used in original MapOf and not used in override MapOf by GopherJS func bucketOf(ktyp, etyp *abi.Type) *abi.Type +//gopherjs:purge Unused type +const debugReflectCall = false + //gopherjs:purge Relates to GC programs not valid for GopherJS func (t *rtype) gcSlice(begin, end uintptr) []byte From 54d40e4ccdf742568ae75e6f14cc4b3f37d4fb41 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 19 Feb 2026 10:22:31 -0700 Subject: [PATCH 41/48] Fixed type compare for DeepEqual --- compiler/natives/src/internal/abi/type.go | 8 ++++---- compiler/natives/src/reflect/reflect.go | 16 +++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 143764ab6..1cab9832e 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -11,7 +11,7 @@ import ( //gopherjs:new const ( idJsType = `jsType` - idReflectType = `reflectType` + idAbiType = `abiType` idKindType = `kindType` idUncommonType = `uncommonType` ) @@ -19,8 +19,8 @@ const ( //gopherjs:new func ReflectType(typ *js.Object) *Type { // If the object already had the reflect type determined, return it. - if typ.Get(idReflectType) != js.Undefined { - return (*Type)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) + if jrt := typ.Get(idAbiType); jrt != js.Undefined { + return (*Type)(unsafe.Pointer(jrt.Unsafe())) } // Create new ABI type. @@ -30,7 +30,7 @@ func ReflectType(typ *js.Object) *Type { Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(abiTyp).Set(idJsType, typ) - typ.Set(idReflectType, js.InternalObject(abiTyp)) + typ.Set(idAbiType, js.InternalObject(abiTyp)) // Add the UncommonType to ABI type if the type has methods. methodSet := js.Global.Call("$methodSet", typ) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index ebcd4763a..1eb82e50b 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -62,12 +62,18 @@ func toAbiType(typ Type) *abi.Type { //gopherjs:replace func toRType(t *abi.Type) *rtype { + const idRType = `rType_` + // rtypes are stored so that two types can be compared with `==`. + if jrt := js.InternalObject(t).Get(idRType); jrt != js.Undefined { + return (*rtype)(unsafe.Pointer(jrt.Unsafe())) + } + rtyp := &rtype{} - // Assign t to the abiType. The abiType is a `*Type` and the t - // field on `rtype` is `Type`. However, this is valid because of how - // pointers and references work in JS. We set this so that the t - // isn't a copy but the actual abiType object. + // Assign t to the abiType. The abiType is a `*Type` and the t field on `rtype` is `Type`. + // However, this is valid because of how pointers and references work in JS. + // We set this so that the t isn't a copy but the actual abiType object. js.InternalObject(rtyp).Set("t", js.InternalObject(t)) + js.InternalObject(t).Set(idRType, js.InternalObject(rtyp)) return rtyp } @@ -1166,7 +1172,7 @@ func (v Value) Clear() { a.SetIndex(i+offset, zeroFn.Invoke()) } // case Map: - // TODO(grantnelson-wf): Finish implementing + // TODO(grantnelson-wf): Finish implementing for go1.21 // mapclear(v.typ(), v.pointer()) default: panic(&ValueError{"reflect.Value.Clear", v.Kind()}) From de98440204503cbbe8d87e7a50ea2e419a35afab Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 19 Feb 2026 12:08:20 -0700 Subject: [PATCH 42/48] Fixed the last type of invalid pointer casting --- compiler/natives/src/internal/bytealg/bytealg.go | 8 ++++++++ compiler/natives/src/reflect/reflect.go | 10 +++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/compiler/natives/src/internal/bytealg/bytealg.go b/compiler/natives/src/internal/bytealg/bytealg.go index 15a3c2d70..db5bcdb4c 100644 --- a/compiler/natives/src/internal/bytealg/bytealg.go +++ b/compiler/natives/src/internal/bytealg/bytealg.go @@ -2,6 +2,7 @@ package bytealg +//gopherjs:replace func Equal(a, b []byte) bool { if len(a) != len(b) { return false @@ -14,6 +15,7 @@ func Equal(a, b []byte) bool { return true } +//gopherjs:replace func IndexByte(b []byte, c byte) int { for i, x := range b { if x == c { @@ -23,6 +25,7 @@ func IndexByte(b []byte, c byte) int { return -1 } +//gopherjs:replace func IndexByteString(s string, c byte) int { for i := 0; i < len(s); i++ { if s[i] == c { @@ -31,3 +34,8 @@ func IndexByteString(s string, c byte) int { } return -1 } + +//gopherjs:replace +func MakeNoZero(n int) []byte { + return make([]byte, n) +} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 1eb82e50b..e7af74a42 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -62,7 +62,11 @@ func toAbiType(typ Type) *abi.Type { //gopherjs:replace func toRType(t *abi.Type) *rtype { - const idRType = `rType_` + const ( + idRType = `rType` + idKindType = `kindType` + ) + // rtypes are stored so that two types can be compared with `==`. if jrt := js.InternalObject(t).Get(idRType); jrt != js.Undefined { return (*rtype)(unsafe.Pointer(jrt.Unsafe())) @@ -74,6 +78,8 @@ func toRType(t *abi.Type) *rtype { // We set this so that the t isn't a copy but the actual abiType object. js.InternalObject(rtyp).Set("t", js.InternalObject(t)) js.InternalObject(t).Set(idRType, js.InternalObject(rtyp)) + // Also copy over kindType to rtype so casts can be made directly from rtype to kindType. + js.InternalObject(rtyp).Set(idKindType, js.InternalObject(t).Get(idKindType)) return rtyp } @@ -1734,8 +1740,6 @@ func toKindTypeExt(src any) *js.Object { switch t := src.(type) { case *rtype: abiTyp = t.common() - case Type: - abiTyp = toAbiType(t) case *abi.Type: abiTyp = t default: From b04da8a47821647bfacbc10a6fa73dbbf46b343f Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 19 Feb 2026 15:01:06 -0700 Subject: [PATCH 43/48] Fixed the remaining issues in reflect --- compiler/natives/src/internal/abi/type.go | 10 +++++---- compiler/natives/src/reflect/reflect.go | 25 +++++++++-------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go index 1cab9832e..2c389f106 100644 --- a/compiler/natives/src/internal/abi/type.go +++ b/compiler/natives/src/internal/abi/type.go @@ -120,8 +120,12 @@ func ReflectType(typ *js.Object) *Type { imethods := make([]Imethod, methods.Length()) for i := range imethods { m := methods.Index(i) + mPkg := internalStr(m.Get("pkg")) + exported := mPkg == "" + name := NewName(internalStr(m.Get("name")), "", exported, false) + name.SetPkgPath(mPkg) imethods[i] = Imethod{ - Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), + Name: ResolveReflectName(name), Typ: ResolveReflectType(ReflectType(m.Get("typ"))), } } @@ -304,9 +308,7 @@ func (n Name) ReadVarint(off int) (int, int) func (n Name) PkgPath() string { return n.pkgPath } //gopherjs:new -func (n Name) SetPkgPath(pkgpath string) { - n.pkgPath = pkgpath -} +func (n *Name) SetPkgPath(pkgpath string) { n.pkgPath = pkgpath } //gopherjs:replace func NewName(n, tag string, exported, embedded bool) Name { diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index e7af74a42..e3173460e 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -774,11 +774,7 @@ func makeMethodValue(op string, v Value) Value { fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { return js.InternalObject(fn).Call("apply", rcvr, arguments) }) - return Value{ - typ_: v.Type().common(), - ptr: unsafe.Pointer(fv.Unsafe()), - flag: v.flag.ro() | flag(Func), - } + return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} } //gopherjs:new @@ -823,7 +819,7 @@ func (t *rtype) Method(i int) (m Method) { } mt := FuncOf(in, out, ft.IsVariadic()) m.Type = mt - prop := js.Global.Call("$methodSet", js.InternalObject(t).Get("jsType")).Index(i).Get("prop").String() + prop := js.Global.Call("$methodSet", jsType(t)).Index(i).Get("prop").String() fn := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { rcvr := arguments[0] return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) @@ -1156,14 +1152,12 @@ func (v Value) extendSlice(n int) Value { return v2 } -//gopherjs:purge +//gopherjs:purge Unused because handled inside of Clear func mapclear(t *abi.Type, m unsafe.Pointer) -//gopherjs:purge +//gopherjs:purge Unused because handled inside of Clear func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) -// TODO(grantnelson-wf): Make sure this is tested since it is new. -// //gopherjs:replace func (v Value) Clear() { switch v.Kind() { @@ -1171,15 +1165,16 @@ func (v Value) Clear() { st := (*sliceType)(unsafe.Pointer(v.typ())) elem := st.Elem zeroFn := elem.JsType().Get("zero") - a := js.InternalObject(v.ptr) + a := js.InternalObject(v.ptr).Call("$get") offset := a.Get("$offset").Int() length := a.Get("$length").Int() + array := a.Get("$array") for i := 0; i < length; i++ { - a.SetIndex(i+offset, zeroFn.Invoke()) + array.SetIndex(i+offset, zeroFn.Invoke()) } - // case Map: - // TODO(grantnelson-wf): Finish implementing for go1.21 - // mapclear(v.typ(), v.pointer()) + case Map: + mapObj := js.InternalObject(v.ptr).Call("$get") + mapObj.Call("clear") default: panic(&ValueError{"reflect.Value.Clear", v.Kind()}) } From e793abbbd949c4d1f5cc7c327f6af7dbef45e71e Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 19 Feb 2026 15:57:33 -0700 Subject: [PATCH 44/48] Fixed the remaining issues in reflect --- compiler/natives/src/reflect/export_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/natives/src/reflect/export_test.go b/compiler/natives/src/reflect/export_test.go index dc98ce923..778fa1d4f 100644 --- a/compiler/natives/src/reflect/export_test.go +++ b/compiler/natives/src/reflect/export_test.go @@ -1,3 +1,5 @@ +//go:build js + package reflect //gopherjs:purge Uses GC, stack, and funcLayout From 203e7145ed8531c3082e98d983a66b28dfb644ff Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 09:37:11 -0700 Subject: [PATCH 45/48] adding the requested comments --- build/build.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build/build.go b/build/build.go index 34d20da55..8961ef85b 100644 --- a/build/build.go +++ b/build/build.go @@ -378,7 +378,10 @@ func augmentOriginalImports(importPath string, file *ast.File) { // augmentOriginalFile is the part of parseAndAugment that processes an // original file AST to augment the source code using the overrides from -// the overlay files. +// the overlay files. The overrides and found maps key with an identifer +// that uniquely identifies the top-level object being augmented. +// The overrides map should be populated with the overrides to apply. +// Found will be populated with the objects that had an override applied. func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, found map[string]struct{}) { anyChange := false for i, decl := range file.Decls { @@ -480,6 +483,11 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, foun // checkOverrides performs a final check of the overrides to ensure that // all overrides that were expected to be found were found and all overrides // that were not expected to be found were not found. +// +// The overrides and found maps key with an identifer +// that uniquely identifies the top-level object being augmented. +// Found is populated with the objects that had an override applied +// so the found keys should be a subset of the keys in the overrides map. func checkOverrides(overrides map[string]overrideInfo, found map[string]struct{}, pkgPath string) error { el := errlist.ErrorList{} for name, info := range overrides { From 51188b75bcd9b40e136271be52990c5d99e61542 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 09:43:44 -0700 Subject: [PATCH 46/48] fixing spelling --- build/build.go | 8 +++++--- compiler/natives/src/reflect/export_test.go | 4 ++-- compiler/natives/src/reflect/reflect.go | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build/build.go b/build/build.go index 8961ef85b..2d5ebf61f 100644 --- a/build/build.go +++ b/build/build.go @@ -378,8 +378,10 @@ func augmentOriginalImports(importPath string, file *ast.File) { // augmentOriginalFile is the part of parseAndAugment that processes an // original file AST to augment the source code using the overrides from -// the overlay files. The overrides and found maps key with an identifer -// that uniquely identifies the top-level object being augmented. +// the overlay files. +// +// The overrides and found maps key with an identifier that uniquely identifies +// the top-level object being augmented. // The overrides map should be populated with the overrides to apply. // Found will be populated with the objects that had an override applied. func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, found map[string]struct{}) { @@ -484,7 +486,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, foun // all overrides that were expected to be found were found and all overrides // that were not expected to be found were not found. // -// The overrides and found maps key with an identifer +// The overrides and found maps key with an identifier // that uniquely identifies the top-level object being augmented. // Found is populated with the objects that had an override applied // so the found keys should be a subset of the keys in the overrides map. diff --git a/compiler/natives/src/reflect/export_test.go b/compiler/natives/src/reflect/export_test.go index 778fa1d4f..d1e700af1 100644 --- a/compiler/natives/src/reflect/export_test.go +++ b/compiler/natives/src/reflect/export_test.go @@ -5,10 +5,10 @@ package reflect //gopherjs:purge Uses GC, stack, and funcLayout func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr, stack, gc, inReg, outReg []byte, ptrs bool) -//gopherjs:purge Uses internal information from map implementsion +//gopherjs:purge Uses internal information from map implementation func MapBucketOf(x, y Type) Type -//gopherjs:purge Uses internal information from map implementsion +//gopherjs:purge Uses internal information from map implementation func CachedBucketOf(m Type) Type //gopherjs:purge Uses the byte name resolution diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index e3173460e..e7db6618b 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1751,7 +1751,7 @@ func toKindTypeExt(src any) *js.Object { return ext } - // Constructe a new kindTypeExt for this type. + // Construct a new kindTypeExt for this type. kindType := js.InternalObject(abiTyp).Get(idKindType) switch abiTyp.Kind() { case abi.Interface: From 6f1821c774d1300c296f5c8934ec906d257f762d Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 10:57:53 -0700 Subject: [PATCH 47/48] Simplifying Tag reading --- compiler/natives/src/internal/abi/utils.go | 89 ------------ .../src/internal/reflectlite/export_test.go | 128 ++++++++++++++++++ .../natives/src/internal/reflectlite/value.go | 52 ------- compiler/natives/src/reflect/reflect.go | 2 +- compiler/utils.go | 46 +------ 5 files changed, 131 insertions(+), 186 deletions(-) diff --git a/compiler/natives/src/internal/abi/utils.go b/compiler/natives/src/internal/abi/utils.go index ba26d92e2..0cf387bc8 100644 --- a/compiler/natives/src/internal/abi/utils.go +++ b/compiler/natives/src/internal/abi/utils.go @@ -14,95 +14,6 @@ import ( // when both reflect and reflectlite already depend on ABI. We can reduce // our native overrides in both locations by putting common code here. -//gopherjs:new -type errorString struct { - s string -} - -//gopherjs:new -func (e *errorString) Error() string { - return e.s -} - -//gopherjs:new -var ErrSyntax = &errorString{"invalid syntax"} - -//gopherjs:new Added to avoid a dependency on strconv.Unquote -func unquote(s string) (string, error) { - if len(s) < 2 { - return s, nil - } - if s[0] == '\'' || s[0] == '"' { - if s[len(s)-1] == s[0] { - return s[1 : len(s)-1], nil - } - return "", ErrSyntax - } - return s, nil -} - -//gopherjs:new -func GetJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := unquote(qvalue) - return value - } - } - return "" -} - -// NewMethodName creates name instance for a method. -// -// Input object is expected to be an entry of the "methods" list of the -// corresponding JS type. -// -//gopherjs:new -func NewMethodName(m *js.Object) Name { - return Name{ - name: internalStr(m.Get("name")), - tag: "", - pkgPath: internalStr(m.Get("pkg")), - exported: internalStr(m.Get("pkg")) == "", - } -} - //gopherjs:new func UnsafeNew(typ *Type) unsafe.Pointer { switch typ.Kind() { diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index 20864b71b..52daeb9b4 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -2,6 +2,14 @@ package reflectlite +import ( + "unsafe" + + "internal/abi" + + "github.com/gopherjs/gopherjs/js" +) + // Field returns the i'th field of the struct v. // It panics if v's Kind is not Struct or i is out of range. // @@ -13,6 +21,126 @@ func Field(v Value, i int) Value { return v.Field(i) } +//gopherjs:new +func (v Value) Field(i int) Value { + tt := v.typ.StructType() + if tt == nil { + panic(&ValueError{"reflect.Value.Field", v.kind()}) + } + + if uint(i) >= uint(len(tt.Fields)) { + panic("reflect: Field index out of range") + } + + prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() + field := &tt.Fields[i] + typ := field.Typ + + fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) + if !field.Name.IsExported() { + if field.Embedded() { + fl |= flagEmbedRO + } else { + fl |= flagStickyRO + } + } + + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { + if jsTag := structTagGet(tag, `js`); jsTag != "" { + for { + v = v.Field(0) + if v.typ == abi.JsObjectPtr { + o := v.object().Get("object") + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), + ).Unsafe()), fl} + } + if v.typ.Kind() == abi.Pointer { + v = v.Elem() + } + } + } + } + + s := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} + } + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) +} + +// This is very similar to the `reflect.StructTag` methods `Get` and `Lookup`. +// +//gopherjs:new +func structTagGet(tag, key string) string { + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, syntaxErr := unquote(qvalue) + if syntaxErr { + break + } + return value + } + } + return "" +} + +//gopherjs:new Added to avoid a dependency on strconv.Unquote +func unquote(s string) (string, bool) { + if len(s) < 2 { + return s, false + } + if s[0] == '\'' || s[0] == '"' { + if s[len(s)-1] == s[0] { + return s[1 : len(s)-1], false + } + return "", true + } + return s, false +} + //gopherjs:purge Used in FirstMethodNameBytes type EmbedWithUnexpMeth struct{} diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index 07e954986..31daacc8d 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -156,58 +156,6 @@ func (v Value) Elem() Value { } } -// TODO(grantnelson-wf): To minimize diffs, this was not moved to export_test. After this is merged into the go1.21 branch, move it. -func (v Value) Field(i int) Value { - tt := v.typ.StructType() - if tt == nil { - panic(&ValueError{"reflect.Value.Field", v.kind()}) - } - - if uint(i) >= uint(len(tt.Fields)) { - panic("reflect: Field index out of range") - } - - prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.Fields[i] - typ := field.Typ - - fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.Name.IsExported() { - if field.Embedded() { - fl |= flagEmbedRO - } else { - fl |= flagStickyRO - } - } - - if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { - if jsTag := abi.GetJsTag(tag); jsTag != "" { - for { - v = v.Field(0) - if v.typ == abi.JsObjectPtr { - o := v.object().Get("object") - return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), - ).Unsafe()), fl} - } - if v.typ.Kind() == abi.Pointer { - v = v.Elem() - } - } - } - } - - s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { - return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( - js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) -} - //gopherjs:purge Unused type type emptyInterface struct{} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index e7db6618b..18469eb16 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1073,7 +1073,7 @@ func (v Value) Field(i int) Value { } if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { - if jsTag := abi.GetJsTag(tag); jsTag != "" { + if jsTag := StructTag(tag).Get(`js`); jsTag != "" { for { v = v.Field(0) if v.typ() == abi.JsObjectPtr { diff --git a/compiler/utils.go b/compiler/utils.go index 20e279730..93afefbf1 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -9,10 +9,10 @@ import ( "go/token" "go/types" "net/url" + "reflect" "regexp" "runtime/debug" "sort" - "strconv" "strings" "text/template" "unicode" @@ -821,49 +821,7 @@ func encodeString(s string) string { } func getJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := strconv.Unquote(qvalue) - return value - } - } - return "" + return reflect.StructTag(tag).Get(`js`) } func needsSpace(c byte) bool { From 8c86d326657ebf4bf572c384e6c5a851f8238a92 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 13:03:03 -0700 Subject: [PATCH 48/48] Updating the godebug link --- compiler/natives/src/internal/godebug/godebug.go | 8 ++++++-- compiler/natives/src/runtime/runtime.go | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/natives/src/internal/godebug/godebug.go b/compiler/natives/src/internal/godebug/godebug.go index 40007a918..25cc092fa 100644 --- a/compiler/natives/src/internal/godebug/godebug.go +++ b/compiler/natives/src/internal/godebug/godebug.go @@ -7,5 +7,9 @@ import _ "unsafe" // go:linkname //go:linkname setUpdate runtime.godebug_setUpdate func setUpdate(update func(def, env string)) -//go:linkname setNewIncNonDefault runtime.godebug_setNewIncNonDefault -func setNewIncNonDefault(newIncNonDefault func(string) func()) +// GOPHERJS: Changing from a linked function to a no-op since this is to give +// runtime the ability to do `newNonDefaultInc(name)` instead of +// `godebug.New(name).IncNonDefault` but GopherJS's runtime doesn't need that. +// +//gopherjs:replace +func setNewIncNonDefault(newIncNonDefault func(string) func()) {} diff --git a/compiler/natives/src/runtime/runtime.go b/compiler/natives/src/runtime/runtime.go index 0984ba941..cbd1a2dad 100644 --- a/compiler/natives/src/runtime/runtime.go +++ b/compiler/natives/src/runtime/runtime.go @@ -501,10 +501,12 @@ func godebug_setUpdate(update func(def, env string)) { godebug_notify(godebugEnvKey, godebugEnv) } -// godebug_setUpdate implements the setNewIncNonDefault in src/internal/godebug/godebug.go -func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { - // TODO(grantnelson-wf): Figure out what to do for this method. -} +// godebug_setNewIncNonDefault implements the setNewIncNonDefault in +// src/internal/godebug/godebug.go. +// GOPHERJS: The GopherJS runtime doesn't need this function so we can remove it. +// +//gopherjs:puge +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) func getEnvString(key string) string { process := js.Global.Get(`process`)