From a718a9a1efa98d61035dbdb4755a40dc6f242c1f Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Sat, 22 Aug 2020 12:58:34 +0900 Subject: [PATCH] Fix anonymous fields --- encode_compile.go | 112 ++++++++++++++++++++++++++++++++++++++++++++-- encode_opcode.go | 50 +++++++++++++++++---- encode_vm.go | 14 +++++- struct_field.go | 14 +++++- 4 files changed, 176 insertions(+), 14 deletions(-) diff --git a/encode_compile.go b/encode_compile.go index 040e0be..4733700 100644 --- a/encode_compile.go +++ b/encode_compile.go @@ -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 diff --git a/encode_opcode.go b/encode_opcode.go index 9fef8bf..59deec9 100644 --- a/encode_opcode.go +++ b/encode_opcode.go @@ -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, } diff --git a/encode_vm.go b/encode_vm.go index edd3edc..8265cde 100644 --- a/encode_vm.go +++ b/encode_vm.go @@ -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 diff --git a/struct_field.go b/struct_field.go index 3352da1..95de4bf 100644 --- a/struct_field.go +++ b/struct_field.go @@ -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"