Skip to content

Commit fe3f70e

Browse files
authored
Merge pull request #1167 from nevkontakte/generics
Initialize type param-dependent types in a generic factory function.
2 parents 73a9b7b + ccf753a commit fe3f70e

File tree

4 files changed

+320
-19
lines changed

4 files changed

+320
-19
lines changed

compiler/package.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414

1515
"github.com/gopherjs/gopherjs/compiler/analysis"
1616
"github.com/gopherjs/gopherjs/compiler/astutil"
17+
"github.com/gopherjs/gopherjs/compiler/typesutil"
1718
"github.com/neelance/astrewrite"
1819
"golang.org/x/tools/go/gcexportdata"
19-
"golang.org/x/tools/go/types/typeutil"
2020
)
2121

2222
// pkgContext maintains compiler context for a specific package.
@@ -28,8 +28,7 @@ type pkgContext struct {
2828
pkgVars map[string]string
2929
objectNames map[types.Object]string
3030
varPtrNames map[*types.Var]string
31-
anonTypes []*types.TypeName
32-
anonTypeMap typeutil.Map
31+
anonTypes typesutil.AnonymousTypes
3332
escapingVars map[*types.Var]bool
3433
indentation int
3534
dependencies map[types.Object]bool
@@ -38,6 +37,14 @@ type pkgContext struct {
3837
errList ErrorList
3938
}
4039

40+
// genericCtx contains compiler context for a generic function or type.
41+
//
42+
// It is used to accumulate information about types and objects that depend on
43+
// type parameters and must be constructed in a generic factory function.
44+
type genericCtx struct {
45+
anonTypes typesutil.AnonymousTypes
46+
}
47+
4148
func (p *pkgContext) SelectionOf(e *ast.SelectorExpr) (selection, bool) {
4249
if sel, ok := p.Selections[e]; ok {
4350
return sel, true
@@ -78,6 +85,8 @@ type funcContext struct {
7885
*analysis.FuncInfo
7986
// Surrounding package context.
8087
pkgCtx *pkgContext
88+
// Surrounding generic function context. nil if non-generic code.
89+
genericCtx *genericCtx
8190
// Function context, surrounding this function definition. For package-level
8291
// functions or methods it is the package-level function context (even though
8392
// it technically doesn't correspond to a function). nil for the package-level
@@ -597,7 +606,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
597606
}
598607

599608
// anonymous types
600-
for _, t := range funcCtx.pkgCtx.anonTypes {
609+
for _, t := range funcCtx.pkgCtx.anonTypes.Ordered() {
601610
d := Decl{
602611
Vars: []string{t.Name()},
603612
DceObjectFilter: t.Name(),
@@ -758,6 +767,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
758767
c := &funcContext{
759768
FuncInfo: info,
760769
pkgCtx: outerContext.pkgCtx,
770+
genericCtx: outerContext.genericCtx,
761771
parent: outerContext,
762772
sigTypes: &signatureTypes{Sig: sig},
763773
allVars: make(map[string]int, len(outerContext.allVars)),
@@ -769,6 +779,9 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
769779
for k, v := range outerContext.allVars {
770780
c.allVars[k] = v
771781
}
782+
if c.sigTypes.IsGeneric() {
783+
c.genericCtx = &genericCtx{}
784+
}
772785
prevEV := c.pkgCtx.escapingVars
773786

774787
var params []string
@@ -786,7 +799,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
786799
}
787800
}
788801

789-
bodyOutput := string(c.CatchOutput(1, func() {
802+
bodyOutput := string(c.CatchOutput(c.bodyIndent(), func() {
790803
if len(c.Blocking) != 0 {
791804
c.pkgCtx.Scopes[body] = c.pkgCtx.Scopes[typ]
792805
c.handleEscapingVars(body)
@@ -888,13 +901,13 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
888901
}
889902

890903
if prefix != "" {
891-
bodyOutput = c.Indentation(1) + "/* */" + prefix + "\n" + bodyOutput
904+
bodyOutput = c.Indentation(c.bodyIndent()) + "/* */" + prefix + "\n" + bodyOutput
892905
}
893906
if suffix != "" {
894-
bodyOutput = bodyOutput + c.Indentation(1) + "/* */" + suffix + "\n"
907+
bodyOutput = bodyOutput + c.Indentation(c.bodyIndent()) + "/* */" + suffix + "\n"
895908
}
896909
if localVarDefs != "" {
897-
bodyOutput = c.Indentation(1) + localVarDefs + bodyOutput
910+
bodyOutput = c.Indentation(c.bodyIndent()) + localVarDefs + bodyOutput
898911
}
899912

900913
c.pkgCtx.escapingVars = prevEV
@@ -907,14 +920,23 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
907920
// from the call site.
908921
// TODO(nevkontakte): Cache function instances for a given combination of type
909922
// parameters.
910-
// TODO(nevkontakte): Generate type parameter arguments and derive all dependent
911-
// types inside the function.
912923
typeParams := []string{}
913924
for i := 0; i < c.sigTypes.Sig.TypeParams().Len(); i++ {
914925
typeParam := c.sigTypes.Sig.TypeParams().At(i)
915926
typeParams = append(typeParams, c.typeName(typeParam))
916927
}
917928

918-
return params, fmt.Sprintf("function%s(%s){ return function(%s) {\n%s%s}; }",
919-
functionName, strings.Join(typeParams, ", "), strings.Join(params, ", "), bodyOutput, c.Indentation(0))
929+
// anonymous types
930+
typesInit := strings.Builder{}
931+
for _, t := range c.genericCtx.anonTypes.Ordered() {
932+
fmt.Fprintf(&typesInit, "%svar %s = $%sType(%s);\n", c.Indentation(1), t.Name(), strings.ToLower(typeKind(t.Type())[5:]), c.initArgs(t.Type()))
933+
}
934+
935+
code := &strings.Builder{}
936+
fmt.Fprintf(code, "function%s(%s){\n", functionName, strings.Join(typeParams, ", "))
937+
fmt.Fprintf(code, "%s", typesInit.String())
938+
fmt.Fprintf(code, "%sreturn function(%s) {\n", c.Indentation(1), strings.Join(params, ", "))
939+
fmt.Fprintf(code, "%s", bodyOutput)
940+
fmt.Fprintf(code, "%s};\n%s}", c.Indentation(1), c.Indentation(0))
941+
return params, code.String()
920942
}

compiler/typesutil/typesutil.go

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package typesutil
22

3-
import "go/types"
3+
import (
4+
"fmt"
5+
"go/types"
46

7+
"golang.org/x/tools/go/types/typeutil"
8+
)
9+
10+
// IsJsPackage returns is the package is github.com/gopherjs/gopherjs/js.
511
func IsJsPackage(pkg *types.Package) bool {
612
return pkg != nil && pkg.Path() == "github.com/gopherjs/gopherjs/js"
713
}
814

15+
// IsJsObject returns true if the type represents a pointer to github.com/gopherjs/gopherjs/js.Object.
916
func IsJsObject(t types.Type) bool {
1017
ptr, isPtr := t.(*types.Pointer)
1118
if !isPtr {
@@ -14,3 +21,92 @@ func IsJsObject(t types.Type) bool {
1421
named, isNamed := ptr.Elem().(*types.Named)
1522
return isNamed && IsJsPackage(named.Obj().Pkg()) && named.Obj().Name() == "Object"
1623
}
24+
25+
// AnonymousTypes maintains a mapping between anonymous types encountered in a
26+
// Go program to equivalent synthetic names types GopherJS generated from them.
27+
//
28+
// This enables a runtime performance optimization where different instances of
29+
// the same anonymous type (e.g. in expression `x := map[int]string{}`) don't
30+
// need to initialize type information (e.g. `$mapType($Int, $String)`) every
31+
// time, but reuse the single synthesized type (e.g. `mapType$1`).
32+
type AnonymousTypes struct {
33+
m typeutil.Map
34+
order []*types.TypeName
35+
}
36+
37+
// Get returns the synthesized type name for the provided anonymous type or nil
38+
// if the type is not registered.
39+
func (at *AnonymousTypes) Get(t types.Type) *types.TypeName {
40+
s, _ := at.m.At(t).(*types.TypeName)
41+
return s
42+
}
43+
44+
// Ordered returns synthesized type names for the registered anonymous types in
45+
// the order they were registered.
46+
func (at *AnonymousTypes) Ordered() []*types.TypeName {
47+
return at.order
48+
}
49+
50+
// Register a synthesized type name for an anonymous type.
51+
func (at *AnonymousTypes) Register(name *types.TypeName, anonType types.Type) {
52+
at.m.Set(anonType, name)
53+
at.order = append(at.order, name)
54+
}
55+
56+
// IsGeneric returns true if the provided type is a type parameter or depends
57+
// on a type parameter.
58+
func IsGeneric(t types.Type) bool {
59+
switch t := t.(type) {
60+
case *types.Array:
61+
return IsGeneric(t.Elem())
62+
case *types.Basic:
63+
return false
64+
case *types.Chan:
65+
return IsGeneric(t.Elem())
66+
case *types.Interface:
67+
for i := 0; i < t.NumMethods(); i++ {
68+
if IsGeneric(t.Method(i).Type()) {
69+
return true
70+
}
71+
}
72+
for i := 0; i < t.NumEmbeddeds(); i++ {
73+
if IsGeneric(t.Embedded(i)) {
74+
return true
75+
}
76+
}
77+
return false
78+
case *types.Map:
79+
return IsGeneric(t.Key()) || IsGeneric(t.Elem())
80+
case *types.Named:
81+
// Named type declarations dependent on a type param are currently not
82+
// supported by the upstream Go compiler.
83+
return false
84+
case *types.Pointer:
85+
return IsGeneric(t.Elem())
86+
case *types.Slice:
87+
return IsGeneric(t.Elem())
88+
case *types.Signature:
89+
for i := 0; i < t.Params().Len(); i++ {
90+
if IsGeneric(t.Params().At(i).Type()) {
91+
return true
92+
}
93+
}
94+
for i := 0; i < t.Results().Len(); i++ {
95+
if IsGeneric(t.Results().At(i).Type()) {
96+
return true
97+
}
98+
}
99+
return false
100+
case *types.Struct:
101+
for i := 0; i < t.NumFields(); i++ {
102+
if IsGeneric(t.Field(i).Type()) {
103+
return true
104+
}
105+
}
106+
return false
107+
case *types.TypeParam:
108+
return true
109+
default:
110+
panic(fmt.Errorf("%v has unexpected type %T", t, t))
111+
}
112+
}

0 commit comments

Comments
 (0)