GoToSocial/vendor/github.com/bytedance/sonic/internal/resolver/resolver.go

215 lines
4.6 KiB
Go

/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package resolver
import (
`fmt`
`reflect`
`strings`
`sync`
)
type FieldOpts int
type OffsetType int
const (
F_omitempty FieldOpts = 1 << iota
F_stringize
)
const (
F_offset OffsetType = iota
F_deref
)
type Offset struct {
Size uintptr
Kind OffsetType
Type reflect.Type
}
type FieldMeta struct {
Name string
Path []Offset
Opts FieldOpts
Type reflect.Type
}
func (self *FieldMeta) String() string {
var path []string
var opts []string
/* dump the field path */
for _, off := range self.Path {
if off.Kind == F_offset {
path = append(path, fmt.Sprintf("%d", off.Size))
} else {
path = append(path, fmt.Sprintf("%d.(*%s)", off.Size, off.Type))
}
}
/* check for "string" */
if (self.Opts & F_stringize) != 0 {
opts = append(opts, "string")
}
/* check for "omitempty" */
if (self.Opts & F_omitempty) != 0 {
opts = append(opts, "omitempty")
}
/* format the field */
return fmt.Sprintf(
"{Field \"%s\" @ %s, opts=%s, type=%s}",
self.Name,
strings.Join(path, "."),
strings.Join(opts, ","),
self.Type,
)
}
func (self *FieldMeta) optimize() {
var n int
var v uintptr
/* merge adjacent offsets */
for _, o := range self.Path {
if v += o.Size; o.Kind == F_deref {
self.Path[n].Size = v
self.Path[n].Type, v = o.Type, 0
self.Path[n].Kind, n = F_deref, n + 1
}
}
/* last offset value */
if v != 0 {
self.Path[n].Size = v
self.Path[n].Type = nil
self.Path[n].Kind = F_offset
n++
}
/* must be at least 1 offset */
if n != 0 {
self.Path = self.Path[:n]
} else {
self.Path = []Offset{{Kind: F_offset}}
}
}
func resolveFields(vt reflect.Type) []FieldMeta {
tfv := typeFields(vt)
ret := []FieldMeta(nil)
/* convert each field */
for _, fv := range tfv.list {
item := vt
path := []Offset(nil)
opts := FieldOpts(0)
/* check for "string" */
if fv.quoted {
opts |= F_stringize
}
/* check for "omitempty" */
if fv.omitEmpty {
opts |= F_omitempty
}
/* dump the field path */
for _, i := range fv.index {
kind := F_offset
fval := item.Field(i)
item = fval.Type
/* deref the pointer if needed */
if item.Kind() == reflect.Ptr {
kind = F_deref
item = item.Elem()
}
/* add to path */
path = append(path, Offset {
Kind: kind,
Type: item,
Size: fval.Offset,
})
}
/* get the index to the last offset */
idx := len(path) - 1
fvt := path[idx].Type
/* do not dereference into fields */
if path[idx].Kind == F_deref {
fvt = reflect.PtrTo(fvt)
path[idx].Kind = F_offset
}
/* add to result */
ret = append(ret, FieldMeta {
Type: fvt,
Opts: opts,
Path: path,
Name: fv.name,
})
}
/* optimize the offsets */
for i := range ret {
ret[i].optimize()
}
/* all done */
return ret
}
var (
fieldLock = sync.RWMutex{}
fieldCache = map[reflect.Type][]FieldMeta{}
)
func ResolveStruct(vt reflect.Type) []FieldMeta {
var ok bool
var fm []FieldMeta
/* attempt to read from cache */
fieldLock.RLock()
fm, ok = fieldCache[vt]
fieldLock.RUnlock()
/* check if it was cached */
if ok {
return fm
}
/* otherwise use write-lock */
fieldLock.Lock()
defer fieldLock.Unlock()
/* double check */
if fm, ok = fieldCache[vt]; ok {
return fm
}
/* resolve the field */
fm = resolveFields(vt)
fieldCache[vt] = fm
return fm
}