1// +build js 2 3package reflect 4 5import ( 6 "errors" 7 "strconv" 8 "unsafe" 9 10 "github.com/gopherjs/gopherjs/js" 11) 12 13var initialized = false 14 15func init() { 16 // avoid dead code elimination 17 used := func(i interface{}) {} 18 used(rtype{}) 19 used(uncommonType{}) 20 used(method{}) 21 used(arrayType{}) 22 used(chanType{}) 23 used(funcType{}) 24 used(interfaceType{}) 25 used(mapType{}) 26 used(ptrType{}) 27 used(sliceType{}) 28 used(structType{}) 29 used(imethod{}) 30 used(structField{}) 31 32 initialized = true 33 uint8Type = TypeOf(uint8(0)).(*rtype) // set for real 34} 35 36func jsType(typ Type) *js.Object { 37 return js.InternalObject(typ).Get("jsType") 38} 39 40func reflectType(typ *js.Object) *rtype { 41 if typ.Get("reflectType") == js.Undefined { 42 rt := &rtype{ 43 size: uintptr(typ.Get("size").Int()), 44 kind: uint8(typ.Get("kind").Int()), 45 str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool())), 46 } 47 js.InternalObject(rt).Set("jsType", typ) 48 typ.Set("reflectType", js.InternalObject(rt)) 49 50 methodSet := js.Global.Call("$methodSet", typ) 51 if methodSet.Length() != 0 || typ.Get("named").Bool() { 52 rt.tflag |= tflagUncommon 53 if typ.Get("named").Bool() { 54 rt.tflag |= tflagNamed 55 } 56 var reflectMethods []method 57 for i := 0; i < methodSet.Length(); i++ { // Exported methods first. 58 m := methodSet.Index(i) 59 exported := internalStr(m.Get("pkg")) == "" 60 if !exported { 61 continue 62 } 63 reflectMethods = append(reflectMethods, method{ 64 name: newNameOff(newName(internalStr(m.Get("name")), "", exported)), 65 mtyp: newTypeOff(reflectType(m.Get("typ"))), 66 }) 67 } 68 xcount := uint16(len(reflectMethods)) 69 for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. 70 m := methodSet.Index(i) 71 exported := internalStr(m.Get("pkg")) == "" 72 if exported { 73 continue 74 } 75 reflectMethods = append(reflectMethods, method{ 76 name: newNameOff(newName(internalStr(m.Get("name")), "", exported)), 77 mtyp: newTypeOff(reflectType(m.Get("typ"))), 78 }) 79 } 80 ut := &uncommonType{ 81 pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false)), 82 mcount: uint16(methodSet.Length()), 83 xcount: xcount, 84 _methods: reflectMethods, 85 } 86 uncommonTypeMap[rt] = ut 87 js.InternalObject(ut).Set("jsType", typ) 88 } 89 90 switch rt.Kind() { 91 case Array: 92 setKindType(rt, &arrayType{ 93 elem: reflectType(typ.Get("elem")), 94 len: uintptr(typ.Get("len").Int()), 95 }) 96 case Chan: 97 dir := BothDir 98 if typ.Get("sendOnly").Bool() { 99 dir = SendDir 100 } 101 if typ.Get("recvOnly").Bool() { 102 dir = RecvDir 103 } 104 setKindType(rt, &chanType{ 105 elem: reflectType(typ.Get("elem")), 106 dir: uintptr(dir), 107 }) 108 case Func: 109 params := typ.Get("params") 110 in := make([]*rtype, params.Length()) 111 for i := range in { 112 in[i] = reflectType(params.Index(i)) 113 } 114 results := typ.Get("results") 115 out := make([]*rtype, results.Length()) 116 for i := range out { 117 out[i] = reflectType(results.Index(i)) 118 } 119 outCount := uint16(results.Length()) 120 if typ.Get("variadic").Bool() { 121 outCount |= 1 << 15 122 } 123 setKindType(rt, &funcType{ 124 rtype: *rt, 125 inCount: uint16(params.Length()), 126 outCount: outCount, 127 _in: in, 128 _out: out, 129 }) 130 case Interface: 131 methods := typ.Get("methods") 132 imethods := make([]imethod, methods.Length()) 133 for i := range imethods { 134 m := methods.Index(i) 135 imethods[i] = imethod{ 136 name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "")), 137 typ: newTypeOff(reflectType(m.Get("typ"))), 138 } 139 } 140 setKindType(rt, &interfaceType{ 141 rtype: *rt, 142 pkgPath: newName(internalStr(typ.Get("pkg")), "", false), 143 methods: imethods, 144 }) 145 case Map: 146 setKindType(rt, &mapType{ 147 key: reflectType(typ.Get("key")), 148 elem: reflectType(typ.Get("elem")), 149 }) 150 case Ptr: 151 setKindType(rt, &ptrType{ 152 elem: reflectType(typ.Get("elem")), 153 }) 154 case Slice: 155 setKindType(rt, &sliceType{ 156 elem: reflectType(typ.Get("elem")), 157 }) 158 case Struct: 159 fields := typ.Get("fields") 160 reflectFields := make([]structField, fields.Length()) 161 for i := range reflectFields { 162 f := fields.Index(i) 163 offsetEmbed := uintptr(i) << 1 164 if f.Get("embedded").Bool() { 165 offsetEmbed |= 1 166 } 167 reflectFields[i] = structField{ 168 name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool()), 169 typ: reflectType(f.Get("typ")), 170 offsetEmbed: offsetEmbed, 171 } 172 } 173 setKindType(rt, &structType{ 174 rtype: *rt, 175 pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false), 176 fields: reflectFields, 177 }) 178 } 179 } 180 181 return (*rtype)(unsafe.Pointer(typ.Get("reflectType").Unsafe())) 182} 183 184func setKindType(rt *rtype, kindType interface{}) { 185 js.InternalObject(rt).Set("kindType", js.InternalObject(kindType)) 186 js.InternalObject(kindType).Set("rtype", js.InternalObject(rt)) 187} 188 189type uncommonType struct { 190 pkgPath nameOff 191 mcount uint16 192 xcount uint16 193 moff uint32 194 195 _methods []method 196} 197 198func (t *uncommonType) methods() []method { 199 return t._methods 200} 201 202func (t *uncommonType) exportedMethods() []method { 203 return t._methods[:t.xcount:t.xcount] 204} 205 206var uncommonTypeMap = make(map[*rtype]*uncommonType) 207 208func (t *rtype) uncommon() *uncommonType { 209 return uncommonTypeMap[t] 210} 211 212type funcType struct { 213 rtype `reflect:"func"` 214 inCount uint16 215 outCount uint16 216 217 _in []*rtype 218 _out []*rtype 219} 220 221func (t *funcType) in() []*rtype { 222 return t._in 223} 224 225func (t *funcType) out() []*rtype { 226 return t._out 227} 228 229type name struct { 230 bytes *byte 231} 232 233type nameData struct { 234 name string 235 tag string 236 exported bool 237} 238 239var nameMap = make(map[*byte]*nameData) 240 241func (n name) name() (s string) { return nameMap[n.bytes].name } 242func (n name) tag() (s string) { return nameMap[n.bytes].tag } 243func (n name) pkgPath() string { return "" } 244func (n name) isExported() bool { return nameMap[n.bytes].exported } 245 246func newName(n, tag string, exported bool) name { 247 b := new(byte) 248 nameMap[b] = &nameData{ 249 name: n, 250 tag: tag, 251 exported: exported, 252 } 253 return name{ 254 bytes: b, 255 } 256} 257 258var nameOffList []name 259 260func (t *rtype) nameOff(off nameOff) name { 261 return nameOffList[int(off)] 262} 263 264func newNameOff(n name) nameOff { 265 i := len(nameOffList) 266 nameOffList = append(nameOffList, n) 267 return nameOff(i) 268} 269 270var typeOffList []*rtype 271 272func (t *rtype) typeOff(off typeOff) *rtype { 273 return typeOffList[int(off)] 274} 275 276func newTypeOff(t *rtype) typeOff { 277 i := len(typeOffList) 278 typeOffList = append(typeOffList, t) 279 return typeOff(i) 280} 281 282func internalStr(strObj *js.Object) string { 283 var c struct{ str string } 284 js.InternalObject(c).Set("str", strObj) // get string without internalizing 285 return c.str 286} 287 288func isWrapped(typ Type) bool { 289 return jsType(typ).Get("wrapped").Bool() 290} 291 292func copyStruct(dst, src *js.Object, typ Type) { 293 fields := jsType(typ).Get("fields") 294 for i := 0; i < fields.Length(); i++ { 295 prop := fields.Index(i).Get("prop").String() 296 dst.Set(prop, src.Get(prop)) 297 } 298} 299 300func makeValue(t Type, v *js.Object, fl flag) Value { 301 rt := t.common() 302 if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { 303 return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} 304 } 305 return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} 306} 307 308func MakeSlice(typ Type, len, cap int) Value { 309 if typ.Kind() != Slice { 310 panic("reflect.MakeSlice of non-slice type") 311 } 312 if len < 0 { 313 panic("reflect.MakeSlice: negative len") 314 } 315 if cap < 0 { 316 panic("reflect.MakeSlice: negative cap") 317 } 318 if len > cap { 319 panic("reflect.MakeSlice: len > cap") 320 } 321 322 return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) 323} 324 325func TypeOf(i interface{}) Type { 326 if !initialized { // avoid error of uint8Type 327 return &rtype{} 328 } 329 if i == nil { 330 return nil 331 } 332 return reflectType(js.InternalObject(i).Get("constructor")) 333} 334 335func ValueOf(i interface{}) Value { 336 if i == nil { 337 return Value{} 338 } 339 return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) 340} 341 342func ArrayOf(count int, elem Type) Type { 343 return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) 344} 345 346func ChanOf(dir ChanDir, t Type) Type { 347 return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) 348} 349 350func FuncOf(in, out []Type, variadic bool) Type { 351 if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { 352 panic("reflect.FuncOf: last arg of variadic func must be slice") 353 } 354 355 jsIn := make([]*js.Object, len(in)) 356 for i, v := range in { 357 jsIn[i] = jsType(v) 358 } 359 jsOut := make([]*js.Object, len(out)) 360 for i, v := range out { 361 jsOut[i] = jsType(v) 362 } 363 return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) 364} 365 366func MapOf(key, elem Type) Type { 367 switch key.Kind() { 368 case Func, Map, Slice: 369 panic("reflect.MapOf: invalid key type " + key.String()) 370 } 371 372 return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) 373} 374 375func (t *rtype) ptrTo() *rtype { 376 return reflectType(js.Global.Call("$ptrType", jsType(t))) 377} 378 379func SliceOf(t Type) Type { 380 return reflectType(js.Global.Call("$sliceType", jsType(t))) 381} 382 383// func StructOf(fields []StructField) Type { 384// jsFields := make([]*js.Object, len(fields)) 385// fset := map[string]struct{}{} 386// for i, f := range fields { 387// if f.Type == nil { 388// panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") 389// } 390 391// name := f.Name 392// if name == "" { 393// // Embedded field 394// if f.Type.Kind() == Ptr { 395// // Embedded ** and *interface{} are illegal 396// elem := f.Type.Elem() 397// if k := elem.Kind(); k == Ptr || k == Interface { 398// panic("reflect.StructOf: illegal anonymous field type " + f.Type.String()) 399// } 400// name = elem.String() 401// } else { 402// name = f.Type.String() 403// } 404// } 405 406// if _, dup := fset[name]; dup { 407// panic("reflect.StructOf: duplicate field " + name) 408// } 409// fset[name] = struct{}{} 410 411// jsf := js.Global.Get("Object").New() 412// jsf.Set("prop", name) 413// jsf.Set("name", name) 414// jsf.Set("exported", true) 415// jsf.Set("typ", jsType(f.Type)) 416// jsf.Set("tag", f.Tag) 417// jsFields[i] = jsf 418// } 419// return reflectType(js.Global.Call("$structType", "", jsFields)) 420// } 421 422func Zero(typ Type) Value { 423 return makeValue(typ, jsType(typ).Call("zero"), 0) 424} 425 426func unsafe_New(typ *rtype) unsafe.Pointer { 427 switch typ.Kind() { 428 case Struct: 429 return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) 430 case Array: 431 return unsafe.Pointer(jsType(typ).Call("zero").Unsafe()) 432 default: 433 return unsafe.Pointer(js.Global.Call("$newDataPointer", jsType(typ).Call("zero"), jsType(typ.ptrTo())).Unsafe()) 434 } 435} 436 437func makeInt(f flag, bits uint64, t Type) Value { 438 typ := t.common() 439 ptr := unsafe_New(typ) 440 switch typ.Kind() { 441 case Int8: 442 *(*int8)(ptr) = int8(bits) 443 case Int16: 444 *(*int16)(ptr) = int16(bits) 445 case Int, Int32: 446 *(*int32)(ptr) = int32(bits) 447 case Int64: 448 *(*int64)(ptr) = int64(bits) 449 case Uint8: 450 *(*uint8)(ptr) = uint8(bits) 451 case Uint16: 452 *(*uint16)(ptr) = uint16(bits) 453 case Uint, Uint32, Uintptr: 454 *(*uint32)(ptr) = uint32(bits) 455 case Uint64: 456 *(*uint64)(ptr) = uint64(bits) 457 } 458 return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} 459} 460 461func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { 462 if typ.Kind() != Func { 463 panic("reflect: call of MakeFunc with non-Func type") 464 } 465 466 t := typ.common() 467 ftyp := (*funcType)(unsafe.Pointer(t)) 468 469 fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 470 args := make([]Value, ftyp.NumIn()) 471 for i := range args { 472 argType := ftyp.In(i).common() 473 args[i] = makeValue(argType, arguments[i], 0) 474 } 475 resultsSlice := fn(args) 476 switch ftyp.NumOut() { 477 case 0: 478 return nil 479 case 1: 480 return resultsSlice[0].object() 481 default: 482 results := js.Global.Get("Array").New(ftyp.NumOut()) 483 for i, r := range resultsSlice { 484 results.SetIndex(i, r.object()) 485 } 486 return results 487 } 488 }) 489 490 return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} 491} 492 493func typedmemmove(t *rtype, dst, src unsafe.Pointer) { 494 js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) 495} 496 497func loadScalar(p unsafe.Pointer, n uintptr) uintptr { 498 return js.InternalObject(p).Call("$get").Unsafe() 499} 500 501func makechan(typ *rtype, size int) (ch unsafe.Pointer) { 502 ctyp := (*chanType)(unsafe.Pointer(typ)) 503 return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.elem), size).Unsafe()) 504} 505 506func makemap(t *rtype, cap int) (m unsafe.Pointer) { 507 return unsafe.Pointer(js.Global.Get("Object").New().Unsafe()) 508} 509 510func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, string) { 511 kv := js.InternalObject(key) 512 if kv.Get("$get") != js.Undefined { 513 kv = kv.Call("$get") 514 } 515 k := jsType(t.Key()).Call("keyFor", kv).String() 516 return kv, k 517} 518 519func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { 520 _, k := keyFor(t, key) 521 entry := js.InternalObject(m).Get(k) 522 if entry == js.Undefined { 523 return nil 524 } 525 return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) 526} 527 528func mapassign(t *rtype, m, key, val unsafe.Pointer) { 529 kv, k := keyFor(t, key) 530 jsVal := js.InternalObject(val).Call("$get") 531 et := t.Elem() 532 if et.Kind() == Struct { 533 newVal := jsType(et).Call("zero") 534 copyStruct(newVal, jsVal, et) 535 jsVal = newVal 536 } 537 entry := js.Global.Get("Object").New() 538 entry.Set("k", kv) 539 entry.Set("v", jsVal) 540 js.InternalObject(m).Set(k, entry) 541} 542 543func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) { 544 _, k := keyFor(t, key) 545 js.InternalObject(m).Delete(k) 546} 547 548type mapIter struct { 549 t Type 550 m *js.Object 551 keys *js.Object 552 i int 553 554 // last is the last object the iterator indicates. If this object exists, the functions that return the 555 // current key or value returns this object, regardless of the current iterator. It is because the current 556 // iterator might be stale due to key deletion in a loop. 557 last *js.Object 558} 559 560func (iter *mapIter) skipUntilValidKey() { 561 for iter.i < iter.keys.Length() { 562 k := iter.keys.Index(iter.i) 563 if iter.m.Get(k.String()) != js.Undefined { 564 break 565 } 566 // The key is already deleted. Move on the next item. 567 iter.i++ 568 } 569} 570 571func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer { 572 return unsafe.Pointer(&mapIter{t, js.InternalObject(m), js.Global.Call("$keys", js.InternalObject(m)), 0, nil}) 573} 574 575func mapiterkey(it unsafe.Pointer) unsafe.Pointer { 576 iter := (*mapIter)(it) 577 var kv *js.Object 578 if iter.last != nil { 579 kv = iter.last 580 } else { 581 iter.skipUntilValidKey() 582 if iter.i == iter.keys.Length() { 583 return nil 584 } 585 k := iter.keys.Index(iter.i) 586 kv = iter.m.Get(k.String()) 587 588 // Record the key-value pair for later accesses. 589 iter.last = kv 590 } 591 return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(iter.t.Key()))).Unsafe()) 592} 593 594func mapitervalue(it unsafe.Pointer) unsafe.Pointer { 595 iter := (*mapIter)(it) 596 var kv *js.Object 597 if iter.last != nil { 598 kv = iter.last 599 } else { 600 iter.skipUntilValidKey() 601 if iter.i == iter.keys.Length() { 602 return nil 603 } 604 k := iter.keys.Index(iter.i) 605 kv = iter.m.Get(k.String()) 606 iter.last = kv 607 } 608 return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), jsType(PtrTo(iter.t.Elem()))).Unsafe()) 609} 610 611func mapiternext(it unsafe.Pointer) { 612 iter := (*mapIter)(it) 613 iter.last = nil 614 iter.i++ 615} 616 617func maplen(m unsafe.Pointer) int { 618 return js.Global.Call("$keys", js.InternalObject(m)).Length() 619} 620 621func cvtDirect(v Value, typ Type) Value { 622 var srcVal = v.object() 623 if srcVal == jsType(v.typ).Get("nil") { 624 return makeValue(typ, jsType(typ).Get("nil"), v.flag) 625 } 626 627 var val *js.Object 628 switch k := typ.Kind(); k { 629 case Slice: 630 slice := jsType(typ).New(srcVal.Get("$array")) 631 slice.Set("$offset", srcVal.Get("$offset")) 632 slice.Set("$length", srcVal.Get("$length")) 633 slice.Set("$capacity", srcVal.Get("$capacity")) 634 val = js.Global.Call("$newDataPointer", slice, jsType(PtrTo(typ))) 635 case Ptr: 636 if typ.Elem().Kind() == Struct { 637 if typ.Elem() == v.typ.Elem() { 638 val = srcVal 639 break 640 } 641 val = jsType(typ).New() 642 copyStruct(val, srcVal, typ.Elem()) 643 break 644 } 645 val = jsType(typ).New(srcVal.Get("$get"), srcVal.Get("$set")) 646 case Struct: 647 val = jsType(typ).Get("ptr").New() 648 copyStruct(val, srcVal, typ) 649 case Array, Bool, Chan, Func, Interface, Map, String: 650 val = js.InternalObject(v.ptr) 651 default: 652 panic(&ValueError{"reflect.Convert", k}) 653 } 654 return Value{typ.common(), unsafe.Pointer(val.Unsafe()), v.flag.ro() | v.flag&flagIndir | flag(typ.Kind())} 655} 656 657func Copy(dst, src Value) int { 658 dk := dst.kind() 659 if dk != Array && dk != Slice { 660 panic(&ValueError{"reflect.Copy", dk}) 661 } 662 if dk == Array { 663 dst.mustBeAssignable() 664 } 665 dst.mustBeExported() 666 667 sk := src.kind() 668 var stringCopy bool 669 if sk != Array && sk != Slice { 670 stringCopy = sk == String && dst.typ.Elem().Kind() == Uint8 671 if !stringCopy { 672 panic(&ValueError{"reflect.Copy", sk}) 673 } 674 } 675 src.mustBeExported() 676 677 if !stringCopy { 678 typesMustMatch("reflect.Copy", dst.typ.Elem(), src.typ.Elem()) 679 } 680 681 dstVal := dst.object() 682 if dk == Array { 683 dstVal = jsType(SliceOf(dst.typ.Elem())).New(dstVal) 684 } 685 686 srcVal := src.object() 687 if sk == Array { 688 srcVal = jsType(SliceOf(src.typ.Elem())).New(srcVal) 689 } 690 691 if stringCopy { 692 return js.Global.Call("$copyString", dstVal, srcVal).Int() 693 } 694 return js.Global.Call("$copySlice", dstVal, srcVal).Int() 695} 696 697func valueInterface(v Value, safe bool) interface{} { 698 if v.flag == 0 { 699 panic(&ValueError{"reflect.Value.Interface", 0}) 700 } 701 if safe && v.flag&flagRO != 0 { 702 panic("reflect.Value.Interface: cannot return value obtained from unexported field or method") 703 } 704 if v.flag&flagMethod != 0 { 705 v = makeMethodValue("Interface", v) 706 } 707 708 if isWrapped(v.typ) { 709 return interface{}(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) 710 } 711 return interface{}(unsafe.Pointer(v.object().Unsafe())) 712} 713 714func ifaceE2I(t *rtype, src interface{}, dst unsafe.Pointer) { 715 js.InternalObject(dst).Call("$set", js.InternalObject(src)) 716} 717 718func methodName() string { 719 return "?FIXME?" 720} 721 722func makeMethodValue(op string, v Value) Value { 723 if v.flag&flagMethod == 0 { 724 panic("reflect: internal error: invalid use of makePartialFunc") 725 } 726 727 _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) 728 rcvr := v.object() 729 if isWrapped(v.typ) { 730 rcvr = jsType(v.typ).New(rcvr) 731 } 732 fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 733 return js.InternalObject(fn).Call("apply", rcvr, arguments) 734 }) 735 return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} 736} 737 738func (t *rtype) pointers() bool { 739 switch t.Kind() { 740 case Ptr, Map, Chan, Func, Struct, Array: 741 return true 742 default: 743 return false 744 } 745} 746 747func (t *rtype) Comparable() bool { 748 switch t.Kind() { 749 case Func, Slice, Map: 750 return false 751 case Array: 752 return t.Elem().Comparable() 753 case Struct: 754 for i := 0; i < t.NumField(); i++ { 755 if !t.Field(i).Type.Comparable() { 756 return false 757 } 758 } 759 } 760 return true 761} 762 763func (t *rtype) Method(i int) (m Method) { 764 if t.Kind() == Interface { 765 tt := (*interfaceType)(unsafe.Pointer(t)) 766 return tt.Method(i) 767 } 768 methods := t.exportedMethods() 769 if i < 0 || i >= len(methods) { 770 panic("reflect: Method index out of range") 771 } 772 p := methods[i] 773 pname := t.nameOff(p.name) 774 m.Name = pname.name() 775 fl := flag(Func) 776 mtyp := t.typeOff(p.mtyp) 777 ft := (*funcType)(unsafe.Pointer(mtyp)) 778 in := make([]Type, 0, 1+len(ft.in())) 779 in = append(in, t) 780 for _, arg := range ft.in() { 781 in = append(in, arg) 782 } 783 out := make([]Type, 0, len(ft.out())) 784 for _, ret := range ft.out() { 785 out = append(out, ret) 786 } 787 mt := FuncOf(in, out, ft.IsVariadic()) 788 m.Type = mt 789 prop := js.Global.Call("$methodSet", js.InternalObject(t).Get("jsType")).Index(i).Get("prop").String() 790 fn := js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} { 791 rcvr := arguments[0] 792 return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) 793 }) 794 m.Func = Value{mt.(*rtype), unsafe.Pointer(fn.Unsafe()), fl} 795 796 m.Index = i 797 return m 798} 799 800func (v Value) object() *js.Object { 801 if v.typ.Kind() == Array || v.typ.Kind() == Struct { 802 return js.InternalObject(v.ptr) 803 } 804 if v.flag&flagIndir != 0 { 805 val := js.InternalObject(v.ptr).Call("$get") 806 if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { 807 switch v.typ.Kind() { 808 case Uint64, Int64: 809 val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) 810 case Complex64, Complex128: 811 val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) 812 case Slice: 813 if val == val.Get("constructor").Get("nil") { 814 val = jsType(v.typ).Get("nil") 815 break 816 } 817 newVal := jsType(v.typ).New(val.Get("$array")) 818 newVal.Set("$offset", val.Get("$offset")) 819 newVal.Set("$length", val.Get("$length")) 820 newVal.Set("$capacity", val.Get("$capacity")) 821 val = newVal 822 } 823 } 824 return js.InternalObject(val.Unsafe()) 825 } 826 return js.InternalObject(v.ptr) 827} 828 829func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { 830 if v.flag&flagMethod != 0 { 831 v = makeMethodValue(context, v) 832 } 833 834 switch { 835 case directlyAssignable(dst, v.typ): 836 // Overwrite type so that they match. 837 // Same memory layout, so no harm done. 838 fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() 839 fl |= flag(dst.Kind()) 840 return Value{dst, v.ptr, fl} 841 842 case implements(dst, v.typ): 843 if target == nil { 844 target = unsafe_New(dst) 845 } 846 // GopherJS: Skip the v.Kind() == Interface && v.IsNil() if statement 847 // from upstream. ifaceE2I below does not panic, and it needs 848 // to run, given its custom implementation. 849 x := valueInterface(v, false) 850 if dst.NumMethod() == 0 { 851 *(*interface{})(target) = x 852 } else { 853 ifaceE2I(dst, x, target) 854 } 855 return Value{dst, target, flagIndir | flag(Interface)} 856 } 857 858 // Failed. 859 panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) 860} 861 862var callHelper = js.Global.Get("$call").Interface().(func(...interface{}) *js.Object) 863 864func (v Value) Cap() int { 865 k := v.kind() 866 switch k { 867 case Array: 868 return v.typ.Len() 869 case Chan, Slice: 870 return v.object().Get("$capacity").Int() 871 } 872 panic(&ValueError{"reflect.Value.Cap", k}) 873} 874 875var jsObjectPtr = reflectType(js.Global.Get("$jsObjectPtr")) 876 877func wrapJsObject(typ Type, val *js.Object) *js.Object { 878 if typ == jsObjectPtr { 879 return jsType(jsObjectPtr).New(val) 880 } 881 return val 882} 883 884func unwrapJsObject(typ Type, val *js.Object) *js.Object { 885 if typ == jsObjectPtr { 886 return val.Get("object") 887 } 888 return val 889} 890 891func (v Value) Elem() Value { 892 switch k := v.kind(); k { 893 case Interface: 894 val := v.object() 895 if val == js.Global.Get("$ifaceNil") { 896 return Value{} 897 } 898 typ := reflectType(val.Get("constructor")) 899 return makeValue(typ, val.Get("$val"), v.flag.ro()) 900 901 case Ptr: 902 if v.IsNil() { 903 return Value{} 904 } 905 val := v.object() 906 tt := (*ptrType)(unsafe.Pointer(v.typ)) 907 fl := v.flag&flagRO | flagIndir | flagAddr 908 fl |= flag(tt.elem.Kind()) 909 return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} 910 911 default: 912 panic(&ValueError{"reflect.Value.Elem", k}) 913 } 914} 915 916func (v Value) Field(i int) Value { 917 if v.kind() != Struct { 918 panic(&ValueError{"reflect.Value.Field", v.kind()}) 919 } 920 tt := (*structType)(unsafe.Pointer(v.typ)) 921 if uint(i) >= uint(len(tt.fields)) { 922 panic("reflect: Field index out of range") 923 } 924 925 prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() 926 field := &tt.fields[i] 927 typ := field.typ 928 929 fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) 930 if !field.name.isExported() { 931 if field.embedded() { 932 fl |= flagEmbedRO 933 } else { 934 fl |= flagStickyRO 935 } 936 } 937 938 if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { 939 if jsTag := getJsTag(tag); jsTag != "" { 940 for { 941 v = v.Field(0) 942 if v.typ == jsObjectPtr { 943 o := v.object().Get("object") 944 return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( 945 js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), 946 js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), 947 ).Unsafe()), fl} 948 } 949 if v.typ.Kind() == Ptr { 950 v = v.Elem() 951 } 952 } 953 } 954 } 955 956 s := js.InternalObject(v.ptr) 957 if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { 958 return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( 959 js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), 960 js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), 961 ).Unsafe()), fl} 962 } 963 return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) 964} 965 966func getJsTag(tag string) string { 967 for tag != "" { 968 // skip leading space 969 i := 0 970 for i < len(tag) && tag[i] == ' ' { 971 i++ 972 } 973 tag = tag[i:] 974 if tag == "" { 975 break 976 } 977 978 // scan to colon. 979 // a space or a quote is a syntax error 980 i = 0 981 for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { 982 i++ 983 } 984 if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { 985 break 986 } 987 name := string(tag[:i]) 988 tag = tag[i+1:] 989 990 // scan quoted string to find value 991 i = 1 992 for i < len(tag) && tag[i] != '"' { 993 if tag[i] == '\\' { 994 i++ 995 } 996 i++ 997 } 998 if i >= len(tag) { 999 break 1000 } 1001 qvalue := string(tag[:i+1]) 1002 tag = tag[i+1:] 1003 1004 if name == "js" { 1005 value, _ := strconv.Unquote(qvalue) 1006 return value 1007 } 1008 } 1009 return "" 1010} 1011 1012func (v Value) Index(i int) Value { 1013 switch k := v.kind(); k { 1014 case Array: 1015 tt := (*arrayType)(unsafe.Pointer(v.typ)) 1016 if i < 0 || i > int(tt.len) { 1017 panic("reflect: array index out of range") 1018 } 1019 typ := tt.elem 1020 fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) 1021 1022 a := js.InternalObject(v.ptr) 1023 if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { 1024 return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( 1025 js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), 1026 js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), 1027 ).Unsafe()), fl} 1028 } 1029 return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) 1030 1031 case Slice: 1032 s := v.object() 1033 if i < 0 || i >= s.Get("$length").Int() { 1034 panic("reflect: slice index out of range") 1035 } 1036 tt := (*sliceType)(unsafe.Pointer(v.typ)) 1037 typ := tt.elem 1038 fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) 1039 1040 i += s.Get("$offset").Int() 1041 a := s.Get("$array") 1042 if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { 1043 return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( 1044 js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), 1045 js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), 1046 ).Unsafe()), fl} 1047 } 1048 return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) 1049 1050 case String: 1051 str := *(*string)(v.ptr) 1052 if i < 0 || i >= len(str) { 1053 panic("reflect: string index out of range") 1054 } 1055 fl := v.flag.ro() | flag(Uint8) | flagIndir 1056 c := str[i] 1057 return Value{uint8Type, unsafe.Pointer(&c), fl} 1058 1059 default: 1060 panic(&ValueError{"reflect.Value.Index", k}) 1061 } 1062} 1063 1064func (v Value) InterfaceData() [2]uintptr { 1065 panic(errors.New("InterfaceData is not supported by GopherJS")) 1066} 1067 1068func (v Value) IsNil() bool { 1069 switch k := v.kind(); k { 1070 case Ptr, Slice: 1071 return v.object() == jsType(v.typ).Get("nil") 1072 case Chan: 1073 return v.object() == js.Global.Get("$chanNil") 1074 case Func: 1075 return v.object() == js.Global.Get("$throwNilPointerError") 1076 case Map: 1077 return v.object() == js.InternalObject(false) 1078 case Interface: 1079 return v.object() == js.Global.Get("$ifaceNil") 1080 case UnsafePointer: 1081 return v.object().Unsafe() == 0 1082 default: 1083 panic(&ValueError{"reflect.Value.IsNil", k}) 1084 } 1085} 1086 1087func (v Value) Len() int { 1088 switch k := v.kind(); k { 1089 case Array, String: 1090 return v.object().Length() 1091 case Slice: 1092 return v.object().Get("$length").Int() 1093 case Chan: 1094 return v.object().Get("$buffer").Get("length").Int() 1095 case Map: 1096 return js.Global.Call("$keys", v.object()).Length() 1097 default: 1098 panic(&ValueError{"reflect.Value.Len", k}) 1099 } 1100} 1101 1102func (v Value) Pointer() uintptr { 1103 switch k := v.kind(); k { 1104 case Chan, Map, Ptr, UnsafePointer: 1105 if v.IsNil() { 1106 return 0 1107 } 1108 return v.object().Unsafe() 1109 case Func: 1110 if v.IsNil() { 1111 return 0 1112 } 1113 return 1 1114 case Slice: 1115 if v.IsNil() { 1116 return 0 1117 } 1118 return v.object().Get("$array").Unsafe() 1119 default: 1120 panic(&ValueError{"reflect.Value.Pointer", k}) 1121 } 1122} 1123 1124func (v Value) Set(x Value) { 1125 v.mustBeAssignable() 1126 x.mustBeExported() 1127 x = x.assignTo("reflect.Set", v.typ, nil) 1128 if v.flag&flagIndir != 0 { 1129 switch v.typ.Kind() { 1130 case Array: 1131 jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) 1132 case Interface: 1133 js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) 1134 case Struct: 1135 copyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) 1136 default: 1137 js.InternalObject(v.ptr).Call("$set", x.object()) 1138 } 1139 return 1140 } 1141 v.ptr = x.ptr 1142} 1143 1144func (v Value) SetBytes(x []byte) { 1145 v.mustBeAssignable() 1146 v.mustBe(Slice) 1147 if v.typ.Elem().Kind() != Uint8 { 1148 panic("reflect.Value.SetBytes of non-byte slice") 1149 } 1150 slice := js.InternalObject(x) 1151 if v.typ.Name() != "" || v.typ.Elem().Name() != "" { 1152 typedSlice := jsType(v.typ).New(slice.Get("$array")) 1153 typedSlice.Set("$offset", slice.Get("$offset")) 1154 typedSlice.Set("$length", slice.Get("$length")) 1155 typedSlice.Set("$capacity", slice.Get("$capacity")) 1156 slice = typedSlice 1157 } 1158 js.InternalObject(v.ptr).Call("$set", slice) 1159} 1160 1161func (v Value) SetCap(n int) { 1162 v.mustBeAssignable() 1163 v.mustBe(Slice) 1164 s := js.InternalObject(v.ptr).Call("$get") 1165 if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { 1166 panic("reflect: slice capacity out of range in SetCap") 1167 } 1168 newSlice := jsType(v.typ).New(s.Get("$array")) 1169 newSlice.Set("$offset", s.Get("$offset")) 1170 newSlice.Set("$length", s.Get("$length")) 1171 newSlice.Set("$capacity", n) 1172 js.InternalObject(v.ptr).Call("$set", newSlice) 1173} 1174 1175func (v Value) SetLen(n int) { 1176 v.mustBeAssignable() 1177 v.mustBe(Slice) 1178 s := js.InternalObject(v.ptr).Call("$get") 1179 if n < 0 || n > s.Get("$capacity").Int() { 1180 panic("reflect: slice length out of range in SetLen") 1181 } 1182 newSlice := jsType(v.typ).New(s.Get("$array")) 1183 newSlice.Set("$offset", s.Get("$offset")) 1184 newSlice.Set("$length", n) 1185 newSlice.Set("$capacity", s.Get("$capacity")) 1186 js.InternalObject(v.ptr).Call("$set", newSlice) 1187} 1188 1189func (v Value) Slice(i, j int) Value { 1190 var ( 1191 cap int 1192 typ Type 1193 s *js.Object 1194 ) 1195 switch kind := v.kind(); kind { 1196 case Array: 1197 if v.flag&flagAddr == 0 { 1198 panic("reflect.Value.Slice: slice of unaddressable array") 1199 } 1200 tt := (*arrayType)(unsafe.Pointer(v.typ)) 1201 cap = int(tt.len) 1202 typ = SliceOf(tt.elem) 1203 s = jsType(typ).New(v.object()) 1204 1205 case Slice: 1206 typ = v.typ 1207 s = v.object() 1208 cap = s.Get("$capacity").Int() 1209 1210 case String: 1211 str := *(*string)(v.ptr) 1212 if i < 0 || j < i || j > len(str) { 1213 panic("reflect.Value.Slice: string slice index out of bounds") 1214 } 1215 return ValueOf(str[i:j]) 1216 1217 default: 1218 panic(&ValueError{"reflect.Value.Slice", kind}) 1219 } 1220 1221 if i < 0 || j < i || j > cap { 1222 panic("reflect.Value.Slice: slice index out of bounds") 1223 } 1224 1225 return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) 1226} 1227 1228func (v Value) Slice3(i, j, k int) Value { 1229 var ( 1230 cap int 1231 typ Type 1232 s *js.Object 1233 ) 1234 switch kind := v.kind(); kind { 1235 case Array: 1236 if v.flag&flagAddr == 0 { 1237 panic("reflect.Value.Slice: slice of unaddressable array") 1238 } 1239 tt := (*arrayType)(unsafe.Pointer(v.typ)) 1240 cap = int(tt.len) 1241 typ = SliceOf(tt.elem) 1242 s = jsType(typ).New(v.object()) 1243 1244 case Slice: 1245 typ = v.typ 1246 s = v.object() 1247 cap = s.Get("$capacity").Int() 1248 1249 default: 1250 panic(&ValueError{"reflect.Value.Slice3", kind}) 1251 } 1252 1253 if i < 0 || j < i || k < j || k > cap { 1254 panic("reflect.Value.Slice3: slice index out of bounds") 1255 } 1256 1257 return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) 1258} 1259 1260func (v Value) Close() { 1261 v.mustBe(Chan) 1262 v.mustBeExported() 1263 js.Global.Call("$close", v.object()) 1264} 1265 1266var selectHelper = js.Global.Get("$select").Interface().(func(...interface{}) *js.Object) 1267 1268func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { 1269 comms := [][]*js.Object{{js.InternalObject(ch)}} 1270 if nb { 1271 comms = append(comms, []*js.Object{}) 1272 } 1273 selectRes := selectHelper(comms) 1274 if nb && selectRes.Index(0).Int() == 1 { 1275 return false, false 1276 } 1277 recvRes := selectRes.Index(1) 1278 js.InternalObject(val).Call("$set", recvRes.Index(0)) 1279 return true, recvRes.Index(1).Bool() 1280} 1281 1282func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { 1283 comms := [][]*js.Object{{js.InternalObject(ch), js.InternalObject(val).Call("$get")}} 1284 if nb { 1285 comms = append(comms, []*js.Object{}) 1286 } 1287 selectRes := selectHelper(comms) 1288 if nb && selectRes.Index(0).Int() == 1 { 1289 return false 1290 } 1291 return true 1292} 1293 1294func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { 1295 comms := make([][]*js.Object, len(rselects)) 1296 for i, s := range rselects { 1297 switch SelectDir(s.dir) { 1298 case SelectDefault: 1299 comms[i] = []*js.Object{} 1300 case SelectRecv: 1301 ch := js.Global.Get("$chanNil") 1302 if js.InternalObject(s.ch) != js.InternalObject(0) { 1303 ch = js.InternalObject(s.ch) 1304 } 1305 comms[i] = []*js.Object{ch} 1306 case SelectSend: 1307 ch := js.Global.Get("$chanNil") 1308 var val *js.Object 1309 if js.InternalObject(s.ch) != js.InternalObject(0) { 1310 ch = js.InternalObject(s.ch) 1311 val = js.InternalObject(s.val).Call("$get") 1312 } 1313 comms[i] = []*js.Object{ch, val} 1314 } 1315 } 1316 selectRes := selectHelper(comms) 1317 c := selectRes.Index(0).Int() 1318 if SelectDir(rselects[c].dir) == SelectRecv { 1319 recvRes := selectRes.Index(1) 1320 js.InternalObject(rselects[c].val).Call("$set", recvRes.Index(0)) 1321 return c, recvRes.Index(1).Bool() 1322 } 1323 return c, false 1324} 1325 1326func DeepEqual(a1, a2 interface{}) bool { 1327 i1 := js.InternalObject(a1) 1328 i2 := js.InternalObject(a2) 1329 if i1 == i2 { 1330 return true 1331 } 1332 if i1 == nil || i2 == nil || i1.Get("constructor") != i2.Get("constructor") { 1333 return false 1334 } 1335 return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) 1336} 1337 1338func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { 1339 if !v1.IsValid() || !v2.IsValid() { 1340 return !v1.IsValid() && !v2.IsValid() 1341 } 1342 if v1.Type() != v2.Type() { 1343 return false 1344 } 1345 if v1.Type() == jsObjectPtr { 1346 return unwrapJsObject(jsObjectPtr, v1.object()) == unwrapJsObject(jsObjectPtr, v2.object()) 1347 } 1348 1349 switch v1.Kind() { 1350 case Array, Map, Slice, Struct: 1351 for _, entry := range visited { 1352 if v1.ptr == entry[0] && v2.ptr == entry[1] { 1353 return true 1354 } 1355 } 1356 visited = append(visited, [2]unsafe.Pointer{v1.ptr, v2.ptr}) 1357 } 1358 1359 switch v1.Kind() { 1360 case Array, Slice: 1361 if v1.Kind() == Slice { 1362 if v1.IsNil() != v2.IsNil() { 1363 return false 1364 } 1365 if v1.object() == v2.object() { 1366 return true 1367 } 1368 } 1369 var n = v1.Len() 1370 if n != v2.Len() { 1371 return false 1372 } 1373 for i := 0; i < n; i++ { 1374 if !deepValueEqualJs(v1.Index(i), v2.Index(i), visited) { 1375 return false 1376 } 1377 } 1378 return true 1379 case Interface: 1380 if v1.IsNil() || v2.IsNil() { 1381 return v1.IsNil() && v2.IsNil() 1382 } 1383 return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) 1384 case Ptr: 1385 return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) 1386 case Struct: 1387 var n = v1.NumField() 1388 for i := 0; i < n; i++ { 1389 if !deepValueEqualJs(v1.Field(i), v2.Field(i), visited) { 1390 return false 1391 } 1392 } 1393 return true 1394 case Map: 1395 if v1.IsNil() != v2.IsNil() { 1396 return false 1397 } 1398 if v1.object() == v2.object() { 1399 return true 1400 } 1401 var keys = v1.MapKeys() 1402 if len(keys) != v2.Len() { 1403 return false 1404 } 1405 for _, k := range keys { 1406 val1 := v1.MapIndex(k) 1407 val2 := v2.MapIndex(k) 1408 if !val1.IsValid() || !val2.IsValid() || !deepValueEqualJs(val1, val2, visited) { 1409 return false 1410 } 1411 } 1412 return true 1413 case Func: 1414 return v1.IsNil() && v2.IsNil() 1415 case UnsafePointer: 1416 return v1.object() == v2.object() 1417 } 1418 1419 return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1, false)), js.InternalObject(valueInterface(v2, false))).Bool() 1420} 1421