Fix anonymous fields

This commit is contained in:
Masaaki Goshima 2020-08-22 12:58:34 +09:00
parent 3e03bdc53f
commit a718a9a1ef
4 changed files with 176 additions and 14 deletions

View File

@ -566,6 +566,60 @@ func (e *Encoder) structField(fieldCode *structFieldCode, valueCode *opcode, tag
} }
return code return code
} }
func (e *Encoder) isNotExistsField(head *structFieldCode) bool {
if head == nil {
return false
}
if head.op != opStructFieldAnonymousHead {
return false
}
if head.next == nil {
return false
}
if head.nextField == nil {
return false
}
if head.nextField.op != opStructAnonymousEnd {
return false
}
if head.next.op == opStructAnonymousEnd {
return true
}
if head.next.op.codeType() != codeStructField {
return false
}
return e.isNotExistsField(head.next.toStructFieldCode())
}
func (e *Encoder) optimizeAnonymousFields(head *structFieldCode) {
code := head
var prev *structFieldCode
for {
if code.op == opStructEnd || code.op == opStructEndIndent {
break
}
if code.op == opStructField || code.op == opStructFieldIndent {
codeType := code.next.op.codeType()
if codeType == codeStructField {
if e.isNotExistsField(code.next.toStructFieldCode()) {
code.next = code.nextField
linkPrevToNextField(prev, code)
code = prev
}
}
}
prev = code
code = code.nextField.toStructFieldCode()
}
}
type structFieldPair struct {
prevField *structFieldCode
curField *structFieldCode
linked bool
}
func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opcode, error) { func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opcode, error) {
if code := e.compiledCode(typ, withIndent); code != nil { if code := e.compiledCode(typ, withIndent); code != nil {
return code, nil return code, nil
@ -588,12 +642,17 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
prevField *structFieldCode prevField *structFieldCode
) )
e.indent++ e.indent++
tags := structTags{}
anonymousFields := map[string]structFieldPair{}
for i := 0; i < fieldNum; i++ { for i := 0; i < fieldNum; i++ {
field := typ.Field(i) field := typ.Field(i)
if isIgnoredStructField(field) { if isIgnoredStructField(field) {
continue continue
} }
tag := structTagFromField(field) tags = append(tags, structTagFromField(field))
}
for i, tag := range tags {
field := tag.field
fieldType := type2rtype(field.Type) fieldType := type2rtype(field.Type)
if isPtr && i == 0 { if isPtr && i == 0 {
// head field of pointer structure at top level // head field of pointer structure at top level
@ -610,14 +669,57 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
} }
if field.Anonymous { if field.Anonymous {
f := valueCode.toStructFieldCode() f := valueCode.toStructFieldCode()
var prevAnonymousField *structFieldCode
for { for {
f.op = f.op.headToAnonymousHead() existsKey := tags.existsKey(f.displayKey)
if f.op == opStructEnd { 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 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 { if f.nextField == nil {
break break
} }
prevAnonymousField = f
f = f.nextField.toStructFieldCode() f = f.nextField.toStructFieldCode()
} }
} }
@ -640,6 +742,7 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
}, },
anonymousKey: field.Anonymous, anonymousKey: field.Anonymous,
key: []byte(key), key: []byte(key),
displayKey: tag.key,
offset: field.Offset, offset: field.Offset,
} }
if fieldIdx == 0 { if fieldIdx == 0 {
@ -690,6 +793,9 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
} }
head.end = structEndCode head.end = structEndCode
code.next = structEndCode code.next = structEndCode
e.optimizeAnonymousFields(head)
ret := (*opcode)(unsafe.Pointer(head)) ret := (*opcode)(unsafe.Pointer(head))
compiled.code = ret compiled.code = ret

View File

@ -53,12 +53,12 @@ func (c *opcode) beforeLastCode() *opcode {
code := c code := c
for { for {
var nextCode *opcode var nextCode *opcode
switch code.op { switch code.op.codeType() {
case opArrayElem, opArrayElemIndent: case codeArrayElem:
nextCode = code.toArrayElemCode().end nextCode = code.toArrayElemCode().end
case opSliceElem, opSliceElemIndent, opRootSliceElemIndent: case codeSliceElem:
nextCode = code.toSliceElemCode().end nextCode = code.toSliceElemCode().end
case opMapKey, opMapKeyIndent, opRootMapKeyIndent: case codeMapKey:
nextCode = code.toMapKeyCode().end nextCode = code.toMapKeyCode().end
default: default:
nextCode = code.next nextCode = code.next
@ -112,13 +112,13 @@ func (c *opcode) dump() string {
codes := []string{} codes := []string{}
for code := c; code.op != opEnd; { for code := c; code.op != opEnd; {
indent := strings.Repeat(" ", code.indent) indent := strings.Repeat(" ", code.indent)
codes = append(codes, fmt.Sprintf("%s%s", indent, code.op)) codes = append(codes, fmt.Sprintf("%s%s ( %p )", indent, code.op, unsafe.Pointer(code)))
switch code.op { switch code.op.codeType() {
case opArrayElem, opArrayElemIndent: case codeArrayElem:
code = code.toArrayElemCode().end code = code.toArrayElemCode().end
case opSliceElem, opSliceElemIndent, opRootSliceElemIndent: case codeSliceElem:
code = code.toSliceElemCode().end code = code.toSliceElemCode().end
case opMapKey, opMapKeyIndent, opRootMapKeyIndent: case codeMapKey:
code = code.toMapKeyCode().end code = code.toMapKeyCode().end
default: default:
code = code.next code = code.next
@ -305,12 +305,43 @@ func (c *arrayElemCode) copy(codeMap map[uintptr]*opcode) *opcode {
type structFieldCode struct { type structFieldCode struct {
*opcodeHeader *opcodeHeader
key []byte key []byte
displayKey string
offset uintptr offset uintptr
anonymousKey bool anonymousKey bool
nextField *opcode nextField *opcode
end *opcode end *opcode
} }
func linkPrevToNextField(prev, cur *structFieldCode) {
prev.nextField = cur.nextField
code := prev.toOpcode()
fcode := cur.toOpcode()
for {
var nextCode *opcode
switch code.op.codeType() {
case codeArrayElem:
nextCode = code.toArrayElemCode().end
case codeSliceElem:
nextCode = code.toSliceElemCode().end
case codeMapKey:
nextCode = code.toMapKeyCode().end
default:
nextCode = code.next
}
if nextCode == fcode {
code.next = fcode.next
break
} else if nextCode.op == opEnd {
break
}
code = nextCode
}
}
func (c *structFieldCode) toOpcode() *opcode {
return (*opcode)(unsafe.Pointer(c))
}
func (c *structFieldCode) copy(codeMap map[uintptr]*opcode) *opcode { func (c *structFieldCode) copy(codeMap map[uintptr]*opcode) *opcode {
if c == nil { if c == nil {
return nil return nil
@ -321,6 +352,7 @@ func (c *structFieldCode) copy(codeMap map[uintptr]*opcode) *opcode {
} }
field := &structFieldCode{ field := &structFieldCode{
key: c.key, key: c.key,
displayKey: c.displayKey,
anonymousKey: c.anonymousKey, anonymousKey: c.anonymousKey,
offset: c.offset, offset: c.offset,
} }

View File

@ -549,6 +549,16 @@ func (e *Encoder) run(code *opcode) error {
code.ptr = ptr code.ptr = ptr
field.nextField.ptr = ptr field.nextField.ptr = ptr
} }
case opStructFieldAnonymousHead:
field := code.toStructFieldCode()
ptr := field.ptr
if ptr == 0 {
code = field.end.next
} else {
code = field.next
code.ptr = ptr
field.nextField.ptr = ptr
}
case opStructFieldPtrHeadInt: case opStructFieldPtrHeadInt:
code.ptr = e.ptrToPtr(code.ptr) code.ptr = e.ptrToPtr(code.ptr)
fallthrough fallthrough
@ -3872,7 +3882,9 @@ func (e *Encoder) run(code *opcode) error {
e.encodeByte(',') e.encodeByte(',')
} }
c := code.toStructFieldCode() c := code.toStructFieldCode()
e.encodeBytes(c.key) if !c.anonymousKey {
e.encodeBytes(c.key)
}
code = code.next code = code.next
code.ptr = c.ptr + c.offset code.ptr = c.ptr + c.offset
c.nextField.ptr = c.ptr c.nextField.ptr = c.ptr

View File

@ -25,6 +25,18 @@ type structTag struct {
key string key string
isOmitEmpty bool isOmitEmpty bool
isString bool isString bool
field reflect.StructField
}
type structTags []*structTag
func (t structTags) existsKey(key string) bool {
for _, tt := range t {
if tt.key == key {
return true
}
}
return false
} }
func structTagFromField(field reflect.StructField) *structTag { func structTagFromField(field reflect.StructField) *structTag {
@ -36,7 +48,7 @@ func structTagFromField(field reflect.StructField) *structTag {
keyName = opts[0] keyName = opts[0]
} }
} }
st := &structTag{key: keyName} st := &structTag{key: keyName, field: field}
if len(opts) > 1 { if len(opts) > 1 {
st.isOmitEmpty = opts[1] == "omitempty" st.isOmitEmpty = opts[1] == "omitempty"
st.isString = opts[1] == "string" st.isString = opts[1] == "string"