Skip to content

Commit

Permalink
support type for generic functions
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Aug 22, 2024
1 parent 3e5ea31 commit 10f4484
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 42 deletions.
18 changes: 15 additions & 3 deletions cmd/internal/export/exportpkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,28 @@ func exportPkg(pkg *Package, sname string, id string, tagList []string) ([]byte,
if !pkg.IsEmpty() {
imports = append(imports, `"reflect"`)
}
var hasToken bool
if len(pkg.UntypedConsts) > 0 || len(pkg.TypedConsts) > 0 {
imports = append(imports, `"go/constant"`)
var hasToken bool
for _, c := range pkg.UntypedConsts {
if strings.Index(c, "token.") >= 0 {

Check warning on line 72 in cmd/internal/export/exportpkg.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/exportpkg.go#L72

wrapperFunc: suggestion: strings.Contains(c, "token.") (gocritic)
hasToken = true
break
}
}
if hasToken {
imports = append(imports, `"go/token"`)
}
if len(pkg.GenericFuncTypeConstructors) > 0 {
imports = append(imports, `"go/types"`)
for _, c := range pkg.GenericFuncTypeConstructors {
if strings.Index(c, "token.") >= 0 {

Check warning on line 81 in cmd/internal/export/exportpkg.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/exportpkg.go#L81

wrapperFunc: suggestion: strings.Contains(c, "token.") (gocritic)
hasToken = true
break
}
}
}
if hasToken {
imports = append(imports, `"go/token"`)
}
tmpl := template_pkg
if pkg.IsEmpty() {
tmpl = template_empty_pkg
Expand All @@ -100,6 +109,7 @@ func exportPkg(pkg *Package, sname string, id string, tagList []string) ([]byte,
"$ALIASTYPES", joinList(pkg.AliasTypes),
"$VARS", joinList(pkg.Vars),
"$FUNCS", joinList(pkg.Funcs),
"$GENERIC_FUNC_TYPE_CONSTRUCTORS", joinList(pkg.GenericFuncTypeConstructors),
"$TYPEDCONSTS", joinList(pkg.TypedConsts),
"$UNTYPEDCONSTS", joinList(pkg.UntypedConsts),
"$TAGS", strings.Join(tagList, "\n"),
Expand Down Expand Up @@ -136,6 +146,7 @@ func init() {
AliasTypes: map[string]reflect.Type{$ALIASTYPES},
Vars: map[string]reflect.Value{$VARS},
Funcs: map[string]reflect.Value{$FUNCS},
GenericFuncTypeConstructors: map[string]igop.GenericFuncTypeConstructor{$GENERIC_FUNC_TYPE_CONSTRUCTORS},
TypedConsts: map[string]igop.TypedConst{$TYPEDCONSTS},
UntypedConsts: map[string]igop.UntypedConst{$UNTYPEDCONSTS},
})
Expand Down Expand Up @@ -185,6 +196,7 @@ func init() {
AliasTypes: map[string]reflect.Type{$ALIASTYPES},
Vars: map[string]reflect.Value{$VARS},
Funcs: map[string]reflect.Value{$FUNCS},
GenericFuncTypeConstructors: map[string]igop.GenericFuncTypeConstructor{$GENERIC_FUNC_TYPE_CONSTRUCTORS},
TypedConsts: map[string]igop.TypedConst{$TYPEDCONSTS},
UntypedConsts: map[string]igop.UntypedConst{$UNTYPEDCONSTS},
Source: source,
Expand Down
138 changes: 120 additions & 18 deletions cmd/internal/export/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,26 +160,27 @@ type Package struct {
*/

type Package struct {
Name string
Path string
Deps []string
NamedTypes []string
Interfaces []string
AliasTypes []string
Vars []string
Funcs []string
Consts []string
TypedConsts []string
UntypedConsts []string
Links []string
Source string
usedPkg bool
Name string
Path string
Deps []string
NamedTypes []string
Interfaces []string
AliasTypes []string
Vars []string
Funcs []string
GenericFuncTypeConstructors []string
Consts []string
TypedConsts []string
UntypedConsts []string
Links []string
Source string
usedPkg bool
}

func (p *Package) IsEmpty() bool {
return len(p.NamedTypes) == 0 && len(p.Interfaces) == 0 &&
len(p.AliasTypes) == 0 && len(p.Vars) == 0 &&
len(p.Funcs) == 0 && len(p.Consts) == 0 &&
len(p.Funcs) == 0 && len(p.GenericFuncTypeConstructors) == 0 && len(p.Consts) == 0 &&
len(p.TypedConsts) == 0 && len(p.UntypedConsts) == 0
}

Expand Down Expand Up @@ -342,6 +343,103 @@ func (p *Program) ExportSource(e *Package, info *loader.PackageInfo) error {
return nil
}

type typeSerializer struct {
typeParams map[*types.TypeParam]string // *types.TypeParam -> local var name
}

func newTypeSerializer() *typeSerializer {
return &typeSerializer{typeParams: make(map[*types.TypeParam]string)}
}

func (s *typeSerializer) serializeNamed(t *types.Named) string {
obj := t.Obj()
pkg := obj.Pkg()
if pkg == nil {
return fmt.Sprintf("types.Universe.Lookup(%q)", obj.Name())
}
str := "func () types.Type {"
str += fmt.Sprintf("if pkg.Path() == %q {\n", pkg.Path())
str += fmt.Sprintf("return pkg.Scope().Lookup(%q).Type()\n", obj.Name())
str += "} else {\n"
str += fmt.Sprintf("return tl.GetPackage(%q).Scope().Lookup(%q).Type()\n", pkg.Path(), obj.Name())
str += "}\n"
str += "}()"
return str
}

func (s *typeSerializer) serializeTypeParam(tp *types.TypeParam) (ref, def string) {
if varName, ok := s.typeParams[tp]; ok {
return varName, ""
}
varName := fmt.Sprintf("tp_%d", len(s.typeParams)+1)
s.typeParams[tp] = varName
def = fmt.Sprintf("%s := types.NewTypeParam(types.NewTypeName(token.NoPos, pkg, %q, nil), %s)\n", varName, tp.Obj().Name(), s.serialize(tp.Constraint()))
return varName, def
}

func (s *typeSerializer) serializeSignature(sig *types.Signature) string {
str := "func () *types.Signature {"
tpVarNames := make([]string, sig.TypeParams().Len())
for i := 0; i < sig.TypeParams().Len(); i++ {
tp := sig.TypeParams().At(i)
tpVarName, tpVarDef := s.serializeTypeParam(tp)
tpVarNames[i] = tpVarName
str += tpVarDef
}
pVarNames := make([]string, sig.Params().Len())
for i := 0; i < sig.Params().Len(); i++ {
p := sig.Params().At(i)
pVarName := fmt.Sprintf("p_%d", i+1)
pVarNames[i] = pVarName
str += fmt.Sprintf("%s := types.NewParam(token.NoPos, pkg, %q, %s)\n", pVarName, p.Name(), s.serialize(p.Type()))
}
rVarNames := make([]string, sig.Results().Len())
for i := 0; i < sig.Results().Len(); i++ {
r := sig.Results().At(i)
rVarName := fmt.Sprintf("r_%d", i)
rVarNames[i] = rVarName
str += fmt.Sprintf("%s := types.NewVar(token.NoPos, pkg, %q, %s)\n", rVarName, r.Name(), s.serialize(r.Type()))
}
str += fmt.Sprintf("return types.NewSignatureType(nil, nil, []*types.TypeParam{%s}, types.NewTuple(%s), types.NewTuple(%s), false)\n",

Check warning on line 403 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L403

Duplicate words (types.NewTuple(%s),) found (dupword)
strings.Join(tpVarNames, ", "), strings.Join(pVarNames, ", "), strings.Join(rVarNames, ", "))
str += "}()"
return str
}

func (s *typeSerializer) serialize(typ types.Type) string {

Check warning on line 409 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L409

cyclomatic: function (*typeSerializer).serialize has cyclomatic complexity 13 (> max enabled 10) (revive)
switch t := typ.(type) {
case *types.Basic:
return fmt.Sprintf("types.Typ[%v]", t.Kind())
case *types.Named:
return s.serializeNamed(t)
case *types.Pointer:
return fmt.Sprintf("types.NewPointer(%s)", s.serialize(t.Elem()))
case *types.Slice:
return fmt.Sprintf("types.NewSlice(%s)", s.serialize(t.Elem()))
case *types.Array:
return fmt.Sprintf("types.NewArray(%s, %v)", s.serialize(t.Elem()), t.Len())
case *types.Map:
return fmt.Sprintf("types.NewMap(%s, %s)", s.serialize(t.Key()), s.serialize(t.Elem()))
case *types.Chan:
return fmt.Sprintf("types.NewChan(%v, %s)", t.Dir(), s.serialize(t.Elem()))
case *types.Signature:
return s.serializeSignature(t)
case *types.Interface:
// if is "any" interface
if t == types.Universe.Lookup("any").Type() {
return `types.Universe.Lookup("any").Type()`
}
log.Panicf("unsupported type non-any interface %T", t)

Check warning on line 432 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L432

deep-exit: calls to log.Panicf only in main() or init() functions (revive)
case *types.TypeParam:
ref, _ := s.serializeTypeParam(t)
return ref
default:
log.Panicf("unsupported type %T", t)

Check warning on line 437 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L437

deep-exit: calls to log.Panicf only in main() or init() functions (revive)
}
log.Panicf("unsupported type %T", typ)

Check warning on line 439 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L439

deep-exit: calls to log.Panicf only in main() or init() functions (revive)
return ""
}

func (p *Program) ExportPkg(path string, sname string) (*Package, error) {

Check warning on line 443 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L443

cognitive complexity 36 of func `(*Program).ExportPkg` is high (> 30) (gocognit)
info := p.prog.Package(path)
if info == nil {
Expand Down Expand Up @@ -375,9 +473,13 @@ func (p *Program) ExportPkg(path string, sname string) (*Package, error) {
e.usedPkg = true
case *types.Func:
if hasTypeParam(t.Type()) {
if !flagExportSource {
log.Println("skip typeparam", t)
}
ts := newTypeSerializer()
sig := t.Type().(*types.Signature)

Check warning on line 477 in cmd/internal/export/loader.go

View check run for this annotation

qiniu-x / golangci-lint

cmd/internal/export/loader.go#L477

unchecked-type-assertion: type cast result is unchecked in t.Type().(*types.Signature) - type assertion will panic if not matched (revive)
str := "func(tl *igop.TypesLoader, pkg *types.Package) *types.Func {\n"
str += fmt.Sprintf("return types.NewFunc(token.NoPos, pkg, %q, %s)\n", t.Name(), ts.serialize(sig))
str += "}"
e.GenericFuncTypeConstructors = append(e.GenericFuncTypeConstructors, fmt.Sprintf("%q : %s", t.Name(), str))

foundGeneric = true
continue
}
Expand Down
21 changes: 11 additions & 10 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -788,16 +788,17 @@ func RunTest(path string, args []string, mode Mode) error {

var (
builtinPkg = &Package{
Name: "builtin",
Path: "github.com/goplus/igop/builtin",
Deps: make(map[string]string),
Interfaces: map[string]reflect.Type{},
NamedTypes: map[string]reflect.Type{},
AliasTypes: map[string]reflect.Type{},
Vars: map[string]reflect.Value{},
Funcs: map[string]reflect.Value{},
TypedConsts: map[string]TypedConst{},
UntypedConsts: map[string]UntypedConst{},
Name: "builtin",
Path: "github.com/goplus/igop/builtin",
Deps: make(map[string]string),
Interfaces: map[string]reflect.Type{},
NamedTypes: map[string]reflect.Type{},
AliasTypes: map[string]reflect.Type{},
Vars: map[string]reflect.Value{},
Funcs: map[string]reflect.Value{},
GenericFuncTypeConstructors: map[string]GenericFuncTypeConstructor{},
TypedConsts: map[string]TypedConst{},
UntypedConsts: map[string]UntypedConst{},
}
builtinPrefix = "Builtin_"
)
Expand Down
29 changes: 18 additions & 11 deletions package.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package igop

import (
"go/constant"
"go/types"
"log"
"reflect"
"sort"
Expand Down Expand Up @@ -62,18 +63,21 @@ type UntypedConst struct {
Value constant.Value
}

type GenericFuncTypeConstructor func(tl *TypesLoader, pkg *types.Package) *types.Func

type Package struct {
Interfaces map[string]reflect.Type
NamedTypes map[string]reflect.Type
AliasTypes map[string]reflect.Type
Vars map[string]reflect.Value
Funcs map[string]reflect.Value
TypedConsts map[string]TypedConst
UntypedConsts map[string]UntypedConst
Deps map[string]string // path -> name
Name string
Path string
Source string
Interfaces map[string]reflect.Type
NamedTypes map[string]reflect.Type
AliasTypes map[string]reflect.Type
Vars map[string]reflect.Value
Funcs map[string]reflect.Value
GenericFuncTypeConstructors map[string]GenericFuncTypeConstructor
TypedConsts map[string]TypedConst
UntypedConsts map[string]UntypedConst
Deps map[string]string // path -> name
Name string
Path string
Source string
}

// merge same package
Expand All @@ -90,6 +94,9 @@ func (p *Package) merge(same *Package) {
for k, v := range same.Funcs {
p.Funcs[k] = v
}
for k, v := range same.GenericFuncTypeConstructors {
p.GenericFuncTypeConstructors[k] = v
}
for k, v := range same.UntypedConsts {
p.UntypedConsts[k] = v
}
Expand Down
7 changes: 7 additions & 0 deletions rtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ func (r *TypesLoader) installPackage(pkg *Package) (err error) {
for name, fn := range pkg.Funcs {
r.InsertFunc(p, name, fn)
}
for name, f := range pkg.GenericFuncTypeConstructors {
r.InsertGenericFunc(p, name, f)
}
for name, v := range pkg.Vars {
r.InsertVar(p, name, v.Elem())
}
Expand Down Expand Up @@ -260,6 +263,10 @@ func (r *TypesLoader) InsertFunc(p *types.Package, name string, v reflect.Value)
p.Scope().Insert(types.NewFunc(token.NoPos, p, name, typ.(*types.Signature)))
}

func (r *TypesLoader) InsertGenericFunc(p *types.Package, name string, f GenericFuncTypeConstructor) {
p.Scope().Insert(f(r, p))
}

func (r *TypesLoader) InsertVar(p *types.Package, name string, v reflect.Value) {
typ := r.ToType(v.Type())
p.Scope().Insert(types.NewVar(token.NoPos, p, name, typ))
Expand Down

0 comments on commit 10f4484

Please sign in to comment.