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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bson/bsoncodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ func (vde ValueDecoderError) Error() string {
if vde.Received.IsValid() {
received = vde.Received.Type().String()
}
if !vde.Received.CanSet() {
received = "unsettable " + received
}
return fmt.Sprintf("%s can only decode valid and settable %s, but got %s", vde.Name, strings.Join(typeKinds, ", "), received)
}

Expand Down
23 changes: 23 additions & 0 deletions bson/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,29 @@ func TestDecodingInterfaces(t *testing.T) {
assert.Equal(t, testStruct{[3]interface{}{&str, &i, nil}}, receiver)
}

return data, &receiver, check
},
},
{
name: "struct with interface slice containing concrete values",
Copy link
Member

Choose a reason for hiding this comment

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

Could we rename this to "overwriting prepopulated slice?"

stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values []interface{}
}

data := docToBytes(struct {
Values []interface{}
}{
Values: []interface{}{1, 2, 3},
})

receiver := testStruct{[]interface{}{7, 8}}

check := func(t *testing.T) {
t.Helper()
assert.Equal(t, testStruct{[]interface{}{1, 2, int32(3)}}, receiver)
}

return data, &receiver, check
},
},
Expand Down
11 changes: 10 additions & 1 deletion bson/default_value_decoders.go
Original file line number Diff line number Diff line change
Expand Up @@ -1351,14 +1351,17 @@ func decodeDefault(dc DecodeContext, vr ValueReader, val reflect.Value) ([]refle
}

var elem reflect.Value
if vDecoder == nil {
if vDecoder == nil && idx < val.Len() {
elem = val.Index(idx).Elem()
switch {
case elem.Kind() != reflect.Ptr || elem.IsNil():
valueDecoder, err := dc.LookupDecoder(elem.Type())
if err != nil {
return nil, err
}
if !elem.CanSet() {
Copy link
Member

Choose a reason for hiding this comment

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

This change is really nuanced. Can we add a comment here noting why we do this? "If an element is unsettable and allocated, it must be overwritten."

Copy link
Member

Choose a reason for hiding this comment

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

I also suggest making the following changes to make this more readable:

isInterfaceSlice := eType.Kind() == reflect.Interface && val.Len() > 0

// If this is not an interface slice with pre-populated elements, we can look
// up the decoder for eType once.
var vDecoder ValueDecoder
if !isInterfaceSlice {
	vDecoder, err = dc.LookupDecoder(eType)
	if err != nil {
		return nil, err
	}
}

for {
	// ...
	if isInterfaceSlice && idx < val.Len() {
		// Decode into an existing interface{} slot.
		
		// ...
	} else {
		// For non-interface slices, or if we've exhuasted the pre-populated
		// slots, we create a fresh value.
		if vDecoder == nil {
			// ...
		}

		//...
	}
}

elem = reflect.New(elem.Type()).Elem()
}
err = valueDecoder.DecodeValue(dc, vr, elem)
if err != nil {
return nil, newDecodeError(strconv.Itoa(idx), err)
Expand All @@ -1380,6 +1383,12 @@ func decodeDefault(dc DecodeContext, vr ValueReader, val reflect.Value) ([]refle
}
}
} else {
if vDecoder == nil {
vDecoder, err = dc.LookupDecoder(eType)
if err != nil {
return nil, err
}
}
elem, err = decodeTypeOrValueWithInfo(vDecoder, dc, vr, eType)
if err != nil {
return nil, newDecodeError(strconv.Itoa(idx), err)
Expand Down
Loading
Loading