blob: f82cd798ae713706636dc4334ea2ae7efd1f3301 [file] [log] [blame]
package compiler
import (
"bytes"
"encoding/json"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"sort"
"strings"
"time"
"github.com/gopherjs/gopherjs/compiler/analysis"
"github.com/gopherjs/gopherjs/compiler/astutil"
"github.com/gopherjs/gopherjs/compiler/internal/symbol"
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
"github.com/gopherjs/gopherjs/compiler/typesutil"
"github.com/gopherjs/gopherjs/internal/experiments"
"golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/go/types/typeutil"
)
// pkgContext maintains compiler context for a specific package.
type pkgContext struct {
*analysis.Info
additionalSelections map[*ast.SelectorExpr]typesutil.Selection
typesCtx *types.Context
typeNames typesutil.TypeNames
pkgVars map[string]string
varPtrNames map[*types.Var]string
anonTypes []*types.TypeName
anonTypeMap typeutil.Map
escapingVars map[*types.Var]bool
indentation int
dependencies map[types.Object]bool
minify bool
fileSet *token.FileSet
errList ErrorList
instanceSet *typeparams.PackageInstanceSets
}
// funcContext maintains compiler context for a specific function.
//
// An instance of this type roughly corresponds to a lexical scope for generated
// JavaScript code (as defined for `var` declarations).
type funcContext struct {
*analysis.FuncInfo
// Surrounding package context.
pkgCtx *pkgContext
// 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
// function context.
parent *funcContext
// Signature of the function this context corresponds to or nil for the
// package-level function context. For generic functions it is the original
// generic signature to make sure result variable identity in the signature
// matches the variable objects referenced in the function body.
sig *typesutil.Signature
// All variable names available in the current function scope. The key is a Go
// variable name and the value is the number of synonymous variable names
// visible from this scope (e.g. due to shadowing). This number is used to
// avoid conflicts when assigning JS variable names for Go variables.
allVars map[string]int
// Local JS variable names defined within this function context. This list
// contains JS variable names assigned to Go variables, as well as other
// auxiliary variables the compiler needs. It is used to generate `var`
// declaration at the top of the function, as well as context save/restore.
localVars []string
// AST expressions representing function's named return values. nil if the
// function has no return values or they are not named.
resultNames []ast.Expr
// Function's internal control flow graph used for generation of a "flattened"
// version of the function when the function is blocking or uses goto.
// TODO(nevkontakte): Describe the exact semantics of this map.
flowDatas map[*types.Label]*flowData
// Number of control flow blocks in a "flattened" function.
caseCounter int
// A mapping from Go labels statements (e.g. labelled loop) to the flow block
// id corresponding to it.
labelCases map[*types.Label]int
// Generated code buffer for the current function.
output []byte
// Generated code that should be emitted at the end of the JS statement.
delayedOutput []byte
// Set to true if source position is available and should be emitted for the
// source map.
posAvailable bool
// Current position in the Go source code.
pos token.Pos
// For each instantiation of a generic function or method, contains the
// current mapping between type parameters and corresponding type arguments.
// The mapping is used to determine concrete types for expressions within the
// instance's context. Can be nil outside of the generic context, in which
// case calling its methods is safe and simply does no substitution.
typeResolver *typeparams.Resolver
// Mapping from function-level objects to JS variable names they have been assigned.
objectNames map[types.Object]string
}
type flowData struct {
postStmt func()
beginCase int
endCase int
}
// ImportContext provides access to information about imported packages.
type ImportContext struct {
// Mapping for an absolute import path to the package type information.
Packages map[string]*types.Package
// Import returns a previously compiled Archive for a dependency package. If
// the Import() call was successful, the corresponding entry must be added to
// the Packages map.
Import func(importPath string) (*Archive, error)
}
// Compile the provided Go sources as a single package.
//
// Import path must be the absolute import path for a package. Provided sources
// are always sorted by name to ensure reproducible JavaScript output.
func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, importContext *ImportContext, minify bool) (_ *Archive, err error) {
defer func() {
e := recover()
if e == nil {
return
}
if fe, ok := bailingOut(e); ok {
// Orderly bailout, return whatever clues we already have.
fmt.Fprintf(fe, `building package %q`, importPath)
err = fe
return
}
// Some other unexpected panic, catch the stack trace and return as an error.
err = bailout(fmt.Errorf("unexpected compiler panic while building package %q: %v", importPath, e))
}()
srcs := sources{
ImportPath: importPath,
Files: files,
FileSet: fileSet,
}.Sort()
tContext := types.NewContext()
typesInfo, typesPkg, err := srcs.TypeCheck(importContext, tContext)
if err != nil {
return nil, err
}
if genErr := typeparams.RequiresGenericsSupport(typesInfo); genErr != nil && !experiments.Env.Generics {
return nil, fmt.Errorf("package %s requires generics support (https://github.com/gopherjs/gopherjs/issues/1013): %w", importPath, genErr)
}
importContext.Packages[srcs.ImportPath] = typesPkg
// Extract all go:linkname compiler directives from the package source.
goLinknames, err := srcs.ParseGoLinknames()
if err != nil {
return nil, err
}
srcs = srcs.Simplified(typesInfo)
isBlocking := func(f *types.Func) bool {
archive, err := importContext.Import(f.Pkg().Path())
if err != nil {
panic(err)
}
fullName := f.FullName()
for _, d := range archive.Declarations {
if string(d.FullName) == fullName {
return d.Blocking
}
}
panic(fullName)
}
tc := typeparams.Collector{
TContext: tContext,
Info: typesInfo,
Instances: &typeparams.PackageInstanceSets{},
}
tc.Scan(typesPkg, srcs.Files...)
instancesByObj := map[types.Object][]typeparams.Instance{}
for _, inst := range tc.Instances.Pkg(typesPkg).Values() {
instancesByObj[inst.Object] = append(instancesByObj[inst.Object], inst)
}
pkgInfo := analysis.AnalyzePkg(srcs.Files, fileSet, typesInfo, typesPkg, isBlocking)
funcCtx := &funcContext{
FuncInfo: pkgInfo.InitFuncInfo,
pkgCtx: &pkgContext{
Info: pkgInfo,
additionalSelections: make(map[*ast.SelectorExpr]typesutil.Selection),
typesCtx: tContext,
pkgVars: make(map[string]string),
varPtrNames: make(map[*types.Var]string),
escapingVars: make(map[*types.Var]bool),
indentation: 1,
dependencies: make(map[types.Object]bool),
minify: minify,
fileSet: srcs.FileSet,
instanceSet: tc.Instances,
},
allVars: make(map[string]int),
flowDatas: map[*types.Label]*flowData{nil: {}},
caseCounter: 1,
labelCases: make(map[*types.Label]int),
objectNames: map[types.Object]string{},
}
for name := range reservedKeywords {
funcCtx.allVars[name] = 1
}
// imports
var importDecls []*Decl
var importedPaths []string
for _, importedPkg := range typesPkg.Imports() {
if importedPkg == types.Unsafe {
// Prior to Go 1.9, unsafe import was excluded by Imports() method,
// but now we do it here to maintain previous behavior.
continue
}
funcCtx.pkgCtx.pkgVars[importedPkg.Path()] = funcCtx.newVariable(importedPkg.Name(), true)
importedPaths = append(importedPaths, importedPkg.Path())
}
sort.Strings(importedPaths)
for _, impPath := range importedPaths {
id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignatureType(nil, nil, nil, nil, nil, false))
call := &ast.CallExpr{Fun: id}
funcCtx.Blocking[call] = true
funcCtx.Flattened[call] = true
importDecls = append(importDecls, &Decl{
Vars: []string{funcCtx.pkgCtx.pkgVars[impPath]},
DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", funcCtx.pkgCtx.pkgVars[impPath], impPath)),
InitCode: funcCtx.CatchOutput(1, func() { funcCtx.translateStmt(&ast.ExprStmt{X: call}, nil) }),
})
}
var functions []*ast.FuncDecl
var vars []*types.Var
for _, file := range srcs.Files {
for _, decl := range file.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
sig := funcCtx.pkgCtx.Defs[d.Name].(*types.Func).Type().(*types.Signature)
if sig.Recv() == nil {
funcCtx.objectName(funcCtx.pkgCtx.Defs[d.Name].(*types.Func)) // register toplevel name
}
if !isBlank(d.Name) {
functions = append(functions, d)
}
case *ast.GenDecl:
switch d.Tok {
case token.TYPE:
for _, spec := range d.Specs {
o := funcCtx.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName)
funcCtx.pkgCtx.typeNames.Add(o)
funcCtx.objectName(o) // register toplevel name
}
case token.VAR:
for _, spec := range d.Specs {
for _, name := range spec.(*ast.ValueSpec).Names {
if !isBlank(name) {
o := funcCtx.pkgCtx.Defs[name].(*types.Var)
vars = append(vars, o)
funcCtx.objectName(o) // register toplevel name
}
}
}
case token.CONST:
// skip, constants are inlined
}
}
}
}
collectDependencies := func(f func()) []string {
funcCtx.pkgCtx.dependencies = make(map[types.Object]bool)
f()
var deps []string
for o := range funcCtx.pkgCtx.dependencies {
qualifiedName := o.Pkg().Path() + "." + o.Name()
if f, ok := o.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil {
deps = append(deps, qualifiedName+"~")
continue
}
deps = append(deps, qualifiedName)
}
sort.Strings(deps)
return deps
}
// variables
var varDecls []*Decl
varsWithInit := make(map[*types.Var]bool)
for _, init := range funcCtx.pkgCtx.InitOrder {
for _, o := range init.Lhs {
varsWithInit[o] = true
}
}
for _, o := range vars {
var d Decl
if !o.Exported() {
d.Vars = []string{funcCtx.objectName(o)}
}
if funcCtx.pkgCtx.HasPointer[o] && !o.Exported() {
d.Vars = append(d.Vars, funcCtx.varPtrName(o))
}
if _, ok := varsWithInit[o]; !ok {
d.DceDeps = collectDependencies(func() {
d.InitCode = []byte(fmt.Sprintf("\t\t%s = %s;\n", funcCtx.objectName(o), funcCtx.translateExpr(funcCtx.zeroValue(o.Type())).String()))
})
}
d.DceObjectFilter = o.Name()
varDecls = append(varDecls, &d)
}
for _, init := range funcCtx.pkgCtx.InitOrder {
lhs := make([]ast.Expr, len(init.Lhs))
for i, o := range init.Lhs {
ident := ast.NewIdent(o.Name())
ident.NamePos = o.Pos()
funcCtx.pkgCtx.Defs[ident] = o
lhs[i] = funcCtx.setType(ident, o.Type())
varsWithInit[o] = true
}
var d Decl
d.DceDeps = collectDependencies(func() {
funcCtx.localVars = nil
d.InitCode = funcCtx.CatchOutput(1, func() {
funcCtx.translateStmt(&ast.AssignStmt{
Lhs: lhs,
Tok: token.DEFINE,
Rhs: []ast.Expr{init.Rhs},
}, nil)
})
d.Vars = append(d.Vars, funcCtx.localVars...)
})
if len(init.Lhs) == 1 {
if !analysis.HasSideEffect(init.Rhs, funcCtx.pkgCtx.Info.Info) {
d.DceObjectFilter = init.Lhs[0].Name()
}
}
varDecls = append(varDecls, &d)
}
// functions
var funcDecls []*Decl
var mainFunc *types.Func
for _, fun := range functions {
o := funcCtx.pkgCtx.Defs[fun.Name].(*types.Func)
sig := o.Type().(*types.Signature)
var instances []typeparams.Instance
if typeparams.SignatureTypeParams(sig) != nil {
instances = instancesByObj[o]
} else {
instances = []typeparams.Instance{{Object: o}}
}
if fun.Recv == nil {
// Auxiliary decl shared by all instances of the function that defines
// package-level variable by which they all are referenced.
// TODO(nevkontakte): Set DCE attributes such that it is eliminated if all
// instances are dead.
varDecl := Decl{}
varDecl.Vars = []string{funcCtx.objectName(o)}
if o.Type().(*types.Signature).TypeParams().Len() != 0 {
varDecl.DeclCode = funcCtx.CatchOutput(0, func() {
funcCtx.Printf("%s = {};", funcCtx.objectName(o))
})
}
funcDecls = append(funcDecls, &varDecl)
}
for _, inst := range instances {
funcInfo := funcCtx.pkgCtx.FuncDeclInfos[o]
d := Decl{
FullName: o.FullName(),
Blocking: len(funcInfo.Blocking) != 0,
}
d.LinkingName = symbol.New(o)
if fun.Recv == nil {
d.RefExpr = funcCtx.instName(inst)
d.DceObjectFilter = o.Name()
switch o.Name() {
case "main":
mainFunc = o
d.DceObjectFilter = ""
case "init":
d.InitCode = funcCtx.CatchOutput(1, func() {
id := funcCtx.newIdent("", types.NewSignatureType( /*recv=*/ nil /*rectTypeParams=*/, nil /*typeParams=*/, nil /*params=*/, nil /*results=*/, nil /*variadic=*/, false))
funcCtx.pkgCtx.Uses[id] = o
call := &ast.CallExpr{Fun: id}
if len(funcCtx.pkgCtx.FuncDeclInfos[o].Blocking) != 0 {
funcCtx.Blocking[call] = true
}
funcCtx.translateStmt(&ast.ExprStmt{X: call}, nil)
})
d.DceObjectFilter = ""
}
} else {
recvType := o.Type().(*types.Signature).Recv().Type()
ptr, isPointer := recvType.(*types.Pointer)
namedRecvType, _ := recvType.(*types.Named)
if isPointer {
namedRecvType = ptr.Elem().(*types.Named)
}
d.NamedRecvType = funcCtx.objectName(namedRecvType.Obj())
d.DceObjectFilter = namedRecvType.Obj().Name()
if !fun.Name.IsExported() {
d.DceMethodFilter = o.Name() + "~"
}
}
d.DceDeps = collectDependencies(func() {
d.DeclCode = funcCtx.translateToplevelFunction(fun, funcInfo, inst)
})
funcDecls = append(funcDecls, &d)
}
}
if typesPkg.Name() == "main" {
if mainFunc == nil {
return nil, fmt.Errorf("missing main function")
}
id := funcCtx.newIdent("", types.NewSignatureType( /*recv=*/ nil /*rectTypeParams=*/, nil /*typeParams=*/, nil /*params=*/, nil /*results=*/, nil /*variadic=*/, false))
funcCtx.pkgCtx.Uses[id] = mainFunc
call := &ast.CallExpr{Fun: id}
ifStmt := &ast.IfStmt{
Cond: funcCtx.newIdent("$pkg === $mainPkg", types.Typ[types.Bool]),
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.ExprStmt{X: call},
&ast.AssignStmt{
Lhs: []ast.Expr{funcCtx.newIdent("$mainFinished", types.Typ[types.Bool])},
Tok: token.ASSIGN,
Rhs: []ast.Expr{funcCtx.newConst(types.Typ[types.Bool], constant.MakeBool(true))},
},
},
},
}
if len(funcCtx.pkgCtx.FuncDeclInfos[mainFunc].Blocking) != 0 {
funcCtx.Blocking[call] = true
funcCtx.Flattened[ifStmt] = true
}
funcDecls = append(funcDecls, &Decl{
InitCode: funcCtx.CatchOutput(1, func() {
funcCtx.translateStmt(ifStmt, nil)
}),
})
}
// named types
var typeDecls []*Decl
for _, o := range funcCtx.pkgCtx.typeNames.Slice() {
if o.IsAlias() {
continue
}
typ := o.Type().(*types.Named)
var instances []typeparams.Instance
if typ.TypeParams() != nil {
instances = instancesByObj[o]
} else {
instances = []typeparams.Instance{{Object: o}}
}
typeName := funcCtx.objectName(o)
varDecl := Decl{Vars: []string{typeName}}
if typ.TypeParams() != nil {
varDecl.DeclCode = funcCtx.CatchOutput(0, func() {
funcCtx.Printf("%s = {};", funcCtx.objectName(o))
})
}
if isPkgLevel(o) {
varDecl.TypeInitCode = funcCtx.CatchOutput(0, func() {
funcCtx.Printf("$pkg.%s = %s;", encodeIdent(o.Name()), funcCtx.objectName(o))
})
}
typeDecls = append(typeDecls, &varDecl)
for _, inst := range instances {
funcCtx.typeResolver = typeparams.NewResolver(funcCtx.pkgCtx.typesCtx, typeparams.ToSlice(typ.TypeParams()), inst.TArgs)
named := typ
if !inst.IsTrivial() {
instantiated, err := types.Instantiate(funcCtx.pkgCtx.typesCtx, typ, inst.TArgs, true)
if err != nil {
return nil, fmt.Errorf("failed to instantiate type %v with args %v: %w", typ, inst.TArgs, err)
}
named = instantiated.(*types.Named)
}
underlying := named.Underlying()
d := Decl{
DceObjectFilter: o.Name(),
}
d.DceDeps = collectDependencies(func() {
d.DeclCode = funcCtx.CatchOutput(0, func() {
size := int64(0)
constructor := "null"
switch t := underlying.(type) {
case *types.Struct:
params := make([]string, t.NumFields())
for i := 0; i < t.NumFields(); i++ {
params[i] = fieldName(t, i) + "_"
}
constructor = fmt.Sprintf("function(%s) {\n\t\tthis.$val = this;\n\t\tif (arguments.length === 0) {\n", strings.Join(params, ", "))
for i := 0; i < t.NumFields(); i++ {
constructor += fmt.Sprintf("\t\t\tthis.%s = %s;\n", fieldName(t, i), funcCtx.translateExpr(funcCtx.zeroValue(t.Field(i).Type())).String())
}
constructor += "\t\t\treturn;\n\t\t}\n"
for i := 0; i < t.NumFields(); i++ {
constructor += fmt.Sprintf("\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i))
}
constructor += "\t}"
case *types.Basic, *types.Array, *types.Slice, *types.Chan, *types.Signature, *types.Interface, *types.Pointer, *types.Map:
size = sizes32.Sizeof(t)
}
if tPointer, ok := underlying.(*types.Pointer); ok {
if _, ok := tPointer.Elem().Underlying().(*types.Array); ok {
// Array pointers have non-default constructors to support wrapping
// of the native objects.
constructor = "$arrayPtrCtor()"
}
}
funcCtx.Printf(`%s = $newType(%d, %s, %q, %t, "%s", %t, %s);`, funcCtx.instName(inst), size, typeKind(typ), inst.TypeString(), o.Name() != "", o.Pkg().Path(), o.Exported(), constructor)
})
d.MethodListCode = funcCtx.CatchOutput(0, func() {
if _, ok := underlying.(*types.Interface); ok {
return
}
var methods []string
var ptrMethods []string
for i := 0; i < named.NumMethods(); i++ {
method := named.Method(i)
name := method.Name()
if reservedKeywords[name] {
name += "$"
}
pkgPath := ""
if !method.Exported() {
pkgPath = method.Pkg().Path()
}
t := method.Type().(*types.Signature)
entry := fmt.Sprintf(`{prop: "%s", name: %s, pkg: "%s", typ: $funcType(%s)}`, name, encodeString(method.Name()), pkgPath, funcCtx.initArgs(t))
if _, isPtr := t.Recv().Type().(*types.Pointer); isPtr {
ptrMethods = append(ptrMethods, entry)
continue
}
methods = append(methods, entry)
}
if len(methods) > 0 {
funcCtx.Printf("%s.methods = [%s];", funcCtx.instName(inst), strings.Join(methods, ", "))
}
if len(ptrMethods) > 0 {
funcCtx.Printf("%s.methods = [%s];", funcCtx.typeName(types.NewPointer(named)), strings.Join(ptrMethods, ", "))
}
})
switch t := underlying.(type) {
case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct:
d.TypeInitCode = funcCtx.CatchOutput(0, func() {
funcCtx.Printf("%s.init(%s);", funcCtx.instName(inst), funcCtx.initArgs(t))
})
}
})
typeDecls = append(typeDecls, &d)
}
funcCtx.typeResolver = nil
}
// anonymous types
for _, t := range funcCtx.pkgCtx.anonTypes {
d := Decl{
Vars: []string{t.Name()},
DceObjectFilter: t.Name(),
}
d.DceDeps = collectDependencies(func() {
d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), funcCtx.initArgs(t.Type())))
})
typeDecls = append(typeDecls, &d)
}
var allDecls []*Decl
for _, d := range append(append(append(importDecls, typeDecls...), varDecls...), funcDecls...) {
d.DeclCode = removeWhitespace(d.DeclCode, minify)
d.MethodListCode = removeWhitespace(d.MethodListCode, minify)
d.TypeInitCode = removeWhitespace(d.TypeInitCode, minify)
d.InitCode = removeWhitespace(d.InitCode, minify)
allDecls = append(allDecls, d)
}
if len(funcCtx.pkgCtx.errList) != 0 {
return nil, funcCtx.pkgCtx.errList
}
exportData := new(bytes.Buffer)
if err := gcexportdata.Write(exportData, nil, typesPkg); err != nil {
return nil, fmt.Errorf("failed to write export data: %w", err)
}
encodedFileSet := new(bytes.Buffer)
if err := srcs.FileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil {
return nil, err
}
return &Archive{
ImportPath: srcs.ImportPath,
Name: typesPkg.Name(),
Imports: importedPaths,
ExportData: exportData.Bytes(),
Declarations: allDecls,
FileSet: encodedFileSet.Bytes(),
Minified: minify,
GoLinknames: goLinknames,
BuildTime: time.Now(),
}, nil
}
func (fc *funcContext) initArgs(ty types.Type) string {
switch t := ty.(type) {
case *types.Array:
return fmt.Sprintf("%s, %d", fc.typeName(t.Elem()), t.Len())
case *types.Chan:
return fmt.Sprintf("%s, %t, %t", fc.typeName(t.Elem()), t.Dir()&types.SendOnly != 0, t.Dir()&types.RecvOnly != 0)
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := range methods {
method := t.Method(i)
pkgPath := ""
if !method.Exported() {
pkgPath = method.Pkg().Path()
}
methods[i] = fmt.Sprintf(`{prop: "%s", name: "%s", pkg: "%s", typ: $funcType(%s)}`, method.Name(), method.Name(), pkgPath, fc.initArgs(method.Type()))
}
return fmt.Sprintf("[%s]", strings.Join(methods, ", "))
case *types.Map:
return fmt.Sprintf("%s, %s", fc.typeName(t.Key()), fc.typeName(t.Elem()))
case *types.Pointer:
return fc.typeName(t.Elem())
case *types.Slice:
return fc.typeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
for i := range params {
params[i] = fc.typeName(t.Params().At(i).Type())
}
results := make([]string, t.Results().Len())
for i := range results {
results[i] = fc.typeName(t.Results().At(i).Type())
}
return fmt.Sprintf("[%s], [%s], %t", strings.Join(params, ", "), strings.Join(results, ", "), t.Variadic())
case *types.Struct:
pkgPath := ""
fields := make([]string, t.NumFields())
for i := range fields {
field := t.Field(i)
if !field.Exported() {
pkgPath = field.Pkg().Path()
}
fields[i] = fmt.Sprintf(`{prop: "%s", name: %s, embedded: %t, exported: %t, typ: %s, tag: %s}`, fieldName(t, i), encodeString(field.Name()), field.Anonymous(), field.Exported(), fc.typeName(field.Type()), encodeString(t.Tag(i)))
}
return fmt.Sprintf(`"%s", [%s]`, pkgPath, strings.Join(fields, ", "))
case *types.TypeParam:
err := bailout(fmt.Errorf(`%v has unexpected generic type parameter %T`, ty, ty))
panic(err)
default:
err := bailout(fmt.Errorf("%v has unexpected type %T", ty, ty))
panic(err)
}
}
func (fc *funcContext) translateToplevelFunction(fun *ast.FuncDecl, info *analysis.FuncInfo, inst typeparams.Instance) []byte {
o := inst.Object.(*types.Func)
sig := o.Type().(*types.Signature)
var recv *ast.Ident
if fun.Recv != nil && fun.Recv.List[0].Names != nil {
recv = fun.Recv.List[0].Names[0]
}
var joinedParams string
primaryFunction := func(funcRef string) []byte {
if fun.Body == nil {
return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", funcRef, o.FullName()))
}
params, fun := translateFunction(fun.Type, recv, fun.Body, fc, sig, info, funcRef, inst)
joinedParams = strings.Join(params, ", ")
return []byte(fmt.Sprintf("\t%s = %s;\n", funcRef, fun))
}
code := bytes.NewBuffer(nil)
if fun.Recv == nil {
funcRef := fc.instName(inst)
code.Write(primaryFunction(funcRef))
if fun.Name.IsExported() {
fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), funcRef)
}
return code.Bytes()
}
recvInst := inst.Recv()
recvInstName := fc.instName(recvInst)
recvType := recvInst.Object.Type().(*types.Named)
funName := fun.Name.Name
if reservedKeywords[funName] {
funName += "$"
}
if _, isStruct := recvType.Underlying().(*types.Struct); isStruct {
code.Write(primaryFunction(recvInstName + ".ptr.prototype." + funName))
fmt.Fprintf(code, "\t%s.prototype.%s = function(%s) { return this.$val.%s(%s); };\n", recvInstName, funName, joinedParams, funName, joinedParams)
return code.Bytes()
}
if ptr, isPointer := sig.Recv().Type().(*types.Pointer); isPointer {
if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray {
code.Write(primaryFunction(recvInstName + ".prototype." + funName))
fmt.Fprintf(code, "\t$ptrType(%s).prototype.%s = function(%s) { return (new %s(this.$get())).%s(%s); };\n", recvInstName, funName, joinedParams, recvInstName, funName, joinedParams)
return code.Bytes()
}
return primaryFunction(fmt.Sprintf("$ptrType(%s).prototype.%s", recvInstName, funName))
}
value := "this.$get()"
if isWrapped(recvType) {
value = fmt.Sprintf("new %s(%s)", recvInstName, value)
}
code.Write(primaryFunction(recvInstName + ".prototype." + funName))
fmt.Fprintf(code, "\t$ptrType(%s).prototype.%s = function(%s) { return %s.%s(%s); };\n", recvInstName, funName, joinedParams, value, funName, joinedParams)
return code.Bytes()
}
func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, outerContext *funcContext, sig *types.Signature, info *analysis.FuncInfo, funcRef string, inst typeparams.Instance) ([]string, string) {
if info == nil {
panic("nil info")
}
c := &funcContext{
FuncInfo: info,
pkgCtx: outerContext.pkgCtx,
parent: outerContext,
allVars: make(map[string]int, len(outerContext.allVars)),
localVars: []string{},
flowDatas: map[*types.Label]*flowData{nil: {}},
caseCounter: 1,
labelCases: make(map[*types.Label]int),
typeResolver: outerContext.typeResolver,
objectNames: map[types.Object]string{},
sig: &typesutil.Signature{Sig: sig},
}
for k, v := range outerContext.allVars {
c.allVars[k] = v
}
prevEV := c.pkgCtx.escapingVars
if sig.TypeParams().Len() > 0 {
c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, typeparams.ToSlice(sig.TypeParams()), inst.TArgs)
} else if sig.RecvTypeParams().Len() > 0 {
c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, typeparams.ToSlice(sig.RecvTypeParams()), inst.TArgs)
}
if c.objectNames == nil {
c.objectNames = map[types.Object]string{}
}
var params []string
for _, param := range typ.Params.List {
if len(param.Names) == 0 {
params = append(params, c.newLocalVariable("param"))
continue
}
for _, ident := range param.Names {
if isBlank(ident) {
params = append(params, c.newLocalVariable("param"))
continue
}
params = append(params, c.objectName(c.pkgCtx.Defs[ident]))
}
}
bodyOutput := string(c.CatchOutput(1, func() {
if len(c.Blocking) != 0 {
c.pkgCtx.Scopes[body] = c.pkgCtx.Scopes[typ]
c.handleEscapingVars(body)
}
if c.sig != nil && c.sig.HasNamedResults() {
c.resultNames = make([]ast.Expr, c.sig.Sig.Results().Len())
for i := 0; i < c.sig.Sig.Results().Len(); i++ {
result := c.sig.Sig.Results().At(i)
typ := c.typeResolver.Substitute(result.Type())
c.Printf("%s = %s;", c.objectName(result), c.translateExpr(c.zeroValue(typ)).String())
id := ast.NewIdent("")
c.pkgCtx.Uses[id] = result
c.resultNames[i] = c.setType(id, typ)
}
}
if recv != nil && !isBlank(recv) {
this := "this"
if isWrapped(c.typeOf(recv)) {
this = "this.$val" // Unwrap receiver value.
}
c.Printf("%s = %s;", c.translateExpr(recv), this)
}
c.translateStmtList(body.List)
if len(c.Flattened) != 0 && !astutil.EndsWithReturn(body.List) {
c.translateStmt(&ast.ReturnStmt{}, nil)
}
}))
sort.Strings(c.localVars)
var prefix, suffix, functionName string
if len(c.Flattened) != 0 {
c.localVars = append(c.localVars, "$s")
prefix = prefix + " $s = $s || 0;"
}
if c.HasDefer {
c.localVars = append(c.localVars, "$deferred")
suffix = " }" + suffix
if len(c.Blocking) != 0 {
suffix = " }" + suffix
}
}
localVarDefs := "" // Function-local var declaration at the top.
if len(c.Blocking) != 0 {
if funcRef == "" {
funcRef = "$b"
functionName = " $b"
}
localVars := append([]string{}, c.localVars...)
// There are several special variables involved in handling blocking functions:
// $r is sometimes used as a temporary variable to store blocking call result.
// $c indicates that a function is being resumed after a blocking call when set to true.
// $f is an object used to save and restore function context for blocking calls.
localVars = append(localVars, "$r")
// If a blocking function is being resumed, initialize local variables from the saved context.
localVarDefs = fmt.Sprintf("var {%s, $c} = $restore(this, {%s});\n", strings.Join(localVars, ", "), strings.Join(params, ", "))
// If the function gets blocked, save local variables for future.
saveContext := fmt.Sprintf("var $f = {$blk: "+funcRef+", $c: true, $r, %s};", strings.Join(c.localVars, ", "))
suffix = " " + saveContext + "return $f;" + suffix
} else if len(c.localVars) > 0 {
// Non-blocking functions simply declare local variables with no need for restore support.
localVarDefs = fmt.Sprintf("var %s;\n", strings.Join(c.localVars, ", "))
}
if c.HasDefer {
prefix = prefix + " var $err = null; try {"
deferSuffix := " } catch(err) { $err = err;"
if len(c.Blocking) != 0 {
deferSuffix += " $s = -1;"
}
if c.resultNames == nil && c.sig.HasResults() {
deferSuffix += fmt.Sprintf(" return%s;", c.translateResults(nil))
}
deferSuffix += " } finally { $callDeferred($deferred, $err);"
if c.resultNames != nil {
deferSuffix += fmt.Sprintf(" if (!$curGoroutine.asleep) { return %s; }", c.translateResults(c.resultNames))
}
if len(c.Blocking) != 0 {
deferSuffix += " if($curGoroutine.asleep) {"
}
suffix = deferSuffix + suffix
}
if len(c.Flattened) != 0 {
prefix = prefix + " s: while (true) { switch ($s) { case 0:"
suffix = " } return; }" + suffix
}
if c.HasDefer {
prefix = prefix + " $deferred = []; $curGoroutine.deferStack.push($deferred);"
}
if prefix != "" {
bodyOutput = c.Indentation(1) + "/* */" + prefix + "\n" + bodyOutput
}
if suffix != "" {
bodyOutput = bodyOutput + c.Indentation(1) + "/* */" + suffix + "\n"
}
if localVarDefs != "" {
bodyOutput = c.Indentation(1) + localVarDefs + bodyOutput
}
c.pkgCtx.escapingVars = prevEV
return params, fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, c.Indentation(0))
}