Fix conflicted anonymous fields

This commit is contained in:
Masaaki Goshima 2020-08-22 15:40:18 +09:00
parent a718a9a1ef
commit 7ada1b2467
3 changed files with 105 additions and 58 deletions

View File

@ -615,9 +615,100 @@ func (e *Encoder) optimizeAnonymousFields(head *structFieldCode) {
}
type structFieldPair struct {
prevField *structFieldCode
curField *structFieldCode
linked bool
prevField *structFieldCode
curField *structFieldCode
isTaggedKey bool
linked bool
}
func (e *Encoder) anonymousStructFieldPairMap(typ *rtype, tags structTags, valueCode *structFieldCode) map[string][]structFieldPair {
//fmt.Println("type = ", typ, "valueCode = ", valueCode.dump())
anonymousFields := map[string][]structFieldPair{}
f := valueCode
var prevAnonymousField *structFieldCode
for {
existsKey := tags.existsKey(f.displayKey)
op := f.op.headToAnonymousHead()
if op != f.op {
if existsKey {
f.op = opStructFieldAnonymousHead
} else {
f.op = op
}
} else if f.op == opStructEnd {
f.op = opStructAnonymousEnd
} else if existsKey {
linkPrevToNextField(prevAnonymousField, f)
}
if f.displayKey == "" {
if f.nextField == nil {
break
}
prevAnonymousField = f
f = f.nextField.toStructFieldCode()
continue
}
anonymousFields[f.displayKey] = append(anonymousFields[f.displayKey], structFieldPair{
prevField: prevAnonymousField,
curField: f,
isTaggedKey: f.isTaggedKey,
})
if f.next != nil && f.nextField != f.next && f.next.op.codeType() == codeStructField {
for k, v := range e.anonymousStructFieldPairMap(typ, tags, f.next.toStructFieldCode()) {
anonymousFields[k] = append(anonymousFields[k], v...)
}
}
if f.nextField == nil {
break
}
prevAnonymousField = f
f = f.nextField.toStructFieldCode()
}
return anonymousFields
}
func (e *Encoder) optimizeConflictAnonymousFields(anonymousFields map[string][]structFieldPair) {
for _, fieldPairs := range anonymousFields {
if len(fieldPairs) == 1 {
continue
}
// conflict anonymous fields
taggedPairs := []structFieldPair{}
for _, fieldPair := range fieldPairs {
if fieldPair.isTaggedKey {
taggedPairs = append(taggedPairs, fieldPair)
} else {
if !fieldPair.linked {
if fieldPair.prevField == nil {
// head operation
fieldPair.curField.op = opStructFieldAnonymousHead
} else {
linkPrevToNextField(fieldPair.prevField, fieldPair.curField)
}
fieldPair.linked = true
}
}
}
if len(taggedPairs) > 1 {
for _, fieldPair := range taggedPairs {
if !fieldPair.linked {
if fieldPair.prevField == nil {
// head operation
fieldPair.curField.op = opStructFieldAnonymousHead
} else {
linkPrevToNextField(fieldPair.prevField, fieldPair.curField)
}
fieldPair.linked = true
}
}
} else {
for _, fieldPair := range taggedPairs {
fieldPair.curField.isTaggedKey = false
}
}
}
}
func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opcode, error) {
@ -643,7 +734,7 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
)
e.indent++
tags := structTags{}
anonymousFields := map[string]structFieldPair{}
anonymousFields := map[string][]structFieldPair{}
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if isIgnoredStructField(field) {
@ -668,59 +759,8 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
return nil, err
}
if field.Anonymous {
f := valueCode.toStructFieldCode()
var prevAnonymousField *structFieldCode
for {
existsKey := tags.existsKey(f.displayKey)
op := f.op.headToAnonymousHead()
if op != f.op {
if existsKey {
f.op = opStructFieldAnonymousHead
} else {
f.op = op
}
} else if f.op == opStructEnd {
f.op = opStructAnonymousEnd
} else if existsKey {
linkPrevToNextField(prevAnonymousField, f)
}
if f.displayKey == "" {
if f.nextField == nil {
break
}
prevAnonymousField = f
f = f.nextField.toStructFieldCode()
continue
}
// conflict anonymous fields
if existsFieldSet, exists := anonymousFields[f.displayKey]; exists {
if !existsFieldSet.linked {
if existsFieldSet.prevField == nil {
// head operation
existsFieldSet.curField.op = opStructFieldAnonymousHead
} else {
linkPrevToNextField(existsFieldSet.prevField, existsFieldSet.curField)
}
existsFieldSet.linked = true
}
if prevAnonymousField == nil {
// head operation
f.op = opStructFieldAnonymousHead
} else {
linkPrevToNextField(prevAnonymousField, f)
}
}
anonymousFields[f.displayKey] = structFieldPair{
prevField: prevAnonymousField,
curField: f,
}
if f.nextField == nil {
break
}
prevAnonymousField = f
f = f.nextField.toStructFieldCode()
for k, v := range e.anonymousStructFieldPairMap(typ, tags, valueCode.toStructFieldCode()) {
anonymousFields[k] = append(anonymousFields[k], v...)
}
}
if fieldNum == 1 && valueCode.op == opPtr {
@ -742,6 +782,7 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
},
anonymousKey: field.Anonymous,
key: []byte(key),
isTaggedKey: tag.isTaggedKey,
displayKey: tag.key,
offset: field.Offset,
}
@ -794,6 +835,7 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
head.end = structEndCode
code.next = structEndCode
e.optimizeConflictAnonymousFields(anonymousFields)
e.optimizeAnonymousFields(head)
ret := (*opcode)(unsafe.Pointer(head))

View File

@ -306,6 +306,7 @@ type structFieldCode struct {
*opcodeHeader
key []byte
displayKey string
isTaggedKey bool
offset uintptr
anonymousKey bool
nextField *opcode
@ -352,6 +353,7 @@ func (c *structFieldCode) copy(codeMap map[uintptr]*opcode) *opcode {
}
field := &structFieldCode{
key: c.key,
isTaggedKey: c.isTaggedKey,
displayKey: c.displayKey,
anonymousKey: c.anonymousKey,
offset: c.offset,

View File

@ -23,6 +23,7 @@ func isIgnoredStructField(field reflect.StructField) bool {
type structTag struct {
key string
isTaggedKey bool
isOmitEmpty bool
isString bool
field reflect.StructField
@ -42,13 +43,15 @@ func (t structTags) existsKey(key string) bool {
func structTagFromField(field reflect.StructField) *structTag {
keyName := field.Name
tag := getTag(field)
st := &structTag{field: field}
opts := strings.Split(tag, ",")
if len(opts) > 0 {
if opts[0] != "" {
keyName = opts[0]
st.isTaggedKey = true
}
}
st := &structTag{key: keyName, field: field}
st.key = keyName
if len(opts) > 1 {
st.isOmitEmpty = opts[1] == "omitempty"
st.isString = opts[1] == "string"