Skip to content

Commit

Permalink
feat(typex): typex for univeral reflect and go type abstract
Browse files Browse the repository at this point in the history
  • Loading branch information
saitofun committed Jun 10, 2024
1 parent 9533098 commit 8de00ca
Show file tree
Hide file tree
Showing 10 changed files with 2,044 additions and 11 deletions.
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ go 1.22.0
require (
github.com/onsi/gomega v1.33.1
github.com/pkg/errors v0.9.1
golang.org/x/tools v0.22.0
)

require (
github.com/google/go-cmp v0.6.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 12 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
332 changes: 332 additions & 0 deletions typex/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
package typex

import (
"go/token"
"go/types"
"reflect"
"strconv"
"strings"
"sync"

"golang.org/x/tools/go/packages"

"github.com/xoctopus/x/misc/must"
)

var (
gGoTypeCache = sync.Map{}
gBasicsCache = sync.Map{}
gPackagesCache = sync.Map{}
)

var (
LoadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
LoadImports = LoadFiles | packages.NeedImports
LoadTypes = LoadImports | packages.NeedTypes | packages.NeedTypesSizes
)

func init() {
for i := range types.Typ {
basic := types.Typ[i]
gBasicsCache.Store(
types.TypeString(basic, nil),
func() types.Type { return basic },
)
}
gBasicsCache.Store("interface {}", func() types.Type {
return types.NewInterfaceType(nil, nil)
})
gBasicsCache.Store("any", func() types.Type {
return types.NewInterfaceType(nil, nil)
})
gBasicsCache.Store("comparable", func() types.Type {
return types.NewInterfaceType(nil, nil)
})
gBasicsCache.Store("error", func() types.Type {
return NewPackage("errors").
Scope().
Lookup("New").
Type().
Underlying().(*types.Signature).
Results().
At(0).
Type()
})
}

func TypeByImportAndName(path, name string) types.Type {
if path == "" {
return TypeByID(name)
}
return TypeByID(path + "." + name)
}

func TypeByID(id string) (t types.Type) {
if v, ok := gGoTypeCache.Load(id); ok {
return v.(types.Type)
}

defer func() {
if t == nil {
t = types.Typ[types.Invalid]
}
gGoTypeCache.Store(id, t)
}()

if id == "" {
return nil
}

if v, ok := gBasicsCache.Load(id); ok {
t = v.(func() types.Type)()
return
}

// map[x]
if idxMapLsb := strings.Index(id, "map["); idxMapLsb == 0 {
idxMapRsb := strings.Index(id, "]")
if idxMapRsb == -1 {
return nil
}
kt := TypeByID(id[4:idxMapRsb])
if kt == nil || kt == types.Typ[types.Invalid] {
return nil
}
vt := TypeByID(id[idxMapRsb+1:])
if vt == nil || vt == types.Typ[types.Invalid] {
return nil
}
t = types.NewMap(kt, vt)
return t
}

// []x [n]x
idxLsb := strings.Index(id, "[")
if idxLsb == 0 {
idxRsb := strings.Index(id, "]")
if idxRsb == -1 {
return nil
}
elem := TypeByID(id[idxRsb+1:])
if elem == nil || elem == types.Typ[types.Invalid] {
return nil
}
if idxRsb == idxLsb+1 {
t = types.NewSlice(elem)
return t
}
n, err := strconv.ParseInt(id[1:idxRsb], 10, 64)
if err != nil {
return t
}
t = types.NewArray(elem, n)
return t
}

// path.Type
if idxLsb == -1 {
if idxDot := strings.LastIndex(id, "."); idxDot > 0 {
pkg := NewPackage(id[0:idxDot])
if pkg == nil {
return nil
}
tpe, ok := pkg.Scope().Lookup(id[idxDot+1:]).(*types.TypeName)
if !ok || tpe == nil {
return nil
}
t = types.NewTypeName(token.NoPos, pkg, tpe.Name(), tpe.Type()).Type()
// t = tpe.Type()
return t
}
return t
}

// path.Type[generics parameters...]
idxRsb := strings.Index(id, "]")
if idxRsb == -1 {
return nil
}
typeid := id[0:idxLsb]
idxDot := strings.LastIndex(typeid, ".")
if idxDot == -1 {
return nil
}
pkg := NewPackage(typeid[:idxDot])
if pkg == nil {
return nil
}
tpe, ok := pkg.Scope().Lookup(typeid[idxDot+1:]).(*types.TypeName)
if !ok || tpe == nil {
return nil
}
named, ok := tpe.Type().(*types.Named)
if !ok {
return nil
}
cloned := types.NewNamed(tpe, tpe.Type().Underlying(), nil)
for i := 0; i < named.NumMethods(); i++ {
cloned.AddMethod(named.Method(i))
}
paramNames := strings.Split(id[idxLsb+1:idxRsb], ",")
paramList := named.TypeParams()
if len(paramNames) == 0 || paramList == nil || paramList.Len() != len(paramNames) {
return nil
}
paramTypes := make([]*types.TypeParam, paramList.Len())
for i := 0; i < paramList.Len(); i++ {
paramType := TypeByID(paramNames[i])
if paramType == nil || paramType == types.Typ[types.Invalid] {
return nil
}
paramTypes[i] = types.NewTypeParam(paramList.At(i).Obj(), paramType)
}
cloned.SetTypeParams(paramTypes)
t = cloned
return t
}

func NewPackage(path string) *types.Package {
if path == "" {
return nil
}

if v, ok := gPackagesCache.Load(path); ok {
return v.(*types.Package)
}

pkgs, err := packages.Load(&packages.Config{
Overlay: make(map[string][]byte),
Tests: true,
Mode: LoadTypes,
}, path)
must.NoErrorWrap(err, "failed to load packages from %s", path)

// if len(pkgs[0].Errors) != 0 {
// return nil
// }

pkg := pkgs[0].Types
gPackagesCache.Store(path, pkg)

return pkg
}

func NewGoTypeFromReflectType(t reflect.Type) types.Type {
if t == nil {
return nil
}

refs := 0
for t.Kind() == reflect.Pointer {
refs++
t = t.Elem()
}

tpe, pkg := t.Name(), t.PkgPath()
if tpe == "error" && pkg == "" {
return must.BeTrueV(gBasicsCache.Load("error")).(func() types.Type)()
}

var gt types.Type
if pkg != "" {
gt = TypeByID(pkg + "." + tpe)
} else {
gt = underlying(t)
}

for refs > 0 {
gt = types.NewPointer(gt)
refs--
}
return gt
}

func underlying(t reflect.Type) types.Type {
if tt, ok := ReflectKindToTypesKind[t.Kind()]; ok {
return types.Typ[tt]
}

switch t.Kind() {
case reflect.Array:
return types.NewArray(
NewGoTypeFromReflectType(t.Elem()),
int64(t.Len()),
)
case reflect.Chan:
var dir = types.SendRecv
switch t.ChanDir() {
case reflect.RecvDir: // 1
dir = types.RecvOnly // 2
case reflect.SendDir: // 2
dir = types.SendOnly // 1
case reflect.BothDir: // 3
dir = types.SendRecv // 0
}
return types.NewChan(dir, NewGoTypeFromReflectType(t.Elem()))
case reflect.Slice:
return types.NewSlice(
NewGoTypeFromReflectType(t.Elem()),
)
case reflect.Map:
return types.NewMap(
NewGoTypeFromReflectType(t.Key()),
NewGoTypeFromReflectType(t.Elem()),
)
case reflect.Func:
ins := make([]*types.Var, t.NumIn())
outs := make([]*types.Var, t.NumOut())
for i := range ins {
in := t.In(i)
ins[i] = types.NewParam(
0,
NewPackage(in.PkgPath()),
"",
NewGoTypeFromReflectType(in),
)
}
for i := range outs {
out := t.Out(i)
outs[i] = types.NewParam(
0,
NewPackage(out.PkgPath()),
"",
NewGoTypeFromReflectType(out),
)
}
return types.NewSignatureType(
nil, nil, nil,
types.NewTuple(ins...),
types.NewTuple(outs...),
t.IsVariadic(),
)
case reflect.Interface:
fns := make([]*types.Func, t.NumMethod())
for i := range fns {
f := t.Method(i)
fns[i] = types.NewFunc(
0,
NewPackage(f.PkgPath),
f.Name,
NewGoTypeFromReflectType(f.Type).(*types.Signature),
)
}
return types.NewInterfaceType(fns, nil).Complete()
case reflect.Struct:
fields := make([]*types.Var, t.NumField())
tags := make([]string, len(fields))
for i := range fields {
f := t.Field(i)
fields[i] = types.NewField(
0,
NewPackage(f.PkgPath),
f.Name,
NewGoTypeFromReflectType(f.Type),
f.Anonymous,
)
tags[i] = string(f.Tag)
}
return types.NewStruct(fields, tags)
default: // [never entered this case] reflect.Invalid, reflect.Pointer
return nil
}
}
Loading

0 comments on commit 8de00ca

Please sign in to comment.