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
}
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) {
if code := e.compiledCode(typ, withIndent); code != nil {
return code, nil
@ -588,12 +642,17 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
prevField *structFieldCode
)
e.indent++
tags := structTags{}
anonymousFields := map[string]structFieldPair{}
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if isIgnoredStructField(field) {
continue
}
tag := structTagFromField(field)
tags = append(tags, structTagFromField(field))
}
for i, tag := range tags {
field := tag.field
fieldType := type2rtype(field.Type)
if isPtr && i == 0 {
// 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 {
f := valueCode.toStructFieldCode()
var prevAnonymousField *structFieldCode
for {
f.op = f.op.headToAnonymousHead()
if f.op == opStructEnd {
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()
}
}
@ -640,6 +742,7 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
},
anonymousKey: field.Anonymous,
key: []byte(key),
displayKey: tag.key,
offset: field.Offset,
}
if fieldIdx == 0 {
@ -690,6 +793,9 @@ func (e *Encoder) compileStruct(typ *rtype, isPtr, root, withIndent bool) (*opco
}
head.end = structEndCode
code.next = structEndCode
e.optimizeAnonymousFields(head)
ret := (*opcode)(unsafe.Pointer(head))
compiled.code = ret

View File

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

View File

@ -549,6 +549,16 @@ func (e *Encoder) run(code *opcode) error {
code.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:
code.ptr = e.ptrToPtr(code.ptr)
fallthrough
@ -3872,7 +3882,9 @@ func (e *Encoder) run(code *opcode) error {
e.encodeByte(',')
}
c := code.toStructFieldCode()
e.encodeBytes(c.key)
if !c.anonymousKey {
e.encodeBytes(c.key)
}
code = code.next
code.ptr = c.ptr + c.offset
c.nextField.ptr = c.ptr

View File

@ -25,6 +25,18 @@ type structTag struct {
key string
isOmitEmpty 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 {
@ -36,7 +48,7 @@ func structTagFromField(field reflect.StructField) *structTag {
keyName = opts[0]
}
}
st := &structTag{key: keyName}
st := &structTag{key: keyName, field: field}
if len(opts) > 1 {
st.isOmitEmpty = opts[1] == "omitempty"
st.isString = opts[1] == "string"