diff --git a/compiler/expressions.go b/compiler/expressions.go index 5d9e37cc9..1d86174ce 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -362,14 +362,14 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if isUnsigned(basic) { shift = ">>>" } - return fc.formatExpr(`(%1s = %2e / %3e, (%1s === %1s && %1s !== 1/0 && %1s !== -1/0) ? %1s %4s 0 : $throwRuntimeError("integer divide by zero"))`, fc.newVariable("_q"), e.X, e.Y, shift) + return fc.formatExpr(`(%1s = %2e / %3e, (%1s === %1s && %1s !== 1/0 && %1s !== -1/0) ? %1s %4s 0 : $throwRuntimeError("integer divide by zero"))`, fc.newLocalVariable("_q"), e.X, e.Y, shift) } if basic.Kind() == types.Float32 { return fc.fixNumber(fc.formatExpr("%e / %e", e.X, e.Y), basic) } return fc.formatExpr("%e / %e", e.X, e.Y) case token.REM: - return fc.formatExpr(`(%1s = %2e %% %3e, %1s === %1s ? %1s : $throwRuntimeError("integer divide by zero"))`, fc.newVariable("_r"), e.X, e.Y) + return fc.formatExpr(`(%1s = %2e %% %3e, %1s === %1s ? %1s : $throwRuntimeError("integer divide by zero"))`, fc.newLocalVariable("_r"), e.X, e.Y) case token.SHL, token.SHR: op := e.Op.String() if e.Op == token.SHR && isUnsigned(basic) { @@ -385,7 +385,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if e.Op == token.SHR && !isUnsigned(basic) { return fc.fixNumber(fc.formatParenExpr("%e >> $min(%f, 31)", e.X, e.Y), basic) } - y := fc.newVariable("y") + y := fc.newLocalVariable("y") return fc.fixNumber(fc.formatExpr("(%s = %f, %s < 32 ? (%e %s %s) : 0)", y, e.Y, y, e.X, op, y), basic) case token.AND, token.OR: if isUnsigned(basic) { @@ -408,7 +408,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if fc.Blocking[e.Y] { skipCase := fc.caseCounter fc.caseCounter++ - resultVar := fc.newVariable("_v") + resultVar := fc.newLocalVariable("_v") fc.Printf("if (!(%s)) { %s = false; $s = %d; continue s; }", fc.translateExpr(e.X), resultVar, skipCase) fc.Printf("%s = %s; case %d:", resultVar, fc.translateExpr(e.Y), skipCase) return fc.formatExpr("%s", resultVar) @@ -418,7 +418,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if fc.Blocking[e.Y] { skipCase := fc.caseCounter fc.caseCounter++ - resultVar := fc.newVariable("_v") + resultVar := fc.newLocalVariable("_v") fc.Printf("if (%s) { %s = true; $s = %d; continue s; }", fc.translateExpr(e.X), resultVar, skipCase) fc.Printf("%s = %s; case %d:", resultVar, fc.translateExpr(e.Y), skipCase) return fc.formatExpr("%s", resultVar) @@ -477,7 +477,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if _, isTuple := exprType.(*types.Tuple); isTuple { return fc.formatExpr( `(%1s = $mapIndex(%2e,%3s), %1s !== undefined ? [%1s.v, true] : [%4e, false])`, - fc.newVariable("_entry"), + fc.newLocalVariable("_entry"), e.X, key, fc.zeroValue(t.Elem()), @@ -485,17 +485,25 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } return fc.formatExpr( `(%1s = $mapIndex(%2e,%3s), %1s !== undefined ? %1s.v : %4e)`, - fc.newVariable("_entry"), + fc.newLocalVariable("_entry"), e.X, key, fc.zeroValue(t.Elem()), ) case *types.Basic: return fc.formatExpr("%e.charCodeAt(%f)", e.X, e.Index) + case *types.Signature: + return fc.translateGenericInstance(e) default: - panic(fmt.Sprintf("Unhandled IndexExpr: %T\n", t)) + panic(fmt.Errorf("unhandled IndexExpr: %T", t)) + } + case *ast.IndexListExpr: + switch t := fc.pkgCtx.TypeOf(e.X).Underlying().(type) { + case *types.Signature: + return fc.translateGenericInstance(e) + default: + panic(fmt.Errorf("unhandled IndexListExpr: %T", t)) } - case *ast.SliceExpr: if b, isBasic := fc.pkgCtx.TypeOf(e.X).Underlying().(*types.Basic); isBasic && isString(b) { switch { @@ -642,13 +650,13 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case "Call": if id, ok := fc.identifierConstant(e.Args[0]); ok { if e.Ellipsis.IsValid() { - objVar := fc.newVariable("obj") + objVar := fc.newLocalVariable("obj") return fc.formatExpr("(%s = %s, %s.%s.apply(%s, %s))", objVar, recv, objVar, id, objVar, externalizeExpr(e.Args[1])) } return fc.formatExpr("%s(%s)", globalRef(id), externalizeArgs(e.Args[1:])) } if e.Ellipsis.IsValid() { - objVar := fc.newVariable("obj") + objVar := fc.newLocalVariable("obj") return fc.formatExpr("(%s = %s, %s[$externalize(%e, $String)].apply(%s, %s))", objVar, recv, objVar, e.Args[0], objVar, externalizeExpr(e.Args[1])) } return fc.formatExpr("%s[$externalize(%e, $String)](%s)", recv, e.Args[0], externalizeArgs(e.Args[1:])) @@ -749,6 +757,10 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case *types.Var, *types.Const: return fc.formatExpr("%s", fc.objectName(o)) case *types.Func: + if _, ok := fc.pkgCtx.Info.Instances[e]; ok { + // Generic function call with auto-inferred types. + return fc.translateGenericInstance(e) + } return fc.formatExpr("%s", fc.objectName(o)) case *types.TypeName: return fc.formatExpr("%s", fc.typeName(o.Type())) @@ -788,6 +800,38 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } } +// translateGenericInstance translates a generic function instantiation. +// +// The returned JS expression evaluates into a callable function with type params +// substituted. +func (fc *funcContext) translateGenericInstance(e ast.Expr) *expression { + var identifier *ast.Ident + switch e := e.(type) { + case *ast.Ident: + identifier = e + case *ast.IndexExpr: + identifier = e.X.(*ast.Ident) + case *ast.IndexListExpr: + identifier = e.X.(*ast.Ident) + default: + err := bailout(fmt.Errorf("unexpected generic instantiation expression type %T at %s", e, fc.pkgCtx.fileSet.Position(e.Pos()))) + panic(err) + } + + instance, ok := fc.pkgCtx.Info.Instances[identifier] + if !ok { + err := fmt.Errorf("no matching generic instantiation for %q at %s", identifier, fc.pkgCtx.fileSet.Position(identifier.Pos())) + bailout(err) + } + typeParams := []string{} + for i := 0; i < instance.TypeArgs.Len(); i++ { + t := instance.TypeArgs.At(i) + typeParams = append(typeParams, fc.typeName(t)) + } + o := fc.pkgCtx.Uses[identifier] + return fc.formatExpr("%s(%s)", fc.objectName(o), strings.Join(typeParams, ", ")) +} + func (fc *funcContext) translateCall(e *ast.CallExpr, sig *types.Signature, fun *expression) *expression { args := fc.translateArgs(sig, e.Args, e.Ellipsis.IsValid()) if fc.Blocking[e] { @@ -795,7 +839,7 @@ func (fc *funcContext) translateCall(e *ast.CallExpr, sig *types.Signature, fun fc.caseCounter++ returnVar := "$r" if sig.Results().Len() != 0 { - returnVar = fc.newVariable("_r") + returnVar = fc.newLocalVariable("_r") } fc.Printf("%[1]s = %[2]s(%[3]s); /* */ $s = %[4]d; case %[4]d: if($c) { $c = false; %[1]s = %[1]s.$blk(); } if (%[1]s && %[1]s.$blk !== undefined) { break s; }", returnVar, fun, strings.Join(args, ", "), resumeCase) if sig.Results().Len() != 0 { @@ -845,7 +889,7 @@ func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, ellipsis := expr.Ellipsis for i := range expr.Args { - v := fc.newVariable("_arg") + v := fc.newLocalVariable("_arg") vars[i] = v // Subtle: the proxy lambda argument needs to be assigned with the type // that the original function expects, and not with the argument @@ -1124,8 +1168,8 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type } if ptr, isPtr := fc.pkgCtx.TypeOf(expr).(*types.Pointer); fc.pkgCtx.Pkg.Path() == "syscall" && isPtr { if s, isStruct := ptr.Elem().Underlying().(*types.Struct); isStruct { - array := fc.newVariable("_array") - target := fc.newVariable("_struct") + array := fc.newLocalVariable("_array") + target := fc.newLocalVariable("_struct") fc.Printf("%s = new Uint8Array(%d);", array, sizes32.Sizeof(s)) fc.Delayed(func() { fc.Printf("%s = %s, %s;", target, fc.translateExpr(expr), fc.loadStruct(array, target, s)) @@ -1173,8 +1217,8 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type // struct pointer when handling syscalls. // TODO(nevkontakte): Add a runtime assertion that the unsafe.Pointer is // indeed pointing at a byte array. - array := fc.newVariable("_array") - target := fc.newVariable("_struct") + array := fc.newLocalVariable("_array") + target := fc.newLocalVariable("_struct") return fc.formatExpr("(%s = %e, %s = %e, %s, %s)", array, expr, target, fc.zeroValue(t.Elem()), fc.loadStruct(array, target, ptrElType), target) } // Convert between structs of different types but identical layouts, @@ -1196,7 +1240,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type // type iPtr *int; var c int = 42; println((iPtr)(&c)); // TODO(nevkontakte): Are there any other cases that fall into this case? exprTypeElem := exprType.Underlying().(*types.Pointer).Elem() - ptrVar := fc.newVariable("_ptr") + ptrVar := fc.newLocalVariable("_ptr") getterConv := fc.translateConversion(fc.setType(&ast.StarExpr{X: fc.newIdent(ptrVar, exprType)}, exprTypeElem), t.Elem()) setterConv := fc.translateConversion(fc.newIdent("$v", t.Elem()), exprTypeElem) return fc.formatExpr("(%1s = %2e, new %3s(function() { return %4s; }, function($v) { %1s.$set(%5s); }, %1s.$target))", ptrVar, expr, fc.typeName(desiredType), getterConv, setterConv) @@ -1268,7 +1312,7 @@ func (fc *funcContext) translateConversionToSlice(expr ast.Expr, desiredType typ } func (fc *funcContext) loadStruct(array, target string, s *types.Struct) string { - view := fc.newVariable("_view") + view := fc.newLocalVariable("_view") code := fmt.Sprintf("%s = new DataView(%s.buffer, %s.byteOffset)", view, array, array) var fields []*types.Var var collectFields func(s *types.Struct, path string) @@ -1398,7 +1442,7 @@ func (fc *funcContext) formatExprInternal(format string, a []interface{}, parens out.WriteByte('(') parens = false } - v := fc.newVariable("x") + v := fc.newLocalVariable("x") out.WriteString(v + " = " + fc.translateExpr(e.(ast.Expr)).String() + ", ") vars[i] = v } diff --git a/compiler/package.go b/compiler/package.go index db68c7208..f360d9af1 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -180,6 +180,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor Implicits: make(map[ast.Node]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), + Instances: make(map[*ast.Ident]types.Instance), } var errList ErrorList @@ -294,7 +295,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor // but now we do it here to maintain previous behavior. continue } - funcCtx.pkgCtx.pkgVars[importedPkg.Path()] = funcCtx.newVariableWithLevel(importedPkg.Name(), true) + funcCtx.pkgCtx.pkgVars[importedPkg.Path()] = funcCtx.newVariable(importedPkg.Name(), varPackage) importedPaths = append(importedPaths, importedPkg.Path()) } sort.Strings(importedPaths) @@ -521,7 +522,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor d.DeclCode = funcCtx.CatchOutput(0, func() { typeName := funcCtx.objectName(o) lhs := typeName - if isPkgLevel(o) { + if typeVarLevel(o) == varPackage { lhs += " = $pkg." + encodeIdent(o.Name()) } size := int64(0) @@ -773,12 +774,12 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, var params []string for _, param := range typ.Params.List { if len(param.Names) == 0 { - params = append(params, c.newVariable("param")) + params = append(params, c.newLocalVariable("param")) continue } for _, ident := range param.Names { if isBlank(ident) { - params = append(params, c.newVariable("param")) + params = append(params, c.newLocalVariable("param")) continue } params = append(params, c.objectName(c.pkgCtx.Defs[ident])) @@ -898,5 +899,22 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, c.pkgCtx.escapingVars = prevEV - return params, fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, c.Indentation(0)) + if !c.sigTypes.IsGeneric() { + return params, fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, c.Indentation(0)) + } + + // Generic functions are generated as factories to allow passing type parameters + // 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)) } diff --git a/compiler/statements.go b/compiler/statements.go index 8e4b913b8..dc457e0d5 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -125,7 +125,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { if s.Init != nil { fc.translateStmt(s.Init, nil) } - refVar := fc.newVariable("_ref") + refVar := fc.newLocalVariable("_ref") var expr ast.Expr switch a := s.Assign.(type) { case *ast.AssignStmt: @@ -187,14 +187,14 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { }, label, fc.Flattened[s]) case *ast.RangeStmt: - refVar := fc.newVariable("_ref") + refVar := fc.newLocalVariable("_ref") fc.Printf("%s = %s;", refVar, fc.translateExpr(s.X)) switch t := fc.pkgCtx.TypeOf(s.X).Underlying().(type) { case *types.Basic: - iVar := fc.newVariable("_i") + iVar := fc.newLocalVariable("_i") fc.Printf("%s = 0;", iVar) - runeVar := fc.newVariable("_rune") + runeVar := fc.newLocalVariable("_rune") fc.translateLoopingStmt(func() string { return iVar + " < " + refVar + ".length" }, s.Body, func() { fc.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Key) { @@ -208,16 +208,16 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { }, label, fc.Flattened[s]) case *types.Map: - iVar := fc.newVariable("_i") + iVar := fc.newLocalVariable("_i") fc.Printf("%s = 0;", iVar) - keysVar := fc.newVariable("_keys") + keysVar := fc.newLocalVariable("_keys") fc.Printf("%s = %s ? %s.keys() : undefined;", keysVar, refVar, refVar) - sizeVar := fc.newVariable("_size") + sizeVar := fc.newLocalVariable("_size") fc.Printf("%s = %s ? %s.size : 0;", sizeVar, refVar, refVar) fc.translateLoopingStmt(func() string { return iVar + " < " + sizeVar }, s.Body, func() { - keyVar := fc.newVariable("_key") - entryVar := fc.newVariable("_entry") + keyVar := fc.newLocalVariable("_key") + entryVar := fc.newLocalVariable("_entry") fc.Printf("%s = %s.next().value;", keyVar, keysVar) fc.Printf("%s = %s.get(%s);", entryVar, refVar, keyVar) fc.translateStmt(&ast.IfStmt{ @@ -248,7 +248,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { length = refVar + ".$length" elemType = t2.Elem() } - iVar := fc.newVariable("_i") + iVar := fc.newLocalVariable("_i") fc.Printf("%s = 0;", iVar) fc.translateLoopingStmt(func() string { return iVar + " < " + length }, s.Body, func() { if !isBlank(s.Key) { @@ -265,7 +265,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { }, label, fc.Flattened[s]) case *types.Chan: - okVar := fc.newIdent(fc.newVariable("_ok"), types.Typ[types.Bool]) + okVar := fc.newIdent(fc.newLocalVariable("_ok"), types.Typ[types.Bool]) key := s.Key tok := s.Tok if key == nil { @@ -354,7 +354,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { if rVal != "" { // If returned expression is non empty, evaluate and store it in a // variable to avoid double-execution in case a deferred function blocks. - rVar := fc.newVariable("$r") + rVar := fc.newLocalVariable("$r") fc.Printf("%s =%s;", rVar, rVal) rVal = " " + rVar } @@ -386,7 +386,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { fc.Printf("%s", fc.translateAssign(lhs, s.Rhs[0], s.Tok == token.DEFINE)) case len(s.Lhs) > 1 && len(s.Rhs) == 1: - tupleVar := fc.newVariable("_tuple") + tupleVar := fc.newLocalVariable("_tuple") fc.Printf("%s = %s;", tupleVar, fc.translateExpr(s.Rhs[0])) tuple := fc.pkgCtx.TypeOf(s.Rhs[0]).(*types.Tuple) for i, lhs := range s.Lhs { @@ -398,7 +398,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { case len(s.Lhs) == len(s.Rhs): tmpVars := make([]string, len(s.Rhs)) for i, rhs := range s.Rhs { - tmpVars[i] = fc.newVariable("_tmp") + tmpVars[i] = fc.newLocalVariable("_tmp") if isBlank(astutil.RemoveParens(s.Lhs[i])) { fc.Printf("$unused(%s);", fc.translateExpr(rhs)) continue @@ -444,7 +444,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { for _, spec := range decl.Specs { o := fc.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) fc.pkgCtx.typeNames = append(fc.pkgCtx.typeNames, o) - fc.pkgCtx.objectNames[o] = fc.newVariableWithLevel(o.Name(), true) + fc.pkgCtx.objectNames[o] = fc.newVariable(o.Name(), varPackage) fc.pkgCtx.dependencies[o] = true } case token.CONST: @@ -478,7 +478,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { fc.translateStmt(&ast.ExprStmt{X: call}, label) case *ast.SelectStmt: - selectionVar := fc.newVariable("_selection") + selectionVar := fc.newLocalVariable("_selection") var channels []string var caseClauses []*ast.CaseClause flattened := false @@ -704,7 +704,7 @@ func (fc *funcContext) translateAssign(lhs, rhs ast.Expr, define bool) string { if typesutil.IsJsObject(fc.pkgCtx.TypeOf(l.Index)) { fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: l.Index.Pos(), Msg: "cannot use js.Object as map key"}) } - keyVar := fc.newVariable("_key") + keyVar := fc.newLocalVariable("_key") return fmt.Sprintf( `%s = %s; (%s || $throwRuntimeError("assignment to entry in nil map")).set(%s.keyFor(%s), { k: %s, v: %s });`, keyVar, @@ -799,7 +799,7 @@ func (fc *funcContext) translateResults(results []ast.Expr) string { return " " + resultExpr } - tmpVar := fc.newVariable("_returncast") + tmpVar := fc.newLocalVariable("_returncast") fc.Printf("%s = %s;", tmpVar, resultExpr) // Not all the return types matched, map everything out for implicit casting diff --git a/compiler/utils.go b/compiler/utils.go index 11793b5be..cf59cd573 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -110,7 +110,7 @@ func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr { return argExprs } - tupleVar := fc.newVariable("_tuple") + tupleVar := fc.newLocalVariable("_tuple") fc.Printf("%s = %s;", tupleVar, fc.translateExpr(argExprs[0])) argExprs = make([]ast.Expr, tuple.Len()) for i := range argExprs { @@ -138,7 +138,7 @@ func (fc *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, arg := fc.translateImplicitConversionWithCloning(argExpr, sigTypes.Param(i, ellipsis)).String() if preserveOrder && fc.pkgCtx.Types[argExpr].Value == nil { - argVar := fc.newVariable("_arg") + argVar := fc.newLocalVariable("_arg") fc.Printf("%s = %s;", argVar, arg) arg = argVar } @@ -233,11 +233,40 @@ func (fc *funcContext) newConst(t types.Type, value constant.Value) ast.Expr { return id } -func (fc *funcContext) newVariable(name string) string { - return fc.newVariableWithLevel(name, false) +// newLocalVariable assigns a new JavaScript variable name for the given Go +// local variable name. In this context "local" means "in scope of the current" +// functionContext. +func (fc *funcContext) newLocalVariable(name string) string { + return fc.newVariable(name, varFuncLocal) } -func (fc *funcContext) newVariableWithLevel(name string, pkgLevel bool) string { +// varLevel specifies at which level a JavaScript variable should be declared. +type varLevel int + +const ( + // A variable defined at a function level (e.g. local variables). + varFuncLocal = iota + // A variable that should be declared in a generic type or function factory. + // This is mainly for type parameters and generic-dependent types. + varGenericFactory + // A variable that should be declared in a package factory. This user is for + // top-level functions, types, etc. + varPackage +) + +// newVariable assigns a new JavaScript variable name for the given Go variable +// or type. +// +// If there is already a variable with the same name visible in the current +// function context (e.g. due to shadowing), the returned name will be suffixed +// with a number to prevent conflict. This is necessary because Go name +// resolution scopes differ from var declarations in JS. +// +// If pkgLevel is true, the variable is declared at the package level and added +// to this functionContext, as well as all parents, but not to the list of local +// variables. If false, it is added to this context only, as well as the list of +// local vars. +func (fc *funcContext) newVariable(name string, level varLevel) string { if name == "" { panic("newVariable: empty name") } @@ -246,7 +275,7 @@ func (fc *funcContext) newVariableWithLevel(name string, pkgLevel bool) string { i := 0 for { offset := int('a') - if pkgLevel { + if level == varPackage { offset = int('A') } j := i @@ -271,9 +300,22 @@ func (fc *funcContext) newVariableWithLevel(name string, pkgLevel bool) string { varName = fmt.Sprintf("%s$%d", name, n) } - if pkgLevel { - for c2 := fc.parent; c2 != nil; c2 = c2.parent { - c2.allVars[name] = n + 1 + // Package-level variables are registered in all outer scopes. + if level == varPackage { + for c := fc.parent; c != nil; c = c.parent { + c.allVars[name] = n + 1 + } + return varName + } + + // Generic-factory level variables are registered in outer scopes up to the + // level of the generic function or method. + if level == varGenericFactory { + for c := fc; c != nil; c = c.parent { + c.allVars[name] = n + 1 + if c.sigTypes.IsGeneric() { + break + } } return varName } @@ -316,12 +358,20 @@ func isVarOrConst(o types.Object) bool { return false } -func isPkgLevel(o types.Object) bool { - return o.Parent() != nil && o.Parent().Parent() == types.Universe +func typeVarLevel(o types.Object) varLevel { + if _, ok := o.Type().(*types.TypeParam); ok { + return varGenericFactory + } + if o.Parent() != nil && o.Parent().Parent() == types.Universe { + return varPackage + } + return varFuncLocal } +// objectName returns a JS identifier corresponding to the given types.Object. +// Repeated calls for the same object will return the same name. func (fc *funcContext) objectName(o types.Object) string { - if isPkgLevel(o) { + if typeVarLevel(o) == varPackage { fc.pkgCtx.dependencies[o] = true if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { @@ -331,7 +381,7 @@ func (fc *funcContext) objectName(o types.Object) string { name, ok := fc.pkgCtx.objectNames[o] if !ok { - name = fc.newVariableWithLevel(o.Name(), isPkgLevel(o)) + name = fc.newVariable(o.Name(), typeVarLevel(o)) fc.pkgCtx.objectNames[o] = name } @@ -342,18 +392,23 @@ func (fc *funcContext) objectName(o types.Object) string { } func (fc *funcContext) varPtrName(o *types.Var) string { - if isPkgLevel(o) && o.Exported() { + if typeVarLevel(o) == varPackage && o.Exported() { return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr" } name, ok := fc.pkgCtx.varPtrNames[o] if !ok { - name = fc.newVariableWithLevel(o.Name()+"$ptr", isPkgLevel(o)) + name = fc.newVariable(o.Name()+"$ptr", typeVarLevel(o)) fc.pkgCtx.varPtrNames[o] = name } return name } +// typeName returns a JS identifier name for the given Go type. +// +// For the built-in types it returns identifiers declared in the prelude. For +// all user-defined or composite types it creates a unique JS identifier and +// will return it on all subsequent calls for the type. func (fc *funcContext) typeName(ty types.Type) string { switch t := ty.(type) { case *types.Basic: @@ -363,16 +418,22 @@ func (fc *funcContext) typeName(ty types.Type) string { return "$error" } return fc.objectName(t.Obj()) + case *types.TypeParam: + return fc.objectName(t.Obj()) case *types.Interface: if t.Empty() { return "$emptyInterface" } } + // For anonymous composite types, generate a synthetic package-level type + // declaration, which will be reused for all instances of this type. This + // improves performance, since runtime won't have to synthesize the same type + // repeatedly. anonType, ok := fc.pkgCtx.anonTypeMap.At(ty).(*types.TypeName) if !ok { - fc.initArgs(ty) // cause all embedded types to be registered - varName := fc.newVariableWithLevel(strings.ToLower(typeKind(ty)[5:])+"Type", true) + fc.initArgs(ty) // cause all dependency types to be registered + varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", varPackage) anonType = types.NewTypeName(token.NoPos, fc.pkgCtx.Pkg, varName, ty) // fake types.TypeName fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType) fc.pkgCtx.anonTypeMap.Set(ty, anonType) @@ -789,6 +850,12 @@ func (st signatureTypes) HasNamedResults() bool { return st.HasResults() && st.Sig.Results().At(0).Name() != "" } +// IsGeneric returns true if the signature represents a generic function or a +// method of a generic type. +func (st signatureTypes) IsGeneric() bool { + return st.Sig.TypeParams().Len() > 0 || st.Sig.RecvTypeParams().Len() > 0 +} + // ErrorAt annotates an error with a position in the source code. func ErrorAt(err error, fset *token.FileSet, pos token.Pos) error { return fmt.Errorf("%s: %w", fset.Position(pos), err)