mirror of
https://github.com/gin-gonic/gin.git
synced 2025-12-13 13:12:17 +08:00
587 lines
14 KiB
Go
587 lines
14 KiB
Go
// Copyright (c) 2012-2020 Ugorji Nwoke. All rights reserved.
|
|
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
|
|
|
//go:build codec.build
|
|
|
|
package codec
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// This tool will monomorphize types scoped to a specific format.
|
|
//
|
|
// This tool only monomorphized the type Name, and not a function Name.
|
|
// Explicitly, generic functions are not supported, as they cannot be monomorphized
|
|
// to a specific format without a corresponding name change.
|
|
//
|
|
// However, for types constrained to encWriter or decReader,
|
|
// which are shared across formats, there's no place to put them without duplication.
|
|
|
|
const genMonoParserMode = parser.AllErrors | parser.SkipObjectResolution
|
|
|
|
var genMonoSpecialFieldTypes = []string{"helperDecReader"}
|
|
|
|
// These functions should take the address of first param when monomorphized
|
|
var genMonoSpecialFunc4Addr = []string{} // {"decByteSlice"}
|
|
|
|
var genMonoImportsToSkip = []string{`"errors"`, `"fmt"`, `"net/rpc"`}
|
|
|
|
var genMonoRefImportsVia_ = [][2]string{
|
|
// {"errors", "New"},
|
|
}
|
|
|
|
var genMonoCallsToSkip = []string{"callMake"}
|
|
|
|
type genMonoFieldState uint
|
|
|
|
const (
|
|
genMonoFieldRecv genMonoFieldState = iota << 1
|
|
genMonoFieldParamsResult
|
|
genMonoFieldStruct
|
|
)
|
|
|
|
type genMonoImports struct {
|
|
set map[string]struct{}
|
|
specs []*ast.ImportSpec
|
|
}
|
|
|
|
type genMono struct {
|
|
files map[string][]byte
|
|
typParam map[string]*ast.Field
|
|
typParamTransient map[string]*ast.Field
|
|
}
|
|
|
|
func (x *genMono) init() {
|
|
x.files = make(map[string][]byte)
|
|
x.typParam = make(map[string]*ast.Field)
|
|
x.typParamTransient = make(map[string]*ast.Field)
|
|
}
|
|
|
|
func (x *genMono) reset() {
|
|
clear(x.typParam)
|
|
clear(x.typParamTransient)
|
|
}
|
|
|
|
func (m *genMono) hdl(hname string) {
|
|
m.reset()
|
|
m.do(hname, []string{"encode.go", "decode.go", hname + ".go"}, []string{"base.notfastpath.go", "base.notfastpath.notmono.go"}, "", "")
|
|
m.do(hname, []string{"base.notfastpath.notmono.go"}, nil, ".notfastpath", ` && (notfastpath || codec.notfastpath)`)
|
|
m.do(hname, []string{"base.fastpath.notmono.generated.go"}, []string{"base.fastpath.generated.go"}, ".fastpath", ` && !notfastpath && !codec.notfastpath`)
|
|
}
|
|
|
|
func (m *genMono) do(hname string, fnames, tnames []string, fnameInfx string, buildTagsSfx string) {
|
|
// keep m.typParams across whole call, as all others use it
|
|
const fnameSfx = ".mono.generated.go"
|
|
fname := hname + fnameInfx + fnameSfx
|
|
|
|
var imports = genMonoImports{set: make(map[string]struct{})}
|
|
|
|
r1, fset := m.merge(fnames, tnames, &imports)
|
|
m.trFile(r1, hname, true)
|
|
|
|
r2, fset := m.merge(fnames, tnames, &imports)
|
|
m.trFile(r2, hname, false)
|
|
|
|
r0 := genMonoOutInit(imports.specs, fname)
|
|
r0.Decls = append(r0.Decls, r1.Decls...)
|
|
r0.Decls = append(r0.Decls, r2.Decls...)
|
|
|
|
// output r1 to a file
|
|
f, err := os.Create(fname)
|
|
halt.onerror(err)
|
|
defer f.Close()
|
|
|
|
var s genMonoStrBuilder
|
|
s.s(`//go:build !notmono && !codec.notmono `).s(buildTagsSfx).s(`
|
|
|
|
// Copyright (c) 2012-2020 Ugorji Nwoke. All rights reserved.
|
|
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
|
|
|
`)
|
|
_, err = f.Write(s.v)
|
|
halt.onerror(err)
|
|
err = format.Node(f, fset, r0)
|
|
halt.onerror(err)
|
|
}
|
|
|
|
func (x *genMono) file(fname string) (b []byte) {
|
|
b = x.files[fname]
|
|
if b == nil {
|
|
var err error
|
|
b, err = os.ReadFile(fname)
|
|
halt.onerror(err)
|
|
x.files[fname] = b
|
|
}
|
|
return
|
|
}
|
|
|
|
func (x *genMono) merge(fNames, tNames []string, imports *genMonoImports) (dst *ast.File, fset *token.FileSet) {
|
|
// typParams used in fnLoadTyps
|
|
var typParams map[string]*ast.Field
|
|
var loadTyps bool
|
|
fnLoadTyps := func(node ast.Node) bool {
|
|
var ok bool
|
|
switch n := node.(type) {
|
|
case *ast.GenDecl:
|
|
if n.Tok == token.TYPE {
|
|
for _, v := range n.Specs {
|
|
nn := v.(*ast.TypeSpec)
|
|
ok = genMonoTypeParamsOk(nn.TypeParams)
|
|
if ok {
|
|
// each decl will have only 1 var/type
|
|
typParams[nn.Name.Name] = nn.TypeParams.List[0]
|
|
if loadTyps {
|
|
dst.Decls = append(dst.Decls, &ast.GenDecl{Tok: n.Tok, Specs: []ast.Spec{v}})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// we only merge top-level methods and types
|
|
fnIdX := func(n *ast.FuncDecl, n2 *ast.IndexExpr) (ok bool) {
|
|
n9, ok9 := n2.Index.(*ast.Ident)
|
|
n3, ok := n2.X.(*ast.Ident) // n3 = type name
|
|
ok = ok && ok9 && n9.Name == "T"
|
|
if ok {
|
|
_, ok = x.typParam[n3.Name]
|
|
}
|
|
return
|
|
}
|
|
|
|
fnLoadMethodsAndImports := func(node ast.Node) bool {
|
|
var ok bool
|
|
switch n := node.(type) {
|
|
case *ast.FuncDecl:
|
|
// TypeParams is nil for methods, as it is defined at the type node
|
|
// instead, look at the name, and
|
|
// if IndexExpr.Index=T, and IndexExpr.X matches a type name seen already
|
|
// then ok = true
|
|
if n.Recv == nil || len(n.Recv.List) != 1 {
|
|
return false
|
|
}
|
|
ok = false
|
|
switch nn := n.Recv.List[0].Type.(type) {
|
|
case *ast.IndexExpr:
|
|
ok = fnIdX(n, nn)
|
|
case *ast.StarExpr:
|
|
switch nn2 := nn.X.(type) {
|
|
case *ast.IndexExpr:
|
|
ok = fnIdX(n, nn2)
|
|
}
|
|
}
|
|
if ok {
|
|
dst.Decls = append(dst.Decls, n)
|
|
}
|
|
return false
|
|
case *ast.GenDecl:
|
|
if n.Tok == token.IMPORT {
|
|
for _, v := range n.Specs {
|
|
nn := v.(*ast.ImportSpec)
|
|
if slices.Contains(genMonoImportsToSkip, nn.Path.Value) {
|
|
continue
|
|
}
|
|
if _, ok = imports.set[nn.Path.Value]; !ok {
|
|
imports.specs = append(imports.specs, nn)
|
|
imports.set[nn.Path.Value] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fset = token.NewFileSet()
|
|
fnLoadAsts := func(names []string) (asts []*ast.File) {
|
|
for _, fname := range names {
|
|
fsrc := x.file(fname)
|
|
f, err := parser.ParseFile(fset, fname, fsrc, genMonoParserMode)
|
|
halt.onerror(err)
|
|
asts = append(asts, f)
|
|
}
|
|
return
|
|
}
|
|
|
|
clear(x.typParamTransient)
|
|
|
|
dst = &ast.File{
|
|
Name: &ast.Ident{Name: "codec"},
|
|
}
|
|
|
|
fs := fnLoadAsts(fNames)
|
|
ts := fnLoadAsts(tNames)
|
|
|
|
loadTyps = true
|
|
typParams = x.typParam
|
|
for _, v := range fs {
|
|
ast.Inspect(v, fnLoadTyps)
|
|
}
|
|
loadTyps = false
|
|
typParams = x.typParamTransient
|
|
for _, v := range ts {
|
|
ast.Inspect(v, fnLoadTyps)
|
|
}
|
|
typParams = nil
|
|
for _, v := range fs {
|
|
ast.Inspect(v, fnLoadMethodsAndImports)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (x *genMono) trFile(r *ast.File, hname string, isbytes bool) {
|
|
fn := func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
case *ast.TypeSpec:
|
|
// type x[T encDriver] struct { ... }
|
|
if !genMonoTypeParamsOk(n.TypeParams) {
|
|
return false
|
|
}
|
|
x.trType(n, hname, isbytes)
|
|
return false
|
|
case *ast.FuncDecl:
|
|
if n.Recv == nil || len(n.Recv.List) != 1 {
|
|
return false
|
|
}
|
|
if _, ok := n.Recv.List[0].Type.(*ast.Ident); ok {
|
|
return false
|
|
}
|
|
tp := x.trMethodSign(n, hname, isbytes) // receiver, params, results
|
|
// handle the body
|
|
x.trMethodBody(n.Body, tp, hname, isbytes)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
ast.Inspect(r, fn)
|
|
|
|
// set type params to nil, and Pos to NoPos
|
|
fn = func(node ast.Node) bool {
|
|
switch n := node.(type) {
|
|
case *ast.FuncType:
|
|
if genMonoTypeParamsOk(n.TypeParams) {
|
|
n.TypeParams = nil
|
|
}
|
|
case *ast.TypeSpec: // for type ...
|
|
if genMonoTypeParamsOk(n.TypeParams) {
|
|
n.TypeParams = nil
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
ast.Inspect(r, fn)
|
|
}
|
|
|
|
func (x *genMono) trType(n *ast.TypeSpec, hname string, isbytes bool) {
|
|
sfx, _, _, hnameUp := genMonoIsBytesVals(hname, isbytes)
|
|
tp := n.TypeParams.List[0]
|
|
switch tp.Type.(*ast.Ident).Name {
|
|
case "encDriver", "decDriver":
|
|
n.Name.Name += hnameUp + sfx
|
|
case "encWriter", "decReader":
|
|
n.Name.Name += sfx
|
|
}
|
|
|
|
// handle the Struct and Array types
|
|
switch nn := n.Type.(type) {
|
|
case *ast.StructType:
|
|
x.trStruct(nn, tp, hname, isbytes)
|
|
case *ast.ArrayType:
|
|
x.trArray(nn, tp, hname, isbytes)
|
|
}
|
|
}
|
|
|
|
func (x *genMono) trMethodSign(n *ast.FuncDecl, hname string, isbytes bool) (tp *ast.Field) {
|
|
// check if recv type is not parameterized
|
|
tp = x.trField(n.Recv.List[0], nil, hname, isbytes, genMonoFieldRecv)
|
|
// handle params and results
|
|
x.trMethodSignNonRecv(n.Type.Params, tp, hname, isbytes)
|
|
x.trMethodSignNonRecv(n.Type.Results, tp, hname, isbytes)
|
|
return
|
|
}
|
|
|
|
func (x *genMono) trMethodSignNonRecv(r *ast.FieldList, tp *ast.Field, hname string, isbytes bool) {
|
|
if r == nil || len(r.List) == 0 {
|
|
return
|
|
}
|
|
for _, v := range r.List {
|
|
x.trField(v, tp, hname, isbytes, genMonoFieldParamsResult)
|
|
}
|
|
}
|
|
|
|
func (x *genMono) trStruct(r *ast.StructType, tp *ast.Field, hname string, isbytes bool) {
|
|
// search for fields, and update accordingly
|
|
// type x[T encDriver] struct { w T }
|
|
// var x *A[T]
|
|
// A[T]
|
|
if r == nil || r.Fields == nil || len(r.Fields.List) == 0 {
|
|
return
|
|
}
|
|
for _, v := range r.Fields.List {
|
|
x.trField(v, tp, hname, isbytes, genMonoFieldStruct)
|
|
}
|
|
}
|
|
|
|
func (x *genMono) trArray(n *ast.ArrayType, tp *ast.Field, hname string, isbytes bool) {
|
|
sfx, _, _, hnameUp := genMonoIsBytesVals(hname, isbytes)
|
|
// type fastpathEs[T encDriver] [56]fastpathE[T]
|
|
// p := tp.Names[0].Name
|
|
switch elt := n.Elt.(type) {
|
|
// case *ast.InterfaceType:
|
|
case *ast.IndexExpr:
|
|
if elt.Index.(*ast.Ident).Name == "T" { // generic
|
|
n.Elt = ast.NewIdent(elt.X.(*ast.Ident).Name + hnameUp + sfx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (x *genMono) trMethodBody(r *ast.BlockStmt, tp *ast.Field, hname string, isbytes bool) {
|
|
// find the parent node for an indexExpr, or a T/*T, and set the value back in there
|
|
|
|
fn := func(pnode ast.Node) bool {
|
|
var pn *ast.Ident
|
|
fnUp := func() {
|
|
x.updateIdentForT(pn, hname, tp, isbytes, false)
|
|
}
|
|
switch n := pnode.(type) {
|
|
// case *ast.SelectorExpr:
|
|
// case *ast.TypeAssertExpr:
|
|
// case *ast.IndexExpr:
|
|
case *ast.StarExpr:
|
|
if genMonoUpdateIndexExprT(&pn, n.X) {
|
|
n.X = pn
|
|
fnUp()
|
|
}
|
|
case *ast.CallExpr:
|
|
for i4, n4 := range n.Args {
|
|
if genMonoUpdateIndexExprT(&pn, n4) {
|
|
n.Args[i4] = pn
|
|
fnUp()
|
|
}
|
|
}
|
|
if n4, ok4 := n.Fun.(*ast.Ident); ok4 && slices.Contains(genMonoSpecialFunc4Addr, n4.Name) {
|
|
n.Args[0] = &ast.UnaryExpr{Op: token.AND, X: n.Args[0].(*ast.SelectorExpr)}
|
|
}
|
|
case *ast.CompositeLit:
|
|
if genMonoUpdateIndexExprT(&pn, n.Type) {
|
|
n.Type = pn
|
|
fnUp()
|
|
}
|
|
case *ast.ArrayType:
|
|
if genMonoUpdateIndexExprT(&pn, n.Elt) {
|
|
n.Elt = pn
|
|
fnUp()
|
|
}
|
|
case *ast.ValueSpec:
|
|
for i2, n2 := range n.Values {
|
|
if genMonoUpdateIndexExprT(&pn, n2) {
|
|
n.Values[i2] = pn
|
|
fnUp()
|
|
}
|
|
}
|
|
if genMonoUpdateIndexExprT(&pn, n.Type) {
|
|
n.Type = pn
|
|
fnUp()
|
|
}
|
|
case *ast.BinaryExpr:
|
|
// early return here, since the 2 things can apply
|
|
if genMonoUpdateIndexExprT(&pn, n.X) {
|
|
n.X = pn
|
|
fnUp()
|
|
}
|
|
if genMonoUpdateIndexExprT(&pn, n.Y) {
|
|
n.Y = pn
|
|
fnUp()
|
|
}
|
|
return true
|
|
}
|
|
return true
|
|
}
|
|
ast.Inspect(r, fn)
|
|
}
|
|
|
|
func (x *genMono) trField(f *ast.Field, tpt *ast.Field, hname string, isbytes bool, state genMonoFieldState) (tp *ast.Field) {
|
|
var pn *ast.Ident
|
|
switch nn := f.Type.(type) {
|
|
case *ast.IndexExpr:
|
|
if genMonoUpdateIndexExprT(&pn, nn) {
|
|
f.Type = pn
|
|
}
|
|
case *ast.StarExpr:
|
|
if genMonoUpdateIndexExprT(&pn, nn.X) {
|
|
nn.X = pn
|
|
}
|
|
case *ast.FuncType:
|
|
x.trMethodSignNonRecv(nn.Params, tpt, hname, isbytes)
|
|
x.trMethodSignNonRecv(nn.Results, tpt, hname, isbytes)
|
|
return
|
|
case *ast.ArrayType:
|
|
x.trArray(nn, tpt, hname, isbytes)
|
|
return
|
|
case *ast.Ident:
|
|
if state == genMonoFieldRecv || nn.Name != "T" {
|
|
return
|
|
}
|
|
pn = nn // "T"
|
|
if state == genMonoFieldParamsResult {
|
|
f.Type = &ast.StarExpr{X: pn}
|
|
}
|
|
}
|
|
if pn == nil {
|
|
return
|
|
}
|
|
|
|
tp = x.updateIdentForT(pn, hname, tpt, isbytes, true)
|
|
return
|
|
}
|
|
|
|
func (x *genMono) updateIdentForT(pn *ast.Ident, hname string, tp *ast.Field,
|
|
isbytes bool, lookupTP bool) (tp2 *ast.Field) {
|
|
sfx, writer, reader, hnameUp := genMonoIsBytesVals(hname, isbytes)
|
|
// handle special ones e.g. helperDecReader et al
|
|
if slices.Contains(genMonoSpecialFieldTypes, pn.Name) {
|
|
pn.Name += sfx
|
|
return
|
|
}
|
|
|
|
if pn.Name != "T" && lookupTP {
|
|
tp = x.typParam[pn.Name]
|
|
if tp == nil {
|
|
tp = x.typParamTransient[pn.Name]
|
|
}
|
|
}
|
|
|
|
paramtyp := tp.Type.(*ast.Ident).Name
|
|
if pn.Name == "T" {
|
|
switch paramtyp {
|
|
case "encDriver", "decDriver":
|
|
pn.Name = hname + genMonoTitleCase(paramtyp) + sfx
|
|
case "encWriter":
|
|
pn.Name = writer
|
|
case "decReader":
|
|
pn.Name = reader
|
|
}
|
|
} else {
|
|
switch paramtyp {
|
|
case "encDriver", "decDriver":
|
|
pn.Name += hnameUp + sfx
|
|
case "encWriter", "decReader":
|
|
pn.Name += sfx
|
|
}
|
|
}
|
|
return tp
|
|
}
|
|
|
|
func genMonoUpdateIndexExprT(pn **ast.Ident, node ast.Node) (pnok bool) {
|
|
*pn = nil
|
|
if n2, ok := node.(*ast.IndexExpr); ok {
|
|
n9, ok9 := n2.Index.(*ast.Ident)
|
|
n3, ok := n2.X.(*ast.Ident)
|
|
if ok && ok9 && n9.Name == "T" {
|
|
*pn, pnok = ast.NewIdent(n3.Name), true
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func genMonoTitleCase(s string) string {
|
|
return strings.ToUpper(s[:1]) + s[1:]
|
|
}
|
|
|
|
func genMonoIsBytesVals(hName string, isbytes bool) (suffix, writer, reader, hNameUp string) {
|
|
hNameUp = genMonoTitleCase(hName)
|
|
if isbytes {
|
|
return "Bytes", "bytesEncAppender", "bytesDecReader", hNameUp
|
|
}
|
|
return "IO", "bufioEncWriter", "ioDecReader", hNameUp
|
|
}
|
|
|
|
func genMonoTypeParamsOk(v *ast.FieldList) (ok bool) {
|
|
if v == nil || v.List == nil || len(v.List) != 1 {
|
|
return false
|
|
}
|
|
pn := v.List[0]
|
|
if len(pn.Names) != 1 {
|
|
return false
|
|
}
|
|
pnName := pn.Names[0].Name
|
|
if pnName != "T" {
|
|
return false
|
|
}
|
|
// ignore any nodes which are not idents e.g. cmp.orderedRv
|
|
vv, ok := pn.Type.(*ast.Ident)
|
|
if !ok {
|
|
return false
|
|
}
|
|
switch vv.Name {
|
|
case "encDriver", "decDriver", "encWriter", "decReader":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func genMonoCopy(src *ast.File) (dst *ast.File) {
|
|
dst = &ast.File{
|
|
Name: &ast.Ident{Name: "codec"},
|
|
}
|
|
dst.Decls = append(dst.Decls, src.Decls...)
|
|
return
|
|
}
|
|
|
|
type genMonoStrBuilder struct {
|
|
v []byte
|
|
}
|
|
|
|
func (x *genMonoStrBuilder) s(v string) *genMonoStrBuilder {
|
|
x.v = append(x.v, v...)
|
|
return x
|
|
}
|
|
|
|
func genMonoOutInit(importSpecs []*ast.ImportSpec, fname string) (f *ast.File) {
|
|
// ParseFile seems to skip the //go:build stanza
|
|
// it should be written directly into the file
|
|
var s genMonoStrBuilder
|
|
s.s(`
|
|
package codec
|
|
|
|
import (
|
|
`)
|
|
for _, v := range importSpecs {
|
|
s.s("\t").s(v.Path.Value).s("\n")
|
|
}
|
|
s.s(")\n")
|
|
for _, v := range genMonoRefImportsVia_ {
|
|
s.s("var _ = ").s(v[0]).s(".").s(v[1]).s("\n")
|
|
}
|
|
f, err := parser.ParseFile(token.NewFileSet(), fname, s.v, genMonoParserMode)
|
|
halt.onerror(err)
|
|
return
|
|
}
|
|
|
|
func genMonoAll() {
|
|
// hdls := []Handle{
|
|
// (*SimpleHandle)(nil),
|
|
// (*JsonHandle)(nil),
|
|
// (*CborHandle)(nil),
|
|
// (*BincHandle)(nil),
|
|
// (*MsgpackHandle)(nil),
|
|
// }
|
|
hdls := []string{"simple", "json", "cbor", "binc", "msgpack"}
|
|
var m genMono
|
|
m.init()
|
|
for _, v := range hdls {
|
|
m.hdl(v)
|
|
}
|
|
}
|