Skip to content

Commit 5232f09

Browse files
committed
Initialize type param-dependent types in a generic factory function.
Instead of being initialized at the package level, they are initialized inside a generic factory, where type params are available. Example: ``` // Go: func _F[T any](t T) { _ = []T{} } // JS: _F = function(T){ var sliceType = $sliceType(T); return function(t) { $unused(new sliceType([])); }; }; ```
1 parent 73a9b7b commit 5232f09

File tree

4 files changed

+323
-19
lines changed

4 files changed

+323
-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: 100 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,95 @@ 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, ok := at.m.At(t).(*types.TypeName)
41+
if !ok {
42+
return nil
43+
}
44+
return s
45+
}
46+
47+
// Ordered returns synthesized type names for the registered anonymous types in
48+
// the order they were registered.
49+
func (at *AnonymousTypes) Ordered() []*types.TypeName {
50+
return at.order
51+
}
52+
53+
// Register a synthesized type name for an anonymous type.
54+
func (at *AnonymousTypes) Register(name *types.TypeName, anonType types.Type) {
55+
at.m.Set(anonType, name)
56+
at.order = append(at.order, name)
57+
}
58+
59+
// IsGeneric returns true if the provided type is a type parameter or depends
60+
// on a type parameter.
61+
func IsGeneric(t types.Type) bool {
62+
switch t := t.(type) {
63+
case *types.Array:
64+
return IsGeneric(t.Elem())
65+
case *types.Basic:
66+
return false
67+
case *types.Chan:
68+
return IsGeneric(t.Elem())
69+
case *types.Interface:
70+
for i := 0; i < t.NumMethods(); i++ {
71+
if IsGeneric(t.Method(i).Type()) {
72+
return true
73+
}
74+
}
75+
for i := 0; i < t.NumEmbeddeds(); i++ {
76+
if IsGeneric(t.Embedded(i)) {
77+
return true
78+
}
79+
}
80+
return false
81+
case *types.Map:
82+
return IsGeneric(t.Key()) || IsGeneric(t.Elem())
83+
case *types.Named:
84+
// Named type declarations dependent on a type param are currently not
85+
// supported by the upstream Go compiler.
86+
return false
87+
case *types.Pointer:
88+
return IsGeneric(t.Elem())
89+
case *types.Slice:
90+
return IsGeneric(t.Elem())
91+
case *types.Signature:
92+
for i := 0; i < t.Params().Len(); i++ {
93+
if IsGeneric(t.Params().At(i).Type()) {
94+
return true
95+
}
96+
}
97+
for i := 0; i < t.Results().Len(); i++ {
98+
if IsGeneric(t.Results().At(i).Type()) {
99+
return true
100+
}
101+
}
102+
return false
103+
case *types.Struct:
104+
for i := 0; i < t.NumFields(); i++ {
105+
if IsGeneric(t.Field(i).Type()) {
106+
return true
107+
}
108+
}
109+
return false
110+
case *types.TypeParam:
111+
return true
112+
default:
113+
panic(fmt.Errorf("%v has unexpected type %T", t, t))
114+
}
115+
}

0 commit comments

Comments
 (0)