From 10f4484ca90313c13177508447fc4b1783b94a14 Mon Sep 17 00:00:00 2001 From: nighca Date: Thu, 22 Aug 2024 20:03:41 +0800 Subject: [PATCH] support type for generic functions --- cmd/internal/export/exportpkg.go | 18 +++- cmd/internal/export/loader.go | 138 +++++++++++++++++++++++++++---- context.go | 21 ++--- package.go | 29 ++++--- rtypes.go | 7 ++ 5 files changed, 171 insertions(+), 42 deletions(-) diff --git a/cmd/internal/export/exportpkg.go b/cmd/internal/export/exportpkg.go index d6c08806..1cdf862d 100644 --- a/cmd/internal/export/exportpkg.go +++ b/cmd/internal/export/exportpkg.go @@ -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 { 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 { + hasToken = true + break + } } } + if hasToken { + imports = append(imports, `"go/token"`) + } tmpl := template_pkg if pkg.IsEmpty() { tmpl = template_empty_pkg @@ -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"), @@ -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}, }) @@ -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, diff --git a/cmd/internal/export/loader.go b/cmd/internal/export/loader.go index c63ec90d..c040c5a8 100644 --- a/cmd/internal/export/loader.go +++ b/cmd/internal/export/loader.go @@ -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 } @@ -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", + strings.Join(tpVarNames, ", "), strings.Join(pVarNames, ", "), strings.Join(rVarNames, ", ")) + str += "}()" + return str +} + +func (s *typeSerializer) serialize(typ types.Type) string { + 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) + case *types.TypeParam: + ref, _ := s.serializeTypeParam(t) + return ref + default: + log.Panicf("unsupported type %T", t) + } + log.Panicf("unsupported type %T", typ) + return "" +} + func (p *Program) ExportPkg(path string, sname string) (*Package, error) { info := p.prog.Package(path) if info == nil { @@ -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) + 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 } diff --git a/context.go b/context.go index 6d0d5edd..f5d72067 100644 --- a/context.go +++ b/context.go @@ -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_" ) diff --git a/package.go b/package.go index dfc498a5..b9dab71e 100644 --- a/package.go +++ b/package.go @@ -18,6 +18,7 @@ package igop import ( "go/constant" + "go/types" "log" "reflect" "sort" @@ -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 @@ -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 } diff --git a/rtypes.go b/rtypes.go index 9ae27d96..aa9cde6a 100644 --- a/rtypes.go +++ b/rtypes.go @@ -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()) } @@ -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))