Skip to content

Initialize type param-dependent types in a generic factory function. #1167

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

Merged
merged 1 commit into from
Oct 27, 2022
Merged
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
46 changes: 34 additions & 12 deletions compiler/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (

"github.com/gopherjs/gopherjs/compiler/analysis"
"github.com/gopherjs/gopherjs/compiler/astutil"
"github.com/gopherjs/gopherjs/compiler/typesutil"
"github.com/neelance/astrewrite"
"golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/go/types/typeutil"
)

// pkgContext maintains compiler context for a specific package.
Expand All @@ -28,8 +28,7 @@ type pkgContext struct {
pkgVars map[string]string
objectNames map[types.Object]string
varPtrNames map[*types.Var]string
anonTypes []*types.TypeName
anonTypeMap typeutil.Map
anonTypes typesutil.AnonymousTypes
escapingVars map[*types.Var]bool
indentation int
dependencies map[types.Object]bool
Expand All @@ -38,6 +37,14 @@ type pkgContext struct {
errList ErrorList
}

// genericCtx contains compiler context for a generic function or type.
//
// It is used to accumulate information about types and objects that depend on
// type parameters and must be constructed in a generic factory function.
type genericCtx struct {
anonTypes typesutil.AnonymousTypes
}

func (p *pkgContext) SelectionOf(e *ast.SelectorExpr) (selection, bool) {
if sel, ok := p.Selections[e]; ok {
return sel, true
Expand Down Expand Up @@ -78,6 +85,8 @@ type funcContext struct {
*analysis.FuncInfo
// Surrounding package context.
pkgCtx *pkgContext
// Surrounding generic function context. nil if non-generic code.
genericCtx *genericCtx
// Function context, surrounding this function definition. For package-level
// functions or methods it is the package-level function context (even though
// it technically doesn't correspond to a function). nil for the package-level
Expand Down Expand Up @@ -597,7 +606,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
}

// anonymous types
for _, t := range funcCtx.pkgCtx.anonTypes {
for _, t := range funcCtx.pkgCtx.anonTypes.Ordered() {
d := Decl{
Vars: []string{t.Name()},
DceObjectFilter: t.Name(),
Expand Down Expand Up @@ -758,6 +767,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
c := &funcContext{
FuncInfo: info,
pkgCtx: outerContext.pkgCtx,
genericCtx: outerContext.genericCtx,
parent: outerContext,
sigTypes: &signatureTypes{Sig: sig},
allVars: make(map[string]int, len(outerContext.allVars)),
Expand All @@ -769,6 +779,9 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
for k, v := range outerContext.allVars {
c.allVars[k] = v
}
if c.sigTypes.IsGeneric() {
c.genericCtx = &genericCtx{}
}
prevEV := c.pkgCtx.escapingVars

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

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

if prefix != "" {
bodyOutput = c.Indentation(1) + "/* */" + prefix + "\n" + bodyOutput
bodyOutput = c.Indentation(c.bodyIndent()) + "/* */" + prefix + "\n" + bodyOutput
}
if suffix != "" {
bodyOutput = bodyOutput + c.Indentation(1) + "/* */" + suffix + "\n"
bodyOutput = bodyOutput + c.Indentation(c.bodyIndent()) + "/* */" + suffix + "\n"
}
if localVarDefs != "" {
bodyOutput = c.Indentation(1) + localVarDefs + bodyOutput
bodyOutput = c.Indentation(c.bodyIndent()) + localVarDefs + bodyOutput
}

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

return params, fmt.Sprintf("function%s(%s){ return function(%s) {\n%s%s}; }",
functionName, strings.Join(typeParams, ", "), strings.Join(params, ", "), bodyOutput, c.Indentation(0))
// anonymous types
typesInit := strings.Builder{}
for _, t := range c.genericCtx.anonTypes.Ordered() {
fmt.Fprintf(&typesInit, "%svar %s = $%sType(%s);\n", c.Indentation(1), t.Name(), strings.ToLower(typeKind(t.Type())[5:]), c.initArgs(t.Type()))
}

code := &strings.Builder{}
fmt.Fprintf(code, "function%s(%s){\n", functionName, strings.Join(typeParams, ", "))
fmt.Fprintf(code, "%s", typesInit.String())
fmt.Fprintf(code, "%sreturn function(%s) {\n", c.Indentation(1), strings.Join(params, ", "))
fmt.Fprintf(code, "%s", bodyOutput)
fmt.Fprintf(code, "%s};\n%s}", c.Indentation(1), c.Indentation(0))
return params, code.String()
}
98 changes: 97 additions & 1 deletion compiler/typesutil/typesutil.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package typesutil

import "go/types"
import (
"fmt"
"go/types"

"golang.org/x/tools/go/types/typeutil"
)

// IsJsPackage returns is the package is github.com/gopherjs/gopherjs/js.
func IsJsPackage(pkg *types.Package) bool {
return pkg != nil && pkg.Path() == "github.com/gopherjs/gopherjs/js"
}

// IsJsObject returns true if the type represents a pointer to github.com/gopherjs/gopherjs/js.Object.
func IsJsObject(t types.Type) bool {
ptr, isPtr := t.(*types.Pointer)
if !isPtr {
Expand All @@ -14,3 +21,92 @@ func IsJsObject(t types.Type) bool {
named, isNamed := ptr.Elem().(*types.Named)
return isNamed && IsJsPackage(named.Obj().Pkg()) && named.Obj().Name() == "Object"
}

// AnonymousTypes maintains a mapping between anonymous types encountered in a
// Go program to equivalent synthetic names types GopherJS generated from them.
//
// This enables a runtime performance optimization where different instances of
// the same anonymous type (e.g. in expression `x := map[int]string{}`) don't
// need to initialize type information (e.g. `$mapType($Int, $String)`) every
// time, but reuse the single synthesized type (e.g. `mapType$1`).
type AnonymousTypes struct {
m typeutil.Map
order []*types.TypeName
}

// Get returns the synthesized type name for the provided anonymous type or nil
// if the type is not registered.
func (at *AnonymousTypes) Get(t types.Type) *types.TypeName {
s, _ := at.m.At(t).(*types.TypeName)
return s
}

// Ordered returns synthesized type names for the registered anonymous types in
// the order they were registered.
func (at *AnonymousTypes) Ordered() []*types.TypeName {
return at.order
}

// Register a synthesized type name for an anonymous type.
func (at *AnonymousTypes) Register(name *types.TypeName, anonType types.Type) {
at.m.Set(anonType, name)
at.order = append(at.order, name)
}

// IsGeneric returns true if the provided type is a type parameter or depends
// on a type parameter.
func IsGeneric(t types.Type) bool {
switch t := t.(type) {
case *types.Array:
return IsGeneric(t.Elem())
case *types.Basic:
return false
case *types.Chan:
return IsGeneric(t.Elem())
case *types.Interface:
for i := 0; i < t.NumMethods(); i++ {
if IsGeneric(t.Method(i).Type()) {
return true
}
}
for i := 0; i < t.NumEmbeddeds(); i++ {
if IsGeneric(t.Embedded(i)) {
return true
}
}
return false
case *types.Map:
return IsGeneric(t.Key()) || IsGeneric(t.Elem())
case *types.Named:
// Named type declarations dependent on a type param are currently not
// supported by the upstream Go compiler.
return false
case *types.Pointer:
return IsGeneric(t.Elem())
case *types.Slice:
return IsGeneric(t.Elem())
case *types.Signature:
for i := 0; i < t.Params().Len(); i++ {
if IsGeneric(t.Params().At(i).Type()) {
return true
}
}
for i := 0; i < t.Results().Len(); i++ {
if IsGeneric(t.Results().At(i).Type()) {
return true
}
}
return false
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
if IsGeneric(t.Field(i).Type()) {
return true
}
}
return false
case *types.TypeParam:
return true
default:
panic(fmt.Errorf("%v has unexpected type %T", t, t))
}
}
Loading