1
0
mirror of https://github.com/golang/go synced 2024-11-22 09:24:41 -07:00

gob: add support for maps.

Because maps are mostly a hidden type, they must be
implemented using reflection values and will not be as
efficient as arrays and slices.

R=rsc
CC=golang-dev
https://golang.org/cl/1127041
This commit is contained in:
Rob Pike 2010-05-05 16:46:39 -07:00
parent 46152bb9fe
commit 7861da737e
6 changed files with 254 additions and 43 deletions

View File

@ -572,6 +572,7 @@ func TestEndToEnd(t *testing.T) {
s2 := "string2" s2 := "string2"
type T1 struct { type T1 struct {
a, b, c int a, b, c int
m map[string]*float
n *[3]float n *[3]float
strs *[2]string strs *[2]string
int64s *[]int64 int64s *[]int64
@ -579,10 +580,13 @@ func TestEndToEnd(t *testing.T) {
y []byte y []byte
t *T2 t *T2
} }
pi := 3.14159
e := 2.71828
t1 := &T1{ t1 := &T1{
a: 17, a: 17,
b: 18, b: 18,
c: -5, c: -5,
m: map[string]*float{"pi": &pi, "e": &e},
n: &[3]float{1.5, 2.5, 3.5}, n: &[3]float{1.5, 2.5, 3.5},
strs: &[2]string{s1, s2}, strs: &[2]string{s1, s2},
int64s: &[]int64{77, 89, 123412342134}, int64s: &[]int64{77, 89, 123412342134},
@ -921,6 +925,7 @@ type IT0 struct {
ignore_g string ignore_g string
ignore_h []byte ignore_h []byte
ignore_i *RT1 ignore_i *RT1
ignore_m map[string]int
c float c float
} }
@ -937,6 +942,7 @@ func TestIgnoredFields(t *testing.T) {
it0.ignore_g = "pay no attention" it0.ignore_g = "pay no attention"
it0.ignore_h = []byte("to the curtain") it0.ignore_h = []byte("to the curtain")
it0.ignore_i = &RT1{3.1, "hi", 7, "hello"} it0.ignore_i = &RT1{3.1, "hi", 7, "hello"}
it0.ignore_m = map[string]int{"one": 1, "two": 2}
b := new(bytes.Buffer) b := new(bytes.Buffer)
NewEncoder(b).Encode(it0) NewEncoder(b).Encode(it0)

View File

@ -447,6 +447,49 @@ func decodeArray(atyp *reflect.ArrayType, state *decodeState, p uintptr, elemOp
return decodeArrayHelper(state, p, elemOp, elemWid, length, elemIndir, ovfl) return decodeArrayHelper(state, p, elemOp, elemWid, length, elemIndir, ovfl)
} }
func decodeIntoValue(state *decodeState, op decOp, indir int, v reflect.Value, ovfl os.ErrorString) reflect.Value {
instr := &decInstr{op, 0, indir, 0, ovfl}
up := unsafe.Pointer(v.Addr())
if indir > 1 {
up = decIndirect(up, indir)
}
op(instr, state, up)
return v
}
func decodeMap(mtyp *reflect.MapType, state *decodeState, p uintptr, keyOp, elemOp decOp, indir, keyIndir, elemIndir int, ovfl os.ErrorString) os.Error {
if indir > 0 {
up := unsafe.Pointer(p)
if *(*unsafe.Pointer)(up) == nil {
// Allocate object.
*(*unsafe.Pointer)(up) = unsafe.New(mtyp)
}
p = *(*uintptr)(up)
}
up := unsafe.Pointer(p)
if *(*unsafe.Pointer)(up) == nil { // maps are represented as a pointer in the runtime
// Allocate map.
*(*unsafe.Pointer)(up) = unsafe.Pointer(reflect.MakeMap(mtyp).Get())
}
// Maps cannot be accessed by moving addresses around the way
// that slices etc. can. We must recover a full reflection value for
// the iteration.
v := reflect.NewValue(unsafe.Unreflect(mtyp, unsafe.Pointer((p)))).(*reflect.MapValue)
n := int(decodeUint(state))
for i := 0; i < n && state.err == nil; i++ {
key := decodeIntoValue(state, keyOp, keyIndir, reflect.MakeZero(mtyp.Key()), ovfl)
if state.err != nil {
break
}
elem := decodeIntoValue(state, elemOp, elemIndir, reflect.MakeZero(mtyp.Elem()), ovfl)
if state.err != nil {
break
}
v.SetElem(key, elem)
}
return state.err
}
func ignoreArrayHelper(state *decodeState, elemOp decOp, length int) os.Error { func ignoreArrayHelper(state *decodeState, elemOp decOp, length int) os.Error {
instr := &decInstr{elemOp, 0, 0, 0, os.ErrorString("no error")} instr := &decInstr{elemOp, 0, 0, 0, os.ErrorString("no error")}
for i := 0; i < length && state.err == nil; i++ { for i := 0; i < length && state.err == nil; i++ {
@ -462,6 +505,18 @@ func ignoreArray(state *decodeState, elemOp decOp, length int) os.Error {
return ignoreArrayHelper(state, elemOp, length) return ignoreArrayHelper(state, elemOp, length)
} }
func ignoreMap(state *decodeState, keyOp, elemOp decOp) os.Error {
n := int(decodeUint(state))
keyInstr := &decInstr{keyOp, 0, 0, 0, os.ErrorString("no error")}
elemInstr := &decInstr{elemOp, 0, 0, 0, os.ErrorString("no error")}
for i := 0; i < n && state.err == nil; i++ {
keyOp(keyInstr, state, nil)
elemOp(elemInstr, state, nil)
}
return state.err
}
func decodeSlice(atyp *reflect.SliceType, state *decodeState, p uintptr, elemOp decOp, elemWid uintptr, indir, elemIndir int, ovfl os.ErrorString) os.Error { func decodeSlice(atyp *reflect.SliceType, state *decodeState, p uintptr, elemOp decOp, elemWid uintptr, indir, elemIndir int, ovfl os.ErrorString) os.Error {
n := int(uintptr(decodeUint(state))) n := int(uintptr(decodeUint(state)))
if indir > 0 { if indir > 0 {
@ -517,6 +572,39 @@ func (dec *Decoder) decOpFor(wireId typeId, rt reflect.Type, name string) (decOp
if !ok { if !ok {
// Special cases // Special cases
switch t := typ.(type) { switch t := typ.(type) {
case *reflect.ArrayType:
name = "element of " + name
elemId := dec.wireType[wireId].arrayT.Elem
elemOp, elemIndir, err := dec.decOpFor(elemId, t.Elem(), name)
if err != nil {
return nil, 0, err
}
ovfl := overflow(name)
op = func(i *decInstr, state *decodeState, p unsafe.Pointer) {
state.err = decodeArray(t, state, uintptr(p), elemOp, t.Elem().Size(), t.Len(), i.indir, elemIndir, ovfl)
}
case *reflect.MapType:
name = "element of " + name
keyId := dec.wireType[wireId].mapT.Key
elemId := dec.wireType[wireId].mapT.Elem
keyOp, keyIndir, err := dec.decOpFor(keyId, t.Key(), name)
if err != nil {
return nil, 0, err
}
elemOp, elemIndir, err := dec.decOpFor(elemId, t.Elem(), name)
if err != nil {
return nil, 0, err
}
ovfl := overflow(name)
op = func(i *decInstr, state *decodeState, p unsafe.Pointer) {
up := unsafe.Pointer(p)
if indir > 1 {
up = decIndirect(up, indir)
}
state.err = decodeMap(t, state, uintptr(up), keyOp, elemOp, i.indir, keyIndir, elemIndir, ovfl)
}
case *reflect.SliceType: case *reflect.SliceType:
name = "element of " + name name = "element of " + name
if _, ok := t.Elem().(*reflect.Uint8Type); ok { if _, ok := t.Elem().(*reflect.Uint8Type); ok {
@ -527,7 +615,7 @@ func (dec *Decoder) decOpFor(wireId typeId, rt reflect.Type, name string) (decOp
if tt, ok := builtinIdToType[wireId]; ok { if tt, ok := builtinIdToType[wireId]; ok {
elemId = tt.(*sliceType).Elem elemId = tt.(*sliceType).Elem
} else { } else {
elemId = dec.wireType[wireId].slice.Elem elemId = dec.wireType[wireId].sliceT.Elem
} }
elemOp, elemIndir, err := dec.decOpFor(elemId, t.Elem(), name) elemOp, elemIndir, err := dec.decOpFor(elemId, t.Elem(), name)
if err != nil { if err != nil {
@ -538,18 +626,6 @@ func (dec *Decoder) decOpFor(wireId typeId, rt reflect.Type, name string) (decOp
state.err = decodeSlice(t, state, uintptr(p), elemOp, t.Elem().Size(), i.indir, elemIndir, ovfl) state.err = decodeSlice(t, state, uintptr(p), elemOp, t.Elem().Size(), i.indir, elemIndir, ovfl)
} }
case *reflect.ArrayType:
name = "element of " + name
elemId := dec.wireType[wireId].array.Elem
elemOp, elemIndir, err := dec.decOpFor(elemId, t.Elem(), name)
if err != nil {
return nil, 0, err
}
ovfl := overflow(name)
op = func(i *decInstr, state *decodeState, p unsafe.Pointer) {
state.err = decodeArray(t, state, uintptr(p), elemOp, t.Elem().Size(), t.Len(), i.indir, elemIndir, ovfl)
}
case *reflect.StructType: case *reflect.StructType:
// Generate a closure that calls out to the engine for the nested type. // Generate a closure that calls out to the engine for the nested type.
enginePtr, err := dec.getDecEnginePtr(wireId, typ) enginePtr, err := dec.getDecEnginePtr(wireId, typ)
@ -575,18 +651,33 @@ func (dec *Decoder) decIgnoreOpFor(wireId typeId) (decOp, os.Error) {
// Special cases // Special cases
wire := dec.wireType[wireId] wire := dec.wireType[wireId]
switch { switch {
case wire.array != nil: case wire.arrayT != nil:
elemId := wire.array.Elem elemId := wire.arrayT.Elem
elemOp, err := dec.decIgnoreOpFor(elemId) elemOp, err := dec.decIgnoreOpFor(elemId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
op = func(i *decInstr, state *decodeState, p unsafe.Pointer) { op = func(i *decInstr, state *decodeState, p unsafe.Pointer) {
state.err = ignoreArray(state, elemOp, wire.array.Len) state.err = ignoreArray(state, elemOp, wire.arrayT.Len)
} }
case wire.slice != nil: case wire.mapT != nil:
elemId := wire.slice.Elem keyId := dec.wireType[wireId].mapT.Key
elemId := dec.wireType[wireId].mapT.Elem
keyOp, err := dec.decIgnoreOpFor(keyId)
if err != nil {
return nil, err
}
elemOp, err := dec.decIgnoreOpFor(elemId)
if err != nil {
return nil, err
}
op = func(i *decInstr, state *decodeState, p unsafe.Pointer) {
state.err = ignoreMap(state, keyOp, elemOp)
}
case wire.sliceT != nil:
elemId := wire.sliceT.Elem
elemOp, err := dec.decIgnoreOpFor(elemId) elemOp, err := dec.decIgnoreOpFor(elemId)
if err != nil { if err != nil {
return nil, err return nil, err
@ -595,7 +686,7 @@ func (dec *Decoder) decIgnoreOpFor(wireId typeId) (decOp, os.Error) {
state.err = ignoreSlice(state, elemOp) state.err = ignoreSlice(state, elemOp)
} }
case wire.strct != nil: case wire.structT != nil:
// Generate a closure that calls out to the engine for the nested type. // Generate a closure that calls out to the engine for the nested type.
enginePtr, err := dec.getIgnoreEnginePtr(wireId) enginePtr, err := dec.getIgnoreEnginePtr(wireId)
if err != nil { if err != nil {
@ -640,11 +731,18 @@ func (dec *Decoder) compatibleType(fr reflect.Type, fw typeId) bool {
return fw == tString return fw == tString
case *reflect.ArrayType: case *reflect.ArrayType:
wire, ok := dec.wireType[fw] wire, ok := dec.wireType[fw]
if !ok || wire.array == nil { if !ok || wire.arrayT == nil {
return false return false
} }
array := wire.array array := wire.arrayT
return ok && t.Len() == array.Len && dec.compatibleType(t.Elem(), array.Elem) return t.Len() == array.Len && dec.compatibleType(t.Elem(), array.Elem)
case *reflect.MapType:
wire, ok := dec.wireType[fw]
if !ok || wire.mapT == nil {
return false
}
mapType := wire.mapT
return dec.compatibleType(t.Key(), mapType.Key) && dec.compatibleType(t.Elem(), mapType.Elem)
case *reflect.SliceType: case *reflect.SliceType:
// Is it an array of bytes? // Is it an array of bytes?
et := t.Elem() et := t.Elem()
@ -656,7 +754,7 @@ func (dec *Decoder) compatibleType(fr reflect.Type, fw typeId) bool {
if tt, ok := builtinIdToType[fw]; ok { if tt, ok := builtinIdToType[fw]; ok {
sw = tt.(*sliceType) sw = tt.(*sliceType)
} else { } else {
sw = dec.wireType[fw].slice sw = dec.wireType[fw].sliceT
} }
elem, _ := indirect(t.Elem()) elem, _ := indirect(t.Elem())
return sw != nil && dec.compatibleType(elem, sw.Elem) return sw != nil && dec.compatibleType(elem, sw.Elem)
@ -677,7 +775,7 @@ func (dec *Decoder) compileDec(remoteId typeId, rt reflect.Type) (engine *decEng
if !ok1 || !ok2 { if !ok1 || !ok2 {
return nil, errNotStruct return nil, errNotStruct
} }
wireStruct = w.strct wireStruct = w.structT
} }
engine = new(decEngine) engine = new(decEngine)
engine.instr = make([]decInstr, len(wireStruct.field)) engine.instr = make([]decInstr, len(wireStruct.field))
@ -760,7 +858,7 @@ func (dec *Decoder) decode(wireId typeId, e interface{}) os.Error {
return err return err
} }
engine := *enginePtr engine := *enginePtr
if engine.numInstr == 0 && st.NumField() > 0 && len(dec.wireType[wireId].strct.field) > 0 { if engine.numInstr == 0 && st.NumField() > 0 && len(dec.wireType[wireId].structT.field) > 0 {
name := rt.Name() name := rt.Name()
return os.ErrorString("gob: type mismatch: no fields matched compiling decoder for " + name) return os.ErrorString("gob: type mismatch: no fields matched compiling decoder for " + name)
} }

View File

@ -22,7 +22,7 @@ const uint64Size = unsafe.Sizeof(uint64(0))
type encoderState struct { type encoderState struct {
b *bytes.Buffer b *bytes.Buffer
err os.Error // error encountered during encoding. err os.Error // error encountered during encoding.
inArray bool // encoding an array element inArray bool // encoding an array element or map key/value pair
fieldnum int // the last field number written. fieldnum int // the last field number written.
buf [1 + uint64Size]byte // buffer used by the encoder; here to avoid allocation. buf [1 + uint64Size]byte // buffer used by the encoder; here to avoid allocation.
} }
@ -297,7 +297,7 @@ func encodeStruct(engine *encEngine, b *bytes.Buffer, basep uintptr) os.Error {
return state.err return state.err
} }
func encodeArray(b *bytes.Buffer, p uintptr, op encOp, elemWid uintptr, length int, elemIndir int) os.Error { func encodeArray(b *bytes.Buffer, p uintptr, op encOp, elemWid uintptr, elemIndir int, length int) os.Error {
state := new(encoderState) state := new(encoderState)
state.b = b state.b = b
state.fieldnum = -1 state.fieldnum = -1
@ -319,6 +319,39 @@ func encodeArray(b *bytes.Buffer, p uintptr, op encOp, elemWid uintptr, length i
return state.err return state.err
} }
func encodeReflectValue(state *encoderState, v reflect.Value, op encOp, indir int) {
for i := 0; i < indir && v != nil; i++ {
v = reflect.Indirect(v)
}
if v == nil {
state.err = os.ErrorString("gob: encodeMap: nil element")
return
}
op(nil, state, unsafe.Pointer(v.Addr()))
}
func encodeMap(b *bytes.Buffer, rt reflect.Type, p uintptr, keyOp, elemOp encOp, keyIndir, elemIndir int) os.Error {
state := new(encoderState)
state.b = b
state.fieldnum = -1
state.inArray = true
// Maps cannot be accessed by moving addresses around the way
// that slices etc. can. We must recover a full reflection value for
// the iteration.
v := reflect.NewValue(unsafe.Unreflect(rt, unsafe.Pointer((p))))
mv := reflect.Indirect(v).(*reflect.MapValue)
keys := mv.Keys()
encodeUint(state, uint64(len(keys)))
for _, key := range keys {
if state.err != nil {
break
}
encodeReflectValue(state, key, keyOp, keyIndir)
encodeReflectValue(state, mv.Elem(key), elemOp, elemIndir)
}
return state.err
}
var encOpMap = map[reflect.Type]encOp{ var encOpMap = map[reflect.Type]encOp{
valueKind(false): encBool, valueKind(false): encBool,
valueKind(int(0)): encInt, valueKind(int(0)): encInt,
@ -344,7 +377,6 @@ func encOpFor(rt reflect.Type) (encOp, int, os.Error) {
typ, indir := indirect(rt) typ, indir := indirect(rt)
op, ok := encOpMap[reflect.Typeof(typ)] op, ok := encOpMap[reflect.Typeof(typ)]
if !ok { if !ok {
typ, _ := indirect(rt)
// Special cases // Special cases
switch t := typ.(type) { switch t := typ.(type) {
case *reflect.SliceType: case *reflect.SliceType:
@ -363,7 +395,7 @@ func encOpFor(rt reflect.Type) (encOp, int, os.Error) {
return return
} }
state.update(i) state.update(i)
state.err = encodeArray(state.b, slice.Data, elemOp, t.Elem().Size(), int(slice.Len), indir) state.err = encodeArray(state.b, slice.Data, elemOp, t.Elem().Size(), indir, int(slice.Len))
} }
case *reflect.ArrayType: case *reflect.ArrayType:
// True arrays have size in the type. // True arrays have size in the type.
@ -373,7 +405,20 @@ func encOpFor(rt reflect.Type) (encOp, int, os.Error) {
} }
op = func(i *encInstr, state *encoderState, p unsafe.Pointer) { op = func(i *encInstr, state *encoderState, p unsafe.Pointer) {
state.update(i) state.update(i)
state.err = encodeArray(state.b, uintptr(p), elemOp, t.Elem().Size(), t.Len(), indir) state.err = encodeArray(state.b, uintptr(p), elemOp, t.Elem().Size(), indir, t.Len())
}
case *reflect.MapType:
keyOp, keyIndir, err := encOpFor(t.Key())
if err != nil {
return nil, 0, err
}
elemOp, elemIndir, err := encOpFor(t.Elem())
if err != nil {
return nil, 0, err
}
op = func(i *encInstr, state *encoderState, p unsafe.Pointer) {
state.update(i)
state.err = encodeMap(state.b, typ, uintptr(p), keyOp, elemOp, keyIndir, elemIndir)
} }
case *reflect.StructType: case *reflect.StructType:
// Generate a closure that calls out to the engine for the nested type. // Generate a closure that calls out to the engine for the nested type.

View File

@ -71,9 +71,8 @@
Structs, arrays and slices are also supported. Strings and arrays of bytes are Structs, arrays and slices are also supported. Strings and arrays of bytes are
supported with a special, efficient representation (see below). supported with a special, efficient representation (see below).
Maps are not supported yet, but they will be. Interfaces, functions, and channels Interfaces, functions, and channels cannot be sent in a gob. Attempting
cannot be sent in a gob. Attempting to encode a value that contains one will to encode a value that contains one will fail.
fail.
The rest of this comment documents the encoding, details that are not important The rest of this comment documents the encoding, details that are not important
for most users. Details are presented bottom-up. for most users. Details are presented bottom-up.
@ -263,10 +262,13 @@ func (enc *Encoder) sendType(origt reflect.Type) {
case *reflect.ArrayType: case *reflect.ArrayType:
// arrays must be sent so we know their lengths and element types. // arrays must be sent so we know their lengths and element types.
break break
case *reflect.MapType:
// maps must be sent so we know their lengths and key/value types.
break
case *reflect.StructType: case *reflect.StructType:
// structs must be sent so we know their fields. // structs must be sent so we know their fields.
break break
case *reflect.ChanType, *reflect.FuncType, *reflect.MapType, *reflect.InterfaceType: case *reflect.ChanType, *reflect.FuncType, *reflect.InterfaceType:
// Probably a bad field in a struct. // Probably a bad field in a struct.
enc.badType(rt) enc.badType(rt)
return return

View File

@ -142,6 +142,31 @@ func (a *arrayType) safeString(seen map[typeId]bool) string {
func (a *arrayType) string() string { return a.safeString(make(map[typeId]bool)) } func (a *arrayType) string() string { return a.safeString(make(map[typeId]bool)) }
// Map type
type mapType struct {
commonType
Key typeId
Elem typeId
}
func newMapType(name string, key, elem gobType) *mapType {
m := &mapType{commonType{name: name}, key.id(), elem.id()}
setTypeId(m)
return m
}
func (m *mapType) safeString(seen map[typeId]bool) string {
if seen[m._id] {
return m.name
}
seen[m._id] = true
key := m.Key.gobType().safeString(seen)
elem := m.Elem.gobType().safeString(seen)
return fmt.Sprintf("map[%s]%s", key, elem)
}
func (m *mapType) string() string { return m.safeString(make(map[typeId]bool)) }
// Slice type // Slice type
type sliceType struct { type sliceType struct {
commonType commonType
@ -239,6 +264,17 @@ func newTypeObject(name string, rt reflect.Type) (gobType, os.Error) {
} }
return newArrayType(name, gt, t.Len()), nil return newArrayType(name, gt, t.Len()), nil
case *reflect.MapType:
kt, err := getType("", t.Key())
if err != nil {
return nil, err
}
vt, err := getType("", t.Elem())
if err != nil {
return nil, err
}
return newMapType(name, kt, vt), nil
case *reflect.SliceType: case *reflect.SliceType:
// []byte == []uint8 is a special case // []byte == []uint8 is a special case
if _, ok := t.Elem().(*reflect.Uint8Type); ok { if _, ok := t.Elem().(*reflect.Uint8Type); ok {
@ -330,16 +366,18 @@ func bootstrapType(name string, e interface{}, expect typeId) typeId {
// using the gob rules for sending a structure, except that we assume the // using the gob rules for sending a structure, except that we assume the
// ids for wireType and structType are known. The relevant pieces // ids for wireType and structType are known. The relevant pieces
// are built in encode.go's init() function. // are built in encode.go's init() function.
// To maintain binary compatibility, if you extend this type, always put
// the new fields last.
type wireType struct { type wireType struct {
array *arrayType arrayT *arrayType
slice *sliceType sliceT *sliceType
strct *structType structT *structType
mapT *mapType
} }
func (w *wireType) name() string { func (w *wireType) name() string {
if w.strct != nil { if w.structT != nil {
return w.strct.name return w.structT.name
} }
return "unknown" return "unknown"
} }
@ -370,14 +408,16 @@ func getTypeInfo(rt reflect.Type) (*typeInfo, os.Error) {
t := info.id.gobType() t := info.id.gobType()
switch typ := rt.(type) { switch typ := rt.(type) {
case *reflect.ArrayType: case *reflect.ArrayType:
info.wire = &wireType{array: t.(*arrayType)} info.wire = &wireType{arrayT: t.(*arrayType)}
case *reflect.MapType:
info.wire = &wireType{mapT: t.(*mapType)}
case *reflect.SliceType: case *reflect.SliceType:
// []byte == []uint8 is a special case handled separately // []byte == []uint8 is a special case handled separately
if _, ok := typ.Elem().(*reflect.Uint8Type); !ok { if _, ok := typ.Elem().(*reflect.Uint8Type); !ok {
info.wire = &wireType{slice: t.(*sliceType)} info.wire = &wireType{sliceT: t.(*sliceType)}
} }
case *reflect.StructType: case *reflect.StructType:
info.wire = &wireType{strct: t.(*structType)} info.wire = &wireType{structT: t.(*structType)}
} }
typeInfoMap[rt] = info typeInfoMap[rt] = info
} }

View File

@ -105,6 +105,26 @@ func TestSliceType(t *testing.T) {
} }
} }
func TestMapType(t *testing.T) {
var m map[string]int
mapStringInt := getTypeUnlocked("map", reflect.Typeof(m))
var newm map[string]int
newMapStringInt := getTypeUnlocked("map1", reflect.Typeof(newm))
if mapStringInt != newMapStringInt {
t.Errorf("second registration of map[string]int creates new type")
}
var b map[string]bool
mapStringBool := getTypeUnlocked("", reflect.Typeof(b))
if mapStringBool == mapStringInt {
t.Errorf("registration of map[string]bool creates same type as map[string]int")
}
str := mapStringBool.string()
expected := "map[string]bool"
if str != expected {
t.Errorf("map printed as %q; expected %q", str, expected)
}
}
type Bar struct { type Bar struct {
x string x string
} }