Skip to content

Commit 3a1dedd

Browse files
committed
fix(encode): handle non-addressable values with pointer receivers (#3)
Add ensureAddr helper that creates an addressable copy when v.CanAddr() is false, instead of returning an error. Applied to encodeCustomValuePtr, marshalValuePtr, marshalBinaryValueAddr, marshalTextValueAddr, and makeExtEncoderAddr. The common case (struct fields) remains zero-cost since they are already addressable. Closes #3
1 parent 9e0e881 commit 3a1dedd

3 files changed

Lines changed: 45 additions & 20 deletions

File tree

encode_value.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,21 @@ func ptrEncoderFunc(typ reflect.Type) encoderFunc {
132132
}
133133
}
134134

135-
func encodeCustomValuePtr(e *Encoder, v reflect.Value) error {
136-
if !v.CanAddr() {
137-
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
135+
// ensureAddr returns v.Addr() if v is addressable, otherwise it allocates
136+
// a new value, copies v into it, and returns the pointer. This allows
137+
// types with pointer receivers to encode even when the source is not
138+
// addressable (e.g. map values, bare literals).
139+
func ensureAddr(v reflect.Value) reflect.Value {
140+
if v.CanAddr() {
141+
return v.Addr()
138142
}
139-
encoder := v.Addr().Interface().(CustomEncoder)
143+
ptr := reflect.New(v.Type())
144+
ptr.Elem().Set(v)
145+
return ptr
146+
}
147+
148+
func encodeCustomValuePtr(e *Encoder, v reflect.Value) error {
149+
encoder := ensureAddr(v).Interface().(CustomEncoder)
140150
return encoder.EncodeMsgpack(e)
141151
}
142152

@@ -150,10 +160,7 @@ func encodeCustomValue(e *Encoder, v reflect.Value) error {
150160
}
151161

152162
func marshalValuePtr(e *Encoder, v reflect.Value) error {
153-
if !v.CanAddr() {
154-
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
155-
}
156-
return marshalValue(e, v.Addr())
163+
return marshalValue(e, ensureAddr(v))
157164
}
158165

159166
func marshalValue(e *Encoder, v reflect.Value) error {
@@ -210,10 +217,7 @@ func nilableType(t reflect.Type) bool {
210217
//------------------------------------------------------------------------------
211218

212219
func marshalBinaryValueAddr(e *Encoder, v reflect.Value) error {
213-
if !v.CanAddr() {
214-
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
215-
}
216-
return marshalBinaryValue(e, v.Addr())
220+
return marshalBinaryValue(e, ensureAddr(v))
217221
}
218222

219223
func marshalBinaryValue(e *Encoder, v reflect.Value) error {
@@ -233,10 +237,7 @@ func marshalBinaryValue(e *Encoder, v reflect.Value) error {
233237
//------------------------------------------------------------------------------
234238

235239
func marshalTextValueAddr(e *Encoder, v reflect.Value) error {
236-
if !v.CanAddr() {
237-
return fmt.Errorf("msgpack: Encode(non-addressable %T)", v.Interface())
238-
}
239-
return marshalTextValue(e, v.Addr())
240+
return marshalTextValue(e, ensureAddr(v))
240241
}
241242

242243
func marshalTextValue(e *Encoder, v reflect.Value) error {

ext.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,7 @@ func makeExtEncoder(
9595

9696
func makeExtEncoderAddr(extEncoder encoderFunc) encoderFunc {
9797
return func(e *Encoder, v reflect.Value) error {
98-
if !v.CanAddr() {
99-
return fmt.Errorf("msgpack: EncodeExt(nonaddressable %T)", v.Interface())
100-
}
101-
return extEncoder(e, v.Addr())
98+
return extEncoder(e, ensureAddr(v))
10299
}
103100
}
104101

types_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,33 @@ var encoderTests = []encoderTest{
289289
{&Intern{A: "foo", B: "foo", C: "foo"}, "83a141a3666f6fa142d48000a143d48000"},
290290
}
291291

292+
// Issue #3: types with pointer receivers should encode even when non-addressable.
293+
type PtrRecvMarshaler uint16
294+
295+
func (p *PtrRecvMarshaler) MarshalMsgpack() ([]byte, error) {
296+
return msgpack.Marshal(uint16(*p))
297+
}
298+
299+
func (p *PtrRecvMarshaler) UnmarshalMsgpack(b []byte) error {
300+
var n uint16
301+
if err := msgpack.Unmarshal(b, &n); err != nil {
302+
return err
303+
}
304+
*p = PtrRecvMarshaler(n)
305+
return nil
306+
}
307+
308+
func TestEncodeNonAddressablePtrReceiver(t *testing.T) {
309+
// Encoding a non-pointer value whose pointer type implements Marshaler.
310+
v := PtrRecvMarshaler(42)
311+
b, err := msgpack.Marshal(v)
312+
require.NoError(t, err)
313+
314+
var out PtrRecvMarshaler
315+
require.NoError(t, msgpack.Unmarshal(b, &out))
316+
require.Equal(t, v, out)
317+
}
318+
292319
func TestEncoder(t *testing.T) {
293320
var buf bytes.Buffer
294321
enc := msgpack.NewEncoder(&buf)

0 commit comments

Comments
 (0)