Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support type for generic functions #274

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
"$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 @@
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 @@
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 {
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,7 +343,104 @@
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 {
return nil, fmt.Errorf("not found path %v", path)
Expand Down Expand Up @@ -375,9 +473,13 @@
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
Loading