From 05f3a1cf75f5f3bcc35c394f6aea08e131c8b7f2 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 18 Sep 2022 16:57:21 +0100 Subject: [PATCH 01/83] Enable "typeparam" test suite in gorepo tests. This should provide us a sufficiently comprehensive test coverage for generics support development. --- tests/gorepo/run.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index dd6b392f8..3308ea5df 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -197,7 +197,7 @@ var ( // dirs are the directories to look for *.go files in. // TODO(bradfitz): just use all directories? - dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs"} + dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "typeparam"} // ratec controls the max number of tests running at a time. ratec chan bool @@ -641,10 +641,10 @@ func (t *test) run() { args = f[1:] } - // GOPHERJS: For now, only run with "run", "cmpout" actions, in "fixedbugs" dir. Skip all others. + // GOPHERJS: For now, only run with "run", "cmpout" actions, in "fixedbugs" and "typeparam" dirs. Skip all others. switch action { case "run", "cmpout": - if filepath.Clean(t.dir) != "fixedbugs" { + if d := filepath.Clean(t.dir); d != "fixedbugs" && d != "typeparam" { action = "skip" } default: @@ -694,6 +694,21 @@ func (t *test) run() { os.Setenv("GOARCH", goarch) } + { + // GopherJS: we don't support -gcflags=-G=3 flag, but it's the default + // behavior anyway. + supportedArgs := []string{} + for _, a := range args { + switch a { + case "-gcflags=-G=3": + continue + default: + supportedArgs = append(supportedArgs, a) + } + } + args = supportedArgs + } + useTmp := true runcmd := func(args ...string) ([]byte, error) { cmd := exec.Command(args[0], args[1:]...) From 750ffdcc95daedcd86c89e89308124e8e6aaf1c5 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 18 Sep 2022 16:59:37 +0100 Subject: [PATCH 02/83] Stop returning an error upon encountering a generic type of function. --- compiler/package.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/compiler/package.go b/compiler/package.go index 93c22f1c5..403f9a355 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -6,7 +6,6 @@ import ( "fmt" "go/ast" "go/constant" - "go/scanner" "go/token" "go/types" "sort" @@ -400,12 +399,6 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor for _, fun := range functions { o := funcCtx.pkgCtx.Defs[fun.Name].(*types.Func) - if fun.Type.TypeParams.NumFields() > 0 { - return nil, scanner.Error{ - Pos: fileSet.Position(fun.Type.TypeParams.Pos()), - Msg: fmt.Sprintf("function %s: type parameters are not supported by GopherJS: https://github.com/gopherjs/gopherjs/issues/1013", o.Name()), - } - } funcInfo := funcCtx.pkgCtx.FuncDeclInfos[o] d := Decl{ FullName: o.FullName(), @@ -489,13 +482,6 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } typeName := funcCtx.objectName(o) - if named, ok := o.Type().(*types.Named); ok && named.TypeParams().Len() > 0 { - return nil, scanner.Error{ - Pos: fileSet.Position(o.Pos()), - Msg: fmt.Sprintf("type %s: type parameters are not supported by GopherJS: https://github.com/gopherjs/gopherjs/issues/1013", o.Name()), - } - } - d := Decl{ Vars: []string{typeName}, DceObjectFilter: o.Name(), From f86fde69853d22b3c97dbb64f9b4b9d351b4c946 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 2 Oct 2022 14:08:32 +0100 Subject: [PATCH 03/83] compiler: improve helpers for indentation management. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename fc.Indent() → fc.Indented() for indenting code generated in a callback. - Add fc.Indentation() to generate the indentation sequence itself. --- compiler/package.go | 8 ++++---- compiler/statements.go | 10 +++++----- compiler/utils.go | 13 +++++++++++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/compiler/package.go b/compiler/package.go index 403f9a355..e90c78bf1 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -856,16 +856,16 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, } if prefix != "" { - bodyOutput = strings.Repeat("\t", c.pkgCtx.indentation+1) + "/* */" + prefix + "\n" + bodyOutput + bodyOutput = c.Indentation(1) + "/* */" + prefix + "\n" + bodyOutput } if suffix != "" { - bodyOutput = bodyOutput + strings.Repeat("\t", c.pkgCtx.indentation+1) + "/* */" + suffix + "\n" + bodyOutput = bodyOutput + c.Indentation(1) + "/* */" + suffix + "\n" } if localVarDefs != "" { - bodyOutput = strings.Repeat("\t", c.pkgCtx.indentation+1) + localVarDefs + bodyOutput + 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, strings.Repeat("\t", c.pkgCtx.indentation)) + return params, fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, c.Indentation(0)) } diff --git a/compiler/statements.go b/compiler/statements.go index fc36453a4..865e6a611 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -100,7 +100,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { data.endCase = fc.caseCounter fc.caseCounter++ - fc.Indent(func() { + fc.Indented(func() { fc.translateStmtList(clause.Body) }) fc.Printf("case %d:", data.endCase) @@ -112,7 +112,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { fc.Printf("%s:", label.Name()) } fc.Printf("switch (0) { default:") - fc.Indent(func() { + fc.Indented(func() { fc.translateStmtList(clause.Body) }) fc.Printf("}") @@ -614,7 +614,7 @@ func (fc *funcContext) translateBranchingStmt(caseClauses []*ast.CaseClause, def for i, clause := range caseClauses { fc.SetPos(clause.Pos()) fc.PrintCond(!flatten, fmt.Sprintf("%sif (%s) {", prefix, condStrs[i]), fmt.Sprintf("case %d:", caseOffset+i)) - fc.Indent(func() { + fc.Indented(func() { fc.translateStmtList(clause.Body) if flatten && (i < len(caseClauses)-1 || defaultClause != nil) && !astutil.EndsWithReturn(clause.Body) { fc.Printf("$s = %d; continue;", endCase) @@ -625,7 +625,7 @@ func (fc *funcContext) translateBranchingStmt(caseClauses []*ast.CaseClause, def if defaultClause != nil { fc.PrintCond(!flatten, prefix+"{", fmt.Sprintf("case %d:", caseOffset+len(caseClauses))) - fc.Indent(func() { + fc.Indented(func() { fc.translateStmtList(defaultClause.Body) }) } @@ -655,7 +655,7 @@ func (fc *funcContext) translateLoopingStmt(cond func() string, body *ast.BlockS } isTerminated := false fc.PrintCond(!flatten, "while (true) {", fmt.Sprintf("case %d:", data.beginCase)) - fc.Indent(func() { + fc.Indented(func() { condStr := cond() if condStr != "true" { fc.PrintCond(!flatten, fmt.Sprintf("if (!(%s)) { break; }", condStr), fmt.Sprintf("if(!(%s)) { $s = %d; continue; }", condStr, data.endCase)) diff --git a/compiler/utils.go b/compiler/utils.go index 5cde7b3c7..338468db0 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -29,7 +29,7 @@ func (fc *funcContext) Write(b []byte) (int, error) { } func (fc *funcContext) Printf(format string, values ...interface{}) { - fc.Write([]byte(strings.Repeat("\t", fc.pkgCtx.indentation))) + fc.Write([]byte(fc.Indentation(0))) fmt.Fprintf(fc, format, values...) fc.Write([]byte{'\n'}) fc.Write(fc.delayedOutput) @@ -57,12 +57,21 @@ func (fc *funcContext) writePos() { } } -func (fc *funcContext) Indent(f func()) { +// Indented increases generated code indentation level by 1 for the code emitted +// from the callback f. +func (fc *funcContext) Indented(f func()) { fc.pkgCtx.indentation++ f() fc.pkgCtx.indentation-- } +// Indentation returns a sequence of "\t" characters appropriate to the current +// generated code indentation level. The `extra` parameter provides relative +// indentation adjustment. +func (fc *funcContext) Indentation(extra int) string { + return strings.Repeat("\t", fc.pkgCtx.indentation+extra) +} + func (fc *funcContext) CatchOutput(indent int, f func()) []byte { origoutput := fc.output fc.output = nil From 161d21d5469ce765f4b3b0e7a35f75a02aa49a32 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 2 Oct 2022 15:15:04 +0100 Subject: [PATCH 04/83] compiler: readability improvements to the functionContext type. - Document semantics of the functionContext fields (to the best of my knowledge, at least). - Make use of the signatureTypes{} helper type for more self-explanatory condition checks in the compiler code. --- compiler/package.go | 69 ++++++++++++++++++++++++++++++------------ compiler/statements.go | 2 +- compiler/utils.go | 11 +++++++ 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/compiler/package.go b/compiler/package.go index e90c78bf1..db68c7208 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -70,22 +70,53 @@ func (sel *fakeSelection) Index() []int { return sel.index } func (sel *fakeSelection) Obj() types.Object { return sel.obj } func (sel *fakeSelection) Type() types.Type { return sel.typ } -// funcContext maintains compiler context for a specific function (lexical scope?). +// 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 - pkgCtx *pkgContext - parent *funcContext - sig *types.Signature - allVars map[string]int - localVars []string - resultNames []ast.Expr - flowDatas map[*types.Label]*flowData - caseCounter int - labelCases map[*types.Label]int - output []byte + // 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 + // Information about function signature types. nil for the package-level + // function context. + sigTypes *signatureTypes + // 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 - posAvailable bool - pos token.Pos + // 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 } type flowData struct { @@ -727,7 +758,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, FuncInfo: info, pkgCtx: outerContext.pkgCtx, parent: outerContext, - sig: sig, + sigTypes: &signatureTypes{Sig: sig}, allVars: make(map[string]int, len(outerContext.allVars)), localVars: []string{}, flowDatas: map[*types.Label]*flowData{nil: {}}, @@ -760,10 +791,10 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, c.handleEscapingVars(body) } - if c.sig != nil && c.sig.Results().Len() != 0 && c.sig.Results().At(0).Name() != "" { - c.resultNames = make([]ast.Expr, c.sig.Results().Len()) - for i := 0; i < c.sig.Results().Len(); i++ { - result := c.sig.Results().At(i) + if c.sigTypes != nil && c.sigTypes.HasNamedResults() { + c.resultNames = make([]ast.Expr, c.sigTypes.Sig.Results().Len()) + for i := 0; i < c.sigTypes.Sig.Results().Len(); i++ { + result := c.sigTypes.Sig.Results().At(i) c.Printf("%s = %s;", c.objectName(result), c.translateExpr(c.zeroValue(result.Type())).String()) id := ast.NewIdent("") c.pkgCtx.Uses[id] = result @@ -833,7 +864,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, if len(c.Blocking) != 0 { deferSuffix += " $s = -1;" } - if c.resultNames == nil && c.sig.Results().Len() > 0 { + if c.resultNames == nil && c.sigTypes.HasResults() { deferSuffix += fmt.Sprintf(" return%s;", c.translateResults(nil)) } deferSuffix += " } finally { $callDeferred($deferred, $err);" diff --git a/compiler/statements.go b/compiler/statements.go index 865e6a611..8e4b913b8 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -773,7 +773,7 @@ func (fc *funcContext) translateAssign(lhs, rhs ast.Expr, define bool) string { } func (fc *funcContext) translateResults(results []ast.Expr) string { - tuple := fc.sig.Results() + tuple := fc.sigTypes.Sig.Results() switch tuple.Len() { case 0: return "" diff --git a/compiler/utils.go b/compiler/utils.go index 338468db0..11793b5be 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -778,6 +778,17 @@ func (st signatureTypes) Param(i int, ellipsis bool) types.Type { return st.VariadicType().(*types.Slice).Elem() } +// HasResults returns true if the function signature returns something. +func (st signatureTypes) HasResults() bool { + return st.Sig.Results().Len() > 0 +} + +// HasNamedResults returns true if the function signature returns something and +// returned results are names (e.g. `func () (val int, err error)`). +func (st signatureTypes) HasNamedResults() bool { + return st.HasResults() && st.Sig.Results().At(0).Name() != "" +} + // 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) From 47679137a52bcc73aaf77535f747c8d3404c287a Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 2 Oct 2022 16:30:07 +0100 Subject: [PATCH 05/83] compiler: Rename and document functions responsible for identifier generation. These functions provide mappings from Go entities (variables, types, functions, etc.) to JS identifiers assigned to them. This commit adds documentation comments and renames a couple of methods to better match their role. --- compiler/expressions.go | 36 +++++++++++++++++------------------ compiler/package.go | 6 +++--- compiler/statements.go | 36 +++++++++++++++++------------------ compiler/utils.go | 42 +++++++++++++++++++++++++++++++++-------- 4 files changed, 73 insertions(+), 47 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 5d9e37cc9..d30b53312 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,7 +485,7 @@ 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()), @@ -642,13 +642,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:])) @@ -795,7 +795,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 +845,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 +1124,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 +1173,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 +1196,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 +1268,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 +1398,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..4c96feb69 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -294,7 +294,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(), true) importedPaths = append(importedPaths, importedPkg.Path()) } sort.Strings(importedPaths) @@ -773,12 +773,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])) diff --git a/compiler/statements.go b/compiler/statements.go index 8e4b913b8..1a83b6a52 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(), true) 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..0dc2ea7dd 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,26 @@ 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, false) } -func (fc *funcContext) newVariableWithLevel(name string, pkgLevel bool) string { +// 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, pkgLevel bool) string { if name == "" { panic("newVariable: empty name") } @@ -320,6 +335,8 @@ func isPkgLevel(o types.Object) bool { return o.Parent() != nil && o.Parent().Parent() == types.Universe } +// 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) { fc.pkgCtx.dependencies[o] = true @@ -331,7 +348,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(), isPkgLevel(o)) fc.pkgCtx.objectNames[o] = name } @@ -348,12 +365,17 @@ func (fc *funcContext) varPtrName(o *types.Var) string { name, ok := fc.pkgCtx.varPtrNames[o] if !ok { - name = fc.newVariableWithLevel(o.Name()+"$ptr", isPkgLevel(o)) + name = fc.newVariable(o.Name()+"$ptr", isPkgLevel(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: @@ -369,10 +391,14 @@ func (fc *funcContext) typeName(ty types.Type) string { } } + // For anonymous composite types, generate a synthetic package-level type + // declaration, which will be reused for all instances of this time. 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) + varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", true) 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) From 3b44f19b7435c84ad0615ba0d21193373394abe8 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 5 Oct 2022 19:51:03 +0100 Subject: [PATCH 06/83] Rudimentary support for passing type parameters into generic functions. Instead of generating an independent function instance for every combination of type parameters at compile time we construct generic function instances at runtime using "generic factory functions". Such a factory takes type params as arguments and returns a concrete instance of the function for the given type params (type param values are captured by the returned function as a closure and can be used as necessary). Here is an abbreviated example of how a generic function is compiled and called: ``` // Go: func F[T any](t T) {} f(1) // JS: F = function(T){ return function(t) {}; }; F($Int)(1); ``` This approach minimizes the size of the generated JS source, which is critical for the client-side use case, at the cost of runtime performance. See https://github.com/gopherjs/gopherjs/issues/1013#issuecomment-1217237123 for the detailed description. Note that the implementation in this commit is far from complete: - Generic function instances are not cached. - Generic types are not supported. - Declaring types dependent on type parameters doesn't work correctly. - Operators (such as `+`) do not work correctly with generic arguments. --- compiler/expressions.go | 48 ++++++++++++++++++++++++++-- compiler/package.go | 24 ++++++++++++-- compiler/statements.go | 2 +- compiler/utils.go | 71 ++++++++++++++++++++++++++++++++--------- 4 files changed, 124 insertions(+), 21 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index d30b53312..1d86174ce 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -492,10 +492,18 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { ) 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 { @@ -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] { diff --git a/compiler/package.go b/compiler/package.go index 4c96feb69..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.newVariable(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) @@ -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 1a83b6a52..dc457e0d5 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -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.newVariable(o.Name(), true) + fc.pkgCtx.objectNames[o] = fc.newVariable(o.Name(), varPackage) fc.pkgCtx.dependencies[o] = true } case token.CONST: diff --git a/compiler/utils.go b/compiler/utils.go index 0dc2ea7dd..cf59cd573 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -237,9 +237,23 @@ func (fc *funcContext) newConst(t types.Type, value constant.Value) ast.Expr { // 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, false) + return fc.newVariable(name, varFuncLocal) } +// 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. // @@ -252,7 +266,7 @@ func (fc *funcContext) newLocalVariable(name string) string { // 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, pkgLevel bool) string { +func (fc *funcContext) newVariable(name string, level varLevel) string { if name == "" { panic("newVariable: empty name") } @@ -261,7 +275,7 @@ func (fc *funcContext) newVariable(name string, pkgLevel bool) string { i := 0 for { offset := int('a') - if pkgLevel { + if level == varPackage { offset = int('A') } j := i @@ -286,9 +300,22 @@ func (fc *funcContext) newVariable(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 } @@ -331,14 +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()) { @@ -348,7 +381,7 @@ func (fc *funcContext) objectName(o types.Object) string { name, ok := fc.pkgCtx.objectNames[o] if !ok { - name = fc.newVariable(o.Name(), isPkgLevel(o)) + name = fc.newVariable(o.Name(), typeVarLevel(o)) fc.pkgCtx.objectNames[o] = name } @@ -359,13 +392,13 @@ 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.newVariable(o.Name()+"$ptr", isPkgLevel(o)) + name = fc.newVariable(o.Name()+"$ptr", typeVarLevel(o)) fc.pkgCtx.varPtrNames[o] = name } return name @@ -385,6 +418,8 @@ 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" @@ -392,13 +427,13 @@ func (fc *funcContext) typeName(ty types.Type) string { } // For anonymous composite types, generate a synthetic package-level type - // declaration, which will be reused for all instances of this time. This + // 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.newVariable(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) @@ -815,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) From faea80451dc1ddd0fd91c1638c8658aed176ebe8 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 15 Oct 2022 20:02:03 +0100 Subject: [PATCH 07/83] 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([])); }; }; ``` --- compiler/package.go | 46 ++++++-- compiler/typesutil/typesutil.go | 98 +++++++++++++++- compiler/typesutil/typesutil_test.go | 168 +++++++++++++++++++++++++++ compiler/utils.go | 27 ++++- 4 files changed, 320 insertions(+), 19 deletions(-) create mode 100644 compiler/typesutil/typesutil_test.go diff --git a/compiler/package.go b/compiler/package.go index f360d9af1..7838e8eb3 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -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. @@ -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 @@ -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 @@ -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 @@ -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(), @@ -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)), @@ -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 @@ -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) @@ -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 @@ -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() } diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 600925b81..44671f309 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -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 { @@ -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)) + } +} diff --git a/compiler/typesutil/typesutil_test.go b/compiler/typesutil/typesutil_test.go new file mode 100644 index 000000000..515cf0a5e --- /dev/null +++ b/compiler/typesutil/typesutil_test.go @@ -0,0 +1,168 @@ +package typesutil + +import ( + "go/token" + "go/types" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestAnonymousTypes(t *testing.T) { + t1 := types.NewSlice(types.Typ[types.String]) + t1Name := types.NewTypeName(token.NoPos, nil, "sliceType$1", t1) + + t2 := types.NewMap(types.Typ[types.Int], t1) + t2Name := types.NewTypeName(token.NoPos, nil, "mapType$1", t2) + + typs := []struct { + typ types.Type + name *types.TypeName + }{ + {typ: t1, name: t1Name}, + {typ: t2, name: t2Name}, + } + + anonTypes := AnonymousTypes{} + for _, typ := range typs { + anonTypes.Register(typ.name, typ.typ) + } + + for _, typ := range typs { + t.Run(typ.name.Name(), func(t *testing.T) { + got := anonTypes.Get(typ.typ) + if got != typ.name { + t.Errorf("Got: anonTypes.Get(%v) = %v. Want: %v.", typ.typ, typ.name, got) + } + }) + } + + gotNames := []string{} + for _, name := range anonTypes.Ordered() { + gotNames = append(gotNames, name.Name()) + } + wantNames := []string{"sliceType$1", "mapType$1"} + if !cmp.Equal(wantNames, gotNames) { + t.Errorf("Got: anonTypes.Ordered() = %v. Want: %v (in the order of registration)", gotNames, wantNames) + } +} + +func TestIsGeneric(t *testing.T) { + T := types.NewTypeParam(types.NewTypeName(token.NoPos, nil, "T", nil), types.NewInterface(nil, nil)) + + tests := []struct { + typ types.Type + want bool + }{ + { + typ: T, + want: true, + }, { + typ: types.Typ[types.Int], + want: false, + }, { + typ: types.NewArray(types.Typ[types.Int], 1), + want: false, + }, { + typ: types.NewArray(T, 1), + want: true, + }, { + typ: types.NewChan(types.SendRecv, types.Typ[types.Int]), + want: false, + }, { + typ: types.NewChan(types.SendRecv, T), + want: true, + }, { + typ: types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "X", types.NewSignatureType( + nil, nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int])), nil, false, + )), + }, + []types.Type{ + types.NewNamed(types.NewTypeName(token.NoPos, nil, "myInt", nil), types.Typ[types.Int], nil), + }, + ), + want: false, + }, { + typ: types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "X", types.NewSignatureType( + nil, nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "x", T)), nil, false, + )), + }, + []types.Type{ + types.NewNamed(types.NewTypeName(token.NoPos, nil, "myInt", nil), types.Typ[types.Int], nil), + }, + ), + want: true, + }, { + typ: types.NewMap(types.Typ[types.Int], types.Typ[types.String]), + want: false, + }, { + typ: types.NewMap(T, types.Typ[types.String]), + want: true, + }, { + typ: types.NewMap(types.Typ[types.Int], T), + want: true, + }, { + typ: types.NewNamed(types.NewTypeName(token.NoPos, nil, "myInt", nil), types.Typ[types.Int], nil), + want: false, + }, { + typ: types.NewPointer(types.Typ[types.Int]), + want: false, + }, { + typ: types.NewPointer(T), + want: true, + }, { + typ: types.NewSlice(types.Typ[types.Int]), + want: false, + }, { + typ: types.NewSlice(T), + want: true, + }, { + typ: types.NewSignatureType( + nil, nil, nil, + types.NewTuple(types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int])), // params + types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), // results + false, + ), + want: false, + }, { + typ: types.NewSignatureType( + nil, nil, nil, + types.NewTuple(types.NewVar(token.NoPos, nil, "x", T)), // params + types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), // results + false, + ), + want: true, + }, { + typ: types.NewSignatureType( + nil, nil, nil, + types.NewTuple(types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int])), // params + types.NewTuple(types.NewVar(token.NoPos, nil, "", T)), // results + false, + ), + want: true, + }, { + typ: types.NewStruct([]*types.Var{ + types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int]), + }, nil), + want: false, + }, { + typ: types.NewStruct([]*types.Var{ + types.NewVar(token.NoPos, nil, "x", T), + }, nil), + want: true, + }, + } + + for _, test := range tests { + t.Run(test.typ.String(), func(t *testing.T) { + got := IsGeneric(test.typ) + if got != test.want { + t.Errorf("Got: IsGeneric(%v) = %v. Want: %v.", test.typ, got, test.want) + } + }) + } +} diff --git a/compiler/utils.go b/compiler/utils.go index cf59cd573..51cd85773 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -72,6 +72,16 @@ func (fc *funcContext) Indentation(extra int) string { return strings.Repeat("\t", fc.pkgCtx.indentation+extra) } +// bodyIndent returns the number of indentations necessary for the function +// body code. Generic functions need deeper indentation to account for the +// surrounding factory function. +func (fc *funcContext) bodyIndent() int { + if fc.sigTypes.IsGeneric() { + return 2 + } + return 1 +} + func (fc *funcContext) CatchOutput(indent int, f func()) []byte { origoutput := fc.output fc.output = nil @@ -245,7 +255,7 @@ type varLevel int const ( // A variable defined at a function level (e.g. local variables). - varFuncLocal = iota + varFuncLocal varLevel = 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 @@ -430,13 +440,18 @@ func (fc *funcContext) typeName(ty types.Type) string { // 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 { + anonTypes := &fc.pkgCtx.anonTypes + level := varPackage + if typesutil.IsGeneric(ty) { + anonTypes = &fc.genericCtx.anonTypes + level = varGenericFactory + } + anonType := anonTypes.Get(ty) + if anonType == nil { fc.initArgs(ty) // cause all dependency types to be registered - varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", varPackage) + varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", level) 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) + anonTypes.Register(anonType, ty) } fc.pkgCtx.dependencies[anonType] = true return anonType.Name() From ff5941efbc7124cc95bb58cfeb30dfbc0a03a4e9 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 16 Oct 2022 14:48:34 +0100 Subject: [PATCH 08/83] Ignore another variant of the flag for enabling generics. --- tests/gorepo/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 3308ea5df..1ead4216e 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -700,7 +700,7 @@ func (t *test) run() { supportedArgs := []string{} for _, a := range args { switch a { - case "-gcflags=-G=3": + case "-gcflags=-G=3", `-gcflags="-G=3"`: continue default: supportedArgs = append(supportedArgs, a) From 18282cb70188353c8883f782dedb08da8ca52127 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 16 Oct 2022 15:15:13 +0100 Subject: [PATCH 09/83] Fix a bug in variable level detection. Previously it would incorrectly return `varGenericFactory` for local variables which type was exactly the type parameter, instead of `varFuncLocal`. This was causing local variables not to get declared in the list of local variables. This change brings typeparams tests to 33 pass / 98 fail. --- compiler/package.go | 2 +- compiler/utils.go | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/package.go b/compiler/package.go index 7838e8eb3..af72e9c86 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -531,7 +531,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 typeVarLevel(o) == varPackage { + if getVarLevel(o) == varPackage { lhs += " = $pkg." + encodeIdent(o.Name()) } size := int64(0) diff --git a/compiler/utils.go b/compiler/utils.go index 51cd85773..fd724b0f8 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -368,8 +368,17 @@ func isVarOrConst(o types.Object) bool { return false } -func typeVarLevel(o types.Object) varLevel { - if _, ok := o.Type().(*types.TypeParam); ok { +func isTypeParameterName(o types.Object) bool { + _, isTypeName := o.(*types.TypeName) + _, isTypeParam := o.Type().(*types.TypeParam) + return isTypeName && isTypeParam +} + +// getVarLevel returns at which level a JavaScript variable for the given object +// should be defined. The object can represent any named Go object: variable, +// type, function, etc. +func getVarLevel(o types.Object) varLevel { + if isTypeParameterName(o) { return varGenericFactory } if o.Parent() != nil && o.Parent().Parent() == types.Universe { @@ -381,7 +390,7 @@ func typeVarLevel(o types.Object) varLevel { // 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 typeVarLevel(o) == varPackage { + if getVarLevel(o) == varPackage { fc.pkgCtx.dependencies[o] = true if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { @@ -391,7 +400,7 @@ func (fc *funcContext) objectName(o types.Object) string { name, ok := fc.pkgCtx.objectNames[o] if !ok { - name = fc.newVariable(o.Name(), typeVarLevel(o)) + name = fc.newVariable(o.Name(), getVarLevel(o)) fc.pkgCtx.objectNames[o] = name } @@ -402,13 +411,13 @@ func (fc *funcContext) objectName(o types.Object) string { } func (fc *funcContext) varPtrName(o *types.Var) string { - if typeVarLevel(o) == varPackage && o.Exported() { + if getVarLevel(o) == varPackage && o.Exported() { return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr" } name, ok := fc.pkgCtx.varPtrNames[o] if !ok { - name = fc.newVariable(o.Name()+"$ptr", typeVarLevel(o)) + name = fc.newVariable(o.Name()+"$ptr", getVarLevel(o)) fc.pkgCtx.varPtrNames[o] = name } return name From 2b5c00991ad5dcece3a283951759669a47cbc307 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 16 Oct 2022 16:11:39 +0100 Subject: [PATCH 10/83] Fix zero value initialization for generic-typed variables. Since we don't know the concrete type at compile time, we use a runtime call `TypeName.zero()` to get the zero value. In the compiler side this works in a bit of a roundabout way: - Statements like `var x int` are converted to `x := 0`, where zero value is determined by the compiler based on the variable's underlying type. - For a generic type `T` the underlying type is interface, so the compiler rewrites `var x T` as `x := nil // x has type T`. This is not entirely correct from the language specification perspective, because there is no universal zero value literal for a generic type T. However, this is the closest approximation we can express as an AST. - When compiling a nil literal into a generic-typed expression we assume this was meant to represent the zero value and invoke the runtime `T.zero()` method for the generic type T. --- compiler/expressions.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index 1d86174ce..fa71a4912 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -768,6 +768,9 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if typesutil.IsJsObject(exprType) { return fc.formatExpr("null") } + if typesutil.IsGeneric(exprType) { + return fc.formatExpr("%s.zero()", fc.typeName(exprType)) + } switch t := exprType.Underlying().(type) { case *types.Basic: if t.Kind() != types.UnsafePointer { From 0dc1f6883a8a3b942157ff2d0f75084de7d5b9b9 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Thu, 3 Nov 2022 13:00:50 +0000 Subject: [PATCH 11/83] Fix incorrect continuation callback for a blocking generic function. Previously it was incorrectly returning a reference to the generic factory function. With this change it correctly returns a reference to the generic function instance. --- compiler/package.go | 7 +++++-- tests/typeparams/blocking_test.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/typeparams/blocking_test.go diff --git a/compiler/package.go b/compiler/package.go index af72e9c86..c36ca44ca 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -781,6 +781,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, } if c.sigTypes.IsGeneric() { c.genericCtx = &genericCtx{} + funcRef = c.newVariable(funcRef, varGenericFactory) } prevEV := c.pkgCtx.escapingVars @@ -935,8 +936,10 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, 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, "%sconst %s = function(%s) {\n", c.Indentation(1), funcRef, strings.Join(params, ", ")) fmt.Fprintf(code, "%s", bodyOutput) - fmt.Fprintf(code, "%s};\n%s}", c.Indentation(1), c.Indentation(0)) + fmt.Fprintf(code, "%s};\n", c.Indentation(1)) + fmt.Fprintf(code, "%sreturn %s;\n", c.Indentation(1), funcRef) + fmt.Fprintf(code, "%s}", c.Indentation(0)) return params, code.String() } diff --git a/tests/typeparams/blocking_test.go b/tests/typeparams/blocking_test.go new file mode 100644 index 000000000..3abdbbd8c --- /dev/null +++ b/tests/typeparams/blocking_test.go @@ -0,0 +1,21 @@ +package typeparams_test + +import ( + "runtime" + "testing" +) + +func _GenericBlocking[T any]() string { + runtime.Gosched() + return "Hello, world." +} + +// TestBlocking verifies that a generic function correctly resumes after a +// blocking operation. +func TestBlocking(t *testing.T) { + got := _GenericBlocking[any]() + want := "Hello, world." + if got != want { + t.Fatalf("Got: _GenericBlocking[any]() = %q. Want: %q.", got, want) + } +} From 4d25fbb63190c0133dc7045c9226c8f5622be222 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Thu, 3 Nov 2022 15:58:48 +0000 Subject: [PATCH 12/83] Support calling methods on wrapped generic types. Since we are not using monomorphization, the generated code must be able to work with both wrapped and non-wrapped types. Here wrapped types are the types like integers, which gopherjs represents at runtime using native JS types like `number` rather than gopherjs-specific objects, which improves execution efficiency. This also means that those types lack Go-specific metadata and are _wrapped_ into gopherjs-specific objects only when necessary, e.g. before calling a method. In many ways this is similar to boxing/unboxing in other languages. This presents a problem for generic code, though, since the types derived from typeparams the compiler can't be sure that they are wrapped or not. We handle this by adding a new `.wrap()` method to each type object. For wrapped types it will wrap the raw value, and for regular types it will be a no-op. Whenever we need a receiver for a type that depends on a typeparam `T`, we will pass the value through `T.wrap()`. This is a slightly bigger overhead for method calls compared to non-generic code, but hopefully JIT should be able to inline it well enough: - For each type the `wrap()` method is static throughout the lifetime of the program and is a pure arrow function. - The function has no branching: it either always returns the same value (for non-wrapped types) or always wraps it. Although I'm not 100% confident this fix covers all edge cases, it seems to work with embedded types and fixes 4 test cases from the typeparam test suite. --- compiler/expressions.go | 4 +++- compiler/package.go | 4 +++- compiler/prelude/types.go | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index fa71a4912..42708f79a 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1295,7 +1295,9 @@ func (fc *funcContext) translateImplicitConversion(expr ast.Expr, desiredType ty // wrap JS object into js.Object struct when converting to interface return fc.formatExpr("new $jsObjectPtr(%e)", expr) } - if isWrapped(exprType) { + if typesutil.IsGeneric(exprType) { + return fc.formatExpr("%s.wrap(%e)", fc.typeName(exprType), expr) + } else if isWrapped(exprType) { return fc.formatExpr("new %s(%e)", fc.typeName(exprType), expr) } if _, isStruct := exprType.Underlying().(*types.Struct); isStruct { diff --git a/compiler/package.go b/compiler/package.go index c36ca44ca..702287078 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -751,7 +751,9 @@ func (fc *funcContext) translateToplevelFunction(fun *ast.FuncDecl, info *analys } value := "this.$get()" - if isWrapped(recvType) { + if typesutil.IsGeneric(recvType) { + value = fmt.Sprintf("%s.wrap(%s)", typeName, value) + } else if isWrapped(recvType) { value = fmt.Sprintf("new %s(%s)", typeName, value) } code.Write(primaryFunction(typeName + ".prototype." + funName)) diff --git a/compiler/prelude/types.go b/compiler/prelude/types.go index 69afe8af1..611aa2c46 100644 --- a/compiler/prelude/types.go +++ b/compiler/prelude/types.go @@ -87,12 +87,14 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindUnsafePointer: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.keyFor = $identity; break; case $kindString: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.keyFor = function(x) { return "$" + x; }; break; @@ -100,6 +102,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindFloat64: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.keyFor = function(x) { return $floatKey(x); }; break; @@ -109,6 +112,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { this.$low = low >>> 0; this.$val = this; }; + typ.wrap = (v) => v; typ.keyFor = function(x) { return x.$high + "$" + x.$low; }; break; @@ -118,6 +122,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { this.$low = low >>> 0; this.$val = this; }; + typ.wrap = (v) => v; typ.keyFor = function(x) { return x.$high + "$" + x.$low; }; break; @@ -127,6 +132,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { this.$imag = $fround(imag); this.$val = this; }; + typ.wrap = (v) => v; typ.keyFor = function(x) { return x.$real + "$" + x.$imag; }; break; @@ -136,12 +142,14 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { this.$imag = imag; this.$val = this; }; + typ.wrap = (v) => v; typ.keyFor = function(x) { return x.$real + "$" + x.$imag; }; break; case $kindArray: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor()); typ.init = function(elem, len) { typ.elem = elem; @@ -163,6 +171,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindChan: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.keyFor = $idKey; typ.init = function(elem, sendOnly, recvOnly) { typ.elem = elem; @@ -174,6 +183,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindFunc: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.init = function(params, results, variadic) { typ.params = params; typ.results = results; @@ -184,6 +194,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindInterface: typ = { implementedBy: {}, missingMethodFor: {} }; + typ.wrap = (v) => v; typ.keyFor = $ifaceKeyFor; typ.init = function(methods) { typ.methods = methods; @@ -196,6 +207,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindMap: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.init = function(key, elem) { typ.key = key; typ.elem = elem; @@ -210,6 +222,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { this.$target = target; this.$val = this; }; + typ.wrap = (v) => v; typ.keyFor = $idKey; typ.init = function(elem) { typ.elem = elem; @@ -229,6 +242,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { this.$capacity = array.length; this.$val = this; }; + typ.wrap = (v) => v; typ.init = function(elem) { typ.elem = elem; typ.comparable = false; @@ -240,6 +254,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) { case $kindStruct: typ = function(v) { this.$val = v; }; typ.wrapped = true; + typ.wrap = (v) => new typ(v); typ.ptr = $newType(4, $kindPtr, "*" + string, false, pkg, exported, constructor); typ.ptr.elem = typ; typ.ptr.prototype.$get = function() { return this; }; From 807df6aa3c98f05ea84ee15fac72e8880c884e28 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 27 Nov 2022 18:32:02 +0000 Subject: [PATCH 13/83] Update minified prelude. --- compiler/prelude/prelude_min.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/prelude/prelude_min.go b/compiler/prelude/prelude_min.go index fa1f3c8e9..38f866302 100644 --- a/compiler/prelude/prelude_min.go +++ b/compiler/prelude/prelude_min.go @@ -3,4 +3,4 @@ package prelude // Minified is an uglifyjs-minified version of Prelude. -const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,d=0;f+=(d+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var p=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(d&=65535))>>>0;return new e.constructor(p,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var d=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(d))}var p={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?p:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return p}},k=h(e,n);if(k!==p)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" +const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,p=0;f+=(p+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var d=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(p&=65535))>>>0;return new e.constructor(d,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).wrap=(e=>e),c.keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).wrap=(e=>e),c.keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).wrap=(e=>e),c.init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var p=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(p))}var d={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?d:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return d}},k=h(e,n);if(k!==d)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" From aceaeb9122b5fc1df476665af624ab23d31879aa Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 27 Nov 2022 18:28:03 +0000 Subject: [PATCH 14/83] Triage and document known generics-related test failures. This gives an overview of outstanding issues to address and also helps noticing possible regressions or unexpected fixes. --- tests/gorepo/run.go | 99 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 1ead4216e..8c7b180c1 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -153,10 +153,106 @@ var knownFails = map[string]failReason{ // These are new tests in Go 1.18 "fixedbugs/issue46938.go": {category: notApplicable, desc: "tests -d=checkptr compiler mode, which GopherJS doesn't support"}, "fixedbugs/issue47928.go": {category: notApplicable, desc: "//go:nointerface is a part of GOEXPERIMENT=fieldtrack and is not supported by GopherJS"}, - "fixedbugs/issue49665.go": {category: other, desc: "attempts to pass -gcflags=-G=3 to enable generics, GopherJS doesn't expect the flag; re-enable in Go 1.19 where the flag is removed"}, "fixedbugs/issue48898.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1128"}, "fixedbugs/issue48536.go": {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"}, "fixedbugs/issue53600.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, + + // Failures related to the lack of generics support. Ideally, this section + // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is + // fixed. + "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/absdiff2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/boundmethod.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/chans.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/combine.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/cons.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/dictionaryCapture-noinline.go": {category: generics, desc: "attempts to pass -gcflags=\"-G=3\" flag, incorrectly parsed by run.go"}, + "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, + "typeparam/eface.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/genembed.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/genembed2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/graph.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, + "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, + "typeparam/interfacearg.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, + "typeparam/issue44688.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue45817.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue46591.go": {category: generics, desc: "undiagnosed: len() returns an invalid value when parameterized types are involved"}, + "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/issue47272.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47713.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47716.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47740.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47740b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47775b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47877.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47901.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47925.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47925b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue47925c.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue47925d.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue48013.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue48042.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48047.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48049.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48225.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48253.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/issue48317.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue48318.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue48344.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, + "typeparam/issue48598.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48602.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48617.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48645a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48645b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48838.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, + "typeparam/issue49421.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue49547.go": {category: generics, desc: "incorrect type strings for parameterized types"}, + "typeparam/issue49659b.go": {category: generics, desc: "incorrect type strings for parameterized types"}, + "typeparam/issue50002.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue50109.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50109b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, + "typeparam/issue50264.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50419.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50642.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50690a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50690b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50690c.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, + "typeparam/issue51303.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, + "typeparam/issue52026.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue53477.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/list.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/list2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/lockable.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/metrics.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, + "typeparam/ordered.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/orderedmap.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/pair.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/sets.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, + "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/stringable.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/struct.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/subdict.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, + "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, + "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, + "typeparam/value.go": {category: generics, desc: "missing support for parameterized type instantiation"}, } type failCategory uint8 @@ -170,6 +266,7 @@ const ( unsureIfGopherJSSupportsThisFeature lowLevelRuntimeDifference // JavaScript runtime behaves differently from Go in ways that are difficult to work around. notApplicable // Test that doesn't need to run under GopherJS; it doesn't apply to the Go language in a general way. + generics // Test requires generics support. ) type failReason struct { From 5cb0848229f049fb5b8176223b5bfea2b2e66a0f Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 4 Dec 2022 18:56:36 +0000 Subject: [PATCH 15/83] Investigate and fix some cases of nil panics in the compiler. Ultimately, they were all caused by the missing support for declaring generic types. However, in some cases the panic is easily avoidable and even fixes a few test cases. --- compiler/utils.go | 2 +- tests/gorepo/run.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/compiler/utils.go b/compiler/utils.go index fd724b0f8..395bf06df 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -323,7 +323,7 @@ func (fc *funcContext) newVariable(name string, level varLevel) string { if level == varGenericFactory { for c := fc; c != nil; c = c.parent { c.allVars[name] = n + 1 - if c.sigTypes.IsGeneric() { + if c.parent != nil && c.parent.genericCtx != fc.genericCtx { break } } diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 8c7b180c1..1d3eea782 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -165,7 +165,7 @@ var knownFails = map[string]failReason{ "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/boundmethod.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/chans.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/combine.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/combine.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/cons.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/dictionaryCapture-noinline.go": {category: generics, desc: "attempts to pass -gcflags=\"-G=3\" flag, incorrectly parsed by run.go"}, "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -178,7 +178,7 @@ var knownFails = map[string]failReason{ "typeparam/graph.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/interfacearg.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/interfacearg.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, "typeparam/issue44688.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue45817.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -196,15 +196,14 @@ var knownFails = map[string]failReason{ "typeparam/issue47925b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue47925c.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue47925d.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/issue48013.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, "typeparam/issue48042.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48047.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48049.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48225.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48253.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue48317.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, - "typeparam/issue48318.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue48317.go": {category: generics, desc: "missing support for parameterized type definition"}, + "typeparam/issue48318.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/issue48344.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue48598.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -214,7 +213,6 @@ var knownFails = map[string]failReason{ "typeparam/issue48645b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48838.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue49421.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, "typeparam/issue49547.go": {category: generics, desc: "incorrect type strings for parameterized types"}, "typeparam/issue49659b.go": {category: generics, desc: "incorrect type strings for parameterized types"}, "typeparam/issue50002.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, @@ -226,7 +224,7 @@ var knownFails = map[string]failReason{ "typeparam/issue50642.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50690a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50690b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue50690c.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/issue50690c.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, @@ -242,12 +240,12 @@ var knownFails = map[string]failReason{ "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, "typeparam/ordered.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/orderedmap.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/pair.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/pair.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/sets.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/stringable.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/struct.go": {category: generics, desc: "undiagnosed: nil pointer panic in the compiler"}, + "typeparam/struct.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/subdict.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, From cb5cf572b48c7c0f4a3a2e1964543238fb890356 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 1 Jan 2023 18:08:56 +0000 Subject: [PATCH 16/83] compiler: factor out utility types for processing Go sources and errors. This is the first step in reducing complexity of the compiler.Compile function. The new `sources` type represents all inputs into the package compilation and simplifies extracting useful information out of them. It is designed to have little business logic of its own and serves as a convenient bridge to other packages like go/types or astrewrite. The ErrorList type is extended with utility methods that reduce verbosity of the calling code. --- compiler/compiler.go | 16 ------ compiler/errors.go | 68 ++++++++++++++++++++++ compiler/linkname.go | 2 +- compiler/package.go | 134 +++++++++++-------------------------------- compiler/sources.go | 122 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 117 deletions(-) create mode 100644 compiler/errors.go create mode 100644 compiler/sources.go diff --git a/compiler/compiler.go b/compiler/compiler.go index e660b1bee..547c0bd2b 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -30,22 +30,6 @@ func init() { } } -type ErrorList []error - -func (err ErrorList) Error() string { - if len(err) == 0 { - return "" - } - return fmt.Sprintf("%s (and %d more errors)", err[0].Error(), len(err[1:])) -} - -func (err ErrorList) Normalize() error { - if len(err) == 0 { - return nil - } - return err -} - // Archive contains intermediate build outputs of a single package. // // This is a logical equivalent of an object file in traditional compilers. diff --git a/compiler/errors.go b/compiler/errors.go new file mode 100644 index 000000000..838e3ece8 --- /dev/null +++ b/compiler/errors.go @@ -0,0 +1,68 @@ +package compiler + +import ( + "errors" + "fmt" +) + +// ErrTooManyErrors is added to the ErrorList by the Trim method. +var ErrTooManyErrors = errors.New("too many errors") + +// ErrorList wraps multiple errors as a single error. +type ErrorList []error + +func (errs ErrorList) Error() string { + if len(errs) == 0 { + return "" + } + return fmt.Sprintf("%s (and %d more errors)", errs[0].Error(), len(errs[1:])) +} + +// ErrOrNil returns nil if ErrorList is empty, or the error otherwise. +func (errs ErrorList) ErrOrNil() error { + if len(errs) == 0 { + return nil + } + return errs +} + +// Append an error to the list. +// +// If err is an instance of ErrorList, the lists are concatenated together, +// otherwise err is appended at the end of the list. If err is nil, the list is +// returned unmodified. +// +// err := DoStuff() +// errList := errList.Append(err) +func (errs ErrorList) Append(err error) ErrorList { + if err == nil { + return errs + } + if err, ok := err.(ErrorList); ok { + return append(errs, err...) + } + return append(errs, err) +} + +// AppendDistinct is similar to Append, but doesn't append the error if it has +// the same message as the last error on the list. +func (errs ErrorList) AppendDistinct(err error) ErrorList { + if l := len(errs); l > 0 { + if prev := errs[l-1]; prev != nil && err.Error() == prev.Error() { + return errs // The new error is the same as the last one, skip it. + } + } + + return errs.Append(err) +} + +// Trim the error list if it has more than limit errors. If the list is trimmed, +// all extraneous errors are replaced with a single ErrTooManyErrors, making the +// returned ErrorList length of limit+1. +func (errs ErrorList) Trim(limit int) ErrorList { + if len(errs) <= limit { + return errs + } + + return append(errs[:limit], ErrTooManyErrors) +} diff --git a/compiler/linkname.go b/compiler/linkname.go index 5e1782462..66ddcc0fb 100644 --- a/compiler/linkname.go +++ b/compiler/linkname.go @@ -166,7 +166,7 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go } } - return directives, errs.Normalize() + return directives, errs.ErrOrNil() } // goLinknameSet is a utility that enables quick lookup of whether a decl is diff --git a/compiler/package.go b/compiler/package.go index 702287078..26a32bf6b 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -15,7 +15,6 @@ 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" ) @@ -134,34 +133,20 @@ type flowData struct { 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 func(string) (*Archive, error) -} - -// packageImporter implements go/types.Importer interface. -type packageImporter struct { - importContext *ImportContext - importError *error // A pointer to importError in Compile. -} - -func (pi packageImporter) Import(path string) (*types.Package, error) { - if path == "unsafe" { - return types.Unsafe, nil - } - - a, err := pi.importContext.Import(path) - if err != nil { - if *pi.importError == nil { - // If import failed, show first error of import only (https://github.com/gopherjs/gopherjs/issues/119). - *pi.importError = err - } - return nil, err - } - - return pi.importContext.Packages[a.ImportPath], nil + // 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() @@ -177,85 +162,25 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor err = bailout(fmt.Errorf("unexpected compiler panic while building package %q: %v", importPath, e)) }() - // Files must be in the same order to get reproducible JS - sort.Slice(files, func(i, j int) bool { - return fileSet.File(files[i].Pos()).Name() > fileSet.File(files[j].Pos()).Name() - }) - - typesInfo := &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), - 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 + srcs := sources{ + ImportPath: importPath, + Files: files, + FileSet: fileSet, + }.Sort() - // Extract all go:linkname compiler directives from the package source. - goLinknames := []GoLinkname{} - for _, file := range files { - found, err := parseGoLinknames(fileSet, importPath, file) - if err != nil { - if errs, ok := err.(ErrorList); ok { - errList = append(errList, errs...) - } else { - errList = append(errList, err) - } - } - goLinknames = append(goLinknames, found...) - } - - var importError error - var previousErr error - config := &types.Config{ - Importer: packageImporter{ - importContext: importContext, - importError: &importError, - }, - Sizes: sizes32, - Error: func(err error) { - if previousErr != nil && previousErr.Error() == err.Error() { - return - } - errList = append(errList, err) - previousErr = err - }, - } - typesPkg, err := config.Check(importPath, fileSet, files, typesInfo) - if importError != nil { - return nil, importError - } - if errList != nil { - if len(errList) > 10 { - pos := token.NoPos - if last, ok := errList[9].(types.Error); ok { - pos = last.Pos - } - errList = append(errList[:10], types.Error{Fset: fileSet, Pos: pos, Msg: "too many errors"}) - } - return nil, errList - } + typesInfo, typesPkg, err := srcs.TypeCheck(importContext) if err != nil { return nil, err } - importContext.Packages[importPath] = typesPkg + importContext.Packages[srcs.ImportPath] = typesPkg - exportData := new(bytes.Buffer) - if err := gcexportdata.Write(exportData, nil, typesPkg); err != nil { - return nil, fmt.Errorf("failed to write export data: %v", err) - } - encodedFileSet := new(bytes.Buffer) - if err := fileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil { + // Extract all go:linkname compiler directives from the package source. + goLinknames, err := srcs.ParseGoLinknames() + if err != nil { return nil, err } - simplifiedFiles := make([]*ast.File, len(files)) - for i, file := range files { - simplifiedFiles[i] = astrewrite.Simplify(file, typesInfo, false) - } + srcs = srcs.Simplified(typesInfo) isBlocking := func(f *types.Func) bool { archive, err := importContext.Import(f.Pkg().Path()) @@ -270,7 +195,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } panic(fullName) } - pkgInfo := analysis.AnalyzePkg(simplifiedFiles, fileSet, typesInfo, typesPkg, isBlocking) + pkgInfo := analysis.AnalyzePkg(srcs.Files, srcs.FileSet, typesInfo, typesPkg, isBlocking) funcCtx := &funcContext{ FuncInfo: pkgInfo.InitFuncInfo, pkgCtx: &pkgContext{ @@ -284,7 +209,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor indentation: 1, dependencies: make(map[types.Object]bool), minify: minify, - fileSet: fileSet, + fileSet: srcs.FileSet, }, allVars: make(map[string]int), flowDatas: map[*types.Label]*flowData{nil: {}}, @@ -322,7 +247,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor var functions []*ast.FuncDecl var vars []*types.Var - for _, file := range simplifiedFiles { + for _, file := range srcs.Files { for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: @@ -630,8 +555,17 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor 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: importPath, + ImportPath: srcs.ImportPath, Name: typesPkg.Name(), Imports: importedPaths, ExportData: exportData.Bytes(), diff --git a/compiler/sources.go b/compiler/sources.go new file mode 100644 index 000000000..4224f81f0 --- /dev/null +++ b/compiler/sources.go @@ -0,0 +1,122 @@ +package compiler + +import ( + "go/ast" + "go/token" + "go/types" + "sort" + + "github.com/neelance/astrewrite" +) + +// sources is a slice of parsed Go sources. +// +// Note that the sources would normally belong to a single logical Go package, +// but they don't have to be a real Go package (i.e. found on the file system) +// or represent a complete package (i.e. it could be only a few source files +// compiled by `gopherjs build foo.go bar.go`). +type sources struct { + // ImportPath representing the sources, if exists. May be empty for "virtual" + // packages like testmain or playground-generated package. + ImportPath string + Files []*ast.File + FileSet *token.FileSet +} + +// Sort the Files slice by the original source name to ensure consistent order +// of processing. This is required for reproducible JavaScript output. +// +// Note this function mutates the original slice. +func (s sources) Sort() sources { + sort.Slice(s.Files, func(i, j int) bool { + return s.FileSet.File(s.Files[i].Pos()).Name() > s.FileSet.File(s.Files[j].Pos()).Name() + }) + return s +} + +// Simplify returns a new sources instance with each Files entry processed by +// astrewrite.Simplify. +func (s sources) Simplified(typesInfo *types.Info) sources { + simplified := sources{ + ImportPath: s.ImportPath, + FileSet: s.FileSet, + } + for _, file := range s.Files { + simplified.Files = append(simplified.Files, astrewrite.Simplify(file, typesInfo, false)) + } + return simplified +} + +// TypeCheck the sources. Returns information about declared package types and +// type information for the supplied AST. +func (s sources) TypeCheck(importContext *ImportContext) (*types.Info, *types.Package, error) { + const errLimit = 10 // Max number of type checking errors to return. + + typesInfo := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + 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 typeErrs ErrorList + + importer := packageImporter{ImportContext: importContext} + + config := &types.Config{ + Importer: &importer, + Sizes: sizes32, + Error: func(err error) { typeErrs = typeErrs.AppendDistinct(err) }, + } + typesPkg, err := config.Check(s.ImportPath, s.FileSet, s.Files, typesInfo) + // If we encountered any import errors, it is likely that the other type errors + // are not meaningful and would be resolved by fixing imports. Return them + // separately, if any. https://github.com/gopherjs/gopherjs/issues/119. + if importer.Errors.ErrOrNil() != nil { + return nil, nil, importer.Errors.Trim(errLimit).ErrOrNil() + } + // Return any other type errors. + if typeErrs.ErrOrNil() != nil { + return nil, nil, typeErrs.Trim(errLimit).ErrOrNil() + } + // Any general errors that may have occurred during type checking. + if err != nil { + return nil, nil, err + } + return typesInfo, typesPkg, nil +} + +// ParseGoLinknames extracts all //go:linkname compiler directive from the sources. +func (s sources) ParseGoLinknames() ([]GoLinkname, error) { + goLinknames := []GoLinkname{} + var errs ErrorList + for _, file := range s.Files { + found, err := parseGoLinknames(s.FileSet, s.ImportPath, file) + errs = errs.Append(err) + goLinknames = append(goLinknames, found...) + } + return goLinknames, errs.ErrOrNil() +} + +// packageImporter implements go/types.Importer interface. +type packageImporter struct { + ImportContext *ImportContext + Errors ErrorList +} + +func (pi *packageImporter) Import(path string) (*types.Package, error) { + if path == "unsafe" { + return types.Unsafe, nil + } + + a, err := pi.ImportContext.Import(path) + if err != nil { + pi.Errors = pi.Errors.AppendDistinct(err) + return nil, err + } + + return pi.ImportContext.Packages[a.ImportPath], nil +} From 146735d251cbddb92bcc824ccd7b910248ea8dd5 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Mon, 2 Jan 2023 16:14:40 +0000 Subject: [PATCH 17/83] compiler: break functions related to Decls out of Compile(). For each package-level entity we emit one or more Decl struct, which contains all JS code fragments and metadata required to produce the final executable script. For each decl type (imports, vars, functions and types) I creates a separate function that contains the logic responsible for its creation (and some auxiliary functions). The main objective is to keep the Compile() function very high-level and clearly reflecting various compilation stages we go through. I tried to add comments to make the code more accessible for future contributors (and future self...), although there are still some aspects I don't fully grasp. Ideally, we would have tests for all these new functions, but that's way more work than I'm able to take on right now. --- compiler/analysis/info.go | 13 + compiler/compiler.go | 46 --- compiler/decls.go | 541 ++++++++++++++++++++++++++++++++ compiler/expressions.go | 4 +- compiler/package.go | 467 ++++++--------------------- compiler/typesutil/typesutil.go | 6 + compiler/utils.go | 79 ++++- 7 files changed, 734 insertions(+), 422 deletions(-) create mode 100644 compiler/decls.go diff --git a/compiler/analysis/info.go b/compiler/analysis/info.go index c984b726f..d73c2fc05 100644 --- a/compiler/analysis/info.go +++ b/compiler/analysis/info.go @@ -92,10 +92,23 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { return funcInfo } +// IsBlocking returns true if the function may contain blocking calls or operations. func (info *Info) IsBlocking(fun *types.Func) bool { return len(info.FuncDeclInfos[fun].Blocking) > 0 } +// VarsWithInitializers returns a set of package-level variables that have +// explicit initializers. +func (info *Info) VarsWithInitializers() map[*types.Var]bool { + result := map[*types.Var]bool{} + for _, init := range info.InitOrder { + for _, o := range init.Lhs { + result[o] = true + } + } + return result +} + func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info { info := &Info{ Info: typesInfo, diff --git a/compiler/compiler.go b/compiler/compiler.go index 547c0bd2b..ac8e9b26f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -82,52 +82,6 @@ func (a *Archive) RegisterTypes(packages map[string]*types.Package) error { return err } -// Decl represents a package-level symbol (e.g. a function, variable or type). -// -// It contains code generated by the compiler for this specific symbol, which is -// grouped by the execution stage it belongs to in the JavaScript runtime. -type Decl struct { - // The package- or receiver-type-qualified name of function or method obj. - // See go/types.Func.FullName(). - FullName string - // A logical equivalent of a symbol name in an object file in the traditional - // Go compiler/linker toolchain. Used by GopherJS to support go:linkname - // directives. Must be set for decls that are supported by go:linkname - // implementation. - LinkingName SymName - // A list of package-level JavaScript variable names this symbol needs to declare. - Vars []string - // NamedRecvType is method named recv declare. - NamedRecvType string - // JavaScript code that declares basic information about a symbol. For a type - // it configures basic information about the type and its identity. For a function - // or method it contains its compiled body. - DeclCode []byte - // JavaScript code that initializes reflection metadata about type's method list. - MethodListCode []byte - // JavaScript code that initializes the rest of reflection metadata about a type - // (e.g. struct fields, array type sizes, element types, etc.). - TypeInitCode []byte - // JavaScript code that needs to be executed during the package init phase to - // set the symbol up (e.g. initialize package-level variable value). - InitCode []byte - // Symbol's identifier used by the dead-code elimination logic, not including - // package path. If empty, the symbol is assumed to be alive and will not be - // eliminated. For methods it is the same as its receiver type identifier. - DceObjectFilter string - // The second part of the identified used by dead-code elimination for methods. - // Empty for other types of symbols. - DceMethodFilter string - // List of fully qualified (including package path) DCE symbol identifiers the - // symbol depends on for dead code elimination purposes. - DceDeps []string - // Set to true if a function performs a blocking operation (I/O or - // synchronization). The compiler will have to generate function code such - // that it can be resumed after a blocking operation completes without - // blocking the main thread in the meantime. - Blocking bool -} - type Dependency struct { Pkg string Type string diff --git a/compiler/decls.go b/compiler/decls.go new file mode 100644 index 000000000..bdf96206b --- /dev/null +++ b/compiler/decls.go @@ -0,0 +1,541 @@ +package compiler + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "sort" + "strings" + + "github.com/gopherjs/gopherjs/compiler/analysis" + "github.com/gopherjs/gopherjs/compiler/typesutil" +) + +// Decl represents a package-level symbol (e.g. a function, variable or type). +// +// It contains code generated by the compiler for this specific symbol, which is +// grouped by the execution stage it belongs to in the JavaScript runtime. +type Decl struct { + // The package- or receiver-type-qualified name of function or method obj. + // See go/types.Func.FullName(). + FullName string + // A logical equivalent of a symbol name in an object file in the traditional + // Go compiler/linker toolchain. Used by GopherJS to support go:linkname + // directives. Must be set for decls that are supported by go:linkname + // implementation. + LinkingName SymName + // A list of package-level JavaScript variable names this symbol needs to declare. + Vars []string + // NamedRecvType is method named recv declare. + NamedRecvType string + // JavaScript code that declares basic information about a symbol. For a type + // it configures basic information about the type and its identity. For a function + // or method it contains its compiled body. + DeclCode []byte + // JavaScript code that initializes reflection metadata about type's method list. + MethodListCode []byte + // JavaScript code that initializes the rest of reflection metadata about a type + // (e.g. struct fields, array type sizes, element types, etc.). + TypeInitCode []byte + // JavaScript code that needs to be executed during the package init phase to + // set the symbol up (e.g. initialize package-level variable value). + InitCode []byte + // Symbol's identifier used by the dead-code elimination logic, not including + // package path. If empty, the symbol is assumed to be alive and will not be + // eliminated. For methods it is the same as its receiver type identifier. + DceObjectFilter string + // The second part of the identifier used by dead-code elimination for methods. + // Empty for other types of symbols. + DceMethodFilter string + // List of fully qualified (including package path) DCE symbol identifiers the + // symbol depends on for dead code elimination purposes. + DceDeps []string + // Set to true if a function performs a blocking operation (I/O or + // synchronization). The compiler will have to generate function code such + // that it can be resumed after a blocking operation completes without + // blocking the main thread in the meantime. + Blocking bool +} + +// minify returns a copy of Decl with unnecessary whitespace removed from the +// JS code. +func (d Decl) minify() Decl { + d.DeclCode = removeWhitespace(d.DeclCode, true) + d.MethodListCode = removeWhitespace(d.MethodListCode, true) + d.TypeInitCode = removeWhitespace(d.TypeInitCode, true) + d.InitCode = removeWhitespace(d.InitCode, true) + return d +} + +// topLevelObjects extracts package-level variables, functions and named types +// from the package AST. +func (fc *funcContext) topLevelObjects(srcs sources) (vars []*types.Var, functions []*ast.FuncDecl, typeNames []*types.TypeName) { + if !fc.IsRoot() { + panic(bailout(fmt.Errorf("functionContext.discoverObjects() must be only called on the package-level context"))) + } + + for _, file := range srcs.Files { + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + sig := fc.pkgCtx.Defs[d.Name].(*types.Func).Type().(*types.Signature) + if sig.Recv() == nil { + fc.objectName(fc.pkgCtx.Defs[d.Name]) // 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 := fc.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) + typeNames = append(typeNames, o) + fc.objectName(o) // register toplevel name + } + case token.VAR: + for _, spec := range d.Specs { + for _, name := range spec.(*ast.ValueSpec).Names { + if !isBlank(name) { + o := fc.pkgCtx.Defs[name].(*types.Var) + vars = append(vars, o) + fc.objectName(o) // register toplevel name + } + } + } + case token.CONST: + // skip, constants are inlined + } + } + } + } + + return vars, functions, typeNames +} + +// importDecls processes import declarations. +// +// For each imported package: +// - A new package-level variable is reserved to refer to symbols from that +// package. +// - A Decl instance is generated to be included in the Archive. +// +// Lists of imported package paths and corresponding Decls is returned to the caller. +func (fc *funcContext) importDecls() (importedPaths []string, importDecls []*Decl) { + if !fc.IsRoot() { + panic(bailout(fmt.Errorf("functionContext.importDecls() must be only called on the package-level context"))) + } + + imports := []*types.Package{} + for _, pkg := range fc.pkgCtx.Pkg.Imports() { + if pkg == 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 + } + imports = append(imports, pkg) + } + + // Deterministic processing order. + sort.Slice(imports, func(i, j int) bool { return imports[i].Path() < imports[j].Path() }) + + for _, pkg := range imports { + importedPaths = append(importedPaths, pkg.Path()) + importDecls = append(importDecls, fc.newImportDecl(pkg)) + } + + return importedPaths, importDecls +} + +// newImportDecl registers the imported package and returns a Decl instance for it. +func (fc *funcContext) newImportDecl(importedPkg *types.Package) *Decl { + pkgVar := fc.importedPkgVar(importedPkg) + return &Decl{ + Vars: []string{pkgVar}, + DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", pkgVar, importedPkg.Path())), + InitCode: fc.CatchOutput(1, func() { fc.translateStmt(fc.importInitializer(importedPkg.Path()), nil) }), + } +} + +// importInitializer calls the imported package $init() function to ensure it is +// initialized before any code in the importer package runs. +func (fc *funcContext) importInitializer(impPath string) ast.Stmt { + pkgVar := fc.pkgCtx.pkgVars[impPath] + id := fc.newIdent(fmt.Sprintf(`%s.$init`, pkgVar), types.NewSignature(nil, nil, nil, false)) + call := &ast.CallExpr{Fun: id} + fc.Blocking[call] = true + fc.Flattened[call] = true + + return &ast.ExprStmt{X: call} +} + +// varDecls translates all package-level variables. +// +// `vars` argument must contain all package-level variables found in the package. +// The method returns corresponding Decls that declare and initialize the vars +// as appropriate. Decls are returned in order necessary to correctly initialize +// the variables, considering possible dependencies between them. +func (fc *funcContext) varDecls(vars []*types.Var) []*Decl { + if !fc.IsRoot() { + panic(bailout(fmt.Errorf("functionContext.varDecls() must be only called on the package-level context"))) + } + + var varDecls []*Decl + varsWithInit := fc.pkgCtx.VarsWithInitializers() + + initializers := []*types.Initializer{} + + // For implicitly-initialized vars we generate synthetic zero-value + // initializers and then process them the same way as explicitly initialized. + for _, o := range vars { + if varsWithInit[o] { + continue + } + initializer := &types.Initializer{ + Lhs: []*types.Var{o}, + Rhs: fc.zeroValue(o.Type()), + } + initializers = append(initializers, initializer) + } + + // Add explicitly-initialized variables to the list. Implicitly-initialized + // variables should be declared first in case explicit initializers depend on + // them. + initializers = append(initializers, fc.pkgCtx.InitOrder...) + + for _, init := range initializers { + varDecls = append(varDecls, fc.newVarDecl(init)) + } + + return varDecls +} + +// newVarDecl creates a new Decl describing a variable, given an explicit +// initializer. +func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { + var d Decl + + assignLHS := []ast.Expr{} + for _, o := range init.Lhs { + assignLHS = append(assignLHS, fc.newIdentFor(o)) + + // For non-exported package-level variables we need to declared a local JS + // variable. Exported variables are represented as properties of the $pkg + // JS object. + if !o.Exported() { + d.Vars = append(d.Vars, fc.objectName(o)) + } + if fc.pkgCtx.HasPointer[o] && !o.Exported() { + d.Vars = append(d.Vars, fc.varPtrName(o)) + } + } + + d.DceDeps = fc.CollectDCEDeps(func() { + fc.localVars = nil + d.InitCode = fc.CatchOutput(1, func() { + fc.translateStmt(&ast.AssignStmt{ + Lhs: assignLHS, + Tok: token.DEFINE, + Rhs: []ast.Expr{init.Rhs}, + }, nil) + }) + + // Initializer code may have introduced auxiliary variables (e.g. for + // handling multi-assignment or blocking calls), add them to the decl too. + d.Vars = append(d.Vars, fc.localVars...) + fc.localVars = nil // Clean up after ourselves. + }) + + if len(init.Lhs) == 1 { + if !analysis.HasSideEffect(init.Rhs, fc.pkgCtx.Info.Info) { + d.DceObjectFilter = init.Lhs[0].Name() + } + } + return &d +} + +// funcDecls translates all package-level function and methods. +// +// `functions` must contain all package-level function and method declarations +// found in the AST. The function returns Decls that define corresponding JS +// functions at runtime. For special functions like init() and main() decls will +// also contain code necessary to invoke them. +func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { + if !fc.IsRoot() { + panic(bailout(fmt.Errorf("functionContext.funcDecls() must be only called on the package-level context"))) + } + + var mainFunc *types.Func + funcDecls := []*Decl{} + for _, fun := range functions { + funcDecls = append(funcDecls, fc.newFuncDecl(fun)) + + if o := fc.pkgCtx.Defs[fun.Name].(*types.Func); o.Name() == "main" { + mainFunc = o // main() function candidate. + } + } + if fc.pkgCtx.IsMain() { + if mainFunc == nil { + return nil, fmt.Errorf("missing main function") + } + // Add a special Decl for invoking main() function after the program has + // been initialized. It must come after all other functions, especially all + // init() functions, otherwise main() will be invoked too early. + funcDecls = append(funcDecls, &Decl{ + InitCode: fc.CatchOutput(1, func() { fc.translateStmt(fc.callMainFunc(mainFunc), nil) }), + }) + } + return funcDecls, nil +} + +// newFuncDecl returns a Decl that defines a package-level function or a method. +func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl) *Decl { + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + + d := &Decl{ + FullName: o.FullName(), + Blocking: fc.pkgCtx.IsBlocking(o), + LinkingName: newSymName(o), + } + + if typesutil.IsMethod(o) { + var namedRecvType *types.Named + + recvType := o.Type().(*types.Signature).Recv().Type() + if ptr, isPointer := recvType.(*types.Pointer); isPointer { + namedRecvType = ptr.Elem().(*types.Named) + } else { + namedRecvType = recvType.(*types.Named) + } + + d.NamedRecvType = fc.objectName(namedRecvType.Obj()) + d.DceObjectFilter = namedRecvType.Obj().Name() + if !fun.Name.IsExported() { + d.DceMethodFilter = o.Name() + "~" + } + } else { + d.Vars = []string{fc.objectName(o)} + switch o.Name() { + case "main": + if fc.pkgCtx.IsMain() { // Found main() function of the program. + d.DceObjectFilter = "" // Always reachable. + } + case "init": + d.InitCode = fc.CatchOutput(1, func() { fc.translateStmt(fc.callInitFunc(o), nil) }) + d.DceObjectFilter = "" // init() function is always reachable. + default: + d.DceObjectFilter = o.Name() + } + } + + d.DceDeps = fc.CollectDCEDeps(func() { d.DeclCode = fc.translateTopLevelFunction(fun) }) + return d +} + +// callInitFunc returns an AST statement for calling the given instance of the +// package's init() function. +func (fc *funcContext) callInitFunc(init *types.Func) ast.Stmt { + id := fc.newIdentFor(init) + call := &ast.CallExpr{Fun: id} + if fc.pkgCtx.IsBlocking(init) { + fc.Blocking[call] = true + } + return &ast.ExprStmt{X: call} +} + +// callMainFunc returns an AST statement for calling the main() function of the +// program, which should be included in the $init() function of the main package. +func (fc *funcContext) callMainFunc(main *types.Func) ast.Stmt { + id := fc.newIdentFor(main) + call := &ast.CallExpr{Fun: id} + ifStmt := &ast.IfStmt{ + Cond: fc.newIdent("$pkg === $mainPkg", types.Typ[types.Bool]), + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.ExprStmt{X: call}, + &ast.AssignStmt{ + Lhs: []ast.Expr{fc.newIdent("$mainFinished", types.Typ[types.Bool])}, + Tok: token.ASSIGN, + Rhs: []ast.Expr{fc.newConst(types.Typ[types.Bool], constant.MakeBool(true))}, + }, + }, + }, + } + if fc.pkgCtx.IsBlocking(main) { + fc.Blocking[call] = true + fc.Flattened[ifStmt] = true + } + + return ifStmt +} + +// namedTypeDecls returns Decls that define all names Go types. +// +// `typeNames` must contain all named types defined in the package, including +// those defined inside function bodies. +func (fc *funcContext) namedTypeDecls(typeNames []*types.TypeName) []*Decl { + if !fc.IsRoot() { + panic(bailout(fmt.Errorf("functionContext.namedTypeDecls() must be only called on the package-level context"))) + } + + decls := []*Decl{} + for _, o := range typeNames { + if o.IsAlias() { + continue + } + + decls = append(decls, fc.newNamedTypeDecl(o)) + } + return decls +} + +// newNamedTypeDecl returns a Decl that represents a named Go type. +func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { + typeName := fc.objectName(o) + d := &Decl{ + Vars: []string{typeName}, + DceObjectFilter: o.Name(), + } + + d.DceDeps = fc.CollectDCEDeps(func() { + // Code that declares a JS type (i.e. prototype) for each Go type. + d.DeclCode = fc.CatchOutput(0, func() { + lhs := typeName + if getVarLevel(o) == varPackage { + // Package-level types are also accessible as properties on the package + // object. + lhs += " = $pkg." + encodeIdent(o.Name()) + } + + size := int64(0) + constructor := "null" + + switch t := o.Type().Underlying().(type) { + case *types.Struct: + constructor = fc.structConstructor(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 := o.Type().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()" + } + } + fc.Printf(`%s = $newType(%d, %s, "%s.%s", %t, "%s", %t, %s);`, + lhs, size, typeKind(o.Type()), o.Pkg().Name(), o.Name(), o.Name() != "", o.Pkg().Path(), o.Exported(), constructor) + }) + + // Reflection metadata about methods the type has. + d.MethodListCode = fc.CatchOutput(0, func() { + named := o.Type().(*types.Named) + if _, ok := named.Underlying().(*types.Interface); ok { + return + } + var methods []string + var ptrMethods []string + for i := 0; i < named.NumMethods(); i++ { + entry, isPtr := fc.methodListEntry(named.Method(i)) + if isPtr { + ptrMethods = append(ptrMethods, entry) + } else { + methods = append(methods, entry) + } + } + if len(methods) > 0 { + fc.Printf("%s.methods = [%s];", fc.typeName(named), strings.Join(methods, ", ")) + } + if len(ptrMethods) > 0 { + fc.Printf("%s.methods = [%s];", fc.typeName(types.NewPointer(named)), strings.Join(ptrMethods, ", ")) + } + }) + + // Certain types need to run additional type-specific logic to fully + // initialize themselves. + switch t := o.Type().Underlying().(type) { + case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct: + d.TypeInitCode = fc.CatchOutput(0, func() { + fc.Printf("%s.init(%s);", fc.objectName(o), fc.initArgs(t)) + }) + } + }) + return d +} + +// structConstructor returns JS constructor function for a struct type. +func (fc *funcContext) structConstructor(t *types.Struct) string { + constructor := &strings.Builder{} + + ctrArgs := make([]string, t.NumFields()) + for i := 0; i < t.NumFields(); i++ { + ctrArgs[i] = fieldName(t, i) + "_" + } + + fmt.Fprintf(constructor, "function(%s) {\n", strings.Join(ctrArgs, ", ")) + fmt.Fprintf(constructor, "\t\tthis.$val = this;\n") + + // If no arguments were passed, zero-initialize all fields. + fmt.Fprintf(constructor, "\t\tif (arguments.length === 0) {\n") + for i := 0; i < t.NumFields(); i++ { + fmt.Fprintf(constructor, "\t\t\tthis.%s = %s;\n", fieldName(t, i), fc.translateExpr(fc.zeroValue(t.Field(i).Type())).String()) + } + fmt.Fprintf(constructor, "\t\t\treturn;\n") + fmt.Fprintf(constructor, "\t\t}\n") + + // Otherwise initialize fields with the provided values. + for i := 0; i < t.NumFields(); i++ { + fmt.Fprintf(constructor, "\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i)) + } + fmt.Fprintf(constructor, "\t}") + return constructor.String() +} + +// methodListEntry returns a JS code fragment that describes the given method +// function for runtime reflection. It returns isPtr=true if the method belongs +// to the pointer-receiver method list. +func (fc *funcContext) methodListEntry(method *types.Func) (entry string, isPtr bool) { + 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, fc.initArgs(t)) + _, isPtr = t.Recv().Type().(*types.Pointer) + return entry, isPtr +} + +// anonTypeDecls returns a list of Decls corresponding to anonymous Go types +// encountered in the package. +// +// `anonTypes` must contain an ordered list of anonymous types with the +// identifiers that were auto-assigned to them. They must be sorted in the +// topological initialization order (e.g. `[]int` is before `struct{f []int}`). +// +// See also typesutil.AnonymousTypes. +func (fc *funcContext) anonTypeDecls(anonTypes []*types.TypeName) []*Decl { + if !fc.IsRoot() { + panic(bailout(fmt.Errorf("functionContext.anonTypeDecls() must be only called on the package-level context"))) + } + + decls := []*Decl{} + for _, t := range anonTypes { + d := Decl{ + Vars: []string{t.Name()}, + DceObjectFilter: t.Name(), + } + d.DceDeps = fc.CollectDCEDeps(func() { + d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type()))) + }) + decls = append(decls, &d) + } + + return decls +} diff --git a/compiler/expressions.go b/compiler/expressions.go index 42708f79a..b589ff977 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -556,7 +556,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { return fc.formatExpr(`$methodVal(%s, "%s")`, fc.makeReceiver(e), sel.Obj().(*types.Func).Name()) case types.MethodExpr: if !sel.Obj().Exported() { - fc.pkgCtx.dependencies[sel.Obj()] = true + fc.DeclareDCEDep(sel.Obj()) } if _, ok := sel.Recv().Underlying().(*types.Interface); ok { return fc.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) @@ -912,7 +912,7 @@ func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { sel, _ := fc.pkgCtx.SelectionOf(e) if !sel.Obj().Exported() { - fc.pkgCtx.dependencies[sel.Obj()] = true + fc.DeclareDCEDep(sel.Obj()) } x := e.X diff --git a/compiler/package.go b/compiler/package.go index 26a32bf6b..69c3fd54f 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "go/ast" - "go/constant" "go/token" "go/types" "sort" @@ -23,8 +22,14 @@ type pkgContext struct { *analysis.Info additionalSelections map[*ast.SelectorExpr]selection - typeNames []*types.TypeName - pkgVars map[string]string + // List of type names declared in the package, including those defined inside + // functions. + typeNames []*types.TypeName + // Mapping from package import paths to JS variables that were assigned to an + // imported package and can be used to access it. + pkgVars map[string]string + // Mapping from a named Go object (e.g. type, func, var...) to a JS variable + // name assigned to them. objectNames map[types.Object]string varPtrNames map[*types.Var]string anonTypes typesutil.AnonymousTypes @@ -36,12 +41,9 @@ 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 +// IsMain returns true if this is the main package of the program. +func (pc *pkgContext) IsMain() bool { + return pc.Pkg.Name() == "main" } func (p *pkgContext) SelectionOf(e *ast.SelectorExpr) (selection, bool) { @@ -54,6 +56,14 @@ func (p *pkgContext) SelectionOf(e *ast.SelectorExpr) (selection, bool) { return nil, false } +// 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 +} + type selection interface { Kind() types.SelectionKind Recv() types.Type @@ -127,6 +137,35 @@ type funcContext struct { pos token.Pos } +// newRootCtx creates a new package-level instance of a functionContext object. +func newRootCtx(srcs sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool, minify bool) *funcContext { + pkgInfo := analysis.AnalyzePkg(srcs.Files, srcs.FileSet, typesInfo, typesPkg, isBlocking) + rootCtx := &funcContext{ + parent: nil, // Package-level context has no parent. + FuncInfo: pkgInfo.InitFuncInfo, + pkgCtx: &pkgContext{ + Info: pkgInfo, + additionalSelections: make(map[*ast.SelectorExpr]selection), + + pkgVars: make(map[string]string), + objectNames: make(map[types.Object]string), + varPtrNames: make(map[*types.Var]string), + escapingVars: make(map[*types.Var]bool), + indentation: 1, + minify: minify, + fileSet: srcs.FileSet, + }, + allVars: make(map[string]int), + flowDatas: map[*types.Label]*flowData{nil: {}}, + caseCounter: 1, + labelCases: make(map[*types.Label]int), + } + for name := range reservedKeywords { + rootCtx.allVars[name] = 1 + } + return rootCtx +} + type flowData struct { postStmt func() beginCase int @@ -143,6 +182,27 @@ type ImportContext struct { Import func(importPath string) (*Archive, error) } +// isBlocking returns true if an _imported_ function is blocking. It will panic +// if the function decl is not found in the imported package or the package +// hasn't been compiled yet. +// +// Note: see analysis.FuncInfo.Blocking if you need to determine if a function +// in the _current_ package is blocking. Usually available via functionContext +// object. +func (ic *ImportContext) isBlocking(f *types.Func) bool { + archive, err := ic.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(bailout(fmt.Errorf("can't determine if function %s is blocking: decl not found in package archive", fullName))) +} + // Compile the provided Go sources as a single package. // // Import path must be the absolute import path for a package. Provided sources @@ -182,377 +242,44 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor 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) - } - pkgInfo := analysis.AnalyzePkg(srcs.Files, srcs.FileSet, typesInfo, typesPkg, isBlocking) - funcCtx := &funcContext{ - FuncInfo: pkgInfo.InitFuncInfo, - pkgCtx: &pkgContext{ - Info: pkgInfo, - additionalSelections: make(map[*ast.SelectorExpr]selection), + rootCtx := newRootCtx(srcs, typesInfo, typesPkg, importContext.isBlocking, minify) - pkgVars: make(map[string]string), - objectNames: make(map[types.Object]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, - }, - allVars: make(map[string]int), - flowDatas: map[*types.Label]*flowData{nil: {}}, - caseCounter: 1, - labelCases: make(map[*types.Label]int), - } - for name := range reservedKeywords { - funcCtx.allVars[name] = 1 - } + importedPaths, importDecls := rootCtx.importDecls() - // 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(), varPackage) - importedPaths = append(importedPaths, importedPkg.Path()) - } - sort.Strings(importedPaths) - for _, impPath := range importedPaths { - id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignature(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) - var recvType types.Type - if sig.Recv() != nil { - recvType = sig.Recv().Type() - if ptr, isPtr := recvType.(*types.Pointer); isPtr { - recvType = ptr.Elem() - } - } - 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 = append(funcCtx.pkgCtx.typeNames, 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 - } - } - } - } + vars, functions, typeNames := rootCtx.topLevelObjects(srcs) - 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 - } + // More named types may be added to the list when function bodies are processed. + rootCtx.pkgCtx.typeNames = typeNames - // 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) + // Translate functions and variables. + varDecls := rootCtx.varDecls(vars) + funcDecls, err := rootCtx.funcDecls(functions) + if err != nil { + return nil, err } - // functions - var funcDecls []*Decl - var mainFunc *types.Func - for _, fun := range functions { - o := funcCtx.pkgCtx.Defs[fun.Name].(*types.Func) + // It is important that we translate types *after* we've processed all + // functions to make sure we've discovered all types declared inside function + // bodies. + typeDecls := rootCtx.namedTypeDecls(rootCtx.pkgCtx.typeNames) - funcInfo := funcCtx.pkgCtx.FuncDeclInfos[o] - d := Decl{ - FullName: o.FullName(), - Blocking: len(funcInfo.Blocking) != 0, - } - d.LinkingName = newSymName(o) - if fun.Recv == nil { - d.Vars = []string{funcCtx.objectName(o)} - 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.NewSignature(nil, nil, nil, 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() + "~" - } - } + // Finally, anonymous types are translated the last, to make sure we've + // discovered all of them referenced in functions, variable and type + // declarations. + typeDecls = append(typeDecls, rootCtx.anonTypeDecls(rootCtx.pkgCtx.anonTypes.Ordered())...) - d.DceDeps = collectDependencies(func() { - d.DeclCode = funcCtx.translateToplevelFunction(fun, funcInfo) - }) - funcDecls = append(funcDecls, &d) - } - if typesPkg.Name() == "main" { - if mainFunc == nil { - return nil, fmt.Errorf("missing main function") - } - id := funcCtx.newIdent("", types.NewSignature(nil, nil, nil, 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) - }), - }) - } + // Combine all decls in a single list in the order they must appear in the + // final program. + allDecls := append(append(append(importDecls, typeDecls...), varDecls...), funcDecls...) - // named types - var typeDecls []*Decl - for _, o := range funcCtx.pkgCtx.typeNames { - if o.IsAlias() { - continue + if minify { + for _, d := range allDecls { + *d = d.minify() } - typeName := funcCtx.objectName(o) - - d := Decl{ - Vars: []string{typeName}, - DceObjectFilter: o.Name(), - } - d.DceDeps = collectDependencies(func() { - d.DeclCode = funcCtx.CatchOutput(0, func() { - typeName := funcCtx.objectName(o) - lhs := typeName - if getVarLevel(o) == varPackage { - lhs += " = $pkg." + encodeIdent(o.Name()) - } - size := int64(0) - constructor := "null" - switch t := o.Type().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 := o.Type().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, "%s.%s", %t, "%s", %t, %s);`, lhs, size, typeKind(o.Type()), o.Pkg().Name(), o.Name(), o.Name() != "", o.Pkg().Path(), o.Exported(), constructor) - }) - d.MethodListCode = funcCtx.CatchOutput(0, func() { - named := o.Type().(*types.Named) - if _, ok := named.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.typeName(named), strings.Join(methods, ", ")) - } - if len(ptrMethods) > 0 { - funcCtx.Printf("%s.methods = [%s];", funcCtx.typeName(types.NewPointer(named)), strings.Join(ptrMethods, ", ")) - } - }) - switch t := o.Type().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.objectName(o), funcCtx.initArgs(t)) - }) - } - }) - typeDecls = append(typeDecls, &d) } - // anonymous types - for _, t := range funcCtx.pkgCtx.anonTypes.Ordered() { - 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 + if len(rootCtx.pkgCtx.errList) != 0 { + return nil, rootCtx.pkgCtx.errList } exportData := new(bytes.Buffer) @@ -627,8 +354,10 @@ func (fc *funcContext) initArgs(ty types.Type) string { } } -func (fc *funcContext) translateToplevelFunction(fun *ast.FuncDecl, info *analysis.FuncInfo) []byte { +func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + info := fc.pkgCtx.FuncDeclInfos[o] + sig := o.Type().(*types.Signature) var recv *ast.Ident if fun.Recv != nil && fun.Recv.List[0].Names != nil { diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 44671f309..13ca3909c 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -110,3 +110,9 @@ func IsGeneric(t types.Type) bool { panic(fmt.Errorf("%v has unexpected type %T", t, t)) } } + +// IsMethod returns true if the passed object is a method. +func IsMethod(o types.Object) bool { + f, ok := o.(*types.Func) + return ok && f.Type().(*types.Signature).Recv() != nil +} diff --git a/compiler/utils.go b/compiler/utils.go index 395bf06df..da8db71ab 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -22,6 +22,11 @@ import ( "github.com/gopherjs/gopherjs/compiler/typesutil" ) +// IsRoot returns true for the package-level context. +func (fc *funcContext) IsRoot() bool { + return fc.parent == nil +} + func (fc *funcContext) Write(b []byte) (int, error) { fc.writePos() fc.output = append(fc.output, b...) @@ -98,6 +103,43 @@ func (fc *funcContext) Delayed(f func()) { fc.delayedOutput = fc.CatchOutput(0, f) } +// CollectDCEDeps captures a list of Go objects (types, functions, etc.) +// the code translated inside f() depends on. The returned list of identifiers +// can be used in dead-code elimination. +// +// Note that calling CollectDCEDeps() inside another CollectDCEDeps() call is +// not allowed. +func (fc *funcContext) CollectDCEDeps(f func()) []string { + if fc.pkgCtx.dependencies != nil { + panic(bailout(fmt.Errorf("called funcContext.CollectDependencies() inside another funcContext.CollectDependencies() call"))) + } + + fc.pkgCtx.dependencies = make(map[types.Object]bool) + defer func() { fc.pkgCtx.dependencies = nil }() + + f() + + var deps []string + for o := range fc.pkgCtx.dependencies { + qualifiedName := o.Pkg().Path() + "." + o.Name() + if typesutil.IsMethod(o) { + qualifiedName += "~" + } + deps = append(deps, qualifiedName) + } + sort.Strings(deps) + return deps +} + +// DeclareDCEDep records that the code that is currently being transpiled +// depends on a given Go object. +func (fc *funcContext) DeclareDCEDep(o types.Object) { + if fc.pkgCtx.dependencies == nil { + return // Dependencies are not being collected. + } + fc.pkgCtx.dependencies[o] = true +} + // expandTupleArgs converts a function call which argument is a tuple returned // by another function into a set of individual call arguments corresponding to // tuple elements. @@ -334,12 +376,20 @@ func (fc *funcContext) newVariable(name string, level varLevel) string { return varName } +// newIdent declares a new Go variable with the given name and type and returns +// an *ast.Ident referring to that object. func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident { - ident := ast.NewIdent(name) - fc.setType(ident, t) obj := types.NewVar(0, fc.pkgCtx.Pkg, name, t) - fc.pkgCtx.Uses[ident] = obj fc.pkgCtx.objectNames[obj] = name + return fc.newIdentFor(obj) +} + +// newIdentFor creates a new *ast.Ident referring to the given Go object. +func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident { + ident := ast.NewIdent(obj.Name()) + ident.NamePos = obj.Pos() + fc.pkgCtx.Uses[ident] = obj + fc.setType(ident, obj.Type()) return ident } @@ -391,7 +441,7 @@ func getVarLevel(o types.Object) varLevel { // Repeated calls for the same object will return the same name. func (fc *funcContext) objectName(o types.Object) string { if getVarLevel(o) == varPackage { - fc.pkgCtx.dependencies[o] = true + fc.DeclareDCEDep(o) if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { return fc.pkgVar(o.Pkg()) + "." + o.Name() @@ -462,10 +512,29 @@ func (fc *funcContext) typeName(ty types.Type) string { anonType = types.NewTypeName(token.NoPos, fc.pkgCtx.Pkg, varName, ty) // fake types.TypeName anonTypes.Register(anonType, ty) } - fc.pkgCtx.dependencies[anonType] = true + fc.DeclareDCEDep(anonType) return anonType.Name() } +// importedPkgVar returns a package-level variable name for accessing an imported +// package. +// +// Allocates a new variable if this is the first call, or returns the existing +// one. The variable is based on the package name (implicitly derived from the +// `package` declaration in the imported package, or explicitly assigned by the +// import decl in the importing source file). +// +// Returns the allocated variable name. +func (fc *funcContext) importedPkgVar(pkg *types.Package) string { + if pkgVar, ok := fc.pkgCtx.pkgVars[pkg.Path()]; ok { + return pkgVar // Already registered. + } + + pkgVar := fc.newVariable(pkg.Name(), varPackage) + fc.pkgCtx.pkgVars[pkg.Path()] = pkgVar + return pkgVar +} + func (fc *funcContext) externalize(s string, t types.Type) string { if typesutil.IsJsObject(t) { return s From 74dc2eabee899d047bad0465ca5f62328af6e532 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 8 Jan 2023 19:41:31 +0000 Subject: [PATCH 18/83] Implement generic type instantiation. Similar to generic functions, we use a generic factory function to instantiate types on demand at runtime. Type instances are cached to avoid overhead of double instantiation and infinite recursion in case two generic type reference each other. The latter is also the reason why we add type instance to the cache before we fully initialized it. Note that this change doesn't yet support defining or calling methods on generic types. Updates https://github.com/gopherjs/gopherjs/issues/1013. --- compiler/compiler.go | 2 +- compiler/decls.go | 118 ++++++++++++++++++++++++++------ compiler/prelude/prelude.go | 3 +- compiler/prelude/prelude_min.go | 2 +- compiler/typesutil/typesutil.go | 10 +++ compiler/utils.go | 10 ++- tests/gorepo/run.go | 9 --- 7 files changed, 119 insertions(+), 35 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index ac8e9b26f..fea8b69cc 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -239,7 +239,7 @@ func WritePkgCode(pkg *Archive, dceSelection map[*Decl]struct{}, gls goLinknameS if _, err := w.Write(removeWhitespace([]byte(fmt.Sprintf("$packages[\"%s\"] = (function() {\n", pkg.ImportPath)), minify)); err != nil { return err } - vars := []string{"$pkg = {}", "$init"} + vars := []string{"$pkg = {}", "$typeInstances = new $Map()", "$init"} var filteredDecls []*Decl for _, d := range pkg.Declarations { if _, ok := dceSelection[d]; ok { diff --git a/compiler/decls.go b/compiler/decls.go index bdf96206b..a7626cb16 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -399,16 +399,21 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { DceObjectFilter: o.Name(), } + extraIndent := 0 // Additional indentation for the type initialization code. + instanceVar := typeName // JS variable the type instance is assigned to. + typeString := fc.typeString(o) // Type string for reflect. + + typeParams := typesutil.TypeParams(o.Type()) + if typeParams != nil { + fc.genericCtx = &genericCtx{} + defer func() { fc.genericCtx = nil }() + extraIndent++ // For generic factory function. + instanceVar = fc.newLocalVariable("instance") // Avoid conflict with factory function name. + } + d.DceDeps = fc.CollectDCEDeps(func() { // Code that declares a JS type (i.e. prototype) for each Go type. - d.DeclCode = fc.CatchOutput(0, func() { - lhs := typeName - if getVarLevel(o) == varPackage { - // Package-level types are also accessible as properties on the package - // object. - lhs += " = $pkg." + encodeIdent(o.Name()) - } - + d.DeclCode = fc.CatchOutput(extraIndent, func() { size := int64(0) constructor := "null" @@ -425,12 +430,12 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { constructor = "$arrayPtrCtor()" } } - fc.Printf(`%s = $newType(%d, %s, "%s.%s", %t, "%s", %t, %s);`, - lhs, size, typeKind(o.Type()), o.Pkg().Name(), o.Name(), o.Name() != "", o.Pkg().Path(), o.Exported(), constructor) + fc.Printf(`%s = $newType(%d, %s, %s, %t, "%s", %t, %s);`, + instanceVar, size, typeKind(o.Type()), typeString, o.Name() != "", o.Pkg().Path(), o.Exported(), constructor) }) // Reflection metadata about methods the type has. - d.MethodListCode = fc.CatchOutput(0, func() { + d.MethodListCode = fc.CatchOutput(extraIndent, func() { named := o.Type().(*types.Named) if _, ok := named.Underlying().(*types.Interface); ok { return @@ -446,10 +451,10 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { } } if len(methods) > 0 { - fc.Printf("%s.methods = [%s];", fc.typeName(named), strings.Join(methods, ", ")) + fc.Printf("%s.methods = [%s];", instanceVar, strings.Join(methods, ", ")) } if len(ptrMethods) > 0 { - fc.Printf("%s.methods = [%s];", fc.typeName(types.NewPointer(named)), strings.Join(ptrMethods, ", ")) + fc.Printf("$ptrType(%s).methods = [%s];", instanceVar, strings.Join(ptrMethods, ", ")) } }) @@ -457,14 +462,83 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { // initialize themselves. switch t := o.Type().Underlying().(type) { case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct: - d.TypeInitCode = fc.CatchOutput(0, func() { - fc.Printf("%s.init(%s);", fc.objectName(o), fc.initArgs(t)) + d.TypeInitCode = fc.CatchOutput(extraIndent, func() { + fc.Printf("%s.init(%s);", instanceVar, fc.initArgs(t)) }) } }) + + if typeParams != nil { + // Generic types are instantiated at runtime by calling an intermediate + // generic factory function. This function combines together all code + // from a regular type Decl (e.g. DeclCode, TypeInitCode, MethodListCode) + // and is defined at DeclCode time. + + typeParamNames := []string{} + for i := 0; i < typeParams.Len(); i++ { + typeParamNames = append(typeParamNames, fc.typeName(typeParams.At(i))) + } + + d.DeclCode = fc.CatchOutput(0, func() { + // Begin generic factory function. + fc.Printf("%s = function(%s) {", typeName, strings.Join(typeParamNames, ", ")) + + fc.Indented(func() { + // If this instance has been instantiated already, reuse the object. + fc.Printf("var %s = $typeInstances.get(%s);", instanceVar, typeString) + fc.Printf("if (%[1]s) { return %[1]s; }", instanceVar) + + // Construct anonymous types which depend on type parameters. + for _, t := range fc.genericCtx.anonTypes.Ordered() { + fc.Printf("var %s = $%sType(%s);", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type())) + } + + // Construct type instance. + fmt.Fprint(fc, string(d.DeclCode)) + fc.Printf("$typeInstances.set(%s, %s)", typeString, instanceVar) + fmt.Fprint(fc, string(d.TypeInitCode)) + fmt.Fprint(fc, string(d.MethodListCode)) + + fc.Printf("return %s;", instanceVar) + }) + + // End generic factory function. + fc.Printf("}") + }) + + // Clean out code that has been absorbed by the generic factory function. + d.TypeInitCode = nil + d.MethodListCode = nil + } + + if getVarLevel(o) == varPackage { + // Package-level types are also accessible as properties on the package + // object. + exported := fc.CatchOutput(0, func() { + fc.Printf("$pkg.%s = %s;", encodeIdent(o.Name()), typeName) + }) + d.DeclCode = append(d.DeclCode, []byte(exported)...) + } return d } +// typeString returns a string with a JavaScript string expression that +// constructs the type string for the provided object. For a generic type this +// will be a template literal that substitutes type parameters at runtime. +func (fc *funcContext) typeString(o types.Object) string { + typeParams := typesutil.TypeParams(o.Type()) + if typeParams == nil { + return fmt.Sprintf(`"%s.%s"`, o.Pkg().Name(), o.Name()) + } + + args := []string{} + for i := 0; i < typeParams.Len(); i++ { + args = append(args, fmt.Sprintf("${%s.string}", fc.typeName(typeParams.At(i)))) + } + + return fmt.Sprintf("`%s.%s[%s]`", o.Pkg().Name(), o.Name(), strings.Join(args, ",")) +} + // structConstructor returns JS constructor function for a struct type. func (fc *funcContext) structConstructor(t *types.Struct) string { constructor := &strings.Builder{} @@ -475,21 +549,21 @@ func (fc *funcContext) structConstructor(t *types.Struct) string { } fmt.Fprintf(constructor, "function(%s) {\n", strings.Join(ctrArgs, ", ")) - fmt.Fprintf(constructor, "\t\tthis.$val = this;\n") + fmt.Fprintf(constructor, "%sthis.$val = this;\n", fc.Indentation(1)) // If no arguments were passed, zero-initialize all fields. - fmt.Fprintf(constructor, "\t\tif (arguments.length === 0) {\n") + fmt.Fprintf(constructor, "%sif (arguments.length === 0) {\n", fc.Indentation(1)) for i := 0; i < t.NumFields(); i++ { - fmt.Fprintf(constructor, "\t\t\tthis.%s = %s;\n", fieldName(t, i), fc.translateExpr(fc.zeroValue(t.Field(i).Type())).String()) + fmt.Fprintf(constructor, "%sthis.%s = %s;\n", fc.Indentation(2), fieldName(t, i), fc.translateExpr(fc.zeroValue(t.Field(i).Type())).String()) } - fmt.Fprintf(constructor, "\t\t\treturn;\n") - fmt.Fprintf(constructor, "\t\t}\n") + fmt.Fprintf(constructor, "%sreturn;\n", fc.Indentation(2)) + fmt.Fprintf(constructor, "%s}\n", fc.Indentation(1)) // Otherwise initialize fields with the provided values. for i := 0; i < t.NumFields(); i++ { - fmt.Fprintf(constructor, "\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i)) + fmt.Fprintf(constructor, "%sthis.%[2]s = %[2]s_;\n", fc.Indentation(1), fieldName(t, i)) } - fmt.Fprintf(constructor, "\t}") + fmt.Fprintf(constructor, "%s}", fc.Indentation(0)) return constructor.String() } diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index ad5ceeb28..9b3b3e12d 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -80,7 +80,8 @@ if (($global.process !== undefined) && $global.require) { // Failed to require util module, keep using console.log(). } } -var $println = console.log +var $println = console.log; +var $Map = $global.Map; var $initAllLinknames = function() { var names = $keys($packages); diff --git a/compiler/prelude/prelude_min.go b/compiler/prelude/prelude_min.go index 38f866302..3ba9c2cb3 100644 --- a/compiler/prelude/prelude_min.go +++ b/compiler/prelude/prelude_min.go @@ -3,4 +3,4 @@ package prelude // Minified is an uglifyjs-minified version of Prelude. -const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,p=0;f+=(p+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var d=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(p&=65535))>>>0;return new e.constructor(d,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).wrap=(e=>e),c.keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).wrap=(e=>e),c.keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).wrap=(e=>e),c.init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var p=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(p))}var d={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?d:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return d}},k=h(e,n);if(k!==d)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" +const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$Map=$global.Map,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,p=0;f+=(p+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var d=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(p&=65535))>>>0;return new e.constructor(d,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).wrap=(e=>e),c.keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).wrap=(e=>e),c.keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).wrap=(e=>e),c.init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var p=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(p))}var d={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?d:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return d}},k=h(e,n);if(k!==d)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 13ca3909c..bce3b5340 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -116,3 +116,13 @@ func IsMethod(o types.Object) bool { f, ok := o.(*types.Func) return ok && f.Type().(*types.Signature).Recv() != nil } + +// TypeParams returns a list of type parameters for a parameterized type, or +// nil otherwise. +func TypeParams(t types.Type) *types.TypeParamList { + named, ok := t.(*types.Named) + if !ok { + return nil + } + return named.TypeParams() +} diff --git a/compiler/utils.go b/compiler/utils.go index da8db71ab..df66efbb7 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -486,7 +486,15 @@ func (fc *funcContext) typeName(ty types.Type) string { if t.Obj().Name() == "error" { return "$error" } - return fc.objectName(t.Obj()) + if t.TypeArgs() == nil { + return fc.objectName(t.Obj()) + } + // Generic types require generic factory call. + args := []string{} + for i := 0; i < t.TypeArgs().Len(); i++ { + args = append(args, fc.typeName(t.TypeArgs().At(i))) + } + return fmt.Sprintf("(%s(%s))", fc.objectName(t.Obj()), strings.Join(args, ",")) case *types.TypeParam: return fc.objectName(t.Obj()) case *types.Interface: diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 1d3eea782..1195fbbef 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -165,7 +165,6 @@ var knownFails = map[string]failReason{ "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/boundmethod.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/chans.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/combine.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/cons.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/dictionaryCapture-noinline.go": {category: generics, desc: "attempts to pass -gcflags=\"-G=3\" flag, incorrectly parsed by run.go"}, "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -178,11 +177,9 @@ var knownFails = map[string]failReason{ "typeparam/graph.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/interfacearg.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, "typeparam/issue44688.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue45817.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue46591.go": {category: generics, desc: "undiagnosed: len() returns an invalid value when parameterized types are involved"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47272.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue47713.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -202,8 +199,6 @@ var knownFails = map[string]failReason{ "typeparam/issue48225.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48253.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue48317.go": {category: generics, desc: "missing support for parameterized type definition"}, - "typeparam/issue48318.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/issue48344.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue48598.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -213,7 +208,6 @@ var knownFails = map[string]failReason{ "typeparam/issue48645b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48838.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue49547.go": {category: generics, desc: "incorrect type strings for parameterized types"}, "typeparam/issue49659b.go": {category: generics, desc: "incorrect type strings for parameterized types"}, "typeparam/issue50002.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue50109.go": {category: generics, desc: "missing support for parameterized type instantiation"}, @@ -224,7 +218,6 @@ var knownFails = map[string]failReason{ "typeparam/issue50642.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50690a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50690b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue50690c.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, @@ -240,12 +233,10 @@ var knownFails = map[string]failReason{ "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, "typeparam/ordered.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/orderedmap.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/pair.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/sets.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/stringable.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/struct.go": {category: generics, desc: "missing support for parameterized type definition"}, "typeparam/subdict.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, From 0765afe16265dfecf640eae23f913b951fdb0f58 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Mon, 16 Jan 2023 20:38:21 +0000 Subject: [PATCH 19/83] Support methods on generic types. Generic type methods are compiled largely in the same way as standalone generic functions, into a generic function factory. However, unlike a standalone function, the method factory is registered in the receiver type factory and is invoked when the type is being instantiated, with type arguments passed to the generic type. The instantiated method is then attached to the type instance. --- compiler/analysis/info.go | 3 ++ compiler/astutil/astutil.go | 16 +++++- compiler/decls.go | 10 ++-- compiler/package.go | 91 ++++++++++++++++++++++----------- compiler/prelude/types.go | 6 +++ compiler/typesutil/typesutil.go | 7 ++- compiler/utils.go | 11 ++++ go.mod | 1 + go.sum | 2 + tests/gorepo/run.go | 16 ------ 10 files changed, 109 insertions(+), 54 deletions(-) diff --git a/compiler/analysis/info.go b/compiler/analysis/info.go index d73c2fc05..aa302edb4 100644 --- a/compiler/analysis/info.go +++ b/compiler/analysis/info.go @@ -9,6 +9,7 @@ import ( "github.com/gopherjs/gopherjs/compiler/astutil" "github.com/gopherjs/gopherjs/compiler/typesutil" + "golang.org/x/exp/typeparams" ) type continueStmt struct { @@ -367,6 +368,8 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor { func (fi *FuncInfo) callToNamedFunc(callee types.Object) { switch o := callee.(type) { case *types.Func: + // For generic methods we want the generic version of the function. + o = typeparams.OriginMethod(o) // TODO(nevkontakte): Can be replaced with o.Origin() in Go 1.19. if recv := o.Type().(*types.Signature).Recv(); recv != nil { if _, ok := recv.Type().Underlying().(*types.Interface); ok { // Conservatively assume that an interface implementation may be blocking. diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index b9c4b54c8..a8a346d82 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -43,6 +43,13 @@ func IsTypeExpr(expr ast.Expr, info *types.Info) bool { case *ast.SelectorExpr: _, ok := info.Uses[e.Sel].(*types.TypeName) return ok + case *ast.IndexExpr: + ident, ok := e.X.(*ast.Ident) + if !ok { + return false + } + _, ok = info.Uses[ident].(*types.TypeName) + return ok case *ast.ParenExpr: return IsTypeExpr(e.X, info) default: @@ -66,8 +73,13 @@ func FuncKey(d *ast.FuncDecl) string { return d.Name.Name } recv := d.Recv.List[0].Type - if star, ok := recv.(*ast.StarExpr); ok { - recv = star.X + switch r := recv.(type) { + case *ast.StarExpr: + recv = r.X + case *ast.IndexExpr: + recv = r.X + case *ast.IndexListExpr: + recv = r.X } return recv.(*ast.Ident).Name + "." + d.Name.Name } diff --git a/compiler/decls.go b/compiler/decls.go index a7626cb16..2a49af039 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -474,10 +474,7 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { // from a regular type Decl (e.g. DeclCode, TypeInitCode, MethodListCode) // and is defined at DeclCode time. - typeParamNames := []string{} - for i := 0; i < typeParams.Len(); i++ { - typeParamNames = append(typeParamNames, fc.typeName(typeParams.At(i))) - } + typeParamNames := fc.typeParamVars(typeParams) d.DeclCode = fc.CatchOutput(0, func() { // Begin generic factory function. @@ -495,7 +492,9 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { // Construct type instance. fmt.Fprint(fc, string(d.DeclCode)) - fc.Printf("$typeInstances.set(%s, %s)", typeString, instanceVar) + fc.Printf("$typeInstances.set(%s, %s);", typeString, instanceVar) + fc.Printf("$instantiateMethods(%s, %s, %s)", instanceVar, fmt.Sprintf("%s.methods", typeName), strings.Join(typeParamNames, ", ")) + fc.Printf("$instantiateMethods(%s, %s, %s)", fmt.Sprintf("$ptrType(%s)", instanceVar), fmt.Sprintf("%s.ptrMethods", typeName), strings.Join(typeParamNames, ", ")) fmt.Fprint(fc, string(d.TypeInitCode)) fmt.Fprint(fc, string(d.MethodListCode)) @@ -504,6 +503,7 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { // End generic factory function. fc.Printf("}") + fc.Printf("%[1]s.methods = {}; %[1]s.ptrMethods = {};", typeName) }) // Clean out code that has been absorbed by the generic factory function. diff --git a/compiler/package.go b/compiler/package.go index 69c3fd54f..f61a0286a 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -365,14 +365,16 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { } var joinedParams string - primaryFunction := func(funcRef string) []byte { + // primaryFunction generates a JS function equivalent of the current Go function + // and assigns it to the JS variable defined by lvalue. + primaryFunction := func(lvalue 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())) + return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName())) } - params, fun := translateFunction(fun.Type, recv, fun.Body, fc, sig, info, funcRef) + params, fun := translateFunction(fun.Type, recv, fun.Body, fc, sig, info, lvalue) joinedParams = strings.Join(params, ", ") - return []byte(fmt.Sprintf("\t%s = %s;\n", funcRef, fun)) + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) } code := bytes.NewBuffer(nil) @@ -398,29 +400,55 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { funName += "$" } + // Objects the method should be assigned to. + prototypeVar := fmt.Sprintf("%s.prototype.%s", typeName, funName) + ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", typeName, funName) + isGeneric := signatureTypes{Sig: sig}.IsGeneric() + if isGeneric { + // Generic method factories are assigned to the generic type factory + // properties, to be invoked at type construction time rather than method + // call time. + prototypeVar = fmt.Sprintf("%s.methods.%s", typeName, funName) + ptrPrototypeVar = fmt.Sprintf("%s.ptrMethods.%s", typeName, funName) + } + + // proxyFunction generates a JS function that forwards the call to the actual + // method implementation for the alternate receiver (e.g. pointer vs + // non-pointer). + proxyFunction := func(lvalue, receiver string) []byte { + fun := fmt.Sprintf("function(%s) { return %s.%s(%s); }", joinedParams, receiver, funName, joinedParams) + if isGeneric { + // For a generic function, we wrap the proxy function in a trivial generic + // factory function for consistency. It is the same for any possible type + // arguments, so we simply ignore them. + fun = fmt.Sprintf("function() { return %s; }", fun) + } + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) + } + if _, isStruct := namedRecvType.Underlying().(*types.Struct); isStruct { - code.Write(primaryFunction(typeName + ".ptr.prototype." + funName)) - fmt.Fprintf(code, "\t%s.prototype.%s = function(%s) { return this.$val.%s(%s); };\n", typeName, funName, joinedParams, funName, joinedParams) + code.Write(primaryFunction(ptrPrototypeVar)) + code.Write(proxyFunction(prototypeVar, "this.$val")) return code.Bytes() } if isPointer { if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray { - code.Write(primaryFunction(typeName + ".prototype." + funName)) - fmt.Fprintf(code, "\t$ptrType(%s).prototype.%s = function(%s) { return (new %s(this.$get())).%s(%s); };\n", typeName, funName, joinedParams, typeName, funName, joinedParams) + code.Write(primaryFunction(prototypeVar)) + code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", typeName))) return code.Bytes() } - return primaryFunction(fmt.Sprintf("$ptrType(%s).prototype.%s", typeName, funName)) + return primaryFunction(ptrPrototypeVar) } - value := "this.$get()" + recvExpr := "this.$get()" if typesutil.IsGeneric(recvType) { - value = fmt.Sprintf("%s.wrap(%s)", typeName, value) + recvExpr = fmt.Sprintf("%s.wrap(%s)", typeName, recvExpr) } else if isWrapped(recvType) { - value = fmt.Sprintf("new %s(%s)", typeName, value) + recvExpr = fmt.Sprintf("new %s(%s)", typeName, recvExpr) } - code.Write(primaryFunction(typeName + ".prototype." + funName)) - fmt.Fprintf(code, "\t$ptrType(%s).prototype.%s = function(%s) { return %s.%s(%s); };\n", typeName, funName, joinedParams, value, funName, joinedParams) + code.Write(primaryFunction(prototypeVar)) + code.Write(proxyFunction(ptrPrototypeVar, recvExpr)) return code.Bytes() } @@ -444,10 +472,23 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, for k, v := range outerContext.allVars { c.allVars[k] = v } + + functionName := "" // Function object name, i.e. identifier after the "function" keyword. + if funcRef == "" { + // Assign a name for the anonymous function. + funcRef = "$b" + functionName = " $b" + } + + // For regular functions instance is directly in the function variable name. + instanceVar := funcRef if c.sigTypes.IsGeneric() { c.genericCtx = &genericCtx{} - funcRef = c.newVariable(funcRef, varGenericFactory) + // For generic function, funcRef refers to the generic factory function, + // allocate a separate variable for a function instance. + instanceVar = c.newVariable("instance", varGenericFactory) } + prevEV := c.pkgCtx.escapingVars var params []string @@ -498,7 +539,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, sort.Strings(c.localVars) - var prefix, suffix, functionName string + var prefix, suffix string if len(c.Flattened) != 0 { c.localVars = append(c.localVars, "$s") @@ -516,11 +557,6 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, 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. @@ -530,7 +566,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, // 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, ", ")) + saveContext := fmt.Sprintf("var $f = {$blk: %s, $c: true, $r, %s};", instanceVar, strings.Join(c.localVars, ", ")) suffix = " " + saveContext + "return $f;" + suffix } else if len(c.localVars) > 0 { @@ -586,11 +622,8 @@ 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. - 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)) - } + typeParams := c.typeParamVars(c.sigTypes.Sig.TypeParams()) + typeParams = append(typeParams, c.typeParamVars(c.sigTypes.Sig.RecvTypeParams())...) // anonymous types typesInit := strings.Builder{} @@ -601,10 +634,10 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, code := &strings.Builder{} fmt.Fprintf(code, "function%s(%s){\n", functionName, strings.Join(typeParams, ", ")) fmt.Fprintf(code, "%s", typesInit.String()) - fmt.Fprintf(code, "%sconst %s = function(%s) {\n", c.Indentation(1), funcRef, strings.Join(params, ", ")) + fmt.Fprintf(code, "%sconst %s = function(%s) {\n", c.Indentation(1), instanceVar, strings.Join(params, ", ")) fmt.Fprintf(code, "%s", bodyOutput) fmt.Fprintf(code, "%s};\n", c.Indentation(1)) - fmt.Fprintf(code, "%sreturn %s;\n", c.Indentation(1), funcRef) + fmt.Fprintf(code, "%sreturn %s;\n", c.Indentation(1), instanceVar) fmt.Fprintf(code, "%s}", c.Indentation(0)) return params, code.String() } diff --git a/compiler/prelude/types.go b/compiler/prelude/types.go index 611aa2c46..bad9526f0 100644 --- a/compiler/prelude/types.go +++ b/compiler/prelude/types.go @@ -480,6 +480,12 @@ var $methodSet = function(typ) { return typ.methodSetCache; }; +var $instantiateMethods = function(typeInstance, methodFactories, ...typeArgs) { + for (let [method, factory] of Object.entries(methodFactories)) { + typeInstance.prototype[method] = factory(...typeArgs); + } +}; + var $Bool = $newType( 1, $kindBool, "bool", true, "", false, null); var $Int = $newType( 4, $kindInt, "int", true, "", false, null); var $Int8 = $newType( 1, $kindInt8, "int8", true, "", false, null); diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index bce3b5340..4ab12f4c1 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -78,8 +78,11 @@ func IsGeneric(t types.Type) bool { 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. + for i := 0; i < t.TypeArgs().Len(); i++ { + if IsGeneric(t.TypeArgs().At(i)) { + return true + } + } return false case *types.Pointer: return IsGeneric(t.Elem()) diff --git a/compiler/utils.go b/compiler/utils.go index df66efbb7..c5a317c2b 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -393,6 +393,17 @@ func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident { return ident } +// typeParamVars returns a list of JS variable names representing type given +// parameters. +func (fc *funcContext) typeParamVars(params *types.TypeParamList) []string { + vars := []string{} + for i := 0; i < params.Len(); i++ { + vars = append(vars, fc.typeName(params.At(i))) + } + + return vars +} + func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t} return e diff --git a/go.mod b/go.mod index 12653d82d..fd09c0c12 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/visualfc/goembed v0.3.3 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 + golang.org/x/exp/typeparams v0.0.0-20230127193734-31bee513bff7 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220412211240-33da011f77ad golang.org/x/tools v0.1.10 diff --git a/go.sum b/go.sum index 01aee12b8..4c7715fae 100644 --- a/go.sum +++ b/go.sum @@ -274,6 +274,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp/typeparams v0.0.0-20230127193734-31bee513bff7 h1:b1tzrw2iCf2wUlbWCLmOD3SdX6hiDxxfanz/zrnIEOs= +golang.org/x/exp/typeparams v0.0.0-20230127193734-31bee513bff7/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 1195fbbef..8a99d353c 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -179,43 +179,30 @@ var knownFails = map[string]failReason{ "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, "typeparam/issue44688.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue45817.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47272.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47713.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue47716.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47740.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47740b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47775b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue47877.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue47901.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47925.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue47925b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue47925c.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue47925d.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue48042.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48047.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48049.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48225.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48253.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue48344.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue48598.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48602.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48617.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48645a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48645b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48838.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue49659b.go": {category: generics, desc: "incorrect type strings for parameterized types"}, "typeparam/issue50002.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue50109.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50109b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50264.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50419.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue50642.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50690a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50690b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, @@ -227,7 +214,6 @@ var knownFails = map[string]failReason{ "typeparam/issue53477.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/list.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/list2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/lockable.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/metrics.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, @@ -236,12 +222,10 @@ var knownFails = map[string]failReason{ "typeparam/sets.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/stringable.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/subdict.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, - "typeparam/value.go": {category: generics, desc: "missing support for parameterized type instantiation"}, } type failCategory uint8 From b067db8a4039373fd0f8dff91bb329282c98c400 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 27 Jan 2023 21:35:03 +0000 Subject: [PATCH 20/83] Update minified prelude. --- compiler/prelude/prelude_min.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/prelude/prelude_min.go b/compiler/prelude/prelude_min.go index 3ba9c2cb3..f37851fdd 100644 --- a/compiler/prelude/prelude_min.go +++ b/compiler/prelude/prelude_min.go @@ -3,4 +3,4 @@ package prelude // Minified is an uglifyjs-minified version of Prelude. -const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$Map=$global.Map,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,p=0;f+=(p+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var d=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(p&=65535))>>>0;return new e.constructor(d,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).wrap=(e=>e),c.keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).wrap=(e=>e),c.keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).wrap=(e=>e),c.init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var p=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(p))}var d={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?d:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return d}},k=h(e,n);if(k!==d)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" +const Minified = "Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if(\"undefined\"!=typeof window?$global=window:\"undefined\"!=typeof self?$global=self:\"undefined\"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error(\"no global object found\");if(\"undefined\"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require(\"fs\");\"object\"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf=\"\",decoder=new TextDecoder(\"utf-8\");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf(\"\\n\");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError(\"invalid memory address or nil pointer dereference\")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require(\"util\");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$Map=$global.Map,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError(\"slice bounds out of range\"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError(\"slice bounds out of range\"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?\"NaN$\"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,p=0;f+=(p+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var d=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(p&=65535))>>>0;return new e.constructor(d,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError(\"integer divide by zero\");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return\"nil\";var n=e.constructor;return n.string+\"$\"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return\"$\"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$high+\"$\"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).wrap=(e=>e),c.keyFor=function(e){return e.$real+\"$\"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,\"\",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}),\"$\")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,\"nilCheck\",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).wrap=(e=>e),c.keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).wrap=(e=>e),c.keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).wrap=(e=>e),c.init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.wrap=(e=>new c(e)),c.ptr=$newType(4,$kindPtr,\"*\"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\\\/g,\"\\\\\\\\\").replace(/\\$/g,\"\\\\$\")}).join(\"$\")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$instantiateMethods=function(e,n,...r){for(let[t,i]of Object.entries(n))e.prototype[t]=i(...r)},$Bool=$newType(1,$kindBool,\"bool\",!0,\"\",!1,null),$Int=$newType(4,$kindInt,\"int\",!0,\"\",!1,null),$Int8=$newType(1,$kindInt8,\"int8\",!0,\"\",!1,null),$Int16=$newType(2,$kindInt16,\"int16\",!0,\"\",!1,null),$Int32=$newType(4,$kindInt32,\"int32\",!0,\"\",!1,null),$Int64=$newType(8,$kindInt64,\"int64\",!0,\"\",!1,null),$Uint=$newType(4,$kindUint,\"uint\",!0,\"\",!1,null),$Uint8=$newType(1,$kindUint8,\"uint8\",!0,\"\",!1,null),$Uint16=$newType(2,$kindUint16,\"uint16\",!0,\"\",!1,null),$Uint32=$newType(4,$kindUint32,\"uint32\",!0,\"\",!1,null),$Uint64=$newType(8,$kindUint64,\"uint64\",!0,\"\",!1,null),$Uintptr=$newType(4,$kindUintptr,\"uintptr\",!0,\"\",!1,null),$Float32=$newType(4,$kindFloat32,\"float32\",!0,\"\",!1,null),$Float64=$newType(8,$kindFloat64,\"float64\",!0,\"\",!1,null),$Complex64=$newType(8,$kindComplex64,\"complex64\",!0,\"\",!1,null),$Complex128=$newType(16,$kindComplex128,\"complex128\",!0,\"\",!1,null),$String=$newType(8,$kindString,\"string\",!0,\"\",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,\"unsafe.Pointer\",!0,\"unsafe\",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+\"$\"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(e.size*n,$kindArray,\"[\"+n+\"]\"+e.string,!1,\"\",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?\"<-\":\"\")+\"chan\"+(n?\"<- \":\" \");n||r||\"<\"!=e.string[0]?t+=e.string:t+=\"(\"+e.string+\")\";var i=n?\"SendChan\":r?\"RecvChan\":\"Chan\",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,\"\",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError(\"makechan: size out of range\"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(\",\")+\"$\"+$mapArray(n,function(e){return e.id}).join(\",\")+\"$\"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]=\"...\"+a[a.length-1].substr(2));var o=\"func(\"+a.join(\", \")+\")\";1===n.length?o+=\" \"+n[0].string:n.length>1&&(o+=\" (\"+$mapArray(n,function(e){return e.string}).join(\", \")+\")\"),i=$newType(4,$kindFunc,o,!1,\"\",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+\",\"+e.name+\",\"+e.typ.id}).join(\"$\"),r=$interfaceTypes[n];if(void 0===r){var t=\"interface {}\";0!==e.length&&(t=\"interface { \"+$mapArray(e,function(e){return(\"\"!==e.pkg?e.pkg+\".\":\"\")+e.name+e.typ.string.substr(4)}).join(\"; \")+\" }\"),r=$newType(8,$kindInterface,t,!1,\"\",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,\"error\",!0,\"\",!1,null);$error.init([{prop:\"Error\",name:\"Error\",pkg:\"\",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+\"$\"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,\"map[\"+e.string+\"]\"+n.string,!1,\"\",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r=new Map,t=0;t2147483647)&&$throwRuntimeError(\"makeslice: len out of range\"),(r<0||r2147483647)&&$throwRuntimeError(\"makeslice: cap out of range\");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError(\"cannot block in JavaScript callback, fix by wrapping code in goroutine\"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError(\"send on closed channel\");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var p=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(p))}var d={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?d:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return d}},k=h(e,n);if(k!==d)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError(\"got array with wrong size from JavaScript native\"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0};\n" From 8d5d32eea9d58835009e4d57293c4a01c9f86027 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 27 Jan 2023 21:24:55 +0000 Subject: [PATCH 21/83] Re-triage test failures that previously depended in generic method support. Evidently the current implementation has a few bugs we will need to iron out... --- tests/gorepo/run.go | 68 ++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 8a99d353c..a539ec373 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -161,68 +161,54 @@ var knownFails = map[string]failReason{ // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is // fixed. "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/absdiff2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/boundmethod.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/chans.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/cons.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/boundmethod.go": {category: generics, desc: "missing support for type conversion of a parameterized type"}, + "typeparam/chans.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, + "typeparam/cons.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/dictionaryCapture-noinline.go": {category: generics, desc: "attempts to pass -gcflags=\"-G=3\" flag, incorrectly parsed by run.go"}, - "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/eface.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/genembed.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/genembed2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/graph.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/genembed.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/genembed2.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/graph.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, - "typeparam/issue44688.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue44688.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/issue47272.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47716.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47877.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47901.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue47925b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/issue47925c.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/issue47925d.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/issue48042.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48049.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, + "typeparam/issue48042.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/issue48049.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue48344.go": {category: generics, desc: "missing support for parameterized type instantiation"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/issue48598.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48602.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48617.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48645b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue48838.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue48602.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/issue48617.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/issue48645b.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/issue48838.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue50002.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/issue50109.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue50109b.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue50109.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50419.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue50690a.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue50690b.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue50690b.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, - "typeparam/issue51303.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, - "typeparam/issue52026.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/issue53477.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/list.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/list2.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/issue52026.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/list2.go": {category: generics, desc: "bug: infinite recursion during type initialization"}, "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/metrics.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/metrics.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, - "typeparam/ordered.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/orderedmap.go": {category: generics, desc: "missing support for parameterized type instantiation"}, - "typeparam/sets.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/ordered.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, + "typeparam/orderedmap.go": {category: generics, desc: "bug: infinite recursion during type initialization"}, + "typeparam/sets.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/subdict.go": {category: generics, desc: "missing support for parameterized type instantiation"}, + "typeparam/subdict.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, From 16f5662a9f5d16894ef6588f995344a11d4e17e9 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 8 Feb 2023 20:47:22 +0000 Subject: [PATCH 22/83] Replace type-switch with a sequence of ifs. Turns out, for a method like `func (v *value[T]) get() T {...}` first `*ast.StarExpr` is unwrapped and then `*ast.IndexExpr`. Using a type-switch allows only one of them to be unwrapped. --- compiler/astutil/astutil.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index a8a346d82..81e9caa1c 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -72,14 +72,16 @@ func FuncKey(d *ast.FuncDecl) string { if d.Recv == nil || len(d.Recv.List) == 0 { return d.Name.Name } + // Each if-statement progressively unwraps receiver type expression. recv := d.Recv.List[0].Type - switch r := recv.(type) { - case *ast.StarExpr: - recv = r.X - case *ast.IndexExpr: - recv = r.X - case *ast.IndexListExpr: - recv = r.X + if star, ok := recv.(*ast.StarExpr); ok { + recv = star.X + } + if index, ok := recv.(*ast.IndexExpr); ok { + recv = index.X + } + if index, ok := recv.(*ast.IndexListExpr); ok { + recv = index.X } return recv.(*ast.Ident).Name + "." + d.Name.Name } From 2e9a5ce26e5938a33e449d144ed6751a296373c6 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 19 Feb 2023 17:34:33 +0000 Subject: [PATCH 23/83] Move function translation code into a separate file. The logic in that function is getting very complex, and generics will add even more complexity. This commit begins breaking it apart for better readability. --- compiler/expressions.go | 2 +- compiler/functions.go | 359 ++++++++++++++++++++++++++++++++++++++++ compiler/package.go | 327 +++--------------------------------- compiler/utils.go | 17 ++ 4 files changed, 398 insertions(+), 307 deletions(-) create mode 100644 compiler/functions.go diff --git a/compiler/expressions.go b/compiler/expressions.go index b589ff977..ce4c167be 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -177,7 +177,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } case *ast.FuncLit: - _, fun := translateFunction(e.Type, nil, e.Body, fc, exprType.(*types.Signature), fc.pkgCtx.FuncLitInfos[e], "") + fun := fc.nestedFunctionContext(fc.pkgCtx.FuncLitInfos[e], exprType.(*types.Signature)).translateFunctionBody(e.Type, nil, e.Body, "") if len(fc.pkgCtx.escapingVars) != 0 { names := make([]string, 0, len(fc.pkgCtx.escapingVars)) for obj := range fc.pkgCtx.escapingVars { diff --git a/compiler/functions.go b/compiler/functions.go new file mode 100644 index 000000000..95e5105ad --- /dev/null +++ b/compiler/functions.go @@ -0,0 +1,359 @@ +package compiler + +import ( + "bytes" + "fmt" + "go/ast" + "go/types" + "sort" + "strings" + + "github.com/gopherjs/gopherjs/compiler/analysis" + "github.com/gopherjs/gopherjs/compiler/astutil" + "github.com/gopherjs/gopherjs/compiler/typesutil" +) + +// functions.go contains logic responsible for translating top-level functions +// and function literals. + +// newFunctionContext creates a new nested context for a function corresponding +// to the provided info. +func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature) *funcContext { + if info == nil { + panic(fmt.Errorf("missing *analysis.FuncInfo")) + } + if sig == nil { + panic(fmt.Errorf("missing *types.Signature")) + } + + c := &funcContext{ + FuncInfo: info, + pkgCtx: fc.pkgCtx, + genericCtx: fc.genericCtx, + parent: fc, + sigTypes: &signatureTypes{Sig: sig}, + allVars: make(map[string]int, len(fc.allVars)), + localVars: []string{}, + flowDatas: map[*types.Label]*flowData{nil: {}}, + caseCounter: 1, + labelCases: make(map[*types.Label]int), + } + // Register all variables from the parent context to avoid shadowing. + for k, v := range fc.allVars { + c.allVars[k] = v + } + + return c +} + +// translateTopLevelFunction translates a top-level function declaration +// (standalone function or method) into a corresponding JS function. +// +// Returns a string with a JavaScript statements that define the function or +// method. For generic functions it returns a generic factory function, which +// instantiates the actual function at runtime given type parameters. For +// methods it returns declarations for both value- and pointer-receiver (if +// appropriate). +func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { + if fun.Recv == nil { + return fc.translateStandaloneFunction(fun) + } + + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + info := fc.pkgCtx.FuncDeclInfos[o] + + sig := o.Type().(*types.Signature) + var recv *ast.Ident + if fun.Recv.List[0].Names != nil { + recv = fun.Recv.List[0].Names[0] + } + nestedFC := fc.nestedFunctionContext(info, sig) + + // primaryFunction generates a JS function equivalent of the current Go function + // and assigns it to the JS variable defined by lvalue. + primaryFunction := func(lvalue 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", lvalue, o.FullName())) + } + + funDef := nestedFC.translateFunctionBody(fun.Type, recv, fun.Body, lvalue) + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, funDef)) + } + + code := bytes.NewBuffer(nil) + + recvType := sig.Recv().Type() + ptr, isPointer := recvType.(*types.Pointer) + namedRecvType, _ := recvType.(*types.Named) + if isPointer { + namedRecvType = ptr.Elem().(*types.Named) + } + typeName := fc.objectName(namedRecvType.Obj()) + funName := fun.Name.Name + if reservedKeywords[funName] { + funName += "$" + } + + // Objects the method should be assigned to. + prototypeVar := fmt.Sprintf("%s.prototype.%s", typeName, funName) + ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", typeName, funName) + isGeneric := signatureTypes{Sig: sig}.IsGeneric() + if isGeneric { + // Generic method factories are assigned to the generic type factory + // properties, to be invoked at type construction time rather than method + // call time. + prototypeVar = fmt.Sprintf("%s.methods.%s", typeName, funName) + ptrPrototypeVar = fmt.Sprintf("%s.ptrMethods.%s", typeName, funName) + } + + // proxyFunction generates a JS function that forwards the call to the actual + // method implementation for the alternate receiver (e.g. pointer vs + // non-pointer). + proxyFunction := func(lvalue, receiver string) []byte { + params := strings.Join(nestedFC.funcParamVars(fun.Type), ", ") + fun := fmt.Sprintf("function(%s) { return %s.%s(%s); }", params, receiver, funName, params) + if isGeneric { + // For a generic function, we wrap the proxy function in a trivial generic + // factory function for consistency. It is the same for any possible type + // arguments, so we simply ignore them. + fun = fmt.Sprintf("function() { return %s; }", fun) + } + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) + } + + if _, isStruct := namedRecvType.Underlying().(*types.Struct); isStruct { + code.Write(primaryFunction(ptrPrototypeVar)) + code.Write(proxyFunction(prototypeVar, "this.$val")) + return code.Bytes() + } + + if isPointer { + if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray { + code.Write(primaryFunction(prototypeVar)) + code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", typeName))) + return code.Bytes() + } + return primaryFunction(ptrPrototypeVar) + } + + recvExpr := "this.$get()" + if typesutil.IsGeneric(recvType) { + recvExpr = fmt.Sprintf("%s.wrap(%s)", typeName, recvExpr) + } else if isWrapped(recvType) { + recvExpr = fmt.Sprintf("new %s(%s)", typeName, recvExpr) + } + code.Write(primaryFunction(prototypeVar)) + code.Write(proxyFunction(ptrPrototypeVar, recvExpr)) + return code.Bytes() +} + +// translateStandaloneFunction translates a package-level function. +// +// It returns a JS statements which define the corresponding function in a +// package context. Exported functions are also assigned to the `$pkg` object. +func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + info := fc.pkgCtx.FuncDeclInfos[o] + sig := o.Type().(*types.Signature) + + if fun.Recv != nil { + panic(fmt.Errorf("expected standalone function, got method: %s", o)) + } + + lvalue := fc.objectName(o) + if fun.Body == nil { + return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName())) + } + body := fc.nestedFunctionContext(info, sig).translateFunctionBody(fun.Type, nil, fun.Body, lvalue) + + code := &bytes.Buffer{} + fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body) + if fun.Name.IsExported() { + fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), lvalue) + } + return code.Bytes() +} + +func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string { + functionName := "" // Function object name, i.e. identifier after the "function" keyword. + if funcRef == "" { + // Assign a name for the anonymous function. + funcRef = "$b" + functionName = " $b" + } + + // For regular functions instance is directly in the function variable name. + instanceVar := funcRef + if fc.sigTypes.IsGeneric() { + fc.genericCtx = &genericCtx{} + // For generic function, funcRef refers to the generic factory function, + // allocate a separate variable for a function instance. + instanceVar = fc.newVariable("instance", varGenericFactory) + } + + prevEV := fc.pkgCtx.escapingVars + + params := fc.funcParamVars(typ) + + bodyOutput := string(fc.CatchOutput(fc.bodyIndent(), func() { + if len(fc.Blocking) != 0 { + fc.pkgCtx.Scopes[body] = fc.pkgCtx.Scopes[typ] + fc.handleEscapingVars(body) + } + + if fc.sigTypes != nil && fc.sigTypes.HasNamedResults() { + fc.resultNames = make([]ast.Expr, fc.sigTypes.Sig.Results().Len()) + for i := 0; i < fc.sigTypes.Sig.Results().Len(); i++ { + result := fc.sigTypes.Sig.Results().At(i) + fc.Printf("%s = %s;", fc.objectName(result), fc.translateExpr(fc.zeroValue(result.Type())).String()) + id := ast.NewIdent("") + fc.pkgCtx.Uses[id] = result + fc.resultNames[i] = fc.setType(id, result.Type()) + } + } + + if recv != nil && !isBlank(recv) { + this := "this" + if isWrapped(fc.pkgCtx.TypeOf(recv)) { + this = "this.$val" // Unwrap receiver value. + } + fc.Printf("%s = %s;", fc.translateExpr(recv), this) + } + + fc.translateStmtList(body.List) + if len(fc.Flattened) != 0 && !astutil.EndsWithReturn(body.List) { + fc.translateStmt(&ast.ReturnStmt{}, nil) + } + })) + + sort.Strings(fc.localVars) + + var prefix, suffix string + + if len(fc.Flattened) != 0 { + fc.localVars = append(fc.localVars, "$s") + prefix = prefix + " $s = $s || 0;" + } + + if fc.HasDefer { + fc.localVars = append(fc.localVars, "$deferred") + suffix = " }" + suffix + if len(fc.Blocking) != 0 { + suffix = " }" + suffix + } + } + + localVarDefs := "" // Function-local var declaration at the top. + + if len(fc.Blocking) != 0 { + localVars := append([]string{}, fc.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: %s, $c: true, $r, %s};", instanceVar, strings.Join(fc.localVars, ", ")) + + suffix = " " + saveContext + "return $f;" + suffix + } else if len(fc.localVars) > 0 { + // Non-blocking functions simply declare local variables with no need for restore support. + localVarDefs = fmt.Sprintf("var %s;\n", strings.Join(fc.localVars, ", ")) + } + + if fc.HasDefer { + prefix = prefix + " var $err = null; try {" + deferSuffix := " } catch(err) { $err = err;" + if len(fc.Blocking) != 0 { + deferSuffix += " $s = -1;" + } + if fc.resultNames == nil && fc.sigTypes.HasResults() { + deferSuffix += fmt.Sprintf(" return%s;", fc.translateResults(nil)) + } + deferSuffix += " } finally { $callDeferred($deferred, $err);" + if fc.resultNames != nil { + deferSuffix += fmt.Sprintf(" if (!$curGoroutine.asleep) { return %s; }", fc.translateResults(fc.resultNames)) + } + if len(fc.Blocking) != 0 { + deferSuffix += " if($curGoroutine.asleep) {" + } + suffix = deferSuffix + suffix + } + + if len(fc.Flattened) != 0 { + prefix = prefix + " s: while (true) { switch ($s) { case 0:" + suffix = " } return; }" + suffix + } + + if fc.HasDefer { + prefix = prefix + " $deferred = []; $curGoroutine.deferStack.push($deferred);" + } + + if prefix != "" { + bodyOutput = fc.Indentation(fc.bodyIndent()) + "/* */" + prefix + "\n" + bodyOutput + } + if suffix != "" { + bodyOutput = bodyOutput + fc.Indentation(fc.bodyIndent()) + "/* */" + suffix + "\n" + } + if localVarDefs != "" { + bodyOutput = fc.Indentation(fc.bodyIndent()) + localVarDefs + bodyOutput + } + + fc.pkgCtx.escapingVars = prevEV + + if !fc.sigTypes.IsGeneric() { + return fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, fc.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. + typeParams := fc.typeParamVars(fc.sigTypes.Sig.TypeParams()) + typeParams = append(typeParams, fc.typeParamVars(fc.sigTypes.Sig.RecvTypeParams())...) + + // anonymous types + typesInit := strings.Builder{} + for _, t := range fc.genericCtx.anonTypes.Ordered() { + fmt.Fprintf(&typesInit, "%svar %s = $%sType(%s);\n", fc.Indentation(1), t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.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, "%sconst %s = function(%s) {\n", fc.Indentation(1), instanceVar, strings.Join(params, ", ")) + fmt.Fprintf(code, "%s", bodyOutput) + fmt.Fprintf(code, "%s};\n", fc.Indentation(1)) + // meta, _ := fc.methodListEntry(fc.pkgCtx.Defs[typ.Name].(*types.Func)) + // fmt.Fprintf(code, "%s%s.metadata = %s", fc.Indentation(1), instanceVar, meta) + fmt.Fprintf(code, "%sreturn %s;\n", fc.Indentation(1), instanceVar) + fmt.Fprintf(code, "%s}", fc.Indentation(0)) + return code.String() +} + +// funcParamVars returns a list of JS variables corresponding to the function +// parameters in the order they are defined in the signature. Unnamed or blank +// parameters are assigned unique synthetic names. +// +// Note that JS doesn't allow blank or repeating function argument names, so +// we must assign unique names to all such blank variables. +func (fc *funcContext) funcParamVars(typ *ast.FuncType) []string { + var params []string + for _, param := range typ.Params.List { + if len(param.Names) == 0 { + params = append(params, fc.newBlankVariable(param.Pos())) + continue + } + for _, ident := range param.Names { + if isBlank(ident) { + params = append(params, fc.newBlankVariable(ident.Pos())) + continue + } + params = append(params, fc.objectName(fc.pkgCtx.Defs[ident])) + } + } + + return params +} diff --git a/compiler/package.go b/compiler/package.go index f61a0286a..096f37ce7 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -7,12 +7,10 @@ import ( "go/ast" "go/token" "go/types" - "sort" "strings" "time" "github.com/gopherjs/gopherjs/compiler/analysis" - "github.com/gopherjs/gopherjs/compiler/astutil" "github.com/gopherjs/gopherjs/compiler/typesutil" "golang.org/x/tools/go/gcexportdata" ) @@ -30,15 +28,19 @@ type pkgContext struct { pkgVars map[string]string // Mapping from a named Go object (e.g. type, func, var...) to a JS variable // name assigned to them. - objectNames map[types.Object]string - varPtrNames map[*types.Var]string - anonTypes typesutil.AnonymousTypes - escapingVars map[*types.Var]bool - indentation int - dependencies map[types.Object]bool - minify bool - fileSet *token.FileSet - errList ErrorList + objectNames map[types.Object]string + varPtrNames map[*types.Var]string + // Mapping from a `_` variable to a synthetic JS variable name representing + // it. Map is keyed by the variable position (we can't use *ast.Ident because + // nameless function parameters may not have it). + blankVarNames map[token.Pos]string + anonTypes typesutil.AnonymousTypes + escapingVars map[*types.Var]bool + indentation int + dependencies map[types.Object]bool + minify bool + fileSet *token.FileSet + errList ErrorList } // IsMain returns true if this is the main package of the program. @@ -147,13 +149,14 @@ func newRootCtx(srcs sources, typesInfo *types.Info, typesPkg *types.Package, is Info: pkgInfo, additionalSelections: make(map[*ast.SelectorExpr]selection), - pkgVars: make(map[string]string), - objectNames: make(map[types.Object]string), - varPtrNames: make(map[*types.Var]string), - escapingVars: make(map[*types.Var]bool), - indentation: 1, - minify: minify, - fileSet: srcs.FileSet, + pkgVars: make(map[string]string), + objectNames: make(map[types.Object]string), + varPtrNames: make(map[*types.Var]string), + blankVarNames: make(map[token.Pos]string), + escapingVars: make(map[*types.Var]bool), + indentation: 1, + minify: minify, + fileSet: srcs.FileSet, }, allVars: make(map[string]int), flowDatas: map[*types.Label]*flowData{nil: {}}, @@ -353,291 +356,3 @@ func (fc *funcContext) initArgs(ty types.Type) string { panic(err) } } - -func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { - o := fc.pkgCtx.Defs[fun.Name].(*types.Func) - info := fc.pkgCtx.FuncDeclInfos[o] - - 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 generates a JS function equivalent of the current Go function - // and assigns it to the JS variable defined by lvalue. - primaryFunction := func(lvalue 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", lvalue, o.FullName())) - } - - params, fun := translateFunction(fun.Type, recv, fun.Body, fc, sig, info, lvalue) - joinedParams = strings.Join(params, ", ") - return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) - } - - code := bytes.NewBuffer(nil) - - if fun.Recv == nil { - funcRef := fc.objectName(o) - code.Write(primaryFunction(funcRef)) - if fun.Name.IsExported() { - fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), funcRef) - } - return code.Bytes() - } - - recvType := sig.Recv().Type() - ptr, isPointer := recvType.(*types.Pointer) - namedRecvType, _ := recvType.(*types.Named) - if isPointer { - namedRecvType = ptr.Elem().(*types.Named) - } - typeName := fc.objectName(namedRecvType.Obj()) - funName := fun.Name.Name - if reservedKeywords[funName] { - funName += "$" - } - - // Objects the method should be assigned to. - prototypeVar := fmt.Sprintf("%s.prototype.%s", typeName, funName) - ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", typeName, funName) - isGeneric := signatureTypes{Sig: sig}.IsGeneric() - if isGeneric { - // Generic method factories are assigned to the generic type factory - // properties, to be invoked at type construction time rather than method - // call time. - prototypeVar = fmt.Sprintf("%s.methods.%s", typeName, funName) - ptrPrototypeVar = fmt.Sprintf("%s.ptrMethods.%s", typeName, funName) - } - - // proxyFunction generates a JS function that forwards the call to the actual - // method implementation for the alternate receiver (e.g. pointer vs - // non-pointer). - proxyFunction := func(lvalue, receiver string) []byte { - fun := fmt.Sprintf("function(%s) { return %s.%s(%s); }", joinedParams, receiver, funName, joinedParams) - if isGeneric { - // For a generic function, we wrap the proxy function in a trivial generic - // factory function for consistency. It is the same for any possible type - // arguments, so we simply ignore them. - fun = fmt.Sprintf("function() { return %s; }", fun) - } - return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) - } - - if _, isStruct := namedRecvType.Underlying().(*types.Struct); isStruct { - code.Write(primaryFunction(ptrPrototypeVar)) - code.Write(proxyFunction(prototypeVar, "this.$val")) - return code.Bytes() - } - - if isPointer { - if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray { - code.Write(primaryFunction(prototypeVar)) - code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", typeName))) - return code.Bytes() - } - return primaryFunction(ptrPrototypeVar) - } - - recvExpr := "this.$get()" - if typesutil.IsGeneric(recvType) { - recvExpr = fmt.Sprintf("%s.wrap(%s)", typeName, recvExpr) - } else if isWrapped(recvType) { - recvExpr = fmt.Sprintf("new %s(%s)", typeName, recvExpr) - } - code.Write(primaryFunction(prototypeVar)) - code.Write(proxyFunction(ptrPrototypeVar, recvExpr)) - return code.Bytes() -} - -func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, outerContext *funcContext, sig *types.Signature, info *analysis.FuncInfo, funcRef string) ([]string, string) { - if info == nil { - panic("nil info") - } - - c := &funcContext{ - FuncInfo: info, - pkgCtx: outerContext.pkgCtx, - genericCtx: outerContext.genericCtx, - parent: outerContext, - sigTypes: &signatureTypes{Sig: sig}, - allVars: make(map[string]int, len(outerContext.allVars)), - localVars: []string{}, - flowDatas: map[*types.Label]*flowData{nil: {}}, - caseCounter: 1, - labelCases: make(map[*types.Label]int), - } - for k, v := range outerContext.allVars { - c.allVars[k] = v - } - - functionName := "" // Function object name, i.e. identifier after the "function" keyword. - if funcRef == "" { - // Assign a name for the anonymous function. - funcRef = "$b" - functionName = " $b" - } - - // For regular functions instance is directly in the function variable name. - instanceVar := funcRef - if c.sigTypes.IsGeneric() { - c.genericCtx = &genericCtx{} - // For generic function, funcRef refers to the generic factory function, - // allocate a separate variable for a function instance. - instanceVar = c.newVariable("instance", varGenericFactory) - } - - prevEV := c.pkgCtx.escapingVars - - 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(c.bodyIndent(), func() { - if len(c.Blocking) != 0 { - c.pkgCtx.Scopes[body] = c.pkgCtx.Scopes[typ] - c.handleEscapingVars(body) - } - - if c.sigTypes != nil && c.sigTypes.HasNamedResults() { - c.resultNames = make([]ast.Expr, c.sigTypes.Sig.Results().Len()) - for i := 0; i < c.sigTypes.Sig.Results().Len(); i++ { - result := c.sigTypes.Sig.Results().At(i) - c.Printf("%s = %s;", c.objectName(result), c.translateExpr(c.zeroValue(result.Type())).String()) - id := ast.NewIdent("") - c.pkgCtx.Uses[id] = result - c.resultNames[i] = c.setType(id, result.Type()) - } - } - - if recv != nil && !isBlank(recv) { - this := "this" - if isWrapped(c.pkgCtx.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 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 { - 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: %s, $c: true, $r, %s};", instanceVar, 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.sigTypes.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(c.bodyIndent()) + "/* */" + prefix + "\n" + bodyOutput - } - if suffix != "" { - bodyOutput = bodyOutput + c.Indentation(c.bodyIndent()) + "/* */" + suffix + "\n" - } - if localVarDefs != "" { - bodyOutput = c.Indentation(c.bodyIndent()) + localVarDefs + bodyOutput - } - - c.pkgCtx.escapingVars = prevEV - - 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. - typeParams := c.typeParamVars(c.sigTypes.Sig.TypeParams()) - typeParams = append(typeParams, c.typeParamVars(c.sigTypes.Sig.RecvTypeParams())...) - - // 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, "%sconst %s = function(%s) {\n", c.Indentation(1), instanceVar, strings.Join(params, ", ")) - fmt.Fprintf(code, "%s", bodyOutput) - fmt.Fprintf(code, "%s};\n", c.Indentation(1)) - fmt.Fprintf(code, "%sreturn %s;\n", c.Indentation(1), instanceVar) - fmt.Fprintf(code, "%s}", c.Indentation(0)) - return params, code.String() -} diff --git a/compiler/utils.go b/compiler/utils.go index c5a317c2b..30e96086b 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -292,6 +292,23 @@ func (fc *funcContext) newLocalVariable(name string) string { return fc.newVariable(name, varFuncLocal) } +// newBlankVariable assigns a new JavaScript variable name for a blank Go +// variable, such as a `_` variable or an unnamed function parameter. +// Such variables can't be referenced by the Go code anywhere aside from where +// it is defined, so position is a good key for it. +func (fc *funcContext) newBlankVariable(pos token.Pos) string { + if !pos.IsValid() { + panic("valid position is required to assign a blank variable name") + } + if name, ok := fc.pkgCtx.blankVarNames[pos]; ok { + return name + } + + name := fc.newLocalVariable("blank") + fc.pkgCtx.blankVarNames[pos] = name + return name +} + // varLevel specifies at which level a JavaScript variable should be declared. type varLevel int From 7866b1ed6c9d0e7428f6caf7c5ab03eba9a6d345 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 19 Feb 2023 17:49:36 +0000 Subject: [PATCH 24/83] Factor out unimplemented function translation. --- compiler/functions.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/functions.go b/compiler/functions.go index 95e5105ad..5cdde9e23 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -73,7 +73,7 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { // and assigns it to the JS variable defined by lvalue. primaryFunction := func(lvalue 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", lvalue, o.FullName())) + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) } funDef := nestedFC.translateFunctionBody(fun.Type, recv, fun.Body, lvalue) @@ -162,7 +162,7 @@ func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { lvalue := fc.objectName(o) if fun.Body == nil { - return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName())) + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) } body := fc.nestedFunctionContext(info, sig).translateFunctionBody(fun.Type, nil, fun.Body, lvalue) @@ -174,6 +174,15 @@ func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { return code.Bytes() } +// unimplementedFunction returns a JS function expression for a Go function +// without a body, which would throw an exception if called. +// +// In Go such functions are either used with a //go:linkname directive or with +// assembler intrinsics, only former of which is supported by GopherJS. +func (fc *funcContext) unimplementedFunction(o *types.Func) string { + return fmt.Sprintf("function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t}", o.FullName()) +} + func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string { functionName := "" // Function object name, i.e. identifier after the "function" keyword. if funcRef == "" { From d15130fefba8693c948c7d3daffb005880609833 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 11 Mar 2023 19:56:01 +0000 Subject: [PATCH 25/83] Remove a special case for methods with array-pointer receivers. This special case doesn't seem to serve any purpose that I can discern. My best guess is that it was necessary at some point, but the compiler has changed to not need it anymore. The compiler seems to wrap the returned value in a pointer-type at a call site as appropriate anyway and defining this method on the value type doesn't seem correct. --- compiler/functions.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/compiler/functions.go b/compiler/functions.go index 5cdde9e23..e4ccbba8b 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -128,11 +128,6 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { } if isPointer { - if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray { - code.Write(primaryFunction(prototypeVar)) - code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", typeName))) - return code.Bytes() - } return primaryFunction(ptrPrototypeVar) } From ccda9188a8667df0860cbc8fa5f428794b247284 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 11 Mar 2023 21:20:45 +0000 Subject: [PATCH 26/83] Refactor method translation code. - Move it into a separate function, similar to translateStandaloneFunction(). - Add some comments explaining quirks of GopherJS's method implementation. --- compiler/expressions.go | 5 +- compiler/functions.go | 114 +++++++++++++++++++++++----------------- compiler/utils.go | 14 +++++ 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index ce4c167be..07689050b 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -691,10 +691,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } } - methodName := sel.Obj().Name() - if reservedKeywords[methodName] { - methodName += "$" - } + methodName := fc.methodName(sel.Obj().(*types.Func)) return fc.translateCall(e, sig, fc.formatExpr("%s.%s", recv, methodName)) case types.FieldVal: diff --git a/compiler/functions.go b/compiler/functions.go index e4ccbba8b..744900244 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -2,6 +2,7 @@ package compiler import ( "bytes" + "errors" "fmt" "go/ast" "go/types" @@ -20,10 +21,10 @@ import ( // to the provided info. func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature) *funcContext { if info == nil { - panic(fmt.Errorf("missing *analysis.FuncInfo")) + panic(errors.New("missing *analysis.FuncInfo")) } if sig == nil { - panic(fmt.Errorf("missing *types.Signature")) + panic(errors.New("missing *types.Signature")) } c := &funcContext{ @@ -49,7 +50,7 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types // translateTopLevelFunction translates a top-level function declaration // (standalone function or method) into a corresponding JS function. // -// Returns a string with a JavaScript statements that define the function or +// Returns a string with JavaScript statements that define the function or // method. For generic functions it returns a generic factory function, which // instantiates the actual function at runtime given type parameters. For // methods it returns declarations for both value- and pointer-receiver (if @@ -58,6 +59,44 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { if fun.Recv == nil { return fc.translateStandaloneFunction(fun) } + return fc.translateMethod(fun) +} + +// translateStandaloneFunction translates a package-level function. +// +// It returns JS statements which define the corresponding function in a +// package context. Exported functions are also assigned to the `$pkg` object. +func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + info := fc.pkgCtx.FuncDeclInfos[o] + sig := o.Type().(*types.Signature) + + if fun.Recv != nil { + panic(fmt.Errorf("expected standalone function, got method: %s", o)) + } + + lvalue := fc.objectName(o) + if fun.Body == nil { + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) + } + body := fc.nestedFunctionContext(info, sig).translateFunctionBody(fun.Type, nil, fun.Body, lvalue) + + code := &bytes.Buffer{} + fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body) + if fun.Name.IsExported() { + fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), lvalue) + } + return code.Bytes() +} + +// translateMethod translates a named type method. +// +// It returns one or more JS statements, which define the methods. Methods with +// non-pointer receiver are automatically defined for the pointer-receiver type. +func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte { + if fun.Recv == nil { + panic(fmt.Errorf("expected a method, got %v", fun)) + } o := fc.pkgCtx.Defs[fun.Name].(*types.Func) info := fc.pkgCtx.FuncDeclInfos[o] @@ -70,7 +109,7 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { nestedFC := fc.nestedFunctionContext(info, sig) // primaryFunction generates a JS function equivalent of the current Go function - // and assigns it to the JS variable defined by lvalue. + // and assigns it to the JS expression defined by lvalue. primaryFunction := func(lvalue string) []byte { if fun.Body == nil { return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) @@ -80,8 +119,6 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, funDef)) } - code := bytes.NewBuffer(nil) - recvType := sig.Recv().Type() ptr, isPointer := recvType.(*types.Pointer) namedRecvType, _ := recvType.(*types.Named) @@ -89,10 +126,7 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { namedRecvType = ptr.Elem().(*types.Named) } typeName := fc.objectName(namedRecvType.Obj()) - funName := fun.Name.Name - if reservedKeywords[funName] { - funName += "$" - } + funName := fc.methodName(o) // Objects the method should be assigned to. prototypeVar := fmt.Sprintf("%s.prototype.%s", typeName, funName) @@ -106,9 +140,16 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { ptrPrototypeVar = fmt.Sprintf("%s.ptrMethods.%s", typeName, funName) } - // proxyFunction generates a JS function that forwards the call to the actual - // method implementation for the alternate receiver (e.g. pointer vs - // non-pointer). + // Methods with pointer receivers are defined only on the pointer type. + if isPointer { + return primaryFunction(ptrPrototypeVar) + } + + // Methods with non-pointer receivers must be defined both for the pointer + // and non-pointer types. To minimize generated code size, we generate a + // complete implementation for only one receiver (non-pointer for most types) + // and define a proxy function on the other, which converts the receiver type + // and forwards the call to the primary implementation. proxyFunction := func(lvalue, receiver string) []byte { params := strings.Join(nestedFC.funcParamVars(fun.Type), ", ") fun := fmt.Sprintf("function(%s) { return %s.%s(%s); }", params, receiver, funName, params) @@ -121,51 +162,26 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) } + // Structs are a special case: because of the JS's reference semantics, the + // actual JS objects correspond to pointer-to-struct types and struct value + // types are emulated via cloning. Because of that, the real method + // implementation is defined on the pointer type. if _, isStruct := namedRecvType.Underlying().(*types.Struct); isStruct { + code := bytes.Buffer{} code.Write(primaryFunction(ptrPrototypeVar)) code.Write(proxyFunction(prototypeVar, "this.$val")) return code.Bytes() } - if isPointer { - return primaryFunction(ptrPrototypeVar) - } - - recvExpr := "this.$get()" + proxyRecvExpr := "this.$get()" if typesutil.IsGeneric(recvType) { - recvExpr = fmt.Sprintf("%s.wrap(%s)", typeName, recvExpr) + proxyRecvExpr = fmt.Sprintf("%s.wrap(%s)", typeName, proxyRecvExpr) } else if isWrapped(recvType) { - recvExpr = fmt.Sprintf("new %s(%s)", typeName, recvExpr) + proxyRecvExpr = fmt.Sprintf("new %s(%s)", typeName, proxyRecvExpr) } + code := bytes.Buffer{} code.Write(primaryFunction(prototypeVar)) - code.Write(proxyFunction(ptrPrototypeVar, recvExpr)) - return code.Bytes() -} - -// translateStandaloneFunction translates a package-level function. -// -// It returns a JS statements which define the corresponding function in a -// package context. Exported functions are also assigned to the `$pkg` object. -func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { - o := fc.pkgCtx.Defs[fun.Name].(*types.Func) - info := fc.pkgCtx.FuncDeclInfos[o] - sig := o.Type().(*types.Signature) - - if fun.Recv != nil { - panic(fmt.Errorf("expected standalone function, got method: %s", o)) - } - - lvalue := fc.objectName(o) - if fun.Body == nil { - return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) - } - body := fc.nestedFunctionContext(info, sig).translateFunctionBody(fun.Type, nil, fun.Body, lvalue) - - code := &bytes.Buffer{} - fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body) - if fun.Name.IsExported() { - fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), lvalue) - } + code.Write(proxyFunction(ptrPrototypeVar, proxyRecvExpr)) return code.Bytes() } @@ -330,6 +346,8 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, fmt.Fprintf(code, "%sconst %s = function(%s) {\n", fc.Indentation(1), instanceVar, strings.Join(params, ", ")) fmt.Fprintf(code, "%s", bodyOutput) fmt.Fprintf(code, "%s};\n", fc.Indentation(1)) + // TODO(nevkontakte): Method list entries for generic type methods should be + // generated inside the generic factory. // meta, _ := fc.methodListEntry(fc.pkgCtx.Defs[typ.Name].(*types.Func)) // fmt.Fprintf(code, "%s%s.metadata = %s", fc.Indentation(1), instanceVar, meta) fmt.Fprintf(code, "%sreturn %s;\n", fc.Indentation(1), instanceVar) diff --git a/compiler/utils.go b/compiler/utils.go index 30e96086b..03989efb4 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -488,6 +488,20 @@ func (fc *funcContext) objectName(o types.Object) string { return name } +// methodName returns a JS identifier (specifically, object property name) +// corresponding to the given method. +func (fc *funcContext) methodName(fun *types.Func) string { + if fun.Type().(*types.Signature).Recv() == nil { + panic(fmt.Errorf("expected a method, got a standalone function %v", fun)) + } + name := fun.Name() + // Method names are scoped to their receiver type and guaranteed to be + // unique within that, so we only need to make sure it's not a reserved keyword + if reservedKeywords[name] { + name += "$" + } + return name +} func (fc *funcContext) varPtrName(o *types.Var) string { if getVarLevel(o) == varPackage && o.Exported() { return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr" From 4d2439585cd7b83a77e8fa81c3f4cee692be3162 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 7 Apr 2023 20:34:46 +0100 Subject: [PATCH 27/83] Assign identity to all function literals and use them as funcRefs. The main change is that we assign explicit names to all function objects that correspond to Go functions (named and literals). Function name is declared as `var f = function nameHere() { ... }` and is visible inside the function scope only. Doing so serves two purposes: - It is an identifier which we can use when saving state of a blocked function to know which function to call upon resumption. - It shows up in the stack trace, which helps distinguish similarly-named functions. For methods, we include the receiver type in the identifier to make A.String and B.String easily distinguishable. The main trick is that we synthesize names for the function literals, which are anonymous as far as go/types is concerned. The upstream Go compiler does something very similar. Lest but not least, with this change the generic-related code path in the funcContext.translateFunctionBody becomes more self-contained, which will become handy in the upcoming commits. --- compiler/expressions.go | 2 +- compiler/functions.go | 94 +++++++++++++++---------- compiler/natives/src/reflect/reflect.go | 5 +- compiler/package.go | 11 +++ compiler/utils.go | 62 +++++++++++++++- 5 files changed, 134 insertions(+), 40 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 07689050b..b2af0d6ee 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -177,7 +177,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } case *ast.FuncLit: - fun := fc.nestedFunctionContext(fc.pkgCtx.FuncLitInfos[e], exprType.(*types.Signature)).translateFunctionBody(e.Type, nil, e.Body, "") + fun := fc.literalFuncContext(e).translateFunctionBody(e.Type, nil, e.Body) if len(fc.pkgCtx.escapingVars) != 0 { names := make([]string, 0, len(fc.pkgCtx.escapingVars)) for obj := range fc.pkgCtx.escapingVars { diff --git a/compiler/functions.go b/compiler/functions.go index 744900244..5da286ee7 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -19,20 +19,21 @@ import ( // newFunctionContext creates a new nested context for a function corresponding // to the provided info. -func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature) *funcContext { +func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, o *types.Func) *funcContext { if info == nil { panic(errors.New("missing *analysis.FuncInfo")) } - if sig == nil { - panic(errors.New("missing *types.Signature")) + if o == nil { + panic(errors.New("missing *types.Func")) } c := &funcContext{ FuncInfo: info, + funcObject: o, pkgCtx: fc.pkgCtx, genericCtx: fc.genericCtx, parent: fc, - sigTypes: &signatureTypes{Sig: sig}, + sigTypes: &signatureTypes{Sig: o.Type().(*types.Signature)}, allVars: make(map[string]int, len(fc.allVars)), localVars: []string{}, flowDatas: map[*types.Label]*flowData{nil: {}}, @@ -44,6 +45,42 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types c.allVars[k] = v } + // Synthesize an identifier by which the function may reference itself. Since + // it appears in the stack trace, it's useful to include the receiver type in + // it. + funcRef := o.Name() + if typeName := c.sigTypes.RecvTypeName(); typeName != "" { + funcRef = typeName + midDot + funcRef + } + c.funcRef = c.newVariable(funcRef, varPackage) + + // If the function has type parameters, create a new generic context for it. + if c.sigTypes.IsGeneric() { + c.genericCtx = &genericCtx{} + } + + return c +} + +// namedFuncContext creates a new funcContext for a named Go function +// (standalone or method). +func (fc *funcContext) namedFuncContext(fun *ast.FuncDecl) *funcContext { + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + info := fc.pkgCtx.FuncDeclInfos[o] + c := fc.nestedFunctionContext(info, o) + + return c +} + +// literalFuncContext creates a new funcContext for a function literal. Since +// go/types doesn't generate *types.Func objects for function literals, we +// generate a synthetic one for it. +func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext { + info := fc.pkgCtx.FuncLitInfos[fun] + sig := fc.pkgCtx.TypeOf(fun).(*types.Signature) + o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig) + + c := fc.nestedFunctionContext(info, o) return c } @@ -68,9 +105,6 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte { // package context. Exported functions are also assigned to the `$pkg` object. func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { o := fc.pkgCtx.Defs[fun.Name].(*types.Func) - info := fc.pkgCtx.FuncDeclInfos[o] - sig := o.Type().(*types.Signature) - if fun.Recv != nil { panic(fmt.Errorf("expected standalone function, got method: %s", o)) } @@ -79,7 +113,7 @@ func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte { if fun.Body == nil { return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) } - body := fc.nestedFunctionContext(info, sig).translateFunctionBody(fun.Type, nil, fun.Body, lvalue) + body := fc.namedFuncContext(fun).translateFunctionBody(fun.Type, nil, fun.Body) code := &bytes.Buffer{} fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body) @@ -99,14 +133,11 @@ func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte { } o := fc.pkgCtx.Defs[fun.Name].(*types.Func) - info := fc.pkgCtx.FuncDeclInfos[o] - - sig := o.Type().(*types.Signature) var recv *ast.Ident if fun.Recv.List[0].Names != nil { recv = fun.Recv.List[0].Names[0] } - nestedFC := fc.nestedFunctionContext(info, sig) + nestedFC := fc.namedFuncContext(fun) // primaryFunction generates a JS function equivalent of the current Go function // and assigns it to the JS expression defined by lvalue. @@ -115,11 +146,11 @@ func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte { return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o))) } - funDef := nestedFC.translateFunctionBody(fun.Type, recv, fun.Body, lvalue) + funDef := nestedFC.translateFunctionBody(fun.Type, recv, fun.Body) return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, funDef)) } - recvType := sig.Recv().Type() + recvType := nestedFC.sigTypes.Sig.Recv().Type() ptr, isPointer := recvType.(*types.Pointer) namedRecvType, _ := recvType.(*types.Named) if isPointer { @@ -131,7 +162,7 @@ func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte { // Objects the method should be assigned to. prototypeVar := fmt.Sprintf("%s.prototype.%s", typeName, funName) ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", typeName, funName) - isGeneric := signatureTypes{Sig: sig}.IsGeneric() + isGeneric := nestedFC.sigTypes.IsGeneric() if isGeneric { // Generic method factories are assigned to the generic type factory // properties, to be invoked at type construction time rather than method @@ -194,23 +225,7 @@ func (fc *funcContext) unimplementedFunction(o *types.Func) string { return fmt.Sprintf("function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t}", o.FullName()) } -func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string { - functionName := "" // Function object name, i.e. identifier after the "function" keyword. - if funcRef == "" { - // Assign a name for the anonymous function. - funcRef = "$b" - functionName = " $b" - } - - // For regular functions instance is directly in the function variable name. - instanceVar := funcRef - if fc.sigTypes.IsGeneric() { - fc.genericCtx = &genericCtx{} - // For generic function, funcRef refers to the generic factory function, - // allocate a separate variable for a function instance. - instanceVar = fc.newVariable("instance", varGenericFactory) - } - +func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt) string { prevEV := fc.pkgCtx.escapingVars params := fc.funcParamVars(typ) @@ -272,10 +287,13 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, // $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") + // funcRef identifies the function object itself, so it doesn't need to be saved + // or restored. + localVars = removeMatching(localVars, fc.funcRef) // 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: %s, $c: true, $r, %s};", instanceVar, strings.Join(fc.localVars, ", ")) + saveContext := fmt.Sprintf("var $f = {$blk: %s, $c: true, %s};", fc.funcRef, strings.Join(localVars, ", ")) suffix = " " + saveContext + "return $f;" + suffix } else if len(fc.localVars) > 0 { @@ -324,9 +342,13 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, fc.pkgCtx.escapingVars = prevEV if !fc.sigTypes.IsGeneric() { - return fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, fc.Indentation(0)) + return fmt.Sprintf("function %s(%s) {\n%s%s}", fc.funcRef, strings.Join(params, ", "), bodyOutput, fc.Indentation(0)) } + // For generic function, funcRef refers to the generic factory function, + // allocate a separate variable for a function instance. + instanceVar := fc.newVariable("instance", varGenericFactory) + // 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 @@ -341,9 +363,9 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, } code := &strings.Builder{} - fmt.Fprintf(code, "function%s(%s){\n", functionName, strings.Join(typeParams, ", ")) + fmt.Fprintf(code, "function(%s){\n", strings.Join(typeParams, ", ")) fmt.Fprintf(code, "%s", typesInit.String()) - fmt.Fprintf(code, "%sconst %s = function(%s) {\n", fc.Indentation(1), instanceVar, strings.Join(params, ", ")) + fmt.Fprintf(code, "%sconst %s = function %s(%s) {\n", fc.Indentation(1), instanceVar, fc.funcRef, strings.Join(params, ", ")) fmt.Fprintf(code, "%s", bodyOutput) fmt.Fprintf(code, "%s};\n", fc.Indentation(1)) // TODO(nevkontakte): Method list entries for generic type methods should be diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 7ed8b5f06..1b5b6c27e 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1734,13 +1734,14 @@ func methodNameSkip() string { } // Function name extracted from the call stack can be different from vanilla // Go. Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey" - // into "Value.SetIterKey". + // or plain "SetIterKey" into "Value.SetIterKey". // This workaround may become obsolete after https://github.com/gopherjs/gopherjs/issues/1085 // is resolved. name := f.Name() idx := len(name) - 1 for idx > 0 { if name[idx] == '.' { + idx++ break } idx-- @@ -1748,7 +1749,7 @@ func methodNameSkip() string { if idx < 0 { return name } - return "Value" + name[idx:] + return "Value." + name[idx:] } func verifyNotInHeapPtr(p uintptr) bool { diff --git a/compiler/package.go b/compiler/package.go index 096f37ce7..a5a8a2dce 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -94,6 +94,15 @@ func (sel *fakeSelection) Type() types.Type { return sel.typ } // JavaScript code (as defined for `var` declarations). type funcContext struct { *analysis.FuncInfo + // Function object this context corresponds to, or nil if the context is + // top-level or doesn't correspond to a function. For function literals, this + // is a synthetic object that assigns a unique identity to the function. + funcObject *types.Func + // JavaScript identifier assigned to the function object (the word after the + // "function" keyword in the generated code). This identifier can be used + // within the function scope to reference the function object. It will also + // appear in the stack trace. + funcRef string // Surrounding package context. pkgCtx *pkgContext // Surrounding generic function context. nil if non-generic code. @@ -137,6 +146,8 @@ type funcContext struct { posAvailable bool // Current position in the Go source code. pos token.Pos + // Number of function literals encountered within the current function context. + funcLitCounter int } // newRootCtx creates a new package-level instance of a functionContext object. diff --git a/compiler/utils.go b/compiler/utils.go index 03989efb4..6abf2194a 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -22,6 +22,11 @@ import ( "github.com/gopherjs/gopherjs/compiler/typesutil" ) +// We use this character as a separator in synthetic identifiers instead of a +// regular dot. This character is safe for use in JS identifiers and helps to +// visually separate components of the name when it appears in a stack trace. +const midDot = "·" + // IsRoot returns true for the package-level context. func (fc *funcContext) IsRoot() bool { return fc.parent == nil @@ -410,6 +415,25 @@ func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident { return ident } +// newLitFuncName generates a new synthetic name for a function literal. +func (fc *funcContext) newLitFuncName() string { + fc.funcLitCounter++ + name := &strings.Builder{} + + // If function literal is defined inside another function, qualify its + // synthetic name with the outer function to make it easier to identify. + if fc.funcObject != nil { + if recvType := fc.sigTypes.RecvTypeName(); recvType != "" { + name.WriteString(recvType) + name.WriteString(midDot) + } + name.WriteString(fc.funcObject.Name()) + name.WriteString(midDot) + } + fmt.Fprintf(name, "func%d", fc.funcLitCounter) + return name.String() +} + // typeParamVars returns a list of JS variable names representing type given // parameters. func (fc *funcContext) typeParamVars(params *types.TypeParamList) []string { @@ -502,6 +526,7 @@ func (fc *funcContext) methodName(fun *types.Func) string { } return name } + func (fc *funcContext) varPtrName(o *types.Var) string { if getVarLevel(o) == varPackage && o.Exported() { return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr" @@ -903,7 +928,15 @@ func rangeCheck(pattern string, constantIndex, array bool) string { } func encodeIdent(name string) string { - return strings.Replace(url.QueryEscape(name), "%", "$", -1) + // Quick-and-dirty way to make any string safe for use as an identifier in JS. + name = url.QueryEscape(name) + // We use unicode middle dot as a visual separator in synthetic identifiers. + // It is safe for use in a JS identifier, so we un-encode it for readability. + name = strings.ReplaceAll(name, "%C2%B7", midDot) + // QueryEscape uses '%' before hex-codes of escaped characters, which is not + // allowed in a JS identifier, use '$' instead. + name = strings.ReplaceAll(name, "%", "$") + return name } // formatJSStructTagVal returns JavaScript code for accessing an object's property @@ -999,6 +1032,23 @@ func (st signatureTypes) IsGeneric() bool { return st.Sig.TypeParams().Len() > 0 || st.Sig.RecvTypeParams().Len() > 0 } +// RecvTypeName returns receiver type name for a method signature. For pointer +// receivers the named type is unwrapped from the pointer type. For non-methods +// an empty string is returned. +func (st signatureTypes) RecvTypeName() string { + recv := st.Sig.Recv() + if recv == nil { + return "" + } + + typ := recv.Type() + if ptrType, ok := typ.(*types.Pointer); ok { + typ = ptrType.Elem() + } + + return typ.(*types.Named).Obj().Name() +} + // 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) @@ -1057,3 +1107,13 @@ func bailingOut(err interface{}) (*FatalError, bool) { fe, ok := err.(*FatalError) return fe, ok } + +func removeMatching[T comparable](haystack []T, needle T) []T { + var result []T + for _, el := range haystack { + if el != needle { + result = append(result, el) + } + } + return result +} From c4e443368b9346903ce0067f1190c93645f73ba6 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 8 Apr 2023 16:30:29 +0100 Subject: [PATCH 28/83] Map method's type params to their canonical receiver type type params. In Go it is allowed to give different type parameter names in the method declaration compared to the receiver type, for example: type A[T any] struct{} func (a A[T1]) Method(x T1) {} To support that go/types creates separate instances of the *types.TypeParam object in the type and method declarations for what is essentially the same entity. This was creating an issue when we were generating method reflection metadata for a generic type if the following conditions are true: - Method gives different names to the type parameters compared to its type. - Any of the types in the method signature depend on the type parameter. Because method metadata is generated in the type's generic factory, the type parameters would be declared in the JS code according to their names in the type declaration. However, the method metadata is generated from the method's *types.Func object, which uses type parameters from the method declaration. This mismatch would result in the undefined variable errors. (Strictly speaking this may also happen when they have the same name, but due to nuances of our JS variable name allocation they may or may not end up being given the same JS variable name and work by accident). We solve this by establishing a mapping between method's type params and their canonical counter parts from the receiver type declaration. When determining a variable name for a type param, we would prefer using the canonical one if it exists, avoiding the mismatch. --- compiler/expressions.go | 2 +- compiler/functions.go | 2 +- compiler/package.go | 24 ++++--- compiler/typesutil/signature.go | 101 +++++++++++++++++++++++++++ compiler/typesutil/typesutil.go | 41 +++++++++++ compiler/typesutil/typesutil_test.go | 50 +++++++++++++ compiler/utils.go | 88 +---------------------- 7 files changed, 212 insertions(+), 96 deletions(-) create mode 100644 compiler/typesutil/signature.go diff --git a/compiler/expressions.go b/compiler/expressions.go index b2af0d6ee..6c3f2fde7 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -871,7 +871,7 @@ func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, isJs = typesutil.IsJsPackage(fc.pkgCtx.Uses[fun.Sel].Pkg()) } sig := fc.pkgCtx.TypeOf(expr.Fun).Underlying().(*types.Signature) - sigTypes := signatureTypes{Sig: sig} + sigTypes := typesutil.Signature{Sig: sig} args := fc.translateArgs(sig, expr.Args, expr.Ellipsis.IsValid()) if !isBuiltin && !isJs { diff --git a/compiler/functions.go b/compiler/functions.go index 5da286ee7..4282b41f6 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -33,7 +33,7 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, o *types.F pkgCtx: fc.pkgCtx, genericCtx: fc.genericCtx, parent: fc, - sigTypes: &signatureTypes{Sig: o.Type().(*types.Signature)}, + sigTypes: &typesutil.Signature{Sig: o.Type().(*types.Signature)}, allVars: make(map[string]int, len(fc.allVars)), localVars: []string{}, flowDatas: map[*types.Label]*flowData{nil: {}}, diff --git a/compiler/package.go b/compiler/package.go index a5a8a2dce..b9a61e14e 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -34,13 +34,20 @@ type pkgContext struct { // it. Map is keyed by the variable position (we can't use *ast.Ident because // nameless function parameters may not have it). blankVarNames map[token.Pos]string - anonTypes typesutil.AnonymousTypes - escapingVars map[*types.Var]bool - indentation int - dependencies map[types.Object]bool - minify bool - fileSet *token.FileSet - errList ErrorList + // Mapping between methods' type parameters and their canonical counterparts + // on the receiver types. This ensures we always use the same identifier for + // the type parameter even if a method declaration gives it a different name + // compared to the receiver type declaration: + // type A[T any] struct{} + // func (a A[T1]) M() {} + canonicalTypeParams typesutil.CanonicalTypeParamMap + anonTypes typesutil.AnonymousTypes + escapingVars map[*types.Var]bool + indentation int + dependencies map[types.Object]bool + minify bool + fileSet *token.FileSet + errList ErrorList } // IsMain returns true if this is the main package of the program. @@ -114,7 +121,7 @@ type funcContext struct { parent *funcContext // Information about function signature types. nil for the package-level // function context. - sigTypes *signatureTypes + sigTypes *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 @@ -261,6 +268,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor importedPaths, importDecls := rootCtx.importDecls() vars, functions, typeNames := rootCtx.topLevelObjects(srcs) + rootCtx.pkgCtx.canonicalTypeParams = typesutil.NewCanonicalTypeParamMap(functions, typesInfo) // More named types may be added to the list when function bodies are processed. rootCtx.pkgCtx.typeNames = typeNames diff --git a/compiler/typesutil/signature.go b/compiler/typesutil/signature.go new file mode 100644 index 000000000..88cd63582 --- /dev/null +++ b/compiler/typesutil/signature.go @@ -0,0 +1,101 @@ +package typesutil + +import ( + "fmt" + "go/types" +) + +// Signature is a helper that provides convenient access to function +// signature type information. +type Signature struct { + Sig *types.Signature +} + +// RequiredParams returns the number of required parameters in the function signature. +func (st Signature) RequiredParams() int { + l := st.Sig.Params().Len() + if st.Sig.Variadic() { + return l - 1 // Last parameter is a slice of variadic params. + } + return l +} + +// VariadicType returns the slice-type corresponding to the signature's variadic +// parameter, or nil of the signature is not variadic. With the exception of +// the special-case `append([]byte{}, "string"...)`, the returned type is +// `*types.Slice` and `.Elem()` method can be used to get the type of individual +// arguments. +func (st Signature) VariadicType() types.Type { + if !st.Sig.Variadic() { + return nil + } + return st.Sig.Params().At(st.Sig.Params().Len() - 1).Type() +} + +// Returns the expected argument type for the i'th argument position. +// +// This function is able to return correct expected types for variadic calls +// both when ellipsis syntax (e.g. myFunc(requiredArg, optionalArgSlice...)) +// is used and when optional args are passed individually. +// +// The returned types may differ from the actual argument expression types if +// there is an implicit type conversion involved (e.g. passing a struct into a +// function that expects an interface). +func (st Signature) Param(i int, ellipsis bool) types.Type { + if i < st.RequiredParams() { + return st.Sig.Params().At(i).Type() + } + if !st.Sig.Variadic() { + // This should never happen if the code was type-checked successfully. + panic(fmt.Errorf("Tried to access parameter %d of a non-variadic signature %s", i, st.Sig)) + } + if ellipsis { + return st.VariadicType() + } + return st.VariadicType().(*types.Slice).Elem() +} + +// HasResults returns true if the function signature returns something. +func (st Signature) HasResults() bool { + return st.Sig.Results().Len() > 0 +} + +// HasNamedResults returns true if the function signature returns something and +// returned results are names (e.g. `func () (val int, err error)`). +func (st Signature) 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 Signature) IsGeneric() bool { + return st.Sig.TypeParams().Len() > 0 || st.Sig.RecvTypeParams().Len() > 0 +} + +// RecvType returns receiver type for a method signature. For pointer receivers +// the named type is unwrapped from the pointer type. For non-methods nil is +// returned. +func (st Signature) RecvType() *types.Named { + recv := st.Sig.Recv() + if recv == nil { + return nil + } + + typ := recv.Type() + if ptrType, ok := typ.(*types.Pointer); ok { + typ = ptrType.Elem() + } + + return typ.(*types.Named) +} + +// RecvTypeName returns receiver type name for a method signature. For pointer +// receivers the named type is unwrapped from the pointer type. For non-methods +// an empty string is returned. +func (st Signature) RecvTypeName() string { + typ := st.RecvType() + if typ == nil { + return "" + } + return typ.Obj().Name() +} diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 4ab12f4c1..48b026d4c 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -2,6 +2,7 @@ package typesutil import ( "fmt" + "go/ast" "go/types" "golang.org/x/tools/go/types/typeutil" @@ -129,3 +130,43 @@ func TypeParams(t types.Type) *types.TypeParamList { } return named.TypeParams() } + +// CanonicalTypeParamMap allows mapping type parameters on method receivers +// to their canonical counterparts on the receiver types. +type CanonicalTypeParamMap map[*types.TypeParam]*types.TypeParam + +// NewCanonicalTypeParamMap creates a mapping between methods' type parameters +// for the provided function decls and their receiver types' type parameters. +// +// Non-method decls are ignored. +func NewCanonicalTypeParamMap(funcs []*ast.FuncDecl, tInfo *types.Info) CanonicalTypeParamMap { + result := CanonicalTypeParamMap{} + for _, fun := range funcs { + o := tInfo.Defs[fun.Name] + sig := Signature{Sig: o.Type().(*types.Signature)} + if sig.Sig.RecvTypeParams().Len() == 0 { + continue + } + tParams := sig.Sig.RecvTypeParams() + recvTParams := sig.RecvType().TypeParams() + if tParams.Len() != recvTParams.Len() { + // This should never happen in a type-checked program. + panic(fmt.Errorf("mismatched number of type parameters on a method %s and its receiver type %s: %d != %d", o, sig.RecvType(), tParams.Len(), recvTParams.Len())) + } + for i := 0; i < tParams.Len(); i++ { + tParam := tParams.At(i) + canonicalTParam := recvTParams.At(i) + result[tParam] = canonicalTParam + } + } + return result +} + +// Lookup returns the canonical version of the given type parameter. If there is +// no canonical version, the type parameter is returned as-is. +func (cm CanonicalTypeParamMap) Lookup(tParam *types.TypeParam) *types.TypeParam { + if canonical, ok := cm[tParam]; ok { + return canonical + } + return tParam +} diff --git a/compiler/typesutil/typesutil_test.go b/compiler/typesutil/typesutil_test.go index 515cf0a5e..b0179be95 100644 --- a/compiler/typesutil/typesutil_test.go +++ b/compiler/typesutil/typesutil_test.go @@ -1,11 +1,13 @@ package typesutil import ( + "go/ast" "go/token" "go/types" "testing" "github.com/google/go-cmp/cmp" + "github.com/gopherjs/gopherjs/internal/srctesting" ) func TestAnonymousTypes(t *testing.T) { @@ -166,3 +168,51 @@ func TestIsGeneric(t *testing.T) { }) } } + +func TestCanonicalTypeParamMap(t *testing.T) { + src := `package main + type A[T any] struct{} + func (a A[T1]) Method(t T1) {} + + func Func[U any](u U) {} + ` + fset := token.NewFileSet() + f := srctesting.Parse(t, fset, src) + info, _ := srctesting.Check(t, fset, f) + + // Extract relevant information about the method Method. + methodDecl := f.Decls[1].(*ast.FuncDecl) + if methodDecl.Name.String() != "Method" { + t.Fatalf("Unexpected function at f.Decls[2] position: %q. Want: Method.", methodDecl.Name.String()) + } + method := info.Defs[methodDecl.Name] + T1 := method.Type().(*types.Signature).Params().At(0).Type().(*types.TypeParam) + if T1.Obj().Name() != "T1" { + t.Fatalf("Unexpected type of the Func's first argument: %s. Want: T1.", T1.Obj().Name()) + } + + // Extract relevant information about the standalone function Func. + funcDecl := f.Decls[2].(*ast.FuncDecl) + if funcDecl.Name.String() != "Func" { + t.Fatalf("Unexpected function at f.Decls[2] position: %q. Want: Func.", funcDecl.Name.String()) + } + fun := info.Defs[funcDecl.Name] + U := fun.Type().(*types.Signature).Params().At(0).Type().(*types.TypeParam) + if U.Obj().Name() != "U" { + t.Fatalf("Unexpected type of the Func's first argument: %s. Want: U.", U.Obj().Name()) + } + + cm := NewCanonicalTypeParamMap([]*ast.FuncDecl{methodDecl, funcDecl}, info) + + // Method's type params canonicalized to their receiver type's. + got := cm.Lookup(T1) + if got.Obj().Name() != "T" { + t.Errorf("Got canonical type parameter %q for %q. Want: T.", got, T1) + } + + // Function's type params don't need canonicalization. + got = cm.Lookup(U) + if got.Obj().Name() != "U" { + t.Errorf("Got canonical type parameter %q for %q. Want: U.", got, U) + } +} diff --git a/compiler/utils.go b/compiler/utils.go index 6abf2194a..f386677ac 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -179,7 +179,7 @@ func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr { func (fc *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, ellipsis bool) []string { argExprs = fc.expandTupleArgs(argExprs) - sigTypes := signatureTypes{Sig: sig} + sigTypes := typesutil.Signature{Sig: sig} if sig.Variadic() && len(argExprs) == 0 { return []string{fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType()))} @@ -563,7 +563,7 @@ func (fc *funcContext) typeName(ty types.Type) string { } return fmt.Sprintf("(%s(%s))", fc.objectName(t.Obj()), strings.Join(args, ",")) case *types.TypeParam: - return fc.objectName(t.Obj()) + return fc.objectName(fc.pkgCtx.canonicalTypeParams.Lookup(t).Obj()) case *types.Interface: if t.Empty() { return "$emptyInterface" @@ -965,90 +965,6 @@ func formatJSStructTagVal(jsTag string) string { return "." + jsTag } -// signatureTypes is a helper that provides convenient access to function -// signature type information. -type signatureTypes struct { - Sig *types.Signature -} - -// RequiredParams returns the number of required parameters in the function signature. -func (st signatureTypes) RequiredParams() int { - l := st.Sig.Params().Len() - if st.Sig.Variadic() { - return l - 1 // Last parameter is a slice of variadic params. - } - return l -} - -// VariadicType returns the slice-type corresponding to the signature's variadic -// parameter, or nil of the signature is not variadic. With the exception of -// the special-case `append([]byte{}, "string"...)`, the returned type is -// `*types.Slice` and `.Elem()` method can be used to get the type of individual -// arguments. -func (st signatureTypes) VariadicType() types.Type { - if !st.Sig.Variadic() { - return nil - } - return st.Sig.Params().At(st.Sig.Params().Len() - 1).Type() -} - -// Returns the expected argument type for the i'th argument position. -// -// This function is able to return correct expected types for variadic calls -// both when ellipsis syntax (e.g. myFunc(requiredArg, optionalArgSlice...)) -// is used and when optional args are passed individually. -// -// The returned types may differ from the actual argument expression types if -// there is an implicit type conversion involved (e.g. passing a struct into a -// function that expects an interface). -func (st signatureTypes) Param(i int, ellipsis bool) types.Type { - if i < st.RequiredParams() { - return st.Sig.Params().At(i).Type() - } - if !st.Sig.Variadic() { - // This should never happen if the code was type-checked successfully. - panic(fmt.Errorf("Tried to access parameter %d of a non-variadic signature %s", i, st.Sig)) - } - if ellipsis { - return st.VariadicType() - } - return st.VariadicType().(*types.Slice).Elem() -} - -// HasResults returns true if the function signature returns something. -func (st signatureTypes) HasResults() bool { - return st.Sig.Results().Len() > 0 -} - -// HasNamedResults returns true if the function signature returns something and -// returned results are names (e.g. `func () (val int, err error)`). -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 -} - -// RecvTypeName returns receiver type name for a method signature. For pointer -// receivers the named type is unwrapped from the pointer type. For non-methods -// an empty string is returned. -func (st signatureTypes) RecvTypeName() string { - recv := st.Sig.Recv() - if recv == nil { - return "" - } - - typ := recv.Type() - if ptrType, ok := typ.(*types.Pointer); ok { - typ = ptrType.Elem() - } - - return typ.(*types.Named).Obj().Name() -} - // 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) From f5ce6a5230da0f73a51bf05d3ff9a689c33d6452 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 8 Apr 2023 19:24:10 +0100 Subject: [PATCH 29/83] Update the list of failing typeparams tests. Out of 13 tests that were previously failing due to the type param mismatch, 12 are now passing. typeparam/issue44688.go is still failing but due to an unrelated bug. --- tests/gorepo/run.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index a539ec373..14a1d282a 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -165,39 +165,27 @@ var knownFails = map[string]failReason{ "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/boundmethod.go": {category: generics, desc: "missing support for type conversion of a parameterized type"}, "typeparam/chans.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/cons.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/dictionaryCapture-noinline.go": {category: generics, desc: "attempts to pass -gcflags=\"-G=3\" flag, incorrectly parsed by run.go"}, "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/genembed.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, - "typeparam/genembed2.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/graph.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, - "typeparam/issue44688.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, + "typeparam/issue44688.go": {category: generics, desc: "bug: generic type may get instantiated before its methods are registered"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, - "typeparam/issue48042.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, - "typeparam/issue48049.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/issue48602.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, - "typeparam/issue48617.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, - "typeparam/issue48645b.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, - "typeparam/issue48838.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue50109.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50690b.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, - "typeparam/issue52026.go": {category: generics, desc: "bug: type parameter is assigned different variable in type and method definition"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/list2.go": {category: generics, desc: "bug: infinite recursion during type initialization"}, "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, From cb270f93c26ec76abd4b7cf12e50d6e9ad9cdf6d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 14 May 2023 20:51:56 +0100 Subject: [PATCH 30/83] Use the existing helper to determine the receiver type. --- compiler/decls.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/decls.go b/compiler/decls.go index 2a49af039..308c9f2b1 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -301,14 +301,8 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl) *Decl { } if typesutil.IsMethod(o) { - var namedRecvType *types.Named - - recvType := o.Type().(*types.Signature).Recv().Type() - if ptr, isPointer := recvType.(*types.Pointer); isPointer { - namedRecvType = ptr.Elem().(*types.Named) - } else { - namedRecvType = recvType.(*types.Named) - } + sig := typesutil.Signature{Sig: o.Type().(*types.Signature)} + namedRecvType := sig.RecvType() d.NamedRecvType = fc.objectName(namedRecvType.Obj()) d.DceObjectFilter = namedRecvType.Obj().Name() From e682a3e95c7433ba6f655b22de15ae4b652c2212 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 14 May 2023 20:57:59 +0100 Subject: [PATCH 31/83] Remove a stale todo. It is, in fact, completed. --- compiler/functions.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler/functions.go b/compiler/functions.go index 4282b41f6..2625a02b1 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -368,10 +368,6 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, fmt.Fprintf(code, "%sconst %s = function %s(%s) {\n", fc.Indentation(1), instanceVar, fc.funcRef, strings.Join(params, ", ")) fmt.Fprintf(code, "%s", bodyOutput) fmt.Fprintf(code, "%s};\n", fc.Indentation(1)) - // TODO(nevkontakte): Method list entries for generic type methods should be - // generated inside the generic factory. - // meta, _ := fc.methodListEntry(fc.pkgCtx.Defs[typ.Name].(*types.Func)) - // fmt.Fprintf(code, "%s%s.metadata = %s", fc.Indentation(1), instanceVar, meta) fmt.Fprintf(code, "%sreturn %s;\n", fc.Indentation(1), instanceVar) fmt.Fprintf(code, "%s}", fc.Indentation(0)) return code.String() From d12c9981b05e9b560cebe42b63b4dc60bb98342e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 10 Jun 2023 16:49:19 +0100 Subject: [PATCH 32/83] Fix infinite recursion when initializing generic types. Prior to this change, when a generic type depended on itself, instantiating it would cause infinite recursion, for example: ```go type List[any T] struct { next *List } ``` In its generic factory function, it would attempt instantiate `*List` before `List`, since the latter depends on the former. However, for that to succeed `List` must be already instantiated, but it wouldn't be, yet. We break the dependency loop in the same way it's done for non-generic types: we declare variables for the anonymous types upfront, so that they could be captured by the type constructor closure, but only assign them values after the type instance has been created and stored in `$typeInstances`. In that case a call to `List(T)` would return the cached instance, avoiding the recursion. --- compiler/decls.go | 15 ++++++++++++--- compiler/typesutil/typesutil.go | 17 ++++++++++++++++- compiler/typesutil/typesutil_test.go | 7 +++++++ tests/gorepo/run.go | 4 ++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/compiler/decls.go b/compiler/decls.go index 308c9f2b1..ede2c6d69 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -479,14 +479,23 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { fc.Printf("var %s = $typeInstances.get(%s);", instanceVar, typeString) fc.Printf("if (%[1]s) { return %[1]s; }", instanceVar) - // Construct anonymous types which depend on type parameters. - for _, t := range fc.genericCtx.anonTypes.Ordered() { - fc.Printf("var %s = $%sType(%s);", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type())) + // Forward-declare variables for the synthesized type names so that they + // could be captured by the type constructor closure. + if fc.genericCtx.anonTypes.Len() != 0 { + fc.Printf("var %s;", strings.Join(fc.genericCtx.anonTypes.Names(), ", ")) } // Construct type instance. fmt.Fprint(fc, string(d.DeclCode)) fc.Printf("$typeInstances.set(%s, %s);", typeString, instanceVar) + + // Construct anonymous types which depend on type parameters. It must be + // done after the type instance has been cached to avoid infinite + // recursion in case the type depends on itself. + for _, t := range fc.genericCtx.anonTypes.Ordered() { + fc.Printf("%s = $%sType(%s);", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type())) + } + fc.Printf("$instantiateMethods(%s, %s, %s)", instanceVar, fmt.Sprintf("%s.methods", typeName), strings.Join(typeParamNames, ", ")) fc.Printf("$instantiateMethods(%s, %s, %s)", fmt.Sprintf("$ptrType(%s)", instanceVar), fmt.Sprintf("%s.ptrMethods", typeName), strings.Join(typeParamNames, ", ")) fmt.Fprint(fc, string(d.TypeInitCode)) diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 48b026d4c..5db18eafd 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -42,12 +42,27 @@ func (at *AnonymousTypes) Get(t types.Type) *types.TypeName { return s } -// Ordered returns synthesized type names for the registered anonymous types in +// Ordered returns synthesized named types for the registered anonymous types in // the order they were registered. func (at *AnonymousTypes) Ordered() []*types.TypeName { return at.order } +// Names returns synthesized type name strings for the registered anonymous +// types in the order they were registered. +func (at *AnonymousTypes) Names() []string { + names := []string{} + for _, t := range at.order { + names = append(names, t.Name()) + } + return names +} + +// Len returns the number of registered anonymous types. +func (at *AnonymousTypes) Len() int { + return len(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) diff --git a/compiler/typesutil/typesutil_test.go b/compiler/typesutil/typesutil_test.go index b0179be95..f47ecd39f 100644 --- a/compiler/typesutil/typesutil_test.go +++ b/compiler/typesutil/typesutil_test.go @@ -30,6 +30,10 @@ func TestAnonymousTypes(t *testing.T) { anonTypes.Register(typ.name, typ.typ) } + if want, got := 2, anonTypes.Len(); want != got { + t.Errorf("Got: anonTypes.Len() = %v. Want: %v.", got, want) + } + for _, typ := range typs { t.Run(typ.name.Name(), func(t *testing.T) { got := anonTypes.Get(typ.typ) @@ -47,6 +51,9 @@ func TestAnonymousTypes(t *testing.T) { if !cmp.Equal(wantNames, gotNames) { t.Errorf("Got: anonTypes.Ordered() = %v. Want: %v (in the order of registration)", gotNames, wantNames) } + if !cmp.Equal(wantNames, anonTypes.Names()) { + t.Errorf("Got: anonTypes.Names() = %v. Want: %v (in the order of registration)", gotNames, wantNames) + } } func TestIsGeneric(t *testing.T) { diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 14a1d282a..12c34dc44 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -187,12 +187,12 @@ var knownFails = map[string]failReason{ "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/list2.go": {category: generics, desc: "bug: infinite recursion during type initialization"}, + "typeparam/list2.go": {category: generics, desc: "bug: generic type may get instantiated before its methods are registered"}, "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/metrics.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, "typeparam/ordered.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/orderedmap.go": {category: generics, desc: "bug: infinite recursion during type initialization"}, + "typeparam/orderedmap.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/sets.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, From da4ba32b75623bb7ed156d9e8970077c7220f893 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 10 Jun 2023 20:23:05 +0100 Subject: [PATCH 33/83] Fix a bug where a type could be instantiated without its methods. Prior to this change the following code would throw an error when attempting to call `a1.m()`: ```go package main type A1[T any] struct{ val T } func (a *A1[T]) m() {} func F(_ *A1[string]) {} func main() { if false { F(nil) } var a1 *A1[string] = &A1[string]{} a1.m() } ``` This happened because decls for anonymous types used in non-generic functions were written to the JS output before decls corresponding to methods of generic types, which would register methods' generic factory functions with its receiver type factory function. If one of the anonymous type decls referenced a generic type instance (`*A1[string]` in the example above), the `A1[string]` type instance would be initialized without methods. This change moves generic factory declarations to be written out before any other code. This ensures that the types are fully defined by the time any non-generic code may reference them. --- compiler/compiler.go | 7 +++++++ compiler/decls.go | 18 +++++++++++++++--- tests/gorepo/run.go | 2 -- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index fea8b69cc..60353e435 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -250,6 +250,13 @@ func WritePkgCode(pkg *Archive, dceSelection map[*Decl]struct{}, gls goLinknameS if _, err := w.Write(removeWhitespace([]byte(fmt.Sprintf("\tvar %s;\n", strings.Join(vars, ", "))), minify)); err != nil { return err } + + for _, d := range filteredDecls { + if _, err := w.Write(d.GenericFactoryCode); err != nil { + return err + } + } + for _, d := range filteredDecls { if _, err := w.Write(d.DeclCode); err != nil { return err diff --git a/compiler/decls.go b/compiler/decls.go index ede2c6d69..a94f99a34 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -34,6 +34,10 @@ type Decl struct { // it configures basic information about the type and its identity. For a function // or method it contains its compiled body. DeclCode []byte + // JavaScript code that declared a factory function for a generic type or + // function, which can be called to create specific instanced of the type or + // function. + GenericFactoryCode []byte // JavaScript code that initializes reflection metadata about type's method list. MethodListCode []byte // JavaScript code that initializes the rest of reflection metadata about a type @@ -300,8 +304,8 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl) *Decl { LinkingName: newSymName(o), } + sig := typesutil.Signature{Sig: o.Type().(*types.Signature)} if typesutil.IsMethod(o) { - sig := typesutil.Signature{Sig: o.Type().(*types.Signature)} namedRecvType := sig.RecvType() d.NamedRecvType = fc.objectName(namedRecvType.Obj()) @@ -324,7 +328,14 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl) *Decl { } } - d.DceDeps = fc.CollectDCEDeps(func() { d.DeclCode = fc.translateTopLevelFunction(fun) }) + d.DceDeps = fc.CollectDCEDeps(func() { + code := fc.translateTopLevelFunction(fun) + if sig.IsGeneric() { + d.GenericFactoryCode = code + } else { + d.DeclCode = code + } + }) return d } @@ -470,7 +481,7 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { typeParamNames := fc.typeParamVars(typeParams) - d.DeclCode = fc.CatchOutput(0, func() { + d.GenericFactoryCode = fc.CatchOutput(0, func() { // Begin generic factory function. fc.Printf("%s = function(%s) {", typeName, strings.Join(typeParamNames, ", ")) @@ -512,6 +523,7 @@ func (fc *funcContext) newNamedTypeDecl(o *types.TypeName) *Decl { // Clean out code that has been absorbed by the generic factory function. d.TypeInitCode = nil d.MethodListCode = nil + d.DeclCode = nil } if getVarLevel(o) == varPackage { diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 12c34dc44..a7ee80d88 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -174,7 +174,6 @@ var knownFails = map[string]failReason{ "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, - "typeparam/issue44688.go": {category: generics, desc: "bug: generic type may get instantiated before its methods are registered"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, @@ -187,7 +186,6 @@ var knownFails = map[string]failReason{ "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/list2.go": {category: generics, desc: "bug: generic type may get instantiated before its methods are registered"}, "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/metrics.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, From e13ad49d1d15ae1f2c8075a89c502ff1fc75ebc6 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 10 Jun 2023 21:21:35 +0100 Subject: [PATCH 34/83] Diagnose some of the typeparam test failures. Also pick up more advanced run args parsing from the upstream to better deal with -gcflags flag. --- tests/gorepo/run.go | 159 ++++++++++++++++++++++++++++++-------------- 1 file changed, 108 insertions(+), 51 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index a7ee80d88..4785d74c7 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -111,9 +111,6 @@ var knownFails = map[string]failReason{ "fixedbugs/issue23188.go": {desc: "incorrect order of evaluation of index operations"}, "fixedbugs/issue24547.go": {desc: "incorrect computing method sets with shadowed methods"}, - // These are new tests in Go 1.11.5 - "fixedbugs/issue28688.go": {category: notApplicable, desc: "testing runtime optimisations"}, - // These are new tests in Go 1.12. "fixedbugs/issue23837.go": {desc: "missing panic on nil pointer-to-empty-struct dereference"}, "fixedbugs/issue27201.go": {desc: "incorrect stack trace for nil dereference in inlined function"}, @@ -123,7 +120,6 @@ var knownFails = map[string]failReason{ // These are new tests in Go 1.12.9. "fixedbugs/issue30977.go": {category: neverTerminates, desc: "does for { runtime.GC() }"}, "fixedbugs/issue32477.go": {category: notApplicable, desc: "uses runtime.SetFinalizer and runtime.GC"}, - "fixedbugs/issue32680.go": {category: notApplicable, desc: "uses -gcflags=-d=ssa/check/on flag"}, // These are new tests in Go 1.13-1.16. "fixedbugs/issue19113.go": {category: lowLevelRuntimeDifference, desc: "JavaScript bit shifts by negative amount don't cause an exception"}, @@ -136,7 +132,6 @@ var knownFails = map[string]failReason{ "fixedbugs/issue30116u.go": {desc: "GopherJS doesn't specify the array/slice index selector in the out-of-bounds message"}, "fixedbugs/issue34395.go": {category: neverTerminates, desc: "https://github.com/gopherjs/gopherjs/issues/1007"}, "fixedbugs/issue35027.go": {category: usesUnsupportedPackage, desc: "uses unsupported conversion to reflect.SliceHeader and -gcflags=-d=checkptr"}, - "fixedbugs/issue35073.go": {category: usesUnsupportedPackage, desc: "uses unsupported flag -gcflags=-d=checkptr"}, "fixedbugs/issue35576.go": {category: lowLevelRuntimeDifference, desc: "GopherJS print/println format for floats differs from Go's"}, "fixedbugs/issue40917.go": {category: notApplicable, desc: "uses pointer arithmetic and unsupported flag -gcflags=-d=checkptr"}, @@ -151,7 +146,6 @@ var knownFails = map[string]failReason{ "fixedbugs/issue50854.go": {category: lowLevelRuntimeDifference, desc: "negative int32 overflow behaves differently in JS"}, // These are new tests in Go 1.18 - "fixedbugs/issue46938.go": {category: notApplicable, desc: "tests -d=checkptr compiler mode, which GopherJS doesn't support"}, "fixedbugs/issue47928.go": {category: notApplicable, desc: "//go:nointerface is a part of GOEXPERIMENT=fieldtrack and is not supported by GopherJS"}, "fixedbugs/issue48898.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1128"}, "fixedbugs/issue48536.go": {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"}, @@ -160,44 +154,43 @@ var knownFails = map[string]failReason{ // Failures related to the lack of generics support. Ideally, this section // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is // fixed. - "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/boundmethod.go": {category: generics, desc: "missing support for type conversion of a parameterized type"}, - "typeparam/chans.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/dictionaryCapture-noinline.go": {category: generics, desc: "attempts to pass -gcflags=\"-G=3\" flag, incorrectly parsed by run.go"}, - "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/graph.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/index.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, - "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, - "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, - "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, - "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, - "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/metrics.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, - "typeparam/ordered.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/orderedmap.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/sets.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, - "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/subdict.go": {category: generics, desc: "undiagnosed: runtime error: comparing uncomparable type undefined"}, - "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, - "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, - "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, + "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/boundmethod.go": {category: generics, desc: "missing support for type conversion of a parameterized type"}, + "typeparam/chans.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, + "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/graph.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/index.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, + "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, + "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, + "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, + "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, + "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, + "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, + "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, + "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/metrics.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, + "typeparam/ordered.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/orderedmap.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/sets.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, + "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/subdict.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, + "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, + "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, } type failCategory uint8 @@ -677,7 +670,11 @@ func (t *test) run() { var args, flags []string wantError := false - f := strings.Fields(action) + f, err := splitQuoted(action) + if err != nil { + t.err = fmt.Errorf("invalid test recipe: %v", err) + return + } if len(f) > 0 { action = f[0] args = f[1:] @@ -737,16 +734,14 @@ func (t *test) run() { } { - // GopherJS: we don't support -gcflags=-G=3 flag, but it's the default - // behavior anyway. + // GopherJS: we don't support any of -gcflags, but for the most part they + // are not too relevant to the outcome of the test. supportedArgs := []string{} for _, a := range args { - switch a { - case "-gcflags=-G=3", `-gcflags="-G=3"`: + if strings.HasPrefix(a, "-gcflags") { continue - default: - supportedArgs = append(supportedArgs, a) } + supportedArgs = append(supportedArgs, a) } args = supportedArgs } @@ -1315,3 +1310,65 @@ func getenv(key, def string) string { } return def } + +// splitQuoted splits the string s around each instance of one or more consecutive +// white space characters while taking into account quotes and escaping, and +// returns an array of substrings of s or an empty list if s contains only white space. +// Single quotes and double quotes are recognized to prevent splitting within the +// quoted region, and are removed from the resulting substrings. If a quote in s +// isn't closed err will be set and r will have the unclosed argument as the +// last element. The backslash is used for escaping. +// +// For example, the following string: +// +// a b:"c d" 'e''f' "g\"" +// +// Would be parsed as: +// +// []string{"a", "b:c d", "ef", `g"`} +// +// [copied from src/go/build/build.go] +func splitQuoted(s string) (r []string, err error) { + var args []string + arg := make([]rune, len(s)) + escaped := false + quoted := false + quote := '\x00' + i := 0 + for _, rune := range s { + switch { + case escaped: + escaped = false + case rune == '\\': + escaped = true + continue + case quote != '\x00': + if rune == quote { + quote = '\x00' + continue + } + case rune == '"' || rune == '\'': + quoted = true + quote = rune + continue + case unicode.IsSpace(rune): + if quoted || i > 0 { + quoted = false + args = append(args, string(arg[:i])) + i = 0 + } + continue + } + arg[i] = rune + i++ + } + if quoted || i > 0 { + args = append(args, string(arg[:i])) + } + if quote != 0 { + err = errors.New("unclosed quote") + } else if escaped { + err = errors.New("unfinished escaping") + } + return args, err +} From 7df6006dcfce3561d812eb32e602c138f6b5ee7e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 25 Jun 2023 17:27:06 +0100 Subject: [PATCH 35/83] Add godoc comments for functions in the astutil method. --- compiler/astutil/astutil.go | 7 ++++++- compiler/filter/assign.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 81e9caa1c..f5e9f81cc 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -8,6 +8,7 @@ import ( "strings" ) +// RemoveParens removed parens around an expression, if any. func RemoveParens(e ast.Expr) ast.Expr { for { p, isParen := e.(*ast.ParenExpr) @@ -18,12 +19,14 @@ func RemoveParens(e ast.Expr) ast.Expr { } } +// SetType of the expression e to type t. func SetType(info *types.Info, t types.Type, e ast.Expr) ast.Expr { info.Types[e] = types.TypeAndValue{Type: t} return e } -func NewIdent(name string, t types.Type, info *types.Info, pkg *types.Package) *ast.Ident { +// NewVarIdent creates a new variable object with the given name and type. +func NewVarIdent(name string, t types.Type, info *types.Info, pkg *types.Package) *ast.Ident { ident := ast.NewIdent(name) info.Types[ident] = types.TypeAndValue{Type: t} obj := types.NewVar(0, pkg, name, t) @@ -31,6 +34,7 @@ func NewIdent(name string, t types.Type, info *types.Info, pkg *types.Package) * return ident } +// IsTypeExpr returns true if expr denotes a type. func IsTypeExpr(expr ast.Expr, info *types.Info) bool { switch e := expr.(type) { case *ast.ArrayType, *ast.ChanType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.StructType: @@ -57,6 +61,7 @@ func IsTypeExpr(expr ast.Expr, info *types.Info) bool { } } +// ImportsUnsafe returns true of the source imports package "unsafe". func ImportsUnsafe(file *ast.File) bool { for _, imp := range file.Imports { if imp.Path.Value == `"unsafe"` { diff --git a/compiler/filter/assign.go b/compiler/filter/assign.go index 2681d4c6a..281cedf87 100644 --- a/compiler/filter/assign.go +++ b/compiler/filter/assign.go @@ -71,7 +71,7 @@ func Assign(stmt ast.Stmt, info *types.Info, pkg *types.Package) ast.Stmt { return e default: - tmpVar := astutil.NewIdent(name, info.TypeOf(e), info, pkg) + tmpVar := astutil.NewVarIdent(name, info.TypeOf(e), info, pkg) list = append(list, &ast.AssignStmt{ Lhs: []ast.Expr{tmpVar}, Tok: token.DEFINE, From 2feeff6057525801592618b97a09f1b5f7488294 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 25 Jun 2023 17:38:14 +0100 Subject: [PATCH 36/83] Factor out a few ATS-related funcContext methods into the astutil pkg. There were already identical or similar methods in the astutil package, so this reduces code duplication a bit. --- compiler/astutil/astutil.go | 12 +++++++++--- compiler/utils.go | 12 ++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index f5e9f81cc..e7ebfce4f 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -27,10 +27,16 @@ func SetType(info *types.Info, t types.Type, e ast.Expr) ast.Expr { // NewVarIdent creates a new variable object with the given name and type. func NewVarIdent(name string, t types.Type, info *types.Info, pkg *types.Package) *ast.Ident { - ident := ast.NewIdent(name) - info.Types[ident] = types.TypeAndValue{Type: t} - obj := types.NewVar(0, pkg, name, t) + obj := types.NewVar(token.NoPos, pkg, name, t) + return NewIdentFor(info, obj) +} + +// NewIdentFor creates a new identifier referencing the given object. +func NewIdentFor(info *types.Info, obj types.Object) *ast.Ident { + ident := ast.NewIdent(obj.Name()) + ident.NamePos = obj.Pos() info.Uses[ident] = obj + SetType(info, obj.Type(), ident) return ident } diff --git a/compiler/utils.go b/compiler/utils.go index 158d727d6..cfca0782b 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -19,6 +19,7 @@ import ( "unicode" "github.com/gopherjs/gopherjs/compiler/analysis" + "github.com/gopherjs/gopherjs/compiler/astutil" "github.com/gopherjs/gopherjs/compiler/typesutil" ) @@ -406,18 +407,14 @@ func (fc *funcContext) newVariable(name string, level varLevel) string { // newIdent declares a new Go variable with the given name and type and returns // an *ast.Ident referring to that object. func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident { - obj := types.NewVar(0, fc.pkgCtx.Pkg, name, t) + obj := types.NewVar(token.NoPos, fc.pkgCtx.Pkg, name, t) fc.pkgCtx.objectNames[obj] = name return fc.newIdentFor(obj) } // newIdentFor creates a new *ast.Ident referring to the given Go object. func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident { - ident := ast.NewIdent(obj.Name()) - ident.NamePos = obj.Pos() - fc.pkgCtx.Uses[ident] = obj - fc.setType(ident, obj.Type()) - return ident + return astutil.NewIdentFor(fc.pkgCtx.Info.Info, obj) } // newLitFuncName generates a new synthetic name for a function literal. @@ -451,8 +448,7 @@ func (fc *funcContext) typeParamVars(params *types.TypeParamList) []string { } func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { - fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t} - return e + return astutil.SetType(fc.pkgCtx.Info.Info, t, e) } func (fc *funcContext) pkgVar(pkg *types.Package) string { From dd5091d2102a3d263213b2f8d57611c4beba24e2 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 25 Jun 2023 18:12:29 +0100 Subject: [PATCH 37/83] Factor out a few typed AST generation methods for better readability. --- compiler/astutil/astutil.go | 27 +++++++++++++++++++++++++++ compiler/expressions.go | 20 +++++--------------- compiler/utils.go | 8 ++++++++ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index e7ebfce4f..4f95388ab 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -172,3 +172,30 @@ func EndsWithReturn(stmts []ast.Stmt) bool { return false } } + +// TypeCast wraps expression e into an AST of type conversion to a type denoted +// by typeExpr. The new AST node is associated with the appropriate type. +func TypeCast(info *types.Info, e ast.Expr, typeExpr ast.Expr) *ast.CallExpr { + cast := &ast.CallExpr{ + Fun: typeExpr, + Lparen: e.Pos(), + Args: []ast.Expr{e}, + Rparen: e.End(), + } + SetType(info, info.TypeOf(typeExpr), cast) + return cast +} + +// TakeAddress wraps expression e into an AST of address-taking operator &e. The +// new AST node is associated with pointer to the type of e. +func TakeAddress(info *types.Info, e ast.Expr) *ast.UnaryExpr { + exprType := info.TypeOf(e) + ptrType := types.NewPointer(exprType) + addrOf := &ast.UnaryExpr{ + OpPos: e.Pos(), + Op: token.AND, + X: e, + } + SetType(info, ptrType, addrOf) + return addrOf +} diff --git a/compiler/expressions.go b/compiler/expressions.go index 57c0e71c6..0e13bcdb3 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -105,13 +105,9 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { // inner composite literal `{}` would has a pointer type. To make sure the // type conversion is handled correctly, we generate the explicit AST for // this. - var rewritten ast.Expr = fc.setType(&ast.UnaryExpr{ - OpPos: e.Pos(), - Op: token.AND, - X: fc.setType(&ast.CompositeLit{ - Elts: e.Elts, - }, ptrType.Elem()), - }, ptrType) + var rewritten ast.Expr = fc.takeAddress(fc.setType(&ast.CompositeLit{ + Elts: e.Elts, + }, ptrType.Elem())) if exprType, ok := exprType.(*types.Named); ok { // Handle a special case when the pointer type is named, e.g.: @@ -119,13 +115,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { // _ = []PS{{}} // In that case the value corresponding to the inner literal `{}` is // initialized as `&S{}` and then converted to `PS`: `[]PS{PS(&S{})}`. - typeCast := fc.setType(&ast.CallExpr{ - Fun: fc.newIdentFor(exprType.Obj()), - Lparen: e.Lbrace, - Args: []ast.Expr{rewritten}, - Rparen: e.Rbrace, - }, exprType) - rewritten = typeCast + rewritten = fc.typeCast(rewritten, fc.newIdentFor(exprType.Obj())) } return fc.translateExpr(rewritten) } @@ -966,7 +956,7 @@ func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { _, pointerExpected := methodsRecvType.(*types.Pointer) if !isPointer && pointerExpected { recvType = types.NewPointer(recvType) - x = fc.setType(&ast.UnaryExpr{Op: token.AND, X: x}, recvType) + x = fc.takeAddress(x) } if isPointer && !pointerExpected { x = fc.setType(x, methodsRecvType) diff --git a/compiler/utils.go b/compiler/utils.go index cfca0782b..6d6985a32 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -451,6 +451,14 @@ func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { return astutil.SetType(fc.pkgCtx.Info.Info, t, e) } +func (fc *funcContext) typeCast(e ast.Expr, typeExpr ast.Expr) *ast.CallExpr { + return astutil.TypeCast(fc.pkgCtx.Info.Info, e, typeExpr) +} + +func (fc *funcContext) takeAddress(e ast.Expr) *ast.UnaryExpr { + return astutil.TakeAddress(fc.pkgCtx.Info.Info, e) +} + func (fc *funcContext) pkgVar(pkg *types.Package) string { if pkg == fc.pkgCtx.Pkg { return "$pkg" From 745110ed714c5b842d391c4bde434056cb91edaa Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 25 Jun 2023 18:19:26 +0100 Subject: [PATCH 38/83] Fix a compiler panic caused by a composite literal of a type param type. Prior to this change, the compiler would panic when encountering the following code: ```go type S struct { f int } func a[P S]() { _ = P{} } ``` Type param can be used in a composite literal expression when its core type (https://go.dev/ref/spec#Core_types) is defined. With this change, we initialize the literal value as a core type of the type param (`S` in the example above) and then explicitly cast it to the type `P`. The two-step process is necessary because gopherjs doesn't know a concrete type of a type parameter at compiler time, and we must handle type conversion dynamically at runtime. Technically, this doesn't work _quite_ correctly yet, since I haven't yel implemented type conversion for type params, but it will once that's done. --- compiler/expressions.go | 18 +++++++++++++++--- compiler/typesutil/coretype.go | 20 ++++++++++++++++++++ compiler/utils.go | 4 ++-- tests/gorepo/run.go | 1 - 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 compiler/typesutil/coretype.go diff --git a/compiler/expressions.go b/compiler/expressions.go index 0e13bcdb3..0d78c6597 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -100,12 +100,24 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { switch e := expr.(type) { case *ast.CompositeLit: + if exprType, ok := exprType.(*types.TypeParam); ok { + // Composite literals can be used with a type parameter if it has a core + // type. However, because at compile time we don't know the concrete type + // the type parameter will take, we initialize the literal with the core + // type and cast it to the a type denoted by the type param at runtime. + litValue := fc.setType(&ast.CompositeLit{ + Elts: e.Elts, + }, typesutil.CoreType(exprType)) + cast := fc.typeCastExpr(litValue, fc.newIdentFor(exprType.Obj())) + return fc.translateExpr(cast) + } + if ptrType, isPointer := exprType.Underlying().(*types.Pointer); isPointer { // Go automatically treats `[]*T{{}}` as `[]*T{&T{}}`, in which case the // inner composite literal `{}` would has a pointer type. To make sure the // type conversion is handled correctly, we generate the explicit AST for // this. - var rewritten ast.Expr = fc.takeAddress(fc.setType(&ast.CompositeLit{ + var rewritten ast.Expr = fc.takeAddressExpr(fc.setType(&ast.CompositeLit{ Elts: e.Elts, }, ptrType.Elem())) @@ -115,7 +127,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { // _ = []PS{{}} // In that case the value corresponding to the inner literal `{}` is // initialized as `&S{}` and then converted to `PS`: `[]PS{PS(&S{})}`. - rewritten = fc.typeCast(rewritten, fc.newIdentFor(exprType.Obj())) + rewritten = fc.typeCastExpr(rewritten, fc.newIdentFor(exprType.Obj())) } return fc.translateExpr(rewritten) } @@ -956,7 +968,7 @@ func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { _, pointerExpected := methodsRecvType.(*types.Pointer) if !isPointer && pointerExpected { recvType = types.NewPointer(recvType) - x = fc.takeAddress(x) + x = fc.takeAddressExpr(x) } if isPointer && !pointerExpected { x = fc.setType(x, methodsRecvType) diff --git a/compiler/typesutil/coretype.go b/compiler/typesutil/coretype.go new file mode 100644 index 000000000..bd366c9b8 --- /dev/null +++ b/compiler/typesutil/coretype.go @@ -0,0 +1,20 @@ +package typesutil + +import ( + "go/types" + _ "unsafe" // for go:linkname +) + +// Currently go/types doesn't offer a public API to determine type's core type. +// Instead of importing a third-party reimplementation, I opted to hook into +// the unexported implementation go/types already has. +// +// If https://github.com/golang/go/issues/60994 gets accepted, we will be able +// to switch to the official API. + +// CoreType of the given type, or nil of it has no core type. +// https://go.dev/ref/spec#Core_types +func CoreType(t types.Type) types.Type { return coreTypeImpl(t) } + +//go:linkname coreTypeImpl go/types.coreType +func coreTypeImpl(t types.Type) types.Type diff --git a/compiler/utils.go b/compiler/utils.go index 6d6985a32..30ae159ae 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -451,11 +451,11 @@ func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { return astutil.SetType(fc.pkgCtx.Info.Info, t, e) } -func (fc *funcContext) typeCast(e ast.Expr, typeExpr ast.Expr) *ast.CallExpr { +func (fc *funcContext) typeCastExpr(e ast.Expr, typeExpr ast.Expr) *ast.CallExpr { return astutil.TypeCast(fc.pkgCtx.Info.Info, e, typeExpr) } -func (fc *funcContext) takeAddress(e ast.Expr) *ast.UnaryExpr { +func (fc *funcContext) takeAddressExpr(e ast.Expr) *ast.UnaryExpr { return astutil.TakeAddress(fc.pkgCtx.Info.Info, e) } diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 25de21109..176461f07 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -172,7 +172,6 @@ var knownFails = map[string]failReason{ "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50833.go": {category: generics, desc: "undiagnosed: compiler panic triggered by a composite literal"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, From 3102673275753079de4194e9c4f8d62206d63104 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 25 Jun 2023 20:17:14 +0100 Subject: [PATCH 39/83] Fix taking a pointer to array/slice element of a typeparam type. Another case of pointer-to-struct being a special case in GopherJS. Both a struct and a pointer to it are essentially represented by the same JS object (due to the pass-by-reference semantics JS objects have), so wrapping it into an extra pointer breaks method calls. Because for type params we can't know whether the concrete type will be a struct or not at compile time, we have to make the check in the runtime. --- compiler/prelude/types.js | 4 ++++ tests/gorepo/run.go | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 6ac00f658..4c91163db 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -661,6 +661,10 @@ var $newDataPointer = function (data, constructor) { }; var $indexPtr = function (array, index, constructor) { + if (constructor.kind == $kindPtr && constructor.elem.kind == $kindStruct) { + // Pointer to a struct is represented by the underlying object itself, no wrappers needed. + return array[index] + } if (array.buffer) { // Pointers to the same underlying ArrayBuffer share cache. var cache = array.buffer.$ptr = array.buffer.$ptr || {}; diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 176461f07..14878a1a5 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -183,7 +183,6 @@ var knownFails = map[string]failReason{ "typeparam/ordered.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/orderedmap.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/sets.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/settable.go": {category: generics, desc: "undiagnosed: len() returns an invalid value on a generic function result"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/subdict.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, From 21f89d316e06af97bcb2fd8d48e7cc929d4e2344 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 25 Jun 2023 20:29:09 +0100 Subject: [PATCH 40/83] Diagnose typeparam/issue51733.go test failure. The test fails because gopherjs doesn't support unsafe well, particularly arbitrary conversions from uintptr to type pointers. The error is not related to type params. In fact, is the original pointer comes from a valid struct instance, the test passes. --- tests/gorepo/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 14878a1a5..e69d9e016 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -149,6 +149,7 @@ var knownFails = map[string]failReason{ "fixedbugs/issue48898.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1128"}, "fixedbugs/issue48536.go": {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"}, "fixedbugs/issue53600.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, + "typeparam/issue51733.go": {category: usesUnsupportedPackage, desc: "unsafe: uintptr to struct pointer conversion is unsupported"}, // Failures related to the lack of generics support. Ideally, this section // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is @@ -175,7 +176,6 @@ var knownFails = map[string]failReason{ "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue51733.go": {category: generics, desc: "undiagnosed: unsafe.Pointer to struct pointer conversion"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/metrics.go": {category: generics, desc: "missing support for the comparable type constraint"}, From 9d4212ecca993ea524feb76805e6d162a69d18f2 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 12 Aug 2023 21:44:03 +0100 Subject: [PATCH 41/83] Foundations of type conversion support for type params. Since GopherJS doesn't generate instantiation-specific code for generic functions and types, we have to do conversion at runtime. This commit creates basic infrastructure for such conversion, and uses int64 and uint64 types as a first demonstration of concept. Each type `T` provides a `T.convertFrom` method, which accepts a value of any compatible type `Q` and returns a corresponding value of type `T`. This conversion will be less efficient compared to non-generic code, since we have to branch based on the input type every time we do the conversion, but this is the price for more compact code. --- compiler/expressions.go | 42 ++++++++------ compiler/prelude/types.js | 89 +++++++++++++++++++++++++++++ tests/typeparams/conversion_test.go | 75 ++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 tests/typeparams/conversion_test.go diff --git a/compiler/expressions.go b/compiler/expressions.go index 0d78c6597..f141aafa1 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1135,13 +1135,21 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type } } - switch t := desiredType.Underlying().(type) { + _, fromTypeParam := exprType.(*types.TypeParam) + _, toTypeParam := desiredType.(*types.TypeParam) + if fromTypeParam || toTypeParam { + // Conversion from or to a type param can only be done at runtime, since the + // concrete type is not known to the compiler at compile time. + return fc.formatExpr("%s.convertFrom(%s.wrap(%e))", fc.typeName(desiredType), fc.typeName(exprType), expr) + } + + switch dst := desiredType.Underlying().(type) { case *types.Basic: switch { - case isInteger(t): + case isInteger(dst): basicExprType := exprType.Underlying().(*types.Basic) switch { - case is64Bit(t): + case is64Bit(dst): if !is64Bit(basicExprType) { if basicExprType.Kind() == types.Uintptr { // this might be an Object returned from reflect.Value.Pointer() return fc.formatExpr("new %1s(0, %2e.constructor === Number ? %2e : 1)", fc.typeName(desiredType), expr) @@ -1150,25 +1158,25 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type } return fc.formatExpr("new %1s(%2h, %2l)", fc.typeName(desiredType), expr) case is64Bit(basicExprType): - if !isUnsigned(t) && !isUnsigned(basicExprType) { - return fc.fixNumber(fc.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t) + if !isUnsigned(dst) && !isUnsigned(basicExprType) { + return fc.fixNumber(fc.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), dst) } - return fc.fixNumber(fc.formatExpr("%s.$low", fc.translateExpr(expr)), t) + return fc.fixNumber(fc.formatExpr("%s.$low", fc.translateExpr(expr)), dst) case isFloat(basicExprType): return fc.formatParenExpr("%e >> 0", expr) case types.Identical(exprType, types.Typ[types.UnsafePointer]): return fc.translateExpr(expr) default: - return fc.fixNumber(fc.translateExpr(expr), t) + return fc.fixNumber(fc.translateExpr(expr), dst) } - case isFloat(t): - if t.Kind() == types.Float32 && exprType.Underlying().(*types.Basic).Kind() == types.Float64 { + case isFloat(dst): + if dst.Kind() == types.Float32 && exprType.Underlying().(*types.Basic).Kind() == types.Float64 { return fc.formatExpr("$fround(%e)", expr) } return fc.formatExpr("%f", expr) - case isComplex(t): + case isComplex(dst): return fc.formatExpr("new %1s(%2r, %2i)", fc.typeName(desiredType), expr) - case isString(t): + case isString(dst): value := fc.translateExpr(expr) switch et := exprType.Underlying().(type) { case *types.Basic: @@ -1187,7 +1195,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type default: panic(fmt.Sprintf("Unhandled conversion: %v\n", et)) } - case t.Kind() == types.UnsafePointer: + case dst.Kind() == types.UnsafePointer: if unary, isUnary := expr.(*ast.UnaryExpr); isUnary && unary.Op == token.AND { if indexExpr, isIndexExpr := unary.X.(*ast.IndexExpr); isIndexExpr { return fc.formatExpr("$sliceToNativeArray(%s)", fc.translateConversionToSlice(indexExpr.X, types.NewSlice(types.Typ[types.Uint8]))) @@ -1218,7 +1226,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type switch et := exprType.Underlying().(type) { case *types.Basic: if isString(et) { - if types.Identical(t.Elem().Underlying(), types.Typ[types.Rune]) { + if types.Identical(dst.Elem().Underlying(), types.Typ[types.Rune]) { return fc.formatExpr("new %s($stringToRunes(%e))", fc.typeName(desiredType), expr) } return fc.formatExpr("new %s($stringToBytes(%e))", fc.typeName(desiredType), expr) @@ -1234,7 +1242,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type break } - switch ptrElType := t.Elem().Underlying().(type) { + switch ptrElType := dst.Elem().Underlying().(type) { case *types.Array: // (*[N]T)(expr) — converting expr to a pointer to an array. if _, ok := exprType.Underlying().(*types.Slice); ok { return fc.formatExpr("$sliceToGoArray(%e, %s)", expr, fc.typeName(desiredType)) @@ -1249,7 +1257,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type // indeed pointing at a byte array. 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) + return fc.formatExpr("(%s = %e, %s = %e, %s, %s)", array, expr, target, fc.zeroValue(dst.Elem()), fc.loadStruct(array, target, ptrElType), target) } // Convert between structs of different types but identical layouts, // for example: @@ -1271,8 +1279,8 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type // TODO(nevkontakte): Are there any other cases that fall into this case? exprTypeElem := exprType.Underlying().(*types.Pointer).Elem() 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) + getterConv := fc.translateConversion(fc.setType(&ast.StarExpr{X: fc.newIdent(ptrVar, exprType)}, exprTypeElem), dst.Elem()) + setterConv := fc.translateConversion(fc.newIdent("$v", dst.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) case *types.Interface: diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 14f435d20..1e8d2825b 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -397,6 +397,53 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { $panic(new $String("invalid kind: " + kind)); } + /** + * convertFrom converts value src to the type typ. + * + * For wrapped types src must be a wrapped value, e.g. for int32 this must be an instance of + * the $Int32 class, rather than the bare JavaScript number. This is required to determine + * the original Go type to convert from. + * + * The returned value will be a representation of typ; for wrapped values it will be unwrapped; + * for example, conversion to int32 will return a bare JavaScript number. This is required + * to make results of type conversion expression consistent with any other expressions of the + * same type. + */ + typ.convertFrom = (src) => $convertIdentity(src, typ); + switch (kind) { + case $kindInt64: + case $kindUint64: + typ.convertFrom = (src) => $convertToInt64(src, typ); + break; + case $kindBool: + case $kindInt: + case $kindInt16: + case $kindInt32: + case $kindInt8: + case $kindUint: + case $kindUint16: + case $kindUint32: + case $kindUint8: + case $kindFloat32: + case $kindFloat64: + case $kindComplex128: + case $kindComplex64: + case $kindString: + case $kindArray: + case $kindSlice: + case $kindMap: + case $kindChan: + case $kindPtr: + case $kindUintptr: + case $kindUnsafePointer: + case $kindFunc: + case $kindInterface: + case $kindStruct: + break; + default: + $panic(new $String("invalid kind: " + kind)); + } + typ.id = $typeIDCounter; $typeIDCounter++; typ.size = size; @@ -792,3 +839,45 @@ var $assertType = (value, type, returnTuple) => { } return returnTuple ? [value, true] : value; }; + +/** + * Trivial type conversion function, which only accepts destination type identical to the src + * type. + * + * For wrapped types, src value must be wrapped, and the return value will be unwrapped. + */ +const $convertIdentity = (src, dstType) => { + const srcType = src.constructor; + if (srcType === dstType) { + // Same type, no conversion necessary. + return srcType.wrapped ? src.$val : src; + } + throw new Error(`unsupported conversion from ${srcType.string} to ${dstType.string}`); +}; + +/** + * Conversion to int64 and uint64 variants. + * + * dstType.kind must be either $kindInt64 or $kindUint64. For wrapped types, src + * value must be wrapped. The returned value is an object instantiated by the + * `dstType` constructor. + */ +const $convertToInt64 = (src, dstType) => { + const srcType = src.constructor; + if (srcType === dstType) { + return src.$val; + } + + switch (srcType.kind) { + case $kindInt64: + case $kindUint64: + return new dstType(src.$val.$high, src.$val.$low); + case $kindUintptr: + // GopherJS quirk: a uintptr value may be an object converted to + // uintptr from unsafe.Pointer. Since we don't have actual pointers, + // we pretend it's value is 1. + return new dstType(0, src.$val.constructor === Number ? src.$val : 1); + default: + return new dstType(0, src.$val); + } +}; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go new file mode 100644 index 000000000..302fed207 --- /dev/null +++ b/tests/typeparams/conversion_test.go @@ -0,0 +1,75 @@ +package typeparams_test + +import ( + "fmt" + "reflect" + "runtime" + "testing" +) + +type numeric interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~float32 | ~float64 | + ~uintptr +} + +type converter interface { + Src() any + Got() any + Want() any + Quirk() bool // Tests GopherJS-specific behavior. +} + +type numericConverter[srcType numeric, dstType numeric] struct { + src srcType + want dstType + quirk bool +} + +func (tc numericConverter[srcType, dstType]) Src() any { + return tc.src +} + +func (tc numericConverter[srcType, dstType]) Got() any { + return dstType(tc.src) +} + +func (tc numericConverter[srcType, dstType]) Want() any { + return tc.want +} + +func (tc numericConverter[srcType, dstType]) Quirk() bool { + return tc.quirk +} + +func TestConversion(t *testing.T) { + type i64 int64 + tests := []converter{ + // $convertToInt64 + numericConverter[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, + numericConverter[int64, uint64]{src: -0x8000000000000000, want: 0x8000000000000000}, + numericConverter[uint, int64]{src: 0xFFFFFFFF, want: 0xFFFFFFFF}, + numericConverter[uint64, int64]{src: 0xFFFFFFFFFFFFFFFF, want: -1}, + numericConverter[uint64, uint64]{src: 0xFFFFFFFFFFFFFFFF, want: 0xFFFFFFFFFFFFFFFF}, + numericConverter[uintptr, uint64]{src: 0xFFFFFFFF, want: 0xFFFFFFFF}, + numericConverter[uintptr, uint64]{src: reflect.ValueOf(&struct{}{}).Pointer(), want: 0x1, quirk: true}, + numericConverter[float32, int64]{src: 2e10, want: 20000000000}, + numericConverter[float64, int64]{src: 2e10, want: 20000000000}, + numericConverter[int64, i64]{src: 1, want: 1}, + numericConverter[i64, int64]{src: 1, want: 1}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%T", test), func(t *testing.T) { + if test.Quirk() && runtime.Compiler != "gopherjs" { + t.Skip("GopherJS-only test") + } + got := test.Got() + want := test.Want() + if !reflect.DeepEqual(want, got) { + t.Errorf("Want: %[1]T(%#[1]v) convert to %[2]T(%#[2]v). Got: %[3]T(%#[3]v)", test.Src(), want, got) + } + }) + } +} From ec2885fe47a38d2b1a5325b3cfed586e5054426a Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 12 Aug 2023 23:03:05 +0100 Subject: [PATCH 42/83] Add support for typeparam conversion to 8,16,32-bit integer types. --- compiler/prelude/types.js | 85 +++++++++++++++++++++++++++-- tests/typeparams/conversion_test.go | 23 ++++++++ 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 1e8d2825b..53417316d 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -415,15 +415,18 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindUint64: typ.convertFrom = (src) => $convertToInt64(src, typ); break; - case $kindBool: - case $kindInt: + case $kindInt8: case $kindInt16: case $kindInt32: - case $kindInt8: - case $kindUint: + case $kindInt: + case $kindUint8: case $kindUint16: case $kindUint32: - case $kindUint8: + case $kindUint: + case $kindUintptr: + typ.convertFrom = (src) => $convertToNativeInt(src, typ); + break; + case $kindBool: case $kindFloat32: case $kindFloat64: case $kindComplex128: @@ -434,7 +437,6 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindMap: case $kindChan: case $kindPtr: - case $kindUintptr: case $kindUnsafePointer: case $kindFunc: case $kindInterface: @@ -840,6 +842,49 @@ var $assertType = (value, type, returnTuple) => { return returnTuple ? [value, true] : value; }; +const $isSigned = (typ) => { + switch (typ.kind) { + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindInt64: + return true; + default: + return false; + } +} + +/** + * Truncate a JavaScript number `n` according to precision of the Go type `typ` + * it is supposed to represent. + */ +const $truncateNumber = (n, typ) => { + switch (typ.kind) { + case $kindInt8: + return n << 24 >> 24; + case $kindUint8: + return n << 24 >>> 24; + case $kindInt16: + return n << 16 >> 16; + case $kindUint16: + return n << 16 >>> 16; + case $kindInt32: + case $kindInt: + return n << 0 >> 0; + case $kindUint32: + case $kindUint: + case $kindUintptr: + return n << 0 >>> 0; + case $kindFloat32: + return $fround(n); + case $kindFloat64: + return n; + default: + $panic(new $String("invalid kind: " + kind)); + } +} + /** * Trivial type conversion function, which only accepts destination type identical to the src * type. @@ -880,4 +925,32 @@ const $convertToInt64 = (src, dstType) => { default: return new dstType(0, src.$val); } +}; + +/** + * Conversion to int and uint types of 32 bits or less. + * + * dstType.kind must be $kindInt{8,16,32} or $kindUint{8,16,32}. For wrapped + * types, src value must be wrapped. The return value will always be a bare + * JavaScript number, since all 32-or-less integers in GopherJS are considered + * wrapped types. + */ +const $convertToNativeInt = (src, dstType) => { + const srcType = src.constructor; + // Since we are returning a bare number, identical kinds means no actual + // conversion is required. + if (srcType.kind === dstType.kind) { + return src.$val; + } + + switch (srcType.kind) { + case $kindInt64: + case $kindUint64: + if ($isSigned(srcType) && $isSigned(dstType)) { // Carry over the sign. + return $truncateNumber(src.$val.$low + ((src.$val.$high >> 31) * 4294967296), dstType); + } + return $truncateNumber(src.$val.$low, dstType); + default: + return $truncateNumber(src.$val, dstType); + } }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 302fed207..cf9c28abd 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -2,6 +2,7 @@ package typeparams_test import ( "fmt" + "math" "reflect" "runtime" "testing" @@ -45,6 +46,7 @@ func (tc numericConverter[srcType, dstType]) Quirk() bool { func TestConversion(t *testing.T) { type i64 int64 + type i32 int32 tests := []converter{ // $convertToInt64 numericConverter[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, @@ -58,6 +60,27 @@ func TestConversion(t *testing.T) { numericConverter[float64, int64]{src: 2e10, want: 20000000000}, numericConverter[int64, i64]{src: 1, want: 1}, numericConverter[i64, int64]{src: 1, want: 1}, + // $convertToNativeInt + numericConverter[int64, int32]{src: math.MaxInt64, want: -1}, + numericConverter[int64, int32]{src: -100, want: -100}, + numericConverter[int64, int32]{src: 0x00C0FFEE4B1D4B1D, want: 0x4B1D4B1D}, + numericConverter[int32, int16]{src: 0x0BAD4B1D, want: 0x4B1D}, + numericConverter[int16, int8]{src: 0x4B1D, want: 0x1D}, + numericConverter[uint64, uint32]{src: 0xDEADC0DE00C0FFEE, want: 0x00C0FFEE}, + numericConverter[uint32, uint16]{src: 0xDEADC0DE, want: 0xC0DE}, + numericConverter[uint16, uint8]{src: 0xC0DE, want: 0xDE}, + numericConverter[float32, int32]{src: 12345678.12345678, want: 12345678}, + numericConverter[float32, int16]{src: 12345678.12345678, want: 24910}, + numericConverter[float64, int32]{src: 12345678.12345678, want: 12345678}, + numericConverter[float64, int16]{src: 12345678.12345678, want: 24910}, + numericConverter[int32, int]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConverter[uint32, uint]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConverter[uint32, uintptr]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConverter[int32, i32]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConverter[i32, int32]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConverter[uint32, int32]{src: 0xFFFFFFFF, want: -1}, + numericConverter[uint16, int16]{src: 0xFFFF, want: -1}, + numericConverter[uint8, int8]{src: 0xFF, want: -1}, } for _, test := range tests { From 2dc3b2988221befcfc1b87f2e249b1d652915e2c Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 10 Sep 2023 17:46:18 +0100 Subject: [PATCH 43/83] Implement support for typeparam conversion to 32 and 64-bit floats. --- compiler/prelude/types.js | 35 ++++++++++++++++++++++++++++- tests/typeparams/conversion_test.go | 13 +++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 53417316d..443d60c71 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -426,9 +426,11 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindUintptr: typ.convertFrom = (src) => $convertToNativeInt(src, typ); break; - case $kindBool: case $kindFloat32: case $kindFloat64: + typ.convertFrom = (src) => $convertToFloat(src, typ); + break; + case $kindBool: case $kindComplex128: case $kindComplex64: case $kindString: @@ -953,4 +955,35 @@ const $convertToNativeInt = (src, dstType) => { default: return $truncateNumber(src.$val, dstType); } +}; + +/** + * Conversion to floating point types. + * + * dstType.kind must be $kindFloat{32,64}. For wrapped types, src value must be + * wrapped. Returned value will always be a bare JavaScript number, since all + * floating point numbers in GopherJS are considered wrapped types. + */ +const $convertToFloat = (src, dstType) => { + const srcType = src.constructor; + // Since we are returning a bare number, identical kinds means no actual + // conversion is required. + if (srcType.kind === dstType.kind) { + return src.$val; + } + + if (dstType.kind == $kindFloat32 && srcType.kind == $kindFloat64) { + return $fround(src.$val); + } + + switch (srcType.kind) { + case $kindInt64: + case $kindUint64: + const val = $flatten64(src.$val); + return (dstType.kind == $kindFloat32) ? $fround(val) : val; + case $kindFloat64: + return (dstType.kind == $kindFloat32) ? $fround(src.$val) : src.$val; + default: + return src.$val; + } }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index cf9c28abd..cdea800ea 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -47,6 +47,8 @@ func (tc numericConverter[srcType, dstType]) Quirk() bool { func TestConversion(t *testing.T) { type i64 int64 type i32 int32 + type f64 float64 + type f32 float32 tests := []converter{ // $convertToInt64 numericConverter[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, @@ -81,6 +83,17 @@ func TestConversion(t *testing.T) { numericConverter[uint32, int32]{src: 0xFFFFFFFF, want: -1}, numericConverter[uint16, int16]{src: 0xFFFF, want: -1}, numericConverter[uint8, int8]{src: 0xFF, want: -1}, + // $convertToFloat + numericConverter[float64, float32]{src: 12345678.1234567890, want: 12345678.0}, + numericConverter[int64, float32]{src: 123456789, want: 123456792.0}, + numericConverter[int32, float32]{src: 12345678, want: 12345678.0}, + numericConverter[f32, float32]{src: 12345678.0, want: 12345678.0}, + numericConverter[float32, f32]{src: 12345678.0, want: 12345678.0}, + numericConverter[float32, float64]{src: 1234567.125000, want: 1234567.125000}, + numericConverter[int64, float64]{src: 12345678, want: 12345678.0}, + numericConverter[int32, float64]{src: 12345678, want: 12345678.0}, + numericConverter[f64, float64]{src: 12345678.0, want: 12345678.0}, + numericConverter[float64, f64]{src: 12345678.0, want: 12345678.0}, } for _, test := range tests { From a1354015fda94f624035a12b9d231938a079cf6e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 27 Oct 2023 21:28:37 +0100 Subject: [PATCH 44/83] Fix minified variable name collision in a type generic factory function. Prior to this change we always canonicalized method's type parameter names to their receiver counterparts. This was mainly necessary to avoid errors when we needed to reference method's type parameters inside a type generic factory function, e.g. when initializing method's reflection metadata. Such implementation, however, was causing a bug when minification is enabled: 1. Methods are compiled first, variable names are allocated to their type parameters and the association is stored in the package context. 2. The allocated variable name is marked as in use within the mathod's functionContext, up to the enclosing generic factory function context, but not at the package level. 3. Type definitions are compiled second. Because the type's typeparams are already associated with variable names, these variable names are used. The process of allocating a new variable is omitted and the previously allocated name is not marked as allocated within the type's generic factory function. 4. A new variable inside the type's generic factory function is allocated, and its name may collide with the type param's variable leading to errors. With this change, canonicalization is only done if we are outside of the method's functionContext. That way no variable name is associated with type's typeparam objects while compiling the method, and the JS variable is correctly allocated and marked as in use. --- compiler/utils.go | 9 +++++- tests/typeparams/low_level_test.go | 49 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/typeparams/low_level_test.go diff --git a/compiler/utils.go b/compiler/utils.go index 30ae159ae..7372aaf35 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -572,7 +572,14 @@ func (fc *funcContext) typeName(ty types.Type) string { } return fmt.Sprintf("(%s(%s))", fc.objectName(t.Obj()), strings.Join(args, ",")) case *types.TypeParam: - return fc.objectName(fc.pkgCtx.canonicalTypeParams.Lookup(t).Obj()) + o := t.Obj() + if fc.funcObject == nil { + // If the current context is not associated with a function or method, + // we processing type declaration, so we canonicalize method's type + // parameter names to their receiver counterparts. + o = fc.pkgCtx.canonicalTypeParams.Lookup(t).Obj() + } + return fc.objectName(o) case *types.Interface: if t.Empty() { return "$emptyInterface" diff --git a/tests/typeparams/low_level_test.go b/tests/typeparams/low_level_test.go new file mode 100644 index 000000000..c5a6a7259 --- /dev/null +++ b/tests/typeparams/low_level_test.go @@ -0,0 +1,49 @@ +package typeparams + +import "testing" + +// This file contains test cases for low-level details of typeparam +// implementation like variable name assignment. + +type TypeParamNameMismatch[T any] struct{} + +func (TypeParamNameMismatch[T1]) M(_ T1) {} + +func TestTypeParamNameMismatch(t *testing.T) { + // This test case exercises the case when the same typeparam is named + // differently between the struct definition and one of its methods. GopherJS + // must allocate the same JS variable name to both instances of the type param + // in order to make it possible for the reflection method data to be evaluated + // within the type's generic factory function. + + a := TypeParamNameMismatch[int]{} + a.M(0) // Make sure the method is not eliminated as dead code. +} + +type ( + TypeParamVariableCollision1[T any] struct{} + TypeParamVariableCollision2[T any] struct{} + TypeParamVariableCollision3[T any] struct{} +) + +func (TypeParamVariableCollision1[T]) M() {} +func (TypeParamVariableCollision2[T]) M() {} +func (TypeParamVariableCollision3[T]) M() {} + +func TestTypeParamVariableCollision(t *testing.T) { + // This test case exercises a situation when in minified mode the variable + // name that gets assigned to the type parameter in the method's generic + // factory function collides with a different variable in the type's generic + // factory function. The bug occurred because the JS variable name allocated + // to the *types.TypeName object behind a type param within the method's + // factory function was not marked as used within type's factory function. + + // Note: to trigger the bug, a package should contain multiple generic types, + // so that sequentially allocated minified variable names get far enough to + // cause the collision. + + // Make sure types and methods are not eliminated as dead code. + TypeParamVariableCollision1[int]{}.M() + TypeParamVariableCollision2[int]{}.M() + TypeParamVariableCollision3[int]{}.M() +} From 4bc6256a30dfed88e79a852789df0f6fcb2c0857 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 10 Sep 2023 18:07:07 +0100 Subject: [PATCH 45/83] Implement typeparam conversion to complex number types. --- compiler/prelude/types.js | 19 ++++++++++++++- tests/typeparams/conversion_test.go | 38 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 443d60c71..c54fd25a5 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -430,9 +430,11 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindFloat64: typ.convertFrom = (src) => $convertToFloat(src, typ); break; - case $kindBool: case $kindComplex128: case $kindComplex64: + typ.convertFrom = (src) => $convertToComplex(src, typ); + break; + case $kindBool: case $kindString: case $kindArray: case $kindSlice: @@ -986,4 +988,19 @@ const $convertToFloat = (src, dstType) => { default: return src.$val; } +}; + +/** + * Conversion to complex types. + * + * dstType.kind must me $kindComplex{64,128}. Src must be another complex type. + * Returned value will always be an oject created by the dstType constructor. + */ +const $convertToComplex = (src, dstType) => { + const srcType = src.constructor; + if (srcType === dstType) { + return src; + } + + return new dstType(src.$real, src.$imag); }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index cdea800ea..487645d91 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -44,11 +44,39 @@ func (tc numericConverter[srcType, dstType]) Quirk() bool { return tc.quirk } +type complex interface { + ~complex64 | ~complex128 +} + +type complexConverter[srcType complex, dstType complex] struct { + src srcType + want dstType +} + +func (tc complexConverter[srcType, dstType]) Src() any { + return tc.src +} + +func (tc complexConverter[srcType, dstType]) Got() any { + return dstType(tc.src) +} + +func (tc complexConverter[srcType, dstType]) Want() any { + return tc.want +} + +func (tc complexConverter[srcType, dstType]) Quirk() bool { + return false +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 type f64 float64 type f32 float32 + type c64 complex64 + type c128 complex128 + tests := []converter{ // $convertToInt64 numericConverter[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, @@ -94,6 +122,11 @@ func TestConversion(t *testing.T) { numericConverter[int32, float64]{src: 12345678, want: 12345678.0}, numericConverter[f64, float64]{src: 12345678.0, want: 12345678.0}, numericConverter[float64, f64]{src: 12345678.0, want: 12345678.0}, + // $convertToComplex + complexConverter[complex64, complex128]{src: 1 + 1i, want: 1 + 1i}, + complexConverter[complex128, complex64]{src: 1 + 1i, want: 1 + 1i}, + complexConverter[complex128, c128]{src: 1 + 1i, want: 1 + 1i}, + complexConverter[complex64, c64]{src: 1 + 1i, want: 1 + 1i}, } for _, test := range tests { @@ -103,6 +136,11 @@ func TestConversion(t *testing.T) { } got := test.Got() want := test.Want() + + if reflect.TypeOf(got) != reflect.TypeOf(want) { + t.Errorf("Want: converted type is: %v. Got: %v.", reflect.TypeOf(want), reflect.TypeOf(got)) + } + if !reflect.DeepEqual(want, got) { t.Errorf("Want: %[1]T(%#[1]v) convert to %[2]T(%#[2]v). Got: %[3]T(%#[3]v)", test.Src(), want, got) } From d2cacfb9e3fb7e731350b57a01a1d7b731bcf39b Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 10 Sep 2023 20:33:30 +0100 Subject: [PATCH 46/83] Add support for conversion from typeparam to string. --- compiler/prelude/types.js | 48 ++++++++++++++++++++++++++++- tests/typeparams/conversion_test.go | 41 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index c54fd25a5..0ab751242 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -434,8 +434,13 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindComplex64: typ.convertFrom = (src) => $convertToComplex(src, typ); break; - case $kindBool: case $kindString: + typ.convertFrom = (src) => $convertToString(src, typ); + break; + case $kindUnsafePointer: + typ.convertFrom = (src) => $convertToUnsafePtr(src, typ); + break; + case $kindBool: case $kindArray: case $kindSlice: case $kindMap: @@ -1003,4 +1008,45 @@ const $convertToComplex = (src, dstType) => { } return new dstType(src.$real, src.$imag); +}; + +/** + * Conversion to string types. + * + * dstType.kind must be $kindString. For wrapped types, src value must be + * wrapped. Returned value will always be a bare JavaScript string. + */ +const $convertToString = (src, dstType) => { + const srcType = src.constructor; + if (srcType === dstType) { + return src.$val; + } + + switch (srcType.kind) { + case $kindInt64: + case $kindUint64: + return $encodeRune(src.$val.$low); + case $kindInt32: + case $kindInt16: + case $kindInt8: + case $kindInt: + case $kindUint32: + case $kindUint16: + case $kindUint8: + case $kindUint: + case $kindUintptr: + return $encodeRune(src.$val); + case $kindString: + return src.$val; + case $kindSlice: + if (srcType.elem.kind === $kindInt32) { // Runes are int32. + return $runesToString(src.$val); + } else if (srcType.elem.kind === $kindUint8) { // Bytes are uint8. + return $bytesToString(src.$val); + } + break; + + } + + throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 487645d91..ccf4ad783 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -69,6 +69,39 @@ func (tc complexConverter[srcType, dstType]) Quirk() bool { return false } +type stringLike interface { + // Ideally, we would test conversions from all integer types. unfortunately, + // that trips up the stringintconv check in `go vet` that is ran by `go test` + // by default. Unfortunately, there is no way to selectively suppress that + // check. + // ~int | ~int8 | ~int16 | ~int32 | ~int64 | + // ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + // ~uintptr | + byte | rune | + ~[]byte | ~[]rune | ~string +} + +type stringConverter[srcType stringLike, dstType ~string] struct { + src srcType + want dstType +} + +func (tc stringConverter[srcType, dstType]) Src() any { + return tc.src +} + +func (tc stringConverter[srcType, dstType]) Got() any { + return dstType(tc.src) +} + +func (tc stringConverter[srcType, dstType]) Want() any { + return tc.want +} + +func (tc stringConverter[srcType, dstType]) Quirk() bool { + return false +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 @@ -76,6 +109,7 @@ func TestConversion(t *testing.T) { type f32 float32 type c64 complex64 type c128 complex128 + type str string tests := []converter{ // $convertToInt64 @@ -127,6 +161,13 @@ func TestConversion(t *testing.T) { complexConverter[complex128, complex64]{src: 1 + 1i, want: 1 + 1i}, complexConverter[complex128, c128]{src: 1 + 1i, want: 1 + 1i}, complexConverter[complex64, c64]{src: 1 + 1i, want: 1 + 1i}, + // $convertToString + stringConverter[str, string]{src: "abc", want: "abc"}, + stringConverter[string, str]{src: "abc", want: "abc"}, + stringConverter[rune, string]{src: 'a', want: "a"}, + stringConverter[byte, string]{src: 'a', want: "a"}, + stringConverter[[]byte, string]{src: []byte{'a', 'b', 'c'}, want: "abc"}, + stringConverter[[]rune, string]{src: []rune{'a', 'b', 'c'}, want: "abc"}, } for _, test := range tests { From 6e35fd458e4705e6d2dcc977a64ecf90e0ec7986 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 10 Sep 2023 20:45:49 +0100 Subject: [PATCH 47/83] Throw an error on conversion between a typeparam and unsafe.Pointer. Unsafe pointers are not well supported by GopherJS and whatever support exists is not well documented. Implementing this conversion will require a great deal of great reverse engineering, and I'm not sure anyone will ever need this. So for now, attempting such a conversion will simply throw an "unimplemented" exception. --- compiler/prelude/types.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 0ab751242..74470200e 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -446,7 +446,6 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindMap: case $kindChan: case $kindPtr: - case $kindUnsafePointer: case $kindFunc: case $kindInterface: case $kindStruct: @@ -1049,4 +1048,15 @@ const $convertToString = (src, dstType) => { } throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); +}; + +/** + * Convert to unsafe.Pointer. + */ +const $convertToUnsafePtr = (src, dstType) => { + // Unsafe pointers are not well supported by GopherJS and whatever support + // exists is not well documented. Implementing this conversion will require + // a great deal of great reverse engineering, and I'm not sure anyone will + // ever need this. + throw new Error(`Conversion between a typeparam and unsafe.Pointer is not implemented.`) }; \ No newline at end of file From d19a766310b6af51ae2557b83641a10cbfd4a7bc Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 10 Sep 2023 21:05:00 +0100 Subject: [PATCH 48/83] Add support for typeparam coversion to booleans. --- compiler/prelude/types.js | 11 +++++++++++ tests/typeparams/conversion_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 74470200e..cc46000b6 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -441,6 +441,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToUnsafePtr(src, typ); break; case $kindBool: + typ.convertFrom = (src) => $convertToBool(src, typ); case $kindArray: case $kindSlice: case $kindMap: @@ -1059,4 +1060,14 @@ const $convertToUnsafePtr = (src, dstType) => { // a great deal of great reverse engineering, and I'm not sure anyone will // ever need this. throw new Error(`Conversion between a typeparam and unsafe.Pointer is not implemented.`) +}; + +/** + * Convert to boolean types. + * + * dstType.kind must be $kindBool. Src must be a wrapped boolean value. Returned + * value will always be a bare JavaScript boolean. + */ +const $convertToBool = (src, dstType) => { + return src.$val; }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index ccf4ad783..08c302fce 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -102,6 +102,27 @@ func (tc stringConverter[srcType, dstType]) Quirk() bool { return false } +type boolConverter[srcType ~bool, dstType ~bool] struct { + src srcType + want dstType +} + +func (tc boolConverter[srcType, dstType]) Src() any { + return tc.src +} + +func (tc boolConverter[srcType, dstType]) Got() any { + return dstType(tc.src) +} + +func (tc boolConverter[srcType, dstType]) Want() any { + return tc.want +} + +func (tc boolConverter[srcType, dstType]) Quirk() bool { + return false +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 @@ -110,6 +131,7 @@ func TestConversion(t *testing.T) { type c64 complex64 type c128 complex128 type str string + type b bool tests := []converter{ // $convertToInt64 @@ -168,6 +190,11 @@ func TestConversion(t *testing.T) { stringConverter[byte, string]{src: 'a', want: "a"}, stringConverter[[]byte, string]{src: []byte{'a', 'b', 'c'}, want: "abc"}, stringConverter[[]rune, string]{src: []rune{'a', 'b', 'c'}, want: "abc"}, + // $convertToBool + boolConverter[b, bool]{src: true, want: true}, + boolConverter[b, bool]{src: false, want: false}, + boolConverter[bool, b]{src: true, want: true}, + boolConverter[bool, b]{src: false, want: false}, } for _, test := range tests { From ecfc04574393ed510a7fd8a05031e5034b557ff6 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 25 Oct 2023 21:28:05 +0100 Subject: [PATCH 49/83] Implement typeparam conversion to interface. In general, this conversion is almost a no-op, except that previously *js.Object instances were not considered a wrapped type (which is incorrect) and were not properly wrapped when converting to an interface. Other than that, GopherJS's representation for an interface value is actually its concrete value. Testing the conversion is a bit tricky, since we can't use reflect because it relies on conversion to `any` interface being correct - something we can't take for granted. To make testing possible, I've refactored the tests a bit and use low-level JS manipulation to inspect values that result from conversion. --- compiler/prelude/jsmapping.js | 2 +- compiler/prelude/types.js | 27 ++- tests/js_test.go | 18 +- tests/typeparams/conversion_test.go | 308 +++++++++++++++------------- 4 files changed, 207 insertions(+), 148 deletions(-) diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index b22454bc3..2225d9fa4 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -1,4 +1,4 @@ -var $jsObjectPtr, $jsErrorPtr; +var $jsObjectPtr, $jsErrorPtr; // Initialized by init() in the runtime package. var $needsExternalization = t => { switch (t.kind) { diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index cc46000b6..362921a61 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -220,6 +220,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { this.$val = this; }; typ.wrap = (v) => v; + typ.wrapped = false; typ.keyFor = $idKey; typ.init = elem => { typ.elem = elem; @@ -253,6 +254,15 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.wrapped = true; typ.wrap = (v) => new typ(v); typ.ptr = $newType(4, $kindPtr, "*" + string, false, pkg, exported, constructor); + if (string === "js.Object" && pkg === "github.com/gopherjs/gopherjs/js") { + // *js.Object is a special case because unlike other pointers it + // passes around a raw JS object without any GopherJS-specific + // metadata. As a result, it must be wrapped to preserve type + // information whenever it's used through an interface or type + // param. However, it's now a "wrapped" type in a complete sense, + // because it's handling is mostly special-cased at the compiler level. + typ.ptr.wrap = (v) => new typ.ptr(v); + } typ.ptr.elem = typ; typ.ptr.prototype.$get = function () { return this; }; typ.ptr.prototype.$set = function (v) { typ.copy(this, v); }; @@ -442,13 +452,16 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { break; case $kindBool: typ.convertFrom = (src) => $convertToBool(src, typ); + break; + case $kindInterface: + typ.convertFrom = (src) => $convertToInterface(src, typ); + break; case $kindArray: case $kindSlice: case $kindMap: case $kindChan: case $kindPtr: case $kindFunc: - case $kindInterface: case $kindStruct: break; default: @@ -1045,7 +1058,6 @@ const $convertToString = (src, dstType) => { return $bytesToString(src.$val); } break; - } throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); @@ -1070,4 +1082,15 @@ const $convertToUnsafePtr = (src, dstType) => { */ const $convertToBool = (src, dstType) => { return src.$val; +}; + +/** + * Convert any type to an interface value. + * + * dstType.kind must be $kindInterface. For wrapped types, src value must be + * wrapped. Since GopherJS represents interfaces as wrapped values of the original + * type, the returned value is always src. + */ +const $convertToInterface = (src, dstType) => { + return src; }; \ No newline at end of file diff --git a/tests/js_test.go b/tests/js_test.go index 7680749dc..3e69849f1 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -326,6 +326,7 @@ func TestInternalizeStruct(t *testing.T) { t.Errorf("Mismatch (-want +got):\n%s", diff) } } + func TestInternalizeStructUnexportedFields(t *testing.T) { type Person struct { Name string @@ -731,19 +732,20 @@ func TestReflection(t *testing.T) { s := S{o} v := reflect.ValueOf(&s).Elem() - if v.Field(0).Interface().(*js.Object).Get("answer").Int() != 42 { - t.Fail() + println(v.Field(0).Interface()) + if got := v.Field(0).Interface().(*js.Object).Get("answer").Int(); got != 42 { + t.Errorf("Got: Accessing JS object property via reflect.Value.Interface() returned %v. Want: 42.", got) } - if v.Field(0).MethodByName("Get").Call([]reflect.Value{reflect.ValueOf("answer")})[0].Interface().(*js.Object).Int() != 42 { - t.Fail() + if got := v.Field(0).MethodByName("Get").Call([]reflect.Value{reflect.ValueOf("answer")})[0].Interface().(*js.Object).Int(); got != 42 { + t.Errorf("Got: accessing JS object property via reflect.Value.Call('Get') returned %v. Want: 42.", got) } v.Field(0).Set(reflect.ValueOf(js.Global.Call("eval", "({ answer: 100 })"))) - if s.Field.Get("answer").Int() != 100 { - t.Fail() + if got := s.Field.Get("answer").Int(); got != 100 { + t.Errorf("Got: setting a field to JS object via reflection failed, got %s. Want: 100.", got) } - if fmt.Sprintf("%+v", s) != "{Field:[object Object]}" { - t.Fail() + if got, want := fmt.Sprintf("%+v", s), "{Field:[object Object]}"; got != want { + t.Errorf("Got: Formatting JS object returned %q. Want: %q.", got, want) } } diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 08c302fce..b59d5cd6c 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -6,8 +6,36 @@ import ( "reflect" "runtime" "testing" + + "github.com/gopherjs/gopherjs/js" ) +// checkConversion is a general type conversion result checker. +// +// The expectation is that got and want have the same underlying Go type, and +// they contain equal values. Src is the original value before type conversion, +// provided for error message purposes. +// +// Note that this function is suitable for checking most conversion results +// except conversion to interfaces. This is because use of reflect APIs requires +// conversion to `any` interface, which must be assumed correct for this test to +// be meaningful. +func checkConversion(t *testing.T, src, got, want any) { + t.Helper() + if reflect.TypeOf(got) != reflect.TypeOf(want) { + t.Errorf("Got: %v. Want: converted type is: %v.", reflect.TypeOf(got), reflect.TypeOf(want)) + } + + if !reflect.DeepEqual(want, got) { + t.Errorf("Got: %[1]T(%#[1]v). Want: %[2]T(%#[2]v) convert to %[3]T(%#[3]v).", got, src, want) + } +} + +// conversionTest is a common interface for type conversion test cases. +type conversionTest interface { + Run(t *testing.T) +} + type numeric interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | @@ -15,58 +43,31 @@ type numeric interface { ~uintptr } -type converter interface { - Src() any - Got() any - Want() any - Quirk() bool // Tests GopherJS-specific behavior. -} - -type numericConverter[srcType numeric, dstType numeric] struct { +type numericConversion[srcType numeric, dstType numeric] struct { src srcType want dstType quirk bool } -func (tc numericConverter[srcType, dstType]) Src() any { - return tc.src -} - -func (tc numericConverter[srcType, dstType]) Got() any { - return dstType(tc.src) -} - -func (tc numericConverter[srcType, dstType]) Want() any { - return tc.want -} +func (tc numericConversion[srcType, dstType]) Run(t *testing.T) { + if tc.quirk && runtime.Compiler != "gopherjs" { + t.Skip("GopherJS-only test") + } -func (tc numericConverter[srcType, dstType]) Quirk() bool { - return tc.quirk + checkConversion(t, tc.src, dstType(tc.src), tc.want) } type complex interface { ~complex64 | ~complex128 } -type complexConverter[srcType complex, dstType complex] struct { +type complexConversion[srcType complex, dstType complex] struct { src srcType want dstType } -func (tc complexConverter[srcType, dstType]) Src() any { - return tc.src -} - -func (tc complexConverter[srcType, dstType]) Got() any { - return dstType(tc.src) -} - -func (tc complexConverter[srcType, dstType]) Want() any { - return tc.want -} - -func (tc complexConverter[srcType, dstType]) Quirk() bool { - return false +func (tc complexConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) } type stringLike interface { @@ -81,46 +82,81 @@ type stringLike interface { ~[]byte | ~[]rune | ~string } -type stringConverter[srcType stringLike, dstType ~string] struct { +type stringConversion[srcType stringLike, dstType ~string] struct { src srcType want dstType } -func (tc stringConverter[srcType, dstType]) Src() any { - return tc.src +func (tc stringConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) } -func (tc stringConverter[srcType, dstType]) Got() any { - return dstType(tc.src) -} - -func (tc stringConverter[srcType, dstType]) Want() any { - return tc.want -} - -func (tc stringConverter[srcType, dstType]) Quirk() bool { - return false -} - -type boolConverter[srcType ~bool, dstType ~bool] struct { +type boolConversion[srcType ~bool, dstType ~bool] struct { src srcType want dstType } -func (tc boolConverter[srcType, dstType]) Src() any { - return tc.src -} - -func (tc boolConverter[srcType, dstType]) Got() any { - return dstType(tc.src) -} - -func (tc boolConverter[srcType, dstType]) Want() any { - return tc.want -} - -func (tc boolConverter[srcType, dstType]) Quirk() bool { - return false +func (tc boolConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +// asString returns a string that reflects internal representation of the object. +// +// There is not specific guarantees about the string format, expect that if two +// strings match, the two objects _almost certainly_ are deeply equal. +func asString(o *js.Object) string { + f := js.Global.Get("Function").New("o", ` + const seen = []; + // JSON.stringify can't deal with circular references, which GopherJS objects + // can have. So when the same object is seen more than once we replace it with + // a string stub. + const suppressCycles = (key, value) => { + if (typeof value !== 'object') { + return value; + } + const idx = seen.indexOf(value); + if (idx !== -1) { + return "[Cycle " + idx + "]" + } + seen.push(value); + return value; + } + return JSON.stringify(o, suppressCycles); + `) + return f.Invoke(o).String() +} + +type interfaceConversion[srcType any] struct { + src srcType +} + +func (tc interfaceConversion[srcType]) Run(t *testing.T) { + // All of the following expressions are semantically equivalent, but may be + // compiled by GopherJS differently, so we test all of them. + var got1 any + got1 = tc.src + var got2 any = tc.src + var got3 any = any(tc.src) + + for i, got := range []any{got1, got2, got3} { + t.Run(fmt.Sprint(i), func(t *testing.T) { + checkConversion(t, tc.src, got, tc.src) + + concrete := got.(srcType) // Must not panic. + if runtime.Compiler == "gopherjs" { + // Can't use reflect.DeepEqual() here because it itself relies on + // conversion to interface, so instead we do some JS-level introspection. + srcRepr := asString(js.InternalObject(tc.src)) + concreteRepr := asString(js.InternalObject(concrete)) + if srcRepr == "" { + t.Fatalf("Got: internal representation of the original value is empty. Want: not empty.") + } + if concreteRepr != srcRepr { + t.Errorf("Got: result of type assertion %q is not equal to the original value %q. Want: values are equal.", concreteRepr, srcRepr) + } + } + }) + } } func TestConversion(t *testing.T) { @@ -132,86 +168,84 @@ func TestConversion(t *testing.T) { type c128 complex128 type str string type b bool + type st struct { + s string + i int + } - tests := []converter{ + tests := []conversionTest{ // $convertToInt64 - numericConverter[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, - numericConverter[int64, uint64]{src: -0x8000000000000000, want: 0x8000000000000000}, - numericConverter[uint, int64]{src: 0xFFFFFFFF, want: 0xFFFFFFFF}, - numericConverter[uint64, int64]{src: 0xFFFFFFFFFFFFFFFF, want: -1}, - numericConverter[uint64, uint64]{src: 0xFFFFFFFFFFFFFFFF, want: 0xFFFFFFFFFFFFFFFF}, - numericConverter[uintptr, uint64]{src: 0xFFFFFFFF, want: 0xFFFFFFFF}, - numericConverter[uintptr, uint64]{src: reflect.ValueOf(&struct{}{}).Pointer(), want: 0x1, quirk: true}, - numericConverter[float32, int64]{src: 2e10, want: 20000000000}, - numericConverter[float64, int64]{src: 2e10, want: 20000000000}, - numericConverter[int64, i64]{src: 1, want: 1}, - numericConverter[i64, int64]{src: 1, want: 1}, + numericConversion[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, + numericConversion[int64, uint64]{src: -0x8000000000000000, want: 0x8000000000000000}, + numericConversion[uint, int64]{src: 0xFFFFFFFF, want: 0xFFFFFFFF}, + numericConversion[uint64, int64]{src: 0xFFFFFFFFFFFFFFFF, want: -1}, + numericConversion[uint64, uint64]{src: 0xFFFFFFFFFFFFFFFF, want: 0xFFFFFFFFFFFFFFFF}, + numericConversion[uintptr, uint64]{src: 0xFFFFFFFF, want: 0xFFFFFFFF}, + numericConversion[uintptr, uint64]{src: reflect.ValueOf(&struct{}{}).Pointer(), want: 0x1, quirk: true}, + numericConversion[float32, int64]{src: 2e10, want: 20000000000}, + numericConversion[float64, int64]{src: 2e10, want: 20000000000}, + numericConversion[int64, i64]{src: 1, want: 1}, + numericConversion[i64, int64]{src: 1, want: 1}, // $convertToNativeInt - numericConverter[int64, int32]{src: math.MaxInt64, want: -1}, - numericConverter[int64, int32]{src: -100, want: -100}, - numericConverter[int64, int32]{src: 0x00C0FFEE4B1D4B1D, want: 0x4B1D4B1D}, - numericConverter[int32, int16]{src: 0x0BAD4B1D, want: 0x4B1D}, - numericConverter[int16, int8]{src: 0x4B1D, want: 0x1D}, - numericConverter[uint64, uint32]{src: 0xDEADC0DE00C0FFEE, want: 0x00C0FFEE}, - numericConverter[uint32, uint16]{src: 0xDEADC0DE, want: 0xC0DE}, - numericConverter[uint16, uint8]{src: 0xC0DE, want: 0xDE}, - numericConverter[float32, int32]{src: 12345678.12345678, want: 12345678}, - numericConverter[float32, int16]{src: 12345678.12345678, want: 24910}, - numericConverter[float64, int32]{src: 12345678.12345678, want: 12345678}, - numericConverter[float64, int16]{src: 12345678.12345678, want: 24910}, - numericConverter[int32, int]{src: 0x00C0FFEE, want: 0x00C0FFEE}, - numericConverter[uint32, uint]{src: 0x00C0FFEE, want: 0x00C0FFEE}, - numericConverter[uint32, uintptr]{src: 0x00C0FFEE, want: 0x00C0FFEE}, - numericConverter[int32, i32]{src: 0x00C0FFEE, want: 0x00C0FFEE}, - numericConverter[i32, int32]{src: 0x00C0FFEE, want: 0x00C0FFEE}, - numericConverter[uint32, int32]{src: 0xFFFFFFFF, want: -1}, - numericConverter[uint16, int16]{src: 0xFFFF, want: -1}, - numericConverter[uint8, int8]{src: 0xFF, want: -1}, + numericConversion[int64, int32]{src: math.MaxInt64, want: -1}, + numericConversion[int64, int32]{src: -100, want: -100}, + numericConversion[int64, int32]{src: 0x00C0FFEE4B1D4B1D, want: 0x4B1D4B1D}, + numericConversion[int32, int16]{src: 0x0BAD4B1D, want: 0x4B1D}, + numericConversion[int16, int8]{src: 0x4B1D, want: 0x1D}, + numericConversion[uint64, uint32]{src: 0xDEADC0DE00C0FFEE, want: 0x00C0FFEE}, + numericConversion[uint32, uint16]{src: 0xDEADC0DE, want: 0xC0DE}, + numericConversion[uint16, uint8]{src: 0xC0DE, want: 0xDE}, + numericConversion[float32, int32]{src: 12345678.12345678, want: 12345678}, + numericConversion[float32, int16]{src: 12345678.12345678, want: 24910}, + numericConversion[float64, int32]{src: 12345678.12345678, want: 12345678}, + numericConversion[float64, int16]{src: 12345678.12345678, want: 24910}, + numericConversion[int32, int]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConversion[uint32, uint]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConversion[uint32, uintptr]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConversion[int32, i32]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConversion[i32, int32]{src: 0x00C0FFEE, want: 0x00C0FFEE}, + numericConversion[uint32, int32]{src: 0xFFFFFFFF, want: -1}, + numericConversion[uint16, int16]{src: 0xFFFF, want: -1}, + numericConversion[uint8, int8]{src: 0xFF, want: -1}, // $convertToFloat - numericConverter[float64, float32]{src: 12345678.1234567890, want: 12345678.0}, - numericConverter[int64, float32]{src: 123456789, want: 123456792.0}, - numericConverter[int32, float32]{src: 12345678, want: 12345678.0}, - numericConverter[f32, float32]{src: 12345678.0, want: 12345678.0}, - numericConverter[float32, f32]{src: 12345678.0, want: 12345678.0}, - numericConverter[float32, float64]{src: 1234567.125000, want: 1234567.125000}, - numericConverter[int64, float64]{src: 12345678, want: 12345678.0}, - numericConverter[int32, float64]{src: 12345678, want: 12345678.0}, - numericConverter[f64, float64]{src: 12345678.0, want: 12345678.0}, - numericConverter[float64, f64]{src: 12345678.0, want: 12345678.0}, + numericConversion[float64, float32]{src: 12345678.1234567890, want: 12345678.0}, + numericConversion[int64, float32]{src: 123456789, want: 123456792.0}, + numericConversion[int32, float32]{src: 12345678, want: 12345678.0}, + numericConversion[f32, float32]{src: 12345678.0, want: 12345678.0}, + numericConversion[float32, f32]{src: 12345678.0, want: 12345678.0}, + numericConversion[float32, float64]{src: 1234567.125000, want: 1234567.125000}, + numericConversion[int64, float64]{src: 12345678, want: 12345678.0}, + numericConversion[int32, float64]{src: 12345678, want: 12345678.0}, + numericConversion[f64, float64]{src: 12345678.0, want: 12345678.0}, + numericConversion[float64, f64]{src: 12345678.0, want: 12345678.0}, // $convertToComplex - complexConverter[complex64, complex128]{src: 1 + 1i, want: 1 + 1i}, - complexConverter[complex128, complex64]{src: 1 + 1i, want: 1 + 1i}, - complexConverter[complex128, c128]{src: 1 + 1i, want: 1 + 1i}, - complexConverter[complex64, c64]{src: 1 + 1i, want: 1 + 1i}, + complexConversion[complex64, complex128]{src: 1 + 1i, want: 1 + 1i}, + complexConversion[complex128, complex64]{src: 1 + 1i, want: 1 + 1i}, + complexConversion[complex128, c128]{src: 1 + 1i, want: 1 + 1i}, + complexConversion[complex64, c64]{src: 1 + 1i, want: 1 + 1i}, // $convertToString - stringConverter[str, string]{src: "abc", want: "abc"}, - stringConverter[string, str]{src: "abc", want: "abc"}, - stringConverter[rune, string]{src: 'a', want: "a"}, - stringConverter[byte, string]{src: 'a', want: "a"}, - stringConverter[[]byte, string]{src: []byte{'a', 'b', 'c'}, want: "abc"}, - stringConverter[[]rune, string]{src: []rune{'a', 'b', 'c'}, want: "abc"}, + stringConversion[str, string]{src: "abc", want: "abc"}, + stringConversion[string, str]{src: "abc", want: "abc"}, + stringConversion[rune, string]{src: 'a', want: "a"}, + stringConversion[byte, string]{src: 'a', want: "a"}, + stringConversion[[]byte, string]{src: []byte{'a', 'b', 'c'}, want: "abc"}, + stringConversion[[]rune, string]{src: []rune{'a', 'b', 'c'}, want: "abc"}, // $convertToBool - boolConverter[b, bool]{src: true, want: true}, - boolConverter[b, bool]{src: false, want: false}, - boolConverter[bool, b]{src: true, want: true}, - boolConverter[bool, b]{src: false, want: false}, + boolConversion[b, bool]{src: true, want: true}, + boolConversion[b, bool]{src: false, want: false}, + boolConversion[bool, b]{src: true, want: true}, + boolConversion[bool, b]{src: false, want: false}, + // $convertToInterface + interfaceConversion[int]{src: 1}, + interfaceConversion[string]{src: "abc"}, + interfaceConversion[string]{src: "abc"}, + interfaceConversion[st]{src: st{s: "abc", i: 1}}, + interfaceConversion[error]{src: fmt.Errorf("test error")}, + interfaceConversion[*js.Object]{src: js.Global}, + interfaceConversion[*int]{src: func(i int) *int { return &i }(1)}, } for _, test := range tests { - t.Run(fmt.Sprintf("%T", test), func(t *testing.T) { - if test.Quirk() && runtime.Compiler != "gopherjs" { - t.Skip("GopherJS-only test") - } - got := test.Got() - want := test.Want() - - if reflect.TypeOf(got) != reflect.TypeOf(want) { - t.Errorf("Want: converted type is: %v. Got: %v.", reflect.TypeOf(want), reflect.TypeOf(got)) - } - - if !reflect.DeepEqual(want, got) { - t.Errorf("Want: %[1]T(%#[1]v) convert to %[2]T(%#[2]v). Got: %[3]T(%#[3]v)", test.Src(), want, got) - } - }) + t.Run(fmt.Sprintf("%T", test), test.Run) } } From 039acdd4172ec060f4653d154fa786352658cd71 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 25 Oct 2023 21:34:16 +0100 Subject: [PATCH 50/83] Update the list of passing typeparam test cases. Two tests temporarily regressed, since try use type conversions that we don't yet support. Previously they passed mostly by a chance - doing no conversion happened to be good enough. --- tests/gorepo/run.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index e69d9e016..cd4ab344c 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -166,13 +166,14 @@ var knownFails = map[string]failReason{ "typeparam/graph.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/index.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/issue23536.go": {category: generics, desc: "missing support for generic byte/rune slice to string conversion"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, + "typeparam/issue48137.go": {category: generics, desc: "unsupported conversion from func() main.Bar to main.Bar"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, + "typeparam/issue50833.go": {category: generics, desc: "unsupported conversion from *main.S to main.PS"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, From f18e89471fd78cebeac118d5afaf58b86471ef8e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 28 Oct 2023 21:04:47 +0100 Subject: [PATCH 51/83] Implement type param conversion support for slices. --- compiler/prelude/types.js | 31 ++++++++++++++++++++++++++++- tests/typeparams/conversion_test.go | 26 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 362921a61..9b9d9ee06 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -456,8 +456,10 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindInterface: typ.convertFrom = (src) => $convertToInterface(src, typ); break; - case $kindArray: case $kindSlice: + typ.convertFrom = (src) => $convertToSlice(src, typ); + break; + case $kindArray: case $kindMap: case $kindChan: case $kindPtr: @@ -1093,4 +1095,31 @@ const $convertToBool = (src, dstType) => { */ const $convertToInterface = (src, dstType) => { return src; +}; + +/** + * Convert any type to a slice value. + * + * dstType.kind must be $kindSlice. For wrapped types, src value must be wrapped. + * The returned value is always a slice type. + */ +const $convertToSlice = (src, dstType) => { + const srcType = src.constructor; + if (srcType === dstType) { + return src; + } + + switch (srcType.kind) { + case $kindString: + if (dstType.elem.kind === $kindInt32) { // Runes are int32. + return new dstType($stringToRunes(src.$val)); + } else if (dstType.elem.kind === $kindUint8) { // Bytes are uint8. + return new dstType($stringToBytes(src.$val)); + } + break; + case $kindSlice: + return $convertSliceType(src, dstType); + break; + } + throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index b59d5cd6c..2e0483f9f 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -159,6 +159,24 @@ func (tc interfaceConversion[srcType]) Run(t *testing.T) { } } +type sliceConversion[elType any, srcType ~[]elType, dstType ~[]elType] struct { + src srcType + want dstType +} + +func (tc sliceConversion[elType, srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +type stringToSliceConversion[dstType []byte | []rune] struct { + src string + want dstType +} + +func (tc stringToSliceConversion[dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 @@ -172,6 +190,7 @@ func TestConversion(t *testing.T) { s string i int } + type sl []byte tests := []conversionTest{ // $convertToInt64 @@ -243,6 +262,13 @@ func TestConversion(t *testing.T) { interfaceConversion[error]{src: fmt.Errorf("test error")}, interfaceConversion[*js.Object]{src: js.Global}, interfaceConversion[*int]{src: func(i int) *int { return &i }(1)}, + // $convertToSlice + sliceConversion[byte, []byte, sl]{src: []byte{1, 2, 3}, want: sl{1, 2, 3}}, + sliceConversion[byte, sl, []byte]{src: sl{1, 2, 3}, want: []byte{1, 2, 3}}, + sliceConversion[byte, []byte, sl]{src: []byte(nil), want: sl(nil)}, + sliceConversion[byte, sl, []byte]{src: sl(nil), want: []byte(nil)}, + stringToSliceConversion[[]byte]{src: "🐞", want: []byte{240, 159, 144, 158}}, + stringToSliceConversion[[]rune]{src: "🐞x", want: []rune{'🐞', 'x'}}, } for _, test := range tests { From e436c07bca7eaeb06e1193e51682268aa392a9ff Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Mon, 30 Oct 2023 17:41:11 +0000 Subject: [PATCH 52/83] Implement type param conversion to pointers. Broadly, this covers two cases: - Slice to pointer-to-array conversions. - Conversion between pointers to types with identical underlying types. Arrays are a special case because pointer to array is a wrapped type, unlike all other pointer types. Structs are a special case as well because pointer to struct is actually represented by the same underlying object as the struct value. --- compiler/prelude/types.js | 48 ++++++++++++++++++++++--- tests/gorepo/run.go | 1 - tests/typeparams/conversion_test.go | 55 +++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 9b9d9ee06..e4feb97f7 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -59,7 +59,7 @@ var $idKey = x => { }; // Creates constructor functions for array pointer types. Returns a new function -// instace each time to make sure each type is independent of the other. +// instance each time to make sure each type is independent of the other. var $arrayPtrCtor = () => { return function (array) { this.$get = () => { return array; }; @@ -224,7 +224,10 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.keyFor = $idKey; typ.init = elem => { typ.elem = elem; - typ.wrapped = (elem.kind === $kindArray); + if (elem.kind === $kindArray) { + typ.wrapped = true; + typ.wrap = (v) => ((v === typ.nil) ? v : new typ(v)); + } typ.nil = new typ($throwNilPointerError, $throwNilPointerError); }; break; @@ -459,10 +462,12 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindSlice: typ.convertFrom = (src) => $convertToSlice(src, typ); break; + case $kindPtr: + typ.convertFrom = (src) => $convertToPointer(src, typ); + break; case $kindArray: case $kindMap: case $kindChan: - case $kindPtr: case $kindFunc: case $kindStruct: break; @@ -1098,7 +1103,7 @@ const $convertToInterface = (src, dstType) => { }; /** - * Convert any type to a slice value. + * Convert to a slice value. * * dstType.kind must be $kindSlice. For wrapped types, src value must be wrapped. * The returned value is always a slice type. @@ -1122,4 +1127,39 @@ const $convertToSlice = (src, dstType) => { break; } throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); +}; + +/** +* Convert to a pointer value. +* +* dstType.kind must be $kindPtr. For wrapped types (specifically, pointers +* to an array), src value must be wrapped. The returned value is a bare JS +* array (typed or untyped), or a pointer object. +*/ +const $convertToPointer = (src, dstType) => { + const srcType = src.constructor; + + if (srcType === dstType) { + return src; + } + + // []T → *[N]T + if (srcType.kind == $kindSlice && dstType.elem.kind == $kindArray) { + return $sliceToGoArray(src, dstType); + } + + if (src === srcType.nil) { + return dstType.nil; + } + + switch (dstType.elem.kind) { + case $kindArray: + // Pointers to arrays are a wrapped type, represented by a native JS array, + // which we return directly. + return src.$val; + case $kindStruct: + return $pointerOfStructConversion(src, dstType); + default: + return new dstType(src.$get, src.$set, src.$target); + } }; \ No newline at end of file diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index cd4ab344c..c118546fc 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -173,7 +173,6 @@ var knownFails = map[string]failReason{ "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50833.go": {category: generics, desc: "unsupported conversion from *main.S to main.PS"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 2e0483f9f..dc9e40c67 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -5,6 +5,7 @@ import ( "math" "reflect" "runtime" + "strings" "testing" "github.com/gopherjs/gopherjs/js" @@ -177,6 +178,40 @@ func (tc stringToSliceConversion[dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type sliceToArrayPtrConversion[elType any, srcType ~[]elType, dstType ~*[3]elType | ~*[0]elType] struct { + src srcType + want dstType + wantPanic string +} + +func (tc sliceToArrayPtrConversion[elType, srcType, dstType]) Run(t *testing.T) { + if tc.wantPanic == "" { + checkConversion(t, tc.src, dstType(tc.src), tc.want) + return + } + + var got dstType + defer func() { + err := recover() + if err == nil { + t.Fatalf("Got: %T(%v) = %v. Want: panic.", got, tc.src, got) + } + if msg := fmt.Sprint(err); !strings.Contains(msg, tc.wantPanic) { + t.Fatalf("Got panic: %v. Want: panic containing %q.", err, tc.wantPanic) + } + }() + got = dstType(tc.src) +} + +type ptrConversion[T any, srcType ~*T, dstType ~*T] struct { + src srcType + want dstType +} + +func (tc ptrConversion[T, srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 @@ -185,12 +220,20 @@ func TestConversion(t *testing.T) { type c64 complex64 type c128 complex128 type str string + type strPtr *string type b bool type st struct { s string i int } + type stPtr *st type sl []byte + type arr [3]byte + type arrPtr *[3]byte + + strVar := "abc" + stVar := st{s: "abc", i: 42} + arrVal := [3]byte{1, 2, 3} tests := []conversionTest{ // $convertToInt64 @@ -269,6 +312,18 @@ func TestConversion(t *testing.T) { sliceConversion[byte, sl, []byte]{src: sl(nil), want: []byte(nil)}, stringToSliceConversion[[]byte]{src: "🐞", want: []byte{240, 159, 144, 158}}, stringToSliceConversion[[]rune]{src: "🐞x", want: []rune{'🐞', 'x'}}, + // $convertToPointer + sliceToArrayPtrConversion[byte, []byte, *[3]byte]{src: []byte{1, 2, 3}, want: &[3]byte{1, 2, 3}}, + sliceToArrayPtrConversion[byte, sl, arrPtr]{src: []byte{1, 2, 3}, want: arrPtr(&[3]byte{1, 2, 3})}, + sliceToArrayPtrConversion[byte, []byte, *[0]byte]{src: nil, want: nil}, + sliceToArrayPtrConversion[byte, []byte, *[3]byte]{src: []byte{1, 2}, wantPanic: "length"}, + sliceToArrayPtrConversion[byte, []byte, *[3]byte]{src: nil, wantPanic: "length"}, + ptrConversion[string, *string, strPtr]{src: &strVar, want: strPtr(&strVar)}, + ptrConversion[string, *string, strPtr]{src: nil, want: nil}, + ptrConversion[[3]byte, *[3]byte, arrPtr]{src: &arrVal, want: arrPtr(&arrVal)}, + ptrConversion[[3]byte, *[3]byte, arrPtr]{src: nil, want: nil}, + ptrConversion[st, *st, stPtr]{src: &stVar, want: stPtr(&stVar)}, + ptrConversion[st, *st, stPtr]{src: nil, want: nil}, } for _, test := range tests { From 772d85d0716bcb22a3485d3d645f41822abd5f52 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 19:13:00 +0000 Subject: [PATCH 53/83] Implement type param conversion to structs and arrays. Since in Go they are passed by value, this is essentially equivalent to copying the value. Even when the type is the same, the copy operation is needed. --- compiler/prelude/types.js | 30 +++++++++++- tests/typeparams/conversion_test.go | 71 +++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index e4feb97f7..28efa00b7 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -466,10 +466,14 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToPointer(src, typ); break; case $kindArray: + typ.convertFrom = (src) => $convertToArray(src, typ); + break; + case $kindStruct: + typ.convertFrom = (src) => $convertToStruct(src, typ); + break; case $kindMap: case $kindChan: case $kindFunc: - case $kindStruct: break; default: $panic(new $String("invalid kind: " + kind)); @@ -1162,4 +1166,28 @@ const $convertToPointer = (src, dstType) => { default: return new dstType(src.$get, src.$set, src.$target); } +}; + +/** + * Convert to struct types. + * + * dstType.kind must be $kindStruct. Src must be a wrapped struct value. Returned + * value will always be a bare JavaScript object representing the struct. + */ +const $convertToStruct = (src, dstType) => { + // Since structs are passed by value, the conversion result must be a copy + // of the original value, even if it is the same type. + return $clone(src.$val, dstType); +}; + +/** + * Convert to array types. + * + * dstType.kind must be $kindArray. Src must be a wrapped array value. Returned + * value will always be a bare JavaScript object representing the array. + */ +const $convertToArray = (src, dstType) => { + // Since arrays are passed by value, the conversion result must be a copy + // of the original value, even if it is the same type. + return $clone(src.$val, dstType); }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index dc9e40c67..8d7f7db52 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -37,6 +37,27 @@ type conversionTest interface { Run(t *testing.T) } +type ( // Named types for use in conversion test cases. + i64 int64 + i32 int32 + f64 float64 + f32 float32 + c64 complex64 + c128 complex128 + str string + strPtr *string + b bool + st struct { + s string + i int + } + st2 st + stPtr *st + sl []byte + arr [3]byte + arrPtr *[3]byte +) + type numeric interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | @@ -212,25 +233,31 @@ func (tc ptrConversion[T, srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } -func TestConversion(t *testing.T) { - type i64 int64 - type i32 int32 - type f64 float64 - type f32 float32 - type c64 complex64 - type c128 complex128 - type str string - type strPtr *string - type b bool - type st struct { - s string - i int - } - type stPtr *st - type sl []byte - type arr [3]byte - type arrPtr *[3]byte +type structConversion[srcType ~struct { + s string + i int +}, dstType ~struct { + s string + i int +}] struct { + src srcType + want dstType +} +func (tc structConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +type arrayConversion[srcType ~[3]byte, dstType ~[3]byte] struct { + src srcType + want dstType +} + +func (tc arrayConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} arrVal := [3]byte{1, 2, 3} @@ -324,6 +351,14 @@ func TestConversion(t *testing.T) { ptrConversion[[3]byte, *[3]byte, arrPtr]{src: nil, want: nil}, ptrConversion[st, *st, stPtr]{src: &stVar, want: stPtr(&stVar)}, ptrConversion[st, *st, stPtr]{src: nil, want: nil}, + // $convertToStruct + structConversion[st, st2]{src: st{i: 42, s: "abc"}, want: st2{s: "abc", i: 42}}, + structConversion[st, struct { + s string + i int + }]{src: st{i: 42, s: "abc"}, want: st2{s: "abc", i: 42}}, + // $convertToArray + arrayConversion[[3]byte, arr]{src: [3]byte{1, 2, 3}, want: arr{1, 2, 3}}, } for _, test := range tests { From 07690760b5d0b4a66fd92e0d9d23e075152ae05d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 19:34:44 +0000 Subject: [PATCH 54/83] Implement type param to map conversion. --- compiler/prelude/types.js | 12 ++++++++++++ tests/typeparams/conversion_test.go | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 28efa00b7..4fa03afdf 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -472,6 +472,8 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToStruct(src, typ); break; case $kindMap: + typ.convertFrom = (src) => $convertToMap(src, typ); + break; case $kindChan: case $kindFunc: break; @@ -1190,4 +1192,14 @@ const $convertToArray = (src, dstType) => { // Since arrays are passed by value, the conversion result must be a copy // of the original value, even if it is the same type. return $clone(src.$val, dstType); +}; + +/** + * Convert to map types. + * + * dstType.kind must be $kindMap. Src must be a wrapped map value. Returned + * value will always be a bare JavaScript object representing the map. + */ +const $convertToMap = (src, dstType) => { + return src.$val; }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 8d7f7db52..2e1b3add1 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -56,6 +56,7 @@ type ( // Named types for use in conversion test cases. sl []byte arr [3]byte arrPtr *[3]byte + m map[string]string ) type numeric interface { @@ -257,6 +258,15 @@ func (tc arrayConversion[srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type mapConversion[srcType ~map[string]string, dstType ~map[string]string] struct { + src srcType + want dstType +} + +func (tc mapConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} @@ -359,6 +369,9 @@ func TestConversion(t *testing.T) { }]{src: st{i: 42, s: "abc"}, want: st2{s: "abc", i: 42}}, // $convertToArray arrayConversion[[3]byte, arr]{src: [3]byte{1, 2, 3}, want: arr{1, 2, 3}}, + // $convertToMap + mapConversion[map[string]string, m]{src: map[string]string{"abc": "def"}, want: m{"abc": "def"}}, + mapConversion[map[string]string, m]{src: nil, want: nil}, } for _, test := range tests { From b66fa5779288949531caaf13f9d4e75e3e02e010 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 20:58:11 +0000 Subject: [PATCH 55/83] reflect.DeepEquals: channels of the same underlying type can be equal. According to the Go spec, channels are equal if they have been created by the same call to make(). Prior to this change reflect.DeepEqual() would return false when comparing two channels, one of this is a named type (e.g. `type ch chan string`) and was created by type conversion from the other one. After this change, DeepEquals behavior becomes consistent with the == operator. Upstream has a similar bug: https://github.com/golang/go/issues/63886. --- compiler/natives/src/reflect/reflect.go | 7 ++++++- tests/misc_test.go | 28 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index d97094e03..d35c0d85b 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1639,7 +1639,7 @@ func DeepEqual(a1, a2 interface{}) bool { if i1 == i2 { return true } - if i1 == nil || i2 == nil || i1.Get("constructor") != i2.Get("constructor") { + if i1 == nil || i2 == nil { return false } return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) @@ -1649,6 +1649,11 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if !v1.IsValid() || !v2.IsValid() { return !v1.IsValid() && !v2.IsValid() } + + if v1.Kind() == Chan && v2.Kind() == Chan { + return v1.object() == v2.object() + } + if v1.Type() != v2.Type() { return false } diff --git a/tests/misc_test.go b/tests/misc_test.go index a38d91c81..fed07d2a2 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -959,3 +959,31 @@ func TestFileSetSize(t *testing.T) { t.Errorf("Got: unsafe.Sizeof(token.FileSet{}) %v, Want: %v", n2, n1) } } + +func TestChanEquality(t *testing.T) { + type ch chan string + + ch1 := make(chan string) + ch2 := make(ch) + ch3 := ch(ch1) + + t.Run("equal", func(t *testing.T) { + if ch1 != ch3 { + t.Errorf("Got: ch1 != ch3. Want: channels created by the same call to make are equal.") + } + if runtime.Compiler != "gopherjs" { + t.Skip("https://github.com/golang/go/issues/63886") + } + if !reflect.DeepEqual(ch1, ch3) { + t.Errorf("Got: reflect.DeepEqual(ch1, ch3) == false. Want: channels created by the same call to make are equal.") + } + }) + t.Run("not equal", func(t *testing.T) { + if ch1 == ch2 { + t.Errorf("Got: ch1 == ch2. Want: channels created by different calls to make are not equal.") + } + if reflect.DeepEqual(ch1, ch2) { + t.Errorf("Got: reflect.DeepEqual(ch1, ch2) == true. Want: channels created by different calls to make are not equal.") + } + }) +} From 485140a520cf79b85f0821d1e38dce51db32a651 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 21:09:44 +0000 Subject: [PATCH 56/83] Implement type param to chan type conversion. --- compiler/prelude/types.js | 12 ++++++++++++ tests/typeparams/conversion_test.go | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 4fa03afdf..df62c60ec 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -475,6 +475,8 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToMap(src, typ); break; case $kindChan: + typ.convertFrom = (src) => $convertToChan(src, typ); + break; case $kindFunc: break; default: @@ -1202,4 +1204,14 @@ const $convertToArray = (src, dstType) => { */ const $convertToMap = (src, dstType) => { return src.$val; +}; + +/** + * Convert to chan types. + * + * dstType.kind must be $kindChan. Src must be a wrapped chan value. Returned + * value will always be a bare $Chan object representing the channel. + */ +const $convertToChan = (src, dstType) => { + return src.$val; }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 2e1b3add1..539957741 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -57,6 +57,7 @@ type ( // Named types for use in conversion test cases. arr [3]byte arrPtr *[3]byte m map[string]string + ch chan string ) type numeric interface { @@ -267,10 +268,20 @@ func (tc mapConversion[srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type chanConversion[srcType ~chan string, dstType ~chan string] struct { + src srcType + want dstType +} + +func (tc chanConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} arrVal := [3]byte{1, 2, 3} + chanVal := make(chan string) tests := []conversionTest{ // $convertToInt64 @@ -372,6 +383,9 @@ func TestConversion(t *testing.T) { // $convertToMap mapConversion[map[string]string, m]{src: map[string]string{"abc": "def"}, want: m{"abc": "def"}}, mapConversion[map[string]string, m]{src: nil, want: nil}, + // $convertToChan + chanConversion[chan string, ch]{src: chanVal, want: ch(chanVal)}, + chanConversion[chan string, ch]{src: nil, want: nil}, } for _, test := range tests { From 1bc040ee2b7ab68e034354f23a27983dc640f135 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 21:13:40 +0000 Subject: [PATCH 57/83] Remove stray debug print. --- tests/js_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/js_test.go b/tests/js_test.go index 3e69849f1..6563098df 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -732,7 +732,6 @@ func TestReflection(t *testing.T) { s := S{o} v := reflect.ValueOf(&s).Elem() - println(v.Field(0).Interface()) if got := v.Field(0).Interface().(*js.Object).Get("answer").Int(); got != 42 { t.Errorf("Got: Accessing JS object property via reflect.Value.Interface() returned %v. Want: 42.", got) } From f2b91ba2fd48d6aa08ef5bd02d5a2ddb8d14239d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 23:04:58 +0000 Subject: [PATCH 58/83] Implement type param to function types conversion support. --- compiler/prelude/types.js | 13 ++++++++++++- tests/gorepo/run.go | 1 - tests/typeparams/conversion_test.go | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index df62c60ec..49baad391 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -478,6 +478,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToChan(src, typ); break; case $kindFunc: + typ.convertFrom = (src) => $convertToFunc(src, typ); break; default: $panic(new $String("invalid kind: " + kind)); @@ -1214,4 +1215,14 @@ const $convertToMap = (src, dstType) => { */ const $convertToChan = (src, dstType) => { return src.$val; -}; \ No newline at end of file +}; + +/** + * Convert to function types. + * + * dstType.kind must be $kindFunc. Src must be a wrapped function value. Returned + * value will always be a bare JavaScript function. + */ +const $convertToFunc = (src, dstType) => { + return src.$val; +}; diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index c118546fc..0e1cb959e 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -168,7 +168,6 @@ var knownFails = map[string]failReason{ "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, - "typeparam/issue48137.go": {category: generics, desc: "unsupported conversion from func() main.Bar to main.Bar"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 539957741..2d9bf02bf 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -58,6 +58,7 @@ type ( // Named types for use in conversion test cases. arrPtr *[3]byte m map[string]string ch chan string + fun func() int ) type numeric interface { @@ -277,11 +278,28 @@ func (tc chanConversion[srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type funcConversion[srcType ~func() int, dstType ~func() int] struct { + src srcType + want dstType +} + +func (tc funcConversion[srcType, dstType]) Run(t *testing.T) { + got := dstType(tc.src) + if reflect.TypeOf(got) != reflect.TypeOf(tc.want) { + t.Errorf("Got: %v. Want: converted type is: %v.", reflect.TypeOf(got), reflect.TypeOf(tc.want)) + } + + if js.InternalObject(got) != js.InternalObject(tc.want) { + t.Errorf("Got: %v != %v. Want: after type conversion function object should remain the same.", got, tc.want) + } +} + func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} arrVal := [3]byte{1, 2, 3} chanVal := make(chan string) + funcVal := func() int { return 42 } tests := []conversionTest{ // $convertToInt64 @@ -386,6 +404,9 @@ func TestConversion(t *testing.T) { // $convertToChan chanConversion[chan string, ch]{src: chanVal, want: ch(chanVal)}, chanConversion[chan string, ch]{src: nil, want: nil}, + // $convertToFunc + funcConversion[func() int, fun]{src: funcVal, want: fun(funcVal)}, + funcConversion[func() int, fun]{src: nil, want: nil}, } for _, test := range tests { From 4e6c8bcb00dcad6fb00fab06fc25dd034cf1afe8 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 23:22:02 +0000 Subject: [PATCH 59/83] Correctly recognize type conversion expressions with multiple typeparams. Prior to this change expressions like `ss[T1, T2](val)` where `ss` is a parameterized type were not recognized as type conversion expressions due to use of the *ast.IndexListExpr node. --- compiler/astutil/astutil.go | 7 +++++++ tests/gorepo/run.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index f97cdaa4f..e6a60c421 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -60,6 +60,13 @@ func IsTypeExpr(expr ast.Expr, info *types.Info) bool { } _, ok = info.Uses[ident].(*types.TypeName) return ok + case *ast.IndexListExpr: + ident, ok := e.X.(*ast.Ident) + if !ok { + return false + } + _, ok = info.Uses[ident].(*types.TypeName) + return ok case *ast.ParenExpr: return IsTypeExpr(e.X, info) default: diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 0e1cb959e..620b7907d 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -172,7 +172,7 @@ var knownFails = map[string]failReason{ "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, From 267a750d1666229b32fb67967de2d735170c423d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 23:46:46 +0000 Subject: [PATCH 60/83] Re-triage gorepo test failures associated with type conversion. At this point both remaining issues seem to be related to other kinds of issues. --- tests/gorepo/run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 620b7907d..fabcd655a 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -157,9 +157,9 @@ var knownFails = map[string]failReason{ "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/boundmethod.go": {category: generics, desc: "missing support for type conversion of a parameterized type"}, + "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, "typeparam/chans.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, From 3606e1e16923af98d242e9925e93fb1a9786832c Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Thu, 2 Nov 2023 21:35:40 +0000 Subject: [PATCH 61/83] Implement support for == operator on type params. --- compiler/expressions.go | 19 +++++++++++++++++-- compiler/typesutil/typesutil.go | 12 ++++++++++++ tests/gorepo/run.go | 16 ++-------------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index f141aafa1..644973cca 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -316,8 +316,23 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { t := fc.pkgCtx.TypeOf(e.X) t2 := fc.pkgCtx.TypeOf(e.Y) - _, isInterface := t2.Underlying().(*types.Interface) - if isInterface || types.Identical(t, types.Typ[types.UntypedNil]) { + + // Exact type param instantiations are not known at compile time, so + // some operators require special handling. + if (typesutil.IsTypeParam(t) || typesutil.IsTypeParam(t2)) && + !(typesutil.IsInterface(t) || typesutil.IsInterface(t2)) { // == operator between an interface and other types is handled below. + if !types.Identical(t, t2) { + // This should never happen. + panic(bailout(fmt.Errorf("%s: binary operator %v is applied to different type param types %s and %s", fc.pkgCtx.fileSet.Position(e.Pos()), e.Op, t, t2))) + } + + switch e.Op { + case token.EQL: + return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) + } + } + + if typesutil.IsInterface(t2) || types.Identical(t, types.Typ[types.UntypedNil]) { t = t2 } diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 5db18eafd..f3d63541c 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -130,6 +130,18 @@ func IsGeneric(t types.Type) bool { } } +// IsTypeParam returns true if the type is a type param. +func IsTypeParam(t types.Type) bool { + _, ok := t.(*types.TypeParam) + return ok +} + +// IsInterface returns true if the type is an interface. +func IsInterface(t types.Type) bool { + _, ok := t.Underlying().(*types.Interface) + return ok && !IsTypeParam(t) +} + // IsMethod returns true if the passed object is a method. func IsMethod(o types.Object) bool { f, ok := o.(*types.Func) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index fabcd655a..ebcdc3b90 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -150,6 +150,7 @@ var knownFails = map[string]failReason{ "fixedbugs/issue48536.go": {category: usesUnsupportedPackage, desc: "https://github.com/gopherjs/gopherjs/issues/1130"}, "fixedbugs/issue53600.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format is different from Go's"}, "typeparam/issue51733.go": {category: usesUnsupportedPackage, desc: "unsafe: uintptr to struct pointer conversion is unsupported"}, + "typeparam/chans.go": {category: neverTerminates, desc: "uses runtime.SetFinalizer() and runtime.GC()."}, // Failures related to the lack of generics support. Ideally, this section // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is @@ -158,32 +159,19 @@ var knownFails = map[string]failReason{ "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, - "typeparam/chans.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/graph.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/index.go": {category: generics, desc: "missing support for the comparable type constraint"}, + "typeparam/fact.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, - "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, - "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/maps.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/metrics.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, - "typeparam/ordered.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/orderedmap.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/sets.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/subdict.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, From bc96d21c585752b230bff15f11bd7e246819a531 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 18:25:39 +0000 Subject: [PATCH 62/83] Revert "reflect.DeepEquals: channels of the same underlying type can be equal." In https://github.com/golang/go/issues/63886 it was pointed out that DeepEqual doc says: "Values of distinct types are never deeply equal", so the existing behavior is, in fact, correct even if somewhat surprising. This reverts commit b66fa5779288949531caaf13f9d4e75e3e02e010. --- compiler/natives/src/reflect/reflect.go | 7 +------ tests/misc_test.go | 28 ------------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index d35c0d85b..d97094e03 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1639,7 +1639,7 @@ func DeepEqual(a1, a2 interface{}) bool { if i1 == i2 { return true } - if i1 == nil || i2 == nil { + if i1 == nil || i2 == nil || i1.Get("constructor") != i2.Get("constructor") { return false } return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) @@ -1649,11 +1649,6 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if !v1.IsValid() || !v2.IsValid() { return !v1.IsValid() && !v2.IsValid() } - - if v1.Kind() == Chan && v2.Kind() == Chan { - return v1.object() == v2.object() - } - if v1.Type() != v2.Type() { return false } diff --git a/tests/misc_test.go b/tests/misc_test.go index fed07d2a2..a38d91c81 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -959,31 +959,3 @@ func TestFileSetSize(t *testing.T) { t.Errorf("Got: unsafe.Sizeof(token.FileSet{}) %v, Want: %v", n2, n1) } } - -func TestChanEquality(t *testing.T) { - type ch chan string - - ch1 := make(chan string) - ch2 := make(ch) - ch3 := ch(ch1) - - t.Run("equal", func(t *testing.T) { - if ch1 != ch3 { - t.Errorf("Got: ch1 != ch3. Want: channels created by the same call to make are equal.") - } - if runtime.Compiler != "gopherjs" { - t.Skip("https://github.com/golang/go/issues/63886") - } - if !reflect.DeepEqual(ch1, ch3) { - t.Errorf("Got: reflect.DeepEqual(ch1, ch3) == false. Want: channels created by the same call to make are equal.") - } - }) - t.Run("not equal", func(t *testing.T) { - if ch1 == ch2 { - t.Errorf("Got: ch1 == ch2. Want: channels created by different calls to make are not equal.") - } - if reflect.DeepEqual(ch1, ch2) { - t.Errorf("Got: reflect.DeepEqual(ch1, ch2) == true. Want: channels created by different calls to make are not equal.") - } - }) -} From af9cff7ac2a64228a1c3169a4d29fcd3e09285e8 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 18:40:52 +0000 Subject: [PATCH 63/83] Move *ast.BinaryExpr translation into a separate method. --- compiler/expressions.go | 371 ++++++++++++++++++++-------------------- 1 file changed, 187 insertions(+), 184 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 644973cca..ee51bdf85 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -306,190 +306,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } case *ast.BinaryExpr: - if e.Op == token.NEQ { - return fc.formatExpr("!(%s)", fc.translateExpr(&ast.BinaryExpr{ - X: e.X, - Op: token.EQL, - Y: e.Y, - })) - } - - t := fc.pkgCtx.TypeOf(e.X) - t2 := fc.pkgCtx.TypeOf(e.Y) - - // Exact type param instantiations are not known at compile time, so - // some operators require special handling. - if (typesutil.IsTypeParam(t) || typesutil.IsTypeParam(t2)) && - !(typesutil.IsInterface(t) || typesutil.IsInterface(t2)) { // == operator between an interface and other types is handled below. - if !types.Identical(t, t2) { - // This should never happen. - panic(bailout(fmt.Errorf("%s: binary operator %v is applied to different type param types %s and %s", fc.pkgCtx.fileSet.Position(e.Pos()), e.Op, t, t2))) - } - - switch e.Op { - case token.EQL: - return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) - } - } - - if typesutil.IsInterface(t2) || types.Identical(t, types.Typ[types.UntypedNil]) { - t = t2 - } - - if basic, isBasic := t.Underlying().(*types.Basic); isBasic && isNumeric(basic) { - if is64Bit(basic) { - switch e.Op { - case token.MUL: - return fc.formatExpr("$mul64(%e, %e)", e.X, e.Y) - case token.QUO: - return fc.formatExpr("$div64(%e, %e, false)", e.X, e.Y) - case token.REM: - return fc.formatExpr("$div64(%e, %e, true)", e.X, e.Y) - case token.SHL: - return fc.formatExpr("$shiftLeft64(%e, %f)", e.X, e.Y) - case token.SHR: - return fc.formatExpr("$shiftRight%s(%e, %f)", toJavaScriptType(basic), e.X, e.Y) - case token.EQL: - return fc.formatExpr("(%1h === %2h && %1l === %2l)", e.X, e.Y) - case token.LSS: - return fc.formatExpr("(%1h < %2h || (%1h === %2h && %1l < %2l))", e.X, e.Y) - case token.LEQ: - return fc.formatExpr("(%1h < %2h || (%1h === %2h && %1l <= %2l))", e.X, e.Y) - case token.GTR: - return fc.formatExpr("(%1h > %2h || (%1h === %2h && %1l > %2l))", e.X, e.Y) - case token.GEQ: - return fc.formatExpr("(%1h > %2h || (%1h === %2h && %1l >= %2l))", e.X, e.Y) - case token.ADD, token.SUB: - return fc.formatExpr("new %3s(%1h %4t %2h, %1l %4t %2l)", e.X, e.Y, fc.typeName(t), e.Op) - case token.AND, token.OR, token.XOR: - return fc.formatExpr("new %3s(%1h %4t %2h, (%1l %4t %2l) >>> 0)", e.X, e.Y, fc.typeName(t), e.Op) - case token.AND_NOT: - return fc.formatExpr("new %3s(%1h & ~%2h, (%1l & ~%2l) >>> 0)", e.X, e.Y, fc.typeName(t)) - default: - panic(e.Op) - } - } - - if isComplex(basic) { - switch e.Op { - case token.EQL: - return fc.formatExpr("(%1r === %2r && %1i === %2i)", e.X, e.Y) - case token.ADD, token.SUB: - return fc.formatExpr("new %3s(%1r %4t %2r, %1i %4t %2i)", e.X, e.Y, fc.typeName(t), e.Op) - case token.MUL: - return fc.formatExpr("new %3s(%1r * %2r - %1i * %2i, %1r * %2i + %1i * %2r)", e.X, e.Y, fc.typeName(t)) - case token.QUO: - return fc.formatExpr("$divComplex(%e, %e)", e.X, e.Y) - default: - panic(e.Op) - } - } - - switch e.Op { - case token.EQL: - return fc.formatParenExpr("%e === %e", e.X, e.Y) - case token.LSS, token.LEQ, token.GTR, token.GEQ: - return fc.formatExpr("%e %t %e", e.X, e.Op, e.Y) - case token.ADD, token.SUB: - return fc.fixNumber(fc.formatExpr("%e %t %e", e.X, e.Op, e.Y), basic) - case token.MUL: - switch basic.Kind() { - case types.Int32, types.Int: - return fc.formatParenExpr("$imul(%e, %e)", e.X, e.Y) - case types.Uint32, types.Uintptr: - return fc.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) - } - return fc.fixNumber(fc.formatExpr("%e * %e", e.X, e.Y), basic) - case token.QUO: - if isInteger(basic) { - // cut off decimals - shift := ">>" - 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.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.newLocalVariable("_r"), e.X, e.Y) - case token.SHL, token.SHR: - op := e.Op.String() - if e.Op == token.SHR && isUnsigned(basic) { - op = ">>>" - } - if v := fc.pkgCtx.Types[e.Y].Value; v != nil { - i, _ := constant.Uint64Val(constant.ToInt(v)) - if i >= 32 { - return fc.formatExpr("0") - } - return fc.fixNumber(fc.formatExpr("%e %s %s", e.X, op, strconv.FormatUint(i, 10)), basic) - } - if e.Op == token.SHR && !isUnsigned(basic) { - return fc.fixNumber(fc.formatParenExpr("%e >> $min(%f, 31)", e.X, e.Y), basic) - } - 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) { - return fc.formatParenExpr("(%e %t %e) >>> 0", e.X, e.Op, e.Y) - } - return fc.formatParenExpr("%e %t %e", e.X, e.Op, e.Y) - case token.AND_NOT: - return fc.fixNumber(fc.formatParenExpr("%e & ~%e", e.X, e.Y), basic) - case token.XOR: - return fc.fixNumber(fc.formatParenExpr("%e ^ %e", e.X, e.Y), basic) - default: - panic(e.Op) - } - } - - switch e.Op { - case token.ADD, token.LSS, token.LEQ, token.GTR, token.GEQ: - return fc.formatExpr("%e %t %e", e.X, e.Op, e.Y) - case token.LAND: - if fc.Blocking[e.Y] { - skipCase := fc.caseCounter - fc.caseCounter++ - 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) - } - return fc.formatExpr("%e && %e", e.X, e.Y) - case token.LOR: - if fc.Blocking[e.Y] { - skipCase := fc.caseCounter - fc.caseCounter++ - 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) - } - return fc.formatExpr("%e || %e", e.X, e.Y) - case token.EQL: - switch u := t.Underlying().(type) { - case *types.Array, *types.Struct: - return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) - case *types.Interface: - return fc.formatExpr("$interfaceIsEqual(%s, %s)", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t)) - case *types.Basic: - if isBoolean(u) { - if b, ok := analysis.BoolValue(e.X, fc.pkgCtx.Info.Info); ok && b { - return fc.translateExpr(e.Y) - } - if b, ok := analysis.BoolValue(e.Y, fc.pkgCtx.Info.Info); ok && b { - return fc.translateExpr(e.X) - } - } - } - return fc.formatExpr("%s === %s", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t)) - default: - panic(e.Op) - } - + return fc.translateBinaryExpr(e) case *ast.ParenExpr: return fc.formatParenExpr("%e", e.X) @@ -845,6 +662,192 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } } +func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { + if e.Op == token.NEQ { + return fc.formatExpr("!(%s)", fc.translateExpr(&ast.BinaryExpr{ + X: e.X, + Op: token.EQL, + Y: e.Y, + })) + } + + t := fc.pkgCtx.TypeOf(e.X) + t2 := fc.pkgCtx.TypeOf(e.Y) + + // Exact type param instantiations are not known at compile time, so + // some operators require special handling. + if (typesutil.IsTypeParam(t) || typesutil.IsTypeParam(t2)) && + !(typesutil.IsInterface(t) || typesutil.IsInterface(t2)) { // == operator between an interface and other types is handled below. + if !types.Identical(t, t2) { + // This should never happen. + panic(bailout(fmt.Errorf("%s: binary operator %v is applied to different type param types %s and %s", fc.pkgCtx.fileSet.Position(e.Pos()), e.Op, t, t2))) + } + + switch e.Op { + case token.EQL: + return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) + } + } + + if typesutil.IsInterface(t2) || types.Identical(t, types.Typ[types.UntypedNil]) { + t = t2 + } + + if basic, isBasic := t.Underlying().(*types.Basic); isBasic && isNumeric(basic) { + if is64Bit(basic) { + switch e.Op { + case token.MUL: + return fc.formatExpr("$mul64(%e, %e)", e.X, e.Y) + case token.QUO: + return fc.formatExpr("$div64(%e, %e, false)", e.X, e.Y) + case token.REM: + return fc.formatExpr("$div64(%e, %e, true)", e.X, e.Y) + case token.SHL: + return fc.formatExpr("$shiftLeft64(%e, %f)", e.X, e.Y) + case token.SHR: + return fc.formatExpr("$shiftRight%s(%e, %f)", toJavaScriptType(basic), e.X, e.Y) + case token.EQL: + return fc.formatExpr("(%1h === %2h && %1l === %2l)", e.X, e.Y) + case token.LSS: + return fc.formatExpr("(%1h < %2h || (%1h === %2h && %1l < %2l))", e.X, e.Y) + case token.LEQ: + return fc.formatExpr("(%1h < %2h || (%1h === %2h && %1l <= %2l))", e.X, e.Y) + case token.GTR: + return fc.formatExpr("(%1h > %2h || (%1h === %2h && %1l > %2l))", e.X, e.Y) + case token.GEQ: + return fc.formatExpr("(%1h > %2h || (%1h === %2h && %1l >= %2l))", e.X, e.Y) + case token.ADD, token.SUB: + return fc.formatExpr("new %3s(%1h %4t %2h, %1l %4t %2l)", e.X, e.Y, fc.typeName(t), e.Op) + case token.AND, token.OR, token.XOR: + return fc.formatExpr("new %3s(%1h %4t %2h, (%1l %4t %2l) >>> 0)", e.X, e.Y, fc.typeName(t), e.Op) + case token.AND_NOT: + return fc.formatExpr("new %3s(%1h & ~%2h, (%1l & ~%2l) >>> 0)", e.X, e.Y, fc.typeName(t)) + default: + panic(e.Op) + } + } + + if isComplex(basic) { + switch e.Op { + case token.EQL: + return fc.formatExpr("(%1r === %2r && %1i === %2i)", e.X, e.Y) + case token.ADD, token.SUB: + return fc.formatExpr("new %3s(%1r %4t %2r, %1i %4t %2i)", e.X, e.Y, fc.typeName(t), e.Op) + case token.MUL: + return fc.formatExpr("new %3s(%1r * %2r - %1i * %2i, %1r * %2i + %1i * %2r)", e.X, e.Y, fc.typeName(t)) + case token.QUO: + return fc.formatExpr("$divComplex(%e, %e)", e.X, e.Y) + default: + panic(e.Op) + } + } + + switch e.Op { + case token.EQL: + return fc.formatParenExpr("%e === %e", e.X, e.Y) + case token.LSS, token.LEQ, token.GTR, token.GEQ: + return fc.formatExpr("%e %t %e", e.X, e.Op, e.Y) + case token.ADD, token.SUB: + return fc.fixNumber(fc.formatExpr("%e %t %e", e.X, e.Op, e.Y), basic) + case token.MUL: + switch basic.Kind() { + case types.Int32, types.Int: + return fc.formatParenExpr("$imul(%e, %e)", e.X, e.Y) + case types.Uint32, types.Uintptr: + return fc.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) + } + return fc.fixNumber(fc.formatExpr("%e * %e", e.X, e.Y), basic) + case token.QUO: + if isInteger(basic) { + // cut off decimals + shift := ">>" + 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.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.newLocalVariable("_r"), e.X, e.Y) + case token.SHL, token.SHR: + op := e.Op.String() + if e.Op == token.SHR && isUnsigned(basic) { + op = ">>>" + } + if v := fc.pkgCtx.Types[e.Y].Value; v != nil { + i, _ := constant.Uint64Val(constant.ToInt(v)) + if i >= 32 { + return fc.formatExpr("0") + } + return fc.fixNumber(fc.formatExpr("%e %s %s", e.X, op, strconv.FormatUint(i, 10)), basic) + } + if e.Op == token.SHR && !isUnsigned(basic) { + return fc.fixNumber(fc.formatParenExpr("%e >> $min(%f, 31)", e.X, e.Y), basic) + } + 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) { + return fc.formatParenExpr("(%e %t %e) >>> 0", e.X, e.Op, e.Y) + } + return fc.formatParenExpr("%e %t %e", e.X, e.Op, e.Y) + case token.AND_NOT: + return fc.fixNumber(fc.formatParenExpr("%e & ~%e", e.X, e.Y), basic) + case token.XOR: + return fc.fixNumber(fc.formatParenExpr("%e ^ %e", e.X, e.Y), basic) + default: + panic(e.Op) + } + } + + switch e.Op { + case token.ADD, token.LSS, token.LEQ, token.GTR, token.GEQ: + return fc.formatExpr("%e %t %e", e.X, e.Op, e.Y) + case token.LAND: + if fc.Blocking[e.Y] { + skipCase := fc.caseCounter + fc.caseCounter++ + 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) + } + return fc.formatExpr("%e && %e", e.X, e.Y) + case token.LOR: + if fc.Blocking[e.Y] { + skipCase := fc.caseCounter + fc.caseCounter++ + 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) + } + return fc.formatExpr("%e || %e", e.X, e.Y) + case token.EQL: + switch u := t.Underlying().(type) { + case *types.Array, *types.Struct: + return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) + case *types.Interface: + return fc.formatExpr("$interfaceIsEqual(%s, %s)", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t)) + case *types.Basic: + if isBoolean(u) { + if b, ok := analysis.BoolValue(e.X, fc.pkgCtx.Info.Info); ok && b { + return fc.translateExpr(e.Y) + } + if b, ok := analysis.BoolValue(e.Y, fc.pkgCtx.Info.Info); ok && b { + return fc.translateExpr(e.X) + } + } + } + return fc.formatExpr("%s === %s", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t)) + default: + panic(e.Op) + } +} + // translateGenericInstance translates a generic function instantiation. // // The returned JS expression evaluates into a callable function with type params From 2e7a5e462a51ef7466080771ee03771591ff861e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 19:40:05 +0000 Subject: [PATCH 64/83] Implement addition operator for type parameters. Admittedly, the additional layer of indirection will make generic code slower than non-generic, but different types implement addition differently, so there is no universal code the compiler could generate that would require indirection. Hopefully, JIT will mostly inline this in practice. --- compiler/expressions.go | 2 + compiler/prelude/types.js | 51 ++++++++++++++---- go.mod | 10 ++-- go.sum | 22 ++++---- tests/typeparams/arithmetics_test.go | 78 ++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 tests/typeparams/arithmetics_test.go diff --git a/compiler/expressions.go b/compiler/expressions.go index ee51bdf85..143525e64 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -686,6 +686,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { switch e.Op { case token.EQL: return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) + case token.ADD: + return fc.formatExpr("%s.add(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 49baad391..9781698b3 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -410,15 +410,48 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { $panic(new $String("invalid kind: " + kind)); } + // Arithmetics operations for types that support it. + // + // Each operation accepts two operands and returns one result. For wrapped types operands are + // passed as bare values and a bare value is returned. + // + // This methods will be called when the exact type is not known at code generation time, for + // example, when operands are type parameters. + switch (kind) { + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindFloat32: + case $kindFloat64: + typ.add = (x, y) => $truncateNumber(x + y, typ); + break; + case $kindInt64: + case $kindUint64: + typ.add = (x, y) => new typ(x.$high + y.$high, x.$low + y.$low); + break; + case $kindComplex64: + case $kindComplex128: + typ.add = (x, y) => new typ(x.$real + y.$real, x.$imag + y.$imag); + break; + case $kindString: + typ.add = (x, y) => x + y; + } + /** * convertFrom converts value src to the type typ. - * + * * For wrapped types src must be a wrapped value, e.g. for int32 this must be an instance of * the $Int32 class, rather than the bare JavaScript number. This is required to determine * the original Go type to convert from. - * + * * The returned value will be a representation of typ; for wrapped values it will be unwrapped; - * for example, conversion to int32 will return a bare JavaScript number. This is required + * for example, conversion to int32 will return a bare JavaScript number. This is required * to make results of type conversion expression consistent with any other expressions of the * same type. */ @@ -926,7 +959,7 @@ const $truncateNumber = (n, typ) => { /** * Trivial type conversion function, which only accepts destination type identical to the src * type. - * + * * For wrapped types, src value must be wrapped, and the return value will be unwrapped. */ const $convertIdentity = (src, dstType) => { @@ -967,7 +1000,7 @@ const $convertToInt64 = (src, dstType) => { /** * Conversion to int and uint types of 32 bits or less. - * + * * dstType.kind must be $kindInt{8,16,32} or $kindUint{8,16,32}. For wrapped * types, src value must be wrapped. The return value will always be a bare * JavaScript number, since all 32-or-less integers in GopherJS are considered @@ -1026,7 +1059,7 @@ const $convertToFloat = (src, dstType) => { /** * Conversion to complex types. - * + * * dstType.kind must me $kindComplex{64,128}. Src must be another complex type. * Returned value will always be an oject created by the dstType constructor. */ @@ -1113,7 +1146,7 @@ const $convertToInterface = (src, dstType) => { /** * Convert to a slice value. - * + * * dstType.kind must be $kindSlice. For wrapped types, src value must be wrapped. * The returned value is always a slice type. */ @@ -1140,8 +1173,8 @@ const $convertToSlice = (src, dstType) => { /** * Convert to a pointer value. -* -* dstType.kind must be $kindPtr. For wrapped types (specifically, pointers +* +* dstType.kind must be $kindPtr. For wrapped types (specifically, pointers * to an array), src value must be wrapped. The returned value is a bare JS * array (typed or untyped), or a pointer object. */ diff --git a/go.mod b/go.mod index 503bb1705..3985b8bc2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/evanw/esbuild v0.18.0 github.com/fsnotify/fsnotify v1.5.1 - github.com/google/go-cmp v0.5.7 + github.com/google/go-cmp v0.5.8 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 @@ -15,13 +15,13 @@ require ( github.com/visualfc/goembed v0.3.3 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/exp/typeparams v0.0.0-20230127193734-31bee513bff7 - golang.org/x/sync v0.3.0 - golang.org/x/sys v0.10.0 - golang.org/x/tools v0.11.0 + golang.org/x/sync v0.4.0 + golang.org/x/sys v0.13.0 + golang.org/x/tools v0.14.0 ) require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect ) diff --git a/go.sum b/go.sum index e2aab76e7..47039c802 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -272,6 +272,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp/typeparams v0.0.0-20230127193734-31bee513bff7 h1:b1tzrw2iCf2wUlbWCLmOD3SdX6hiDxxfanz/zrnIEOs= golang.org/x/exp/typeparams v0.0.0-20230127193734-31bee513bff7/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -299,7 +301,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -359,8 +361,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -405,8 +407,8 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -471,14 +473,12 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go new file mode 100644 index 000000000..21912151c --- /dev/null +++ b/tests/typeparams/arithmetics_test.go @@ -0,0 +1,78 @@ +package typeparams_test + +import ( + "fmt" + "go/token" + "testing" + + "golang.org/x/exp/constraints" +) + +type testCaseI interface { + Run(t *testing.T) + String() string +} + +type testCase[T comparable] struct { + op func(x, y T) T + opName token.Token + x T + y T + want T +} + +func (tc *testCase[T]) Run(t *testing.T) { + got := tc.op(tc.x, tc.y) + if got != tc.want { + t.Errorf("Got: %v %v %v = %v. Want: %v.", tc.x, tc.opName, tc.y, got, tc.want) + } +} + +func (tc *testCase[T]) String() string { + return fmt.Sprintf("%T/%v%v%v", tc.x, tc.x, tc.opName, tc.y) +} + +type addable interface { + constraints.Integer | constraints.Float | constraints.Complex | string +} + +func add[T addable](x, y T) T { + return x + y +} + +func addTC[T addable](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: add[T], + opName: token.ADD, + x: x, + y: y, + want: want, + } +} + +func TestAdd(t *testing.T) { + tests := []testCaseI{ + addTC[int](1, 2, 3), + addTC[uint](1, 2, 3), + addTC[uintptr](1, 2, 3), + addTC[int8](1, 2, 3), + addTC[int16](1, 2, 3), + addTC[int32](1, 2, 3), + addTC[uint8](1, 2, 3), + addTC[uint16](1, 2, 3), + addTC[uint32](1, 2, 3), + addTC[int8](127, 2, -127), // Overflow. + addTC[uint8](255, 2, 1), // Overflow. + addTC[float32](1.5, 1.1, 2.6), + addTC[float64](1.5, 1.1, 2.6), + addTC[int64](0x00000030FFFFFFFF, 0x0000000100000002, 0x0000003200000001), + addTC[uint64](0x00000030FFFFFFFF, 0x0000000100000002, 0x0000003200000001), + addTC[string]("abc", "def", "abcdef"), + addTC[complex64](1+2i, 3+4i, 4+6i), + addTC[complex128](1+2i, 3+4i, 4+6i), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From 11fdd9694517679e6c076697d514adc00a5dc307 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 19:54:39 +0000 Subject: [PATCH 65/83] Implement subtraction operator support for type parameters. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 3 ++ tests/typeparams/arithmetics_test.go | 44 ++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index 143525e64..87ffbd33a 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -688,6 +688,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) case token.ADD: return fc.formatExpr("%s.add(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.SUB: + return fc.formatExpr("%s.sub(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 9781698b3..a1e57551e 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -430,14 +430,17 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindFloat32: case $kindFloat64: typ.add = (x, y) => $truncateNumber(x + y, typ); + typ.sub = (x, y) => $truncateNumber(x - y, typ); break; case $kindInt64: case $kindUint64: typ.add = (x, y) => new typ(x.$high + y.$high, x.$low + y.$low); + typ.sub = (x, y) => new typ(x.$high - y.$high, x.$low - y.$low); break; case $kindComplex64: case $kindComplex128: typ.add = (x, y) => new typ(x.$real + y.$real, x.$imag + y.$imag); + typ.sub = (x, y) => new typ(x.$real - y.$real, x.$imag - y.$imag); break; case $kindString: typ.add = (x, y) => x + y; diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index 21912151c..4466f8f28 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -76,3 +76,47 @@ func TestAdd(t *testing.T) { t.Run(test.String(), test.Run) } } + +type subtractable interface { + constraints.Integer | constraints.Float | constraints.Complex +} + +func subtract[T subtractable](x, y T) T { + return x - y +} + +func subTC[T subtractable](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: subtract[T], + opName: token.SUB, + x: x, + y: y, + want: want, + } +} + +func TestSubtract(t *testing.T) { + tests := []testCaseI{ + subTC[int](3, 1, 2), + subTC[uint](3, 1, 2), + subTC[uintptr](3, 1, 2), + subTC[int8](3, 1, 2), + subTC[int16](3, 1, 2), + subTC[int32](3, 1, 2), + subTC[uint8](3, 1, 2), + subTC[uint16](3, 1, 2), + subTC[uint32](3, 1, 2), + subTC[int8](-127, 2, 127), // Overflow. + subTC[uint8](1, 2, 255), // Overflow. + subTC[float32](2.5, 1.4, 1.1), + subTC[float64](2.5, 1.4, 1.1), + subTC[int64](0x0000003200000001, 0x0000000100000002, 0x00000030FFFFFFFF), + subTC[uint64](0x0000003200000001, 0x0000000100000002, 0x00000030FFFFFFFF), + subTC[complex64](10+11i, 2+1i, 8+10i), + subTC[complex128](10+11i, 2+1i, 8+10i), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From 98523b44997a3490386d7a68f989cddb47d808d1 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 20:56:29 +0000 Subject: [PATCH 66/83] Support multiplication operator for type params. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 19 +++++++++--- tests/typeparams/arithmetics_test.go | 44 ++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 87ffbd33a..b397ce039 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -690,6 +690,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.add(%e, %e)", fc.typeName(t), e.X, e.Y) case token.SUB: return fc.formatExpr("%s.sub(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.MUL: + return fc.formatExpr("%s.mul(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index a1e57551e..7d0f2708e 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -418,29 +418,40 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { // This methods will be called when the exact type is not known at code generation time, for // example, when operands are type parameters. switch (kind) { - case $kindInt: case $kindInt8: case $kindInt16: - case $kindInt32: case $kindUint: case $kindUint8: case $kindUint16: - case $kindUint32: - case $kindUintptr: case $kindFloat32: case $kindFloat64: typ.add = (x, y) => $truncateNumber(x + y, typ); typ.sub = (x, y) => $truncateNumber(x - y, typ); + typ.mul = (x, y) => $truncateNumber(x * y, typ); + break; + case $kindUint32: + case $kindUintptr: + typ.add = (x, y) => $truncateNumber(x + y, typ); + typ.sub = (x, y) => $truncateNumber(x - y, typ); + typ.mul = (x, y) => $imul(x, y) >>> 0; + break; + case $kindInt: + case $kindInt32: + typ.add = (x, y) => $truncateNumber(x + y, typ); + typ.sub = (x, y) => $truncateNumber(x - y, typ); + typ.mul = (x, y) => $imul(x, y); break; case $kindInt64: case $kindUint64: typ.add = (x, y) => new typ(x.$high + y.$high, x.$low + y.$low); typ.sub = (x, y) => new typ(x.$high - y.$high, x.$low - y.$low); + typ.mul = (x, y) => $mul64(x, y); break; case $kindComplex64: case $kindComplex128: typ.add = (x, y) => new typ(x.$real + y.$real, x.$imag + y.$imag); typ.sub = (x, y) => new typ(x.$real - y.$real, x.$imag - y.$imag); + typ.mul = (x, y) => new typ(x.$real * y.$real - x.$imag * y.$imag, x.$real * y.$imag + x.$imag * y.$real); break; case $kindString: typ.add = (x, y) => x + y; diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index 4466f8f28..804971390 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -120,3 +120,47 @@ func TestSubtract(t *testing.T) { t.Run(test.String(), test.Run) } } + +type muiltipliable interface { + constraints.Integer | constraints.Float | constraints.Complex +} + +func mul[T muiltipliable](x, y T) T { + return x * y +} + +func mulTC[T muiltipliable](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: mul[T], + opName: token.MUL, + x: x, + y: y, + want: want, + } +} + +func TestMul(t *testing.T) { + tests := []testCaseI{ + mulTC[int](2, 3, 6), + mulTC[uint](2, 3, 6), + mulTC[uintptr](2, 3, 6), + mulTC[int8](2, 3, 6), + mulTC[int16](2, 3, 6), + mulTC[int32](2, 3, 6), + mulTC[uint8](2, 3, 6), + mulTC[uint16](2, 3, 6), + mulTC[uint32](2, 3, 6), + mulTC[int8](127, 3, 125), // Overflow. + mulTC[uint8](250, 3, 238), // Overflow. + mulTC[float32](2.5, 1.4, 3.5), + mulTC[float64](2.5, 1.4, 3.5), + mulTC[int64](0x0000003200000001, 0x0000000100000002, 0x0000006500000002), + mulTC[uint64](0x0000003200000001, 0x0000000100000002, 0x0000006500000002), + mulTC[complex64](1+2i, 3+4i, -5+10i), + mulTC[complex128](1+2i, 3+4i, -5+10i), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From 2e76d2dd39434a7e1e852cb3a62023959985f8e4 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 21:18:38 +0000 Subject: [PATCH 67/83] Support division operator for type parameters. --- compiler/expressions.go | 2 + compiler/prelude/numeric.js | 9 ++++ compiler/prelude/types.js | 17 ++++++- tests/typeparams/arithmetics_test.go | 66 +++++++++++++++++++++------- 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index b397ce039..a43c6d0d2 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -692,6 +692,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.sub(%e, %e)", fc.typeName(t), e.X, e.Y) case token.MUL: return fc.formatExpr("%s.mul(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.QUO: + return fc.formatExpr("%s.div(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/numeric.js b/compiler/prelude/numeric.js index 5cfd7644d..fdd7cbbe2 100644 --- a/compiler/prelude/numeric.js +++ b/compiler/prelude/numeric.js @@ -116,6 +116,15 @@ var $mul64 = (x, y) => { return r; }; +const $idiv = (x, y) => { + const result = x / y; + if (result === result && result != 1/0 && result != -1/0) { + return result; + } else { + $throwRuntimeError("integer divide by zero"); + } +}; + var $div64 = (x, y, returnRemainder) => { if (y.$high === 0 && y.$low === 0) { $throwRuntimeError("integer divide by zero"); diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 7d0f2708e..c8e7c26a4 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -420,38 +420,53 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { switch (kind) { case $kindInt8: case $kindInt16: - case $kindUint: + typ.add = (x, y) => $truncateNumber(x + y, typ); + typ.sub = (x, y) => $truncateNumber(x - y, typ); + typ.mul = (x, y) => $truncateNumber(x * y, typ); + typ.div = (x, y) => $idiv(x, y) >> 0; + break; case $kindUint8: case $kindUint16: + typ.add = (x, y) => $truncateNumber(x + y, typ); + typ.sub = (x, y) => $truncateNumber(x - y, typ); + typ.mul = (x, y) => $truncateNumber(x * y, typ); + typ.div = (x, y) => $idiv(x, y) >>> 0; + break; case $kindFloat32: case $kindFloat64: typ.add = (x, y) => $truncateNumber(x + y, typ); typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $truncateNumber(x * y, typ); + typ.div = (x, y) => $truncateNumber(x / y, typ); break; + case $kindUint: case $kindUint32: case $kindUintptr: typ.add = (x, y) => $truncateNumber(x + y, typ); typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $imul(x, y) >>> 0; + typ.div = (x, y) => $idiv(x, y) >>> 0; break; case $kindInt: case $kindInt32: typ.add = (x, y) => $truncateNumber(x + y, typ); typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $imul(x, y); + typ.div = (x, y) => $idiv(x, y) >> 0; break; case $kindInt64: case $kindUint64: typ.add = (x, y) => new typ(x.$high + y.$high, x.$low + y.$low); typ.sub = (x, y) => new typ(x.$high - y.$high, x.$low - y.$low); typ.mul = (x, y) => $mul64(x, y); + typ.div = (x, y) => $div64(x, y, false); break; case $kindComplex64: case $kindComplex128: typ.add = (x, y) => new typ(x.$real + y.$real, x.$imag + y.$imag); typ.sub = (x, y) => new typ(x.$real - y.$real, x.$imag - y.$imag); typ.mul = (x, y) => new typ(x.$real * y.$real - x.$imag * y.$imag, x.$real * y.$imag + x.$imag * y.$real); + typ.div = (x, y) => $divComplex(x, y); break; case $kindString: typ.add = (x, y) => x + y; diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index 804971390..dc6add1f0 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -8,6 +8,14 @@ import ( "golang.org/x/exp/constraints" ) +type arithmetic interface { + constraints.Integer | constraints.Float | constraints.Complex +} + +type addable interface { + arithmetic | string +} + type testCaseI interface { Run(t *testing.T) String() string @@ -32,10 +40,6 @@ func (tc *testCase[T]) String() string { return fmt.Sprintf("%T/%v%v%v", tc.x, tc.x, tc.opName, tc.y) } -type addable interface { - constraints.Integer | constraints.Float | constraints.Complex | string -} - func add[T addable](x, y T) T { return x + y } @@ -77,15 +81,11 @@ func TestAdd(t *testing.T) { } } -type subtractable interface { - constraints.Integer | constraints.Float | constraints.Complex -} - -func subtract[T subtractable](x, y T) T { +func subtract[T arithmetic](x, y T) T { return x - y } -func subTC[T subtractable](x, y, want T) *testCase[T] { +func subTC[T arithmetic](x, y, want T) *testCase[T] { return &testCase[T]{ op: subtract[T], opName: token.SUB, @@ -121,15 +121,11 @@ func TestSubtract(t *testing.T) { } } -type muiltipliable interface { - constraints.Integer | constraints.Float | constraints.Complex -} - -func mul[T muiltipliable](x, y T) T { +func mul[T arithmetic](x, y T) T { return x * y } -func mulTC[T muiltipliable](x, y, want T) *testCase[T] { +func mulTC[T arithmetic](x, y, want T) *testCase[T] { return &testCase[T]{ op: mul[T], opName: token.MUL, @@ -164,3 +160,41 @@ func TestMul(t *testing.T) { t.Run(test.String(), test.Run) } } + +func div[T arithmetic](x, y T) T { + return x / y +} + +func divTC[T arithmetic](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: div[T], + opName: token.QUO, + x: x, + y: y, + want: want, + } +} + +func TestDiv(t *testing.T) { + tests := []testCaseI{ + divTC[int](7, 2, 3), + divTC[uint](7, 2, 3), + divTC[uintptr](7, 2, 3), + divTC[int8](7, 2, 3), + divTC[int16](7, 2, 3), + divTC[int32](7, 2, 3), + divTC[uint8](7, 2, 3), + divTC[uint16](7, 2, 3), + divTC[uint32](7, 2, 3), + divTC[float32](3.5, 2.5, 1.4), + divTC[float64](3.5, 2.5, 1.4), + divTC[int64](0x0000006500000003, 0x0000003200000001, 2), + divTC[uint64](0x0000006500000003, 0x0000003200000001, 2), + divTC[complex64](-5+10i, 1+2i, 3+4i), + divTC[complex128](-5+10i, 1+2i, 3+4i), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From 14241fd52d748e24740ce2d0271bae5f10141472 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 21:27:30 +0000 Subject: [PATCH 68/83] Support remainder operator for type params. --- compiler/expressions.go | 2 ++ compiler/prelude/numeric.js | 9 ++++++++ compiler/prelude/types.js | 19 ++++++++++------ tests/typeparams/arithmetics_test.go | 34 ++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index a43c6d0d2..17d611bd2 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -694,6 +694,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.mul(%e, %e)", fc.typeName(t), e.X, e.Y) case token.QUO: return fc.formatExpr("%s.div(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.REM: + return fc.formatExpr("%s.rem(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/numeric.js b/compiler/prelude/numeric.js index fdd7cbbe2..3b14c6783 100644 --- a/compiler/prelude/numeric.js +++ b/compiler/prelude/numeric.js @@ -125,6 +125,15 @@ const $idiv = (x, y) => { } }; +const $irem = (x, y) => { + const result = x % y; + if (result === result) { + return result; + } else { + $throwRuntimeError("integer divide by zero"); + } +}; + var $div64 = (x, y, returnRemainder) => { if (y.$high === 0 && y.$low === 0) { $throwRuntimeError("integer divide by zero"); diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index c8e7c26a4..eb08e1e74 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -424,6 +424,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $truncateNumber(x * y, typ); typ.div = (x, y) => $idiv(x, y) >> 0; + typ.rem = $irem; break; case $kindUint8: case $kindUint16: @@ -431,13 +432,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $truncateNumber(x * y, typ); typ.div = (x, y) => $idiv(x, y) >>> 0; - break; - case $kindFloat32: - case $kindFloat64: - typ.add = (x, y) => $truncateNumber(x + y, typ); - typ.sub = (x, y) => $truncateNumber(x - y, typ); - typ.mul = (x, y) => $truncateNumber(x * y, typ); - typ.div = (x, y) => $truncateNumber(x / y, typ); + typ.rem = $irem; break; case $kindUint: case $kindUint32: @@ -446,6 +441,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $imul(x, y) >>> 0; typ.div = (x, y) => $idiv(x, y) >>> 0; + typ.rem = $irem; break; case $kindInt: case $kindInt32: @@ -453,6 +449,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.sub = (x, y) => $truncateNumber(x - y, typ); typ.mul = (x, y) => $imul(x, y); typ.div = (x, y) => $idiv(x, y) >> 0; + typ.rem = $irem; break; case $kindInt64: case $kindUint64: @@ -460,6 +457,14 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.sub = (x, y) => new typ(x.$high - y.$high, x.$low - y.$low); typ.mul = (x, y) => $mul64(x, y); typ.div = (x, y) => $div64(x, y, false); + typ.rem = (x, y) => $div64(x, y, true); + break; + case $kindFloat32: + case $kindFloat64: + typ.add = (x, y) => $truncateNumber(x + y, typ); + typ.sub = (x, y) => $truncateNumber(x - y, typ); + typ.mul = (x, y) => $truncateNumber(x * y, typ); + typ.div = (x, y) => $truncateNumber(x / y, typ); break; case $kindComplex64: case $kindComplex128: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index dc6add1f0..88c2a8eb2 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -198,3 +198,37 @@ func TestDiv(t *testing.T) { t.Run(test.String(), test.Run) } } + +func rem[T constraints.Integer](x, y T) T { + return x % y +} + +func remTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: rem[T], + opName: token.REM, + x: x, + y: y, + want: want, + } +} + +func TestRemainder(t *testing.T) { + tests := []testCaseI{ + remTC[int](7, 2, 1), + remTC[uint](7, 2, 1), + remTC[uintptr](7, 2, 1), + remTC[int8](7, 2, 1), + remTC[int16](7, 2, 1), + remTC[int32](7, 2, 1), + remTC[uint8](7, 2, 1), + remTC[uint16](7, 2, 1), + remTC[uint32](7, 2, 1), + remTC[int64](0x0000006500000003, 0x0000003200000001, 0x100000001), + remTC[uint64](0x0000006500000003, 0x0000003200000001, 0x100000001), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From 2260a572bca130f857753d0e883ac7a2dad370b8 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 21:40:08 +0000 Subject: [PATCH 69/83] Implement bitwise and operator for type parameters. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 5 ++++ tests/typeparams/arithmetics_test.go | 34 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index 17d611bd2..7441c2191 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -696,6 +696,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.div(%e, %e)", fc.typeName(t), e.X, e.Y) case token.REM: return fc.formatExpr("%s.rem(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.AND: + return fc.formatExpr("%s.and(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index eb08e1e74..291e5f4a5 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -425,6 +425,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.mul = (x, y) => $truncateNumber(x * y, typ); typ.div = (x, y) => $idiv(x, y) >> 0; typ.rem = $irem; + typ.and = (x, y) => (x & y); break; case $kindUint8: case $kindUint16: @@ -433,6 +434,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.mul = (x, y) => $truncateNumber(x * y, typ); typ.div = (x, y) => $idiv(x, y) >>> 0; typ.rem = $irem; + typ.and = (x, y) => (x & y) >>> 0; break; case $kindUint: case $kindUint32: @@ -442,6 +444,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.mul = (x, y) => $imul(x, y) >>> 0; typ.div = (x, y) => $idiv(x, y) >>> 0; typ.rem = $irem; + typ.and = (x, y) => (x & y) >>> 0; break; case $kindInt: case $kindInt32: @@ -450,6 +453,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.mul = (x, y) => $imul(x, y); typ.div = (x, y) => $idiv(x, y) >> 0; typ.rem = $irem; + typ.and = (x, y) => (x & y); break; case $kindInt64: case $kindUint64: @@ -458,6 +462,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.mul = (x, y) => $mul64(x, y); typ.div = (x, y) => $div64(x, y, false); typ.rem = (x, y) => $div64(x, y, true); + typ.and = (x, y) => new typ(x.$high & y.$high, (x.$low & y.$low) >>> 0); break; case $kindFloat32: case $kindFloat64: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index 88c2a8eb2..c98e8b9fe 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -232,3 +232,37 @@ func TestRemainder(t *testing.T) { t.Run(test.String(), test.Run) } } + +func and[T constraints.Integer](x, y T) T { + return x & y +} + +func andTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: and[T], + opName: token.AND, + x: x, + y: y, + want: want, + } +} + +func TestBitwiseAnd(t *testing.T) { + tests := []testCaseI{ + andTC[int](0x0011, 0x0101, 0x0001), + andTC[uint](0x0011, 0x0101, 0x0001), + andTC[uintptr](0x0011, 0x0101, 0x0001), + andTC[int8](0x11, 0x01, 0x01), + andTC[int16](0x0011, 0x0101, 0x0001), + andTC[int32](0x0011, 0x0101, 0x0001), + andTC[uint8](0x11, 0x01, 0x01), + andTC[uint16](0x0011, 0x0101, 0x0001), + andTC[uint32](0x0011, 0x0101, 0x0001), + andTC[int64](0x0000001100000011, 0x0000010100000101, 0x0000000100000001), + andTC[uint64](0x0000001100000011, 0x0000010100000101, 0x0000000100000001), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From c6df48a7c6688e52e97aa83b5ea4054b30b279a2 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 21:45:54 +0000 Subject: [PATCH 70/83] Support bitwise or operator for type params. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 5 ++++ tests/typeparams/arithmetics_test.go | 34 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index 7441c2191..1d18cd85e 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -698,6 +698,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.rem(%e, %e)", fc.typeName(t), e.X, e.Y) case token.AND: return fc.formatExpr("%s.and(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.OR: + return fc.formatExpr("%s.or(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 291e5f4a5..82220ba0e 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -426,6 +426,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.div = (x, y) => $idiv(x, y) >> 0; typ.rem = $irem; typ.and = (x, y) => (x & y); + typ.or = (x, y) => (x | y); break; case $kindUint8: case $kindUint16: @@ -435,6 +436,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.div = (x, y) => $idiv(x, y) >>> 0; typ.rem = $irem; typ.and = (x, y) => (x & y) >>> 0; + typ.or = (x, y) => (x | y) >>> 0; break; case $kindUint: case $kindUint32: @@ -445,6 +447,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.div = (x, y) => $idiv(x, y) >>> 0; typ.rem = $irem; typ.and = (x, y) => (x & y) >>> 0; + typ.or = (x, y) => (x | y) >>> 0; break; case $kindInt: case $kindInt32: @@ -454,6 +457,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.div = (x, y) => $idiv(x, y) >> 0; typ.rem = $irem; typ.and = (x, y) => (x & y); + typ.or = (x, y) => (x | y); break; case $kindInt64: case $kindUint64: @@ -463,6 +467,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.div = (x, y) => $div64(x, y, false); typ.rem = (x, y) => $div64(x, y, true); typ.and = (x, y) => new typ(x.$high & y.$high, (x.$low & y.$low) >>> 0); + typ.or = (x, y) => new typ(x.$high | y.$high, (x.$low | y.$low) >>> 0); break; case $kindFloat32: case $kindFloat64: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index c98e8b9fe..db5a5cb14 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -266,3 +266,37 @@ func TestBitwiseAnd(t *testing.T) { t.Run(test.String(), test.Run) } } + +func or[T constraints.Integer](x, y T) T { + return x | y +} + +func orTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: or[T], + opName: token.OR, + x: x, + y: y, + want: want, + } +} + +func TestBitwiseOr(t *testing.T) { + tests := []testCaseI{ + orTC[int](0x0011, 0x0101, 0x0111), + orTC[uint](0x0011, 0x0101, 0x0111), + orTC[uintptr](0x0011, 0x0101, 0x0111), + orTC[int8](0x11, 0x01, 0x11), + orTC[int16](0x0011, 0x0101, 0x0111), + orTC[int32](0x0011, 0x0101, 0x0111), + orTC[uint8](0x11, 0x01, 0x11), + orTC[uint16](0x0011, 0x0101, 0x0111), + orTC[uint32](0x0011, 0x0101, 0x0111), + orTC[int64](0x0000001100000011, 0x0000010100000101, 0x0000011100000111), + orTC[uint64](0x0000001100000011, 0x0000010100000101, 0x0000011100000111), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From af59ead94c75c0e2e95c175fa702ee0dee9c1293 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 21:50:53 +0000 Subject: [PATCH 71/83] Support bitwise xor operator for type parameters. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 5 ++++ tests/typeparams/arithmetics_test.go | 34 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index 1d18cd85e..0591586a7 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -700,6 +700,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.and(%e, %e)", fc.typeName(t), e.X, e.Y) case token.OR: return fc.formatExpr("%s.or(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.XOR: + return fc.formatExpr("%s.xor(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 82220ba0e..3cc3c4708 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -427,6 +427,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.rem = $irem; typ.and = (x, y) => (x & y); typ.or = (x, y) => (x | y); + typ.xor = (x, y) => $truncateNumber(x ^ y, typ); break; case $kindUint8: case $kindUint16: @@ -437,6 +438,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.rem = $irem; typ.and = (x, y) => (x & y) >>> 0; typ.or = (x, y) => (x | y) >>> 0; + typ.xor = (x, y) => $truncateNumber(x ^ y, typ); break; case $kindUint: case $kindUint32: @@ -448,6 +450,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.rem = $irem; typ.and = (x, y) => (x & y) >>> 0; typ.or = (x, y) => (x | y) >>> 0; + typ.xor = (x, y) => $truncateNumber(x ^ y, typ); break; case $kindInt: case $kindInt32: @@ -458,6 +461,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.rem = $irem; typ.and = (x, y) => (x & y); typ.or = (x, y) => (x | y); + typ.xor = (x, y) => $truncateNumber(x ^ y, typ); break; case $kindInt64: case $kindUint64: @@ -468,6 +472,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.rem = (x, y) => $div64(x, y, true); typ.and = (x, y) => new typ(x.$high & y.$high, (x.$low & y.$low) >>> 0); typ.or = (x, y) => new typ(x.$high | y.$high, (x.$low | y.$low) >>> 0); + typ.xor = (x, y) => new typ(x.$high ^ y.$high, (x.$low ^ y.$low) >>> 0); break; case $kindFloat32: case $kindFloat64: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index db5a5cb14..7fc465037 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -300,3 +300,37 @@ func TestBitwiseOr(t *testing.T) { t.Run(test.String(), test.Run) } } + +func xor[T constraints.Integer](x, y T) T { + return x ^ y +} + +func xorTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: xor[T], + opName: token.XOR, + x: x, + y: y, + want: want, + } +} + +func TestBitwiseXor(t *testing.T) { + tests := []testCaseI{ + xorTC[int](0x0011, 0x0101, 0x0110), + xorTC[uint](0x0011, 0x0101, 0x0110), + xorTC[uintptr](0x0011, 0x0101, 0x0110), + xorTC[int8](0x11, 0x01, 0x10), + xorTC[int16](0x0011, 0x0101, 0x0110), + xorTC[int32](0x0011, 0x0101, 0x0110), + xorTC[uint8](0x11, 0x01, 0x10), + xorTC[uint16](0x0011, 0x0101, 0x0110), + xorTC[uint32](0x0011, 0x0101, 0x0110), + xorTC[int64](0x0000001100000011, 0x0000010100000101, 0x0000011000000110), + xorTC[uint64](0x0000001100000011, 0x0000010100000101, 0x0000011000000110), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From f4c090459131e0728f5cd6fdca9b4ccc69721610 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 21:58:25 +0000 Subject: [PATCH 72/83] Support "bit clear" (aka "and not") bitwise operator for type params. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 5 ++++ tests/typeparams/arithmetics_test.go | 34 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index 0591586a7..523bfbf17 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -702,6 +702,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.or(%e, %e)", fc.typeName(t), e.X, e.Y) case token.XOR: return fc.formatExpr("%s.xor(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.AND_NOT: + return fc.formatExpr("%s.andNot(%e, %e)", fc.typeName(t), e.X, e.Y) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 3cc3c4708..ed1d18360 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -428,6 +428,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.and = (x, y) => (x & y); typ.or = (x, y) => (x | y); typ.xor = (x, y) => $truncateNumber(x ^ y, typ); + typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); break; case $kindUint8: case $kindUint16: @@ -439,6 +440,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.and = (x, y) => (x & y) >>> 0; typ.or = (x, y) => (x | y) >>> 0; typ.xor = (x, y) => $truncateNumber(x ^ y, typ); + typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); break; case $kindUint: case $kindUint32: @@ -451,6 +453,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.and = (x, y) => (x & y) >>> 0; typ.or = (x, y) => (x | y) >>> 0; typ.xor = (x, y) => $truncateNumber(x ^ y, typ); + typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); break; case $kindInt: case $kindInt32: @@ -462,6 +465,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.and = (x, y) => (x & y); typ.or = (x, y) => (x | y); typ.xor = (x, y) => $truncateNumber(x ^ y, typ); + typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); break; case $kindInt64: case $kindUint64: @@ -473,6 +477,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.and = (x, y) => new typ(x.$high & y.$high, (x.$low & y.$low) >>> 0); typ.or = (x, y) => new typ(x.$high | y.$high, (x.$low | y.$low) >>> 0); typ.xor = (x, y) => new typ(x.$high ^ y.$high, (x.$low ^ y.$low) >>> 0); + typ.andNot = (x, y) => new typ(x.$high & ~y.$high, (x.$low & ~y.$low) >>> 0); break; case $kindFloat32: case $kindFloat64: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index 7fc465037..0fc3419ba 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -334,3 +334,37 @@ func TestBitwiseXor(t *testing.T) { t.Run(test.String(), test.Run) } } + +func andNot[T constraints.Integer](x, y T) T { + return x &^ y +} + +func andNotTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: andNot[T], + opName: token.AND_NOT, + x: x, + y: y, + want: want, + } +} + +func TestBitwiseAndNot(t *testing.T) { + tests := []testCaseI{ + andNotTC[int](0x0011, 0x0101, 0x0010), + andNotTC[uint](0x0011, 0x0101, 0x0010), + andNotTC[uintptr](0x0011, 0x0101, 0x0010), + andNotTC[int8](0x11, 0x01, 0x10), + andNotTC[int16](0x0011, 0x0101, 0x0010), + andNotTC[int32](0x0011, 0x0101, 0x0010), + andNotTC[uint8](0x11, 0x01, 0x10), + andNotTC[uint16](0x0011, 0x0101, 0x0010), + andNotTC[uint32](0x0011, 0x0101, 0x0010), + andNotTC[int64](0x0000001100000011, 0x0000010100000101, 0x0000001000000010), + andNotTC[uint64](0x0000001100000011, 0x0000010100000101, 0x0000001000000010), + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From ac1391894e0927870a68bf79df9a9c6b1270b6d6 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 22:38:21 +0000 Subject: [PATCH 73/83] Implement bitwise shift left operator for type parameters. --- compiler/expressions.go | 2 ++ compiler/prelude/numeric.js | 5 +++- compiler/prelude/types.js | 5 ++++ tests/typeparams/arithmetics_test.go | 40 ++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 523bfbf17..7339bbc42 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -704,6 +704,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.xor(%e, %e)", fc.typeName(t), e.X, e.Y) case token.AND_NOT: return fc.formatExpr("%s.andNot(%e, %e)", fc.typeName(t), e.X, e.Y) + case token.SHL: + return fc.formatExpr("%s.shl(%e, $flatten64(%e, %s))", fc.typeName(t), e.X, e.Y, fc.typeName(t2)) } } diff --git a/compiler/prelude/numeric.js b/compiler/prelude/numeric.js index 3b14c6783..4eb2730e4 100644 --- a/compiler/prelude/numeric.js +++ b/compiler/prelude/numeric.js @@ -30,7 +30,10 @@ var $floatKey = f => { return String(f); }; -var $flatten64 = x => { +var $flatten64 = (x, typ) => { + if (typ && typ.kind != $kindInt64 && typ.kind != $kindUint64) { + return x; // Not a 64-bit number, no need to flatten. + } return x.$high * 4294967296 + x.$low; }; diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index ed1d18360..081518aca 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -429,6 +429,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.or = (x, y) => (x | y); typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); + typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; break; case $kindUint8: case $kindUint16: @@ -441,6 +442,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.or = (x, y) => (x | y) >>> 0; typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); + typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; break; case $kindUint: case $kindUint32: @@ -454,6 +456,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.or = (x, y) => (x | y) >>> 0; typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); + typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; break; case $kindInt: case $kindInt32: @@ -466,6 +469,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.or = (x, y) => (x | y); typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); + typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; break; case $kindInt64: case $kindUint64: @@ -478,6 +482,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.or = (x, y) => new typ(x.$high | y.$high, (x.$low | y.$low) >>> 0); typ.xor = (x, y) => new typ(x.$high ^ y.$high, (x.$low ^ y.$low) >>> 0); typ.andNot = (x, y) => new typ(x.$high & ~y.$high, (x.$low & ~y.$low) >>> 0); + typ.shl = (x, y) => $shiftLeft64(x, y); break; case $kindFloat32: case $kindFloat64: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index 0fc3419ba..d53969924 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -3,6 +3,7 @@ package typeparams_test import ( "fmt" "go/token" + "math/bits" "testing" "golang.org/x/exp/constraints" @@ -368,3 +369,42 @@ func TestBitwiseAndNot(t *testing.T) { t.Run(test.String(), test.Run) } } + +func leftShift[T constraints.Integer](x, y T) T { + return x << y +} + +func leftShiftTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: leftShift[T], + opName: token.SHL, + x: x, + y: y, + want: want, + } +} + +func TestBitwiseShitLeft(t *testing.T) { + tests := []testCaseI{ + leftShiftTC[int8](0x48, 1, -0x70), + leftShiftTC[int16](0x4008, 1, -0x7ff0), + leftShiftTC[int32](0x40000008, 1, -0x7ffffff0), + leftShiftTC[uint8](0x88, 1, 0x10), + leftShiftTC[uint16](0x8008, 1, 0x0010), + leftShiftTC[uint32](0x80000008, 1, 0x00000010), + leftShiftTC[int64](0x4000000000000008, 1, -0x7ffffffffffffff0), + leftShiftTC[uint64](0x8000000000000008, 1, 0x0000000000000010), + } + + if bits.UintSize == 32 { + tests = append(tests, + leftShiftTC[int](0x40000008, 1, -0x7ffffff0), + leftShiftTC[uint](0x80000008, 1, 0x00000010), + leftShiftTC[uintptr](0x80000008, 1, 0x00000010), + ) + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From a15f55d4fbccaf194ec09e75fba67735b8bc3f81 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Fri, 3 Nov 2023 22:57:00 +0000 Subject: [PATCH 74/83] Implement bitwise right shift operator for type params. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 7 ++++- tests/typeparams/arithmetics_test.go | 43 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 7339bbc42..8acb0fc16 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -706,6 +706,8 @@ func (fc *funcContext) translateBinaryExpr(e *ast.BinaryExpr) *expression { return fc.formatExpr("%s.andNot(%e, %e)", fc.typeName(t), e.X, e.Y) case token.SHL: return fc.formatExpr("%s.shl(%e, $flatten64(%e, %s))", fc.typeName(t), e.X, e.Y, fc.typeName(t2)) + case token.SHR: + return fc.formatExpr("%s.shr(%e, $flatten64(%e, %s))", fc.typeName(t), e.X, e.Y, fc.typeName(t2)) } } diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 081518aca..24ee4e074 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -430,6 +430,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; + typ.shr = (x, y) => $truncateNumber(x >> $min(y, 31), typ); break; case $kindUint8: case $kindUint16: @@ -443,6 +444,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; + typ.shr = (x, y) => (y < 32) ? $truncateNumber(x >>> y, typ) : 0; break; case $kindUint: case $kindUint32: @@ -457,6 +459,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; + typ.shr = (x, y) => (y < 32) ? $truncateNumber(x >>> y, typ) : 0; break; case $kindInt: case $kindInt32: @@ -470,6 +473,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.xor = (x, y) => $truncateNumber(x ^ y, typ); typ.andNot = (x, y) => $truncateNumber(x & ~y, typ); typ.shl = (x, y) => (y < 32) ? $truncateNumber(x << y, typ) : 0; + typ.shr = (x, y) => $truncateNumber(x >> $min(y, 31), typ); break; case $kindInt64: case $kindUint64: @@ -482,7 +486,8 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.or = (x, y) => new typ(x.$high | y.$high, (x.$low | y.$low) >>> 0); typ.xor = (x, y) => new typ(x.$high ^ y.$high, (x.$low ^ y.$low) >>> 0); typ.andNot = (x, y) => new typ(x.$high & ~y.$high, (x.$low & ~y.$low) >>> 0); - typ.shl = (x, y) => $shiftLeft64(x, y); + typ.shl = $shiftLeft64; + typ.shr = (kind === $kindInt64) ? $shiftRightInt64 : $shiftRightUint64; break; case $kindFloat32: case $kindFloat64: diff --git a/tests/typeparams/arithmetics_test.go b/tests/typeparams/arithmetics_test.go index d53969924..8ac534203 100644 --- a/tests/typeparams/arithmetics_test.go +++ b/tests/typeparams/arithmetics_test.go @@ -394,6 +394,8 @@ func TestBitwiseShitLeft(t *testing.T) { leftShiftTC[uint32](0x80000008, 1, 0x00000010), leftShiftTC[int64](0x4000000000000008, 1, -0x7ffffffffffffff0), leftShiftTC[uint64](0x8000000000000008, 1, 0x0000000000000010), + leftShiftTC[uint32](0xFFFFFFFF, 32, 0), + leftShiftTC[int32](-0x80000000, 32, 0), } if bits.UintSize == 32 { @@ -408,3 +410,44 @@ func TestBitwiseShitLeft(t *testing.T) { t.Run(test.String(), test.Run) } } + +func rightShift[T constraints.Integer](x, y T) T { + return x >> y +} + +func rightShiftTC[T constraints.Integer](x, y, want T) *testCase[T] { + return &testCase[T]{ + op: rightShift[T], + opName: token.SHR, + x: x, + y: y, + want: want, + } +} + +func TestBitwiseShitRight(t *testing.T) { + tests := []testCaseI{ + rightShiftTC[int8](-0x70, 1, -0x38), + rightShiftTC[int16](-0x7ff0, 1, -0x3ff8), + rightShiftTC[int32](-0x7ffffff0, 1, -0x3ffffff8), + rightShiftTC[uint8](0x80, 1, 0x40), + rightShiftTC[uint16](0x8010, 1, 0x4008), + rightShiftTC[uint32](0x80000010, 1, 0x40000008), + rightShiftTC[int64](-0x7ffffffffffffff0, 1, -0x3ffffffffffffff8), + rightShiftTC[uint64](0x8000000000000010, 1, 0x4000000000000008), + rightShiftTC[uint32](0xFFFFFFFF, 32, 0), + rightShiftTC[int32](-0x80000000, 32, -1), + } + + if bits.UintSize == 32 { + tests = append(tests, + rightShiftTC[int](-0x7ffffff0, 1, -0x3ffffff8), + rightShiftTC[uint](0x80000010, 1, 0x40000008), + rightShiftTC[uintptr](0x80000010, 1, 0x40000008), + ) + } + + for _, test := range tests { + t.Run(test.String(), test.Run) + } +} From 9c5536857516fcd26d965abf502ae9bd4c614a46 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 4 Nov 2023 15:19:15 +0000 Subject: [PATCH 75/83] Re-triage failing generic test cases. --- tests/gorepo/run.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index ebcdc3b90..e8c026492 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -155,23 +155,22 @@ var knownFails = map[string]failReason{ // Failures related to the lack of generics support. Ideally, this section // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is // fixed. - "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/absdiff2.go": {category: generics, desc: "missing support for basic literals with type params"}, + "typeparam/absdiff3.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/fact.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/issue47258.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, - "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/list.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, - "typeparam/slices.go": {category: generics, desc: "missing operator support for generic types"}, + "typeparam/slices.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, From 2052d8c5cc19ac18d3f3e7573220cd54c876c38b Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 4 Nov 2023 15:42:57 +0000 Subject: [PATCH 76/83] Handle untyped constants with type params. Untyped constants present a challenge that the same literal value (e.g. "0xFFFF") has different runtime representations for different types. With type params we don't know the exact type at run time, so we can't pick the right representation at compile time. Instead, we do this in two steps: 1. Pick a basic type that can precisely represent the constant and initialize the value for that type. 2. Generate code to perform runtime type conversion from the known type to the type parameter's concrete type. Untyped nil is a special, simpler case: since for all types that untyped nil can be converted to also happened to have nil as zero value, whenever we detect such conversion we call for type's zero value, which would give us the appropriate nil representation. --- compiler/astutil/astutil.go | 70 ++++++++++++++++++++ compiler/astutil/astutil_test.go | 41 ++++++++++++ compiler/expressions.go | 18 +++++- compiler/prelude/types.js | 11 +++- tests/gorepo/run.go | 11 +--- tests/typeparams/literals_test.go | 102 ++++++++++++++++++++++++++++++ 6 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 tests/typeparams/literals_test.go diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index e6a60c421..4f1be3fbb 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -3,6 +3,7 @@ package astutil import ( "fmt" "go/ast" + "go/constant" "go/token" "go/types" "strings" @@ -25,6 +26,12 @@ func SetType(info *types.Info, t types.Type, e ast.Expr) ast.Expr { return e } +// SetTypeAndValue of the expression e to type t. +func SetTypeAndValue(info *types.Info, t types.Type, val constant.Value, e ast.Expr) ast.Expr { + info.Types[e] = types.TypeAndValue{Type: t, Value: val} + return e +} + // NewVarIdent creates a new variable object with the given name and type. func NewVarIdent(name string, t types.Type, info *types.Info, pkg *types.Package) *ast.Ident { obj := types.NewVar(token.NoPos, pkg, name, t) @@ -226,3 +233,66 @@ func TakeAddress(info *types.Info, e ast.Expr) *ast.UnaryExpr { SetType(info, ptrType, addrOf) return addrOf } + +// MakeTypedConstant takes an untyped constant value and makes an AST expression +// representing it with a concrete type that can represent the constant precisely. +func MakeTypedConstant(info *types.Info, val constant.Value) ast.Expr { + switch val.Kind() { + case constant.String: + e := &ast.BasicLit{ + Kind: token.STRING, + Value: val.ExactString(), + } + + return SetTypeAndValue(info, types.Typ[types.String], val, e) + case constant.Float: + e := &ast.BasicLit{ + Kind: token.FLOAT, + Value: val.ExactString(), + } + + return SetTypeAndValue(info, types.Typ[types.Float64], val, e) + case constant.Int: + bits := constant.BitLen(val) + sign := constant.Sign(val) + + var t types.Type + if bits <= 32 && sign >= 0 { + t = types.Typ[types.Uint32] + } else if bits <= 32 && sign < 0 { + t = types.Typ[types.Int32] + } else if sign >= 0 { + t = types.Typ[types.Uint64] + } else { + t = types.Typ[types.Int64] + } + + e := &ast.BasicLit{ + Kind: token.INT, + Value: val.ExactString(), + } + return SetTypeAndValue(info, t, val, e) + case constant.Complex: + e := &ast.BasicLit{ + Kind: token.IMAG, + // Cheat: don't bother with generating a plausible complex expression. + // We would have to construct a complicated expression to construct a + // complex value. However, when dealing with constants, GopherJS doesn't + // actually inspect the AST, only the types.TypeAndValue object it gets + // from type analyzer. + // + // All we really need here is an ast.Expr we can associate with a + // types.TypeAndValue instance, so we just do a token effort to return + // something. + Value: "", + } + return SetTypeAndValue(info, types.Typ[types.Complex128], val, e) + case constant.Bool: + e := &ast.Ident{ + Name: val.ExactString(), + } + return SetTypeAndValue(info, types.Typ[types.Bool], val, e) + default: + panic(fmt.Errorf("unexpected constant kind %s: %v", val.Kind(), val)) + } +} diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index a996ae73f..30e9ea09c 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -1,7 +1,10 @@ package astutil import ( + "go/ast" + "go/constant" "go/token" + "go/types" "testing" "github.com/gopherjs/gopherjs/internal/srctesting" @@ -181,3 +184,41 @@ func TestEndsWithReturn(t *testing.T) { }) } } + +func TestMakeTypedConstant(t *testing.T) { + tests := []struct { + value constant.Value + want types.Type + }{{ + value: constant.MakeString("abc"), + want: types.Typ[types.String], + }, { + value: constant.MakeInt64(0xFFFFFFFF), + want: types.Typ[types.Uint32], + }, { + value: constant.MakeInt64(-0x80000000), + want: types.Typ[types.Int32], + }, { + value: constant.MakeUint64(0xFFFFFFFFFFFFFFFF), + want: types.Typ[types.Uint64], + }, { + value: constant.MakeInt64(-0x8000000000000000), + want: types.Typ[types.Int64], + }} + + for _, test := range tests { + t.Run(test.value.ExactString(), func(t *testing.T) { + info := &types.Info{Types: map[ast.Expr]types.TypeAndValue{}} + e := MakeTypedConstant(info, test.value) + tv := info.Types[e] + + if tv.Type != test.want { + t.Errorf("Got: constant %s assigned type %s. Want: %s", test.value, tv.Type, test.want) + } + + if tv.Value != test.value { + t.Errorf("Got: associated constant value is %s. Want: %s (the same as original).", tv.Value, test.value) + } + }) + } +} diff --git a/compiler/expressions.go b/compiler/expressions.go index 8acb0fc16..6b69747f3 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -35,6 +35,19 @@ func (e *expression) StringWithParens() string { func (fc *funcContext) translateExpr(expr ast.Expr) *expression { exprType := fc.pkgCtx.TypeOf(expr) if value := fc.pkgCtx.Types[expr].Value; value != nil { + if tParam, ok := exprType.(*types.TypeParam); ok { + // If we are dealing with a type param, we don't know which concrete type + // it will be instantiated with, so we don't know how to represent the + // constant value ahead of time. Instead, generate a typed constant and + // perform type conversion to the instantiated type at runtime. + return fc.translateExpr( + fc.typeCastExpr( + astutil.MakeTypedConstant(fc.pkgCtx.Info.Info, value), + fc.newIdentFor(tParam.Obj()), + ), + ) + } + basic := exprType.Underlying().(*types.Basic) switch { case isBoolean(basic): @@ -633,7 +646,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { switch t := exprType.Underlying().(type) { case *types.Basic: if t.Kind() != types.UnsafePointer { - panic("unexpected basic type") + panic(fmt.Errorf("unexpected basic type: %v", t)) } return fc.formatExpr("0") case *types.Slice, *types.Pointer: @@ -1178,6 +1191,9 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type _, fromTypeParam := exprType.(*types.TypeParam) _, toTypeParam := desiredType.(*types.TypeParam) if fromTypeParam || toTypeParam { + if t, ok := exprType.Underlying().(*types.Basic); ok && t.Kind() == types.UntypedNil { + return fc.formatExpr("%s.zero()", fc.typeName(desiredType)) + } // Conversion from or to a type param can only be done at runtime, since the // concrete type is not known to the compiler at compile time. return fc.formatExpr("%s.convertFrom(%s.wrap(%e))", fc.typeName(desiredType), fc.typeName(exprType), expr) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 24ee4e074..7bd046055 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -1133,7 +1133,16 @@ const $convertToComplex = (src, dstType) => { return src; } - return new dstType(src.$real, src.$imag); + switch (srcType.kind) { + case $kindComplex64: + case $kindComplex128: + return new dstType(src.$real, src.$imag); + default: + // Although normally Go doesn't allow conversion from int/float types + // to complex, it does allow conversion from untyped constants. + const real = (srcType.kind === $kindUint64 || srcType.kind === $kindInt64) ? $flatten64(src) : src.$val; + return new dstType(real, 0); + } }; /** diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index e8c026492..57a1d3943 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -155,22 +155,17 @@ var knownFails = map[string]failReason{ // Failures related to the lack of generics support. Ideally, this section // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is // fixed. - "typeparam/absdiff2.go": {category: generics, desc: "missing support for basic literals with type params"}, - "typeparam/absdiff3.go": {category: generics, desc: "missing support for basic literals with type params"}, + "typeparam/absdiff2.go": {category: generics, desc: "missing support for unary minus operator"}, + "typeparam/absdiff3.go": {category: generics, desc: "missing support for unary minus operator"}, "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, - "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for basic literals with type params"}, + "typeparam/dictionaryCapture.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/fact.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/issue47258.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, - "typeparam/list.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, - "typeparam/slices.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, diff --git a/tests/typeparams/literals_test.go b/tests/typeparams/literals_test.go new file mode 100644 index 000000000..cd3f2937b --- /dev/null +++ b/tests/typeparams/literals_test.go @@ -0,0 +1,102 @@ +package typeparams_test + +import ( + "fmt" + "testing" + + "golang.org/x/exp/constraints" +) + +func intLit[T constraints.Integer]() T { + var i T = 1 + return i +} + +func runeLit[T rune]() T { + var r T = 'a' + return r +} + +func floatLit[T constraints.Float]() T { + var f T = 1.1 + return f +} + +func complexLit[T constraints.Complex]() T { + var c T = 1 + 2i + return c +} + +func complexLit2[T constraints.Complex]() T { + var c T = 1 + return c +} + +func strLit[T string]() T { + var s T = "abc" + return s +} + +func boolLit[T bool]() T { + var b T = true + return b +} + +func nilLit[T *int]() T { + var p T = nil + return p +} + +func TestLiterals(t *testing.T) { + tests := []struct { + got any + want any + }{{ + got: intLit[int32](), + want: int32(1), + }, { + got: intLit[uint32](), + want: uint32(1), + }, { + got: intLit[int64](), + want: int64(1), + }, { + got: intLit[uint64](), + want: uint64(1), + }, { + got: runeLit[rune](), + want: 'a', + }, { + got: floatLit[float32](), + want: float32(1.1), + }, { + got: floatLit[float64](), + want: float64(1.1), + }, { + got: complexLit[complex64](), + want: complex64(1 + 2i), + }, { + got: complexLit[complex128](), + want: complex128(1 + 2i), + }, { + got: complexLit2[complex128](), + want: complex128(1), + }, { + got: strLit[string](), + want: "abc", + }, { + got: boolLit[bool](), + want: true, + }, { + got: nilLit[*int](), + want: (*int)(nil), + }} + + for _, test := range tests { + t.Run(fmt.Sprintf("%T/%v", test.want, test.want), func(t *testing.T) { + if test.got != test.want { + t.Errorf("Got: %v. Want: %v.", test.got, test.want) + } + }) + } +} From 73df10d14f6cb11abc0ced3706e891405ee1ad2b Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 4 Nov 2023 20:43:25 +0000 Subject: [PATCH 77/83] Support builtin make() for type parameters. Slice, channel and map types' constructors now have a .make() method that implements the builtin for the corresponding type. The compiler simply calls the builtin without a need to introspect the type. --- compiler/expressions.go | 28 ++++------- compiler/prelude/types.js | 7 +++ tests/typeparams/builtins_test.go | 83 +++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 tests/typeparams/builtins_test.go diff --git a/compiler/expressions.go b/compiler/expressions.go index 6b69747f3..cad919a1d 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1049,26 +1049,16 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args return fc.formatExpr("$newDataPointer(%e, %s)", fc.zeroValue(t.Elem()), fc.typeName(t)) } case "make": - switch argType := fc.pkgCtx.TypeOf(args[0]).Underlying().(type) { - case *types.Slice: - t := fc.typeName(fc.pkgCtx.TypeOf(args[0])) - if len(args) == 3 { - return fc.formatExpr("$makeSlice(%s, %f, %f)", t, args[1], args[2]) - } - return fc.formatExpr("$makeSlice(%s, %f)", t, args[1]) - case *types.Map: - if len(args) == 2 && fc.pkgCtx.Types[args[1]].Value == nil { - return fc.formatExpr(`((%1f < 0 || %1f > 2147483647) ? $throwRuntimeError("makemap: size out of range") : new $global.Map())`, args[1]) - } - return fc.formatExpr("new $global.Map()") - case *types.Chan: - length := "0" - if len(args) == 2 { - length = fc.formatExpr("%f", args[1]).String() - } - return fc.formatExpr("new $Chan(%s, %s)", fc.typeName(fc.pkgCtx.TypeOf(args[0]).Underlying().(*types.Chan).Elem()), length) + typeName := fc.typeName(fc.pkgCtx.TypeOf(args[0])) + switch len(args) { + case 1: + return fc.formatExpr("%s.$make()", typeName) + case 2: + return fc.formatExpr("%s.$make(%f)", typeName, args[1]) + case 3: + return fc.formatExpr("%s.$make(%f, %f)", typeName, args[1], args[2]) default: - panic(fmt.Sprintf("Unhandled make type: %T\n", argType)) + panic(fmt.Errorf("builtin make(): invalid number of arguments: %d", len(args))) } case "len": switch argType := fc.pkgCtx.TypeOf(args[0]).Underlying().(type) { diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 7bd046055..11b09434a 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -175,6 +175,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.sendOnly = sendOnly; typ.recvOnly = recvOnly; }; + typ.$make = (bufsize) => new $Chan(typ.elem, bufsize ? bufsize : 0); break; case $kindFunc: @@ -210,6 +211,11 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.elem = elem; typ.comparable = false; }; + typ.$make = (size) => { + if (size === undefined) { size = 0; } + if (size < 0 || size > 2147483647) { $throwRuntimeError("makemap: size out of range"); } + return new $global.Map(); + }; break; case $kindPtr: @@ -250,6 +256,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.nativeArray = $nativeArray(elem.kind); typ.nil = new typ([]); }; + typ.$make = (size, capacity) => $makeSlice(typ, size, capacity); break; case $kindStruct: diff --git a/tests/typeparams/builtins_test.go b/tests/typeparams/builtins_test.go new file mode 100644 index 000000000..481adf532 --- /dev/null +++ b/tests/typeparams/builtins_test.go @@ -0,0 +1,83 @@ +package typeparams_test + +import ( + "fmt" + "testing" +) + +func TestMake(t *testing.T) { + t.Run("slice", func(t *testing.T) { + tests := []struct { + slice []int + wantStr string + wantLen int + wantCap int + }{{ + slice: make([]int, 1), + wantStr: "[]int{0}", + wantLen: 1, + wantCap: 1, + }, { + slice: make([]int, 1, 2), + wantStr: "[]int{0}", + wantLen: 1, + wantCap: 2, + }} + + for i, test := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + if got := fmt.Sprintf("%#v", test.slice); got != test.wantStr { + t.Errorf("Got: fmt.Sprint(%v) = %q. Want: %q.", test.slice, got, test.wantStr) + } + if got := len(test.slice); got != test.wantLen { + t.Errorf("Got: len(%v) = %d. Want: %d.", test.slice, got, test.wantLen) + } + if got := cap(test.slice); got != test.wantCap { + t.Errorf("Got: cap(%v) = %d. Want: %d.", test.slice, got, test.wantCap) + } + }) + } + }) + + t.Run("map", func(t *testing.T) { + tests := []map[int]int{ + make(map[int]int), + make(map[int]int, 1), + } + + for i, test := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + want := "map[int]int{}" + got := fmt.Sprintf("%#v", test) + if want != got { + t.Errorf("Got: fmt.Sprint(%v) = %q. Want: %q.", test, got, want) + } + }) + } + }) + + t.Run("chan", func(t *testing.T) { + tests := []struct { + ch chan int + wantCap int + }{{ + ch: make(chan int), + wantCap: 0, + }, { + ch: make(chan int, 1), + wantCap: 1, + }} + + for i, test := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + wantStr := "chan int" + if got := fmt.Sprintf("%T", test.ch); got != wantStr { + t.Errorf("Got: fmt.Sprint(%v) = %q. Want: %q.", test.ch, got, wantStr) + } + if got := cap(test.ch); got != test.wantCap { + t.Errorf("Got: cap(%v) = %d. Want: %d.", test.ch, got, test.wantCap) + } + }) + } + }) +} From ad18f2c2ea9d7041fd17d59d28b2bac4f9754e0f Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 4 Nov 2023 21:53:10 +0000 Subject: [PATCH 78/83] Implement len() builtin for type parameters. Similar to make(), each relevant type constructor has a $len() method that takes an unwrapped type value and returns its length. Whenever len() is called with a type param, the compiler emits a call to `Type.$len(value)`. Unlike make(), I kept compile-time specializations of len(), since they are much more likely to be called in performance-critical path and would be cheaper than a call to `T.$len()`. --- compiler/expressions.go | 2 ++ compiler/prelude/types.js | 6 +++++ tests/typeparams/builtins_test.go | 43 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/compiler/expressions.go b/compiler/expressions.go index cad919a1d..e89d46171 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1072,6 +1072,8 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args return fc.formatExpr("(%e ? %e.size : 0)", args[0], args[0]) case *types.Chan: return fc.formatExpr("%e.$buffer.length", args[0]) + case *types.Interface: // *types.TypeParam has interface as underlying type. + return fc.formatExpr("%s.$len(%e)", fc.typeName(fc.pkgCtx.TypeOf(args[0])), args[0]) // length of array is constant default: panic(fmt.Sprintf("Unhandled len type: %T\n", argType)) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 11b09434a..072607338 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -93,6 +93,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.wrapped = true; typ.wrap = (v) => new typ(v); typ.keyFor = x => { return "$" + x; }; + typ.$len = (v) => v.length; break; case $kindFloat32: @@ -163,6 +164,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.ptr.init(typ); Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError }); }; + typ.$len = (v) => typ.len; break; case $kindChan: @@ -176,6 +178,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.recvOnly = recvOnly; }; typ.$make = (bufsize) => new $Chan(typ.elem, bufsize ? bufsize : 0); + typ.$len = (v) => v.$buffer.length; break; case $kindFunc: @@ -216,6 +219,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { if (size < 0 || size > 2147483647) { $throwRuntimeError("makemap: size out of range"); } return new $global.Map(); }; + typ.$len = (v) => v ? v.size : 0; break; case $kindPtr: @@ -236,6 +240,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { } typ.nil = new typ($throwNilPointerError, $throwNilPointerError); }; + typ.$len = (v) => typ.elem.$len(typ.wrapped ? v : v.$get()); break; case $kindSlice: @@ -257,6 +262,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.nil = new typ([]); }; typ.$make = (size, capacity) => $makeSlice(typ, size, capacity); + typ.$len = (v) => v.$length; break; case $kindStruct: diff --git a/tests/typeparams/builtins_test.go b/tests/typeparams/builtins_test.go index 481adf532..08e09a0e2 100644 --- a/tests/typeparams/builtins_test.go +++ b/tests/typeparams/builtins_test.go @@ -81,3 +81,46 @@ func TestMake(t *testing.T) { } }) } + +func _len[T []int | *[3]int | map[int]int | chan int | string](x T) int { + return len(x) +} + +func TestLen(t *testing.T) { + ch := make(chan int, 2) + ch <- 1 + + tests := []struct { + desc string + got int + want int + }{{ + desc: "string", + got: _len("abcd"), + want: 4, + }, { + desc: "[]int", + got: _len([]int{1, 2, 3}), + want: 3, + }, { + desc: "[3]int", + got: _len(&[3]int{1}), + want: 3, + }, { + desc: "map[int]int", + got: _len(map[int]int{1: 1, 2: 2}), + want: 2, + }, { + desc: "chan int", + got: _len(ch), + want: 1, + }} + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + if test.got != test.want { + t.Errorf("Got: len() = %d. Want: %d.", test.got, test.want) + } + }) + } +} From 11065e6054620498000abe35ece31670aa76105f Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 4 Nov 2023 22:09:03 +0000 Subject: [PATCH 79/83] Fix fc.formatExpr("%f") to work with type param types. %f instructs the format to "flatten" all numeric types into a JS number. To do that correctly, you need to know whether the passed number is 64-bit or less. In case of the type parameter we pass the type constructor as a second argument to determine that at runtime. --- compiler/expressions.go | 7 ++++++- tests/gorepo/run.go | 26 ++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index e89d46171..80830671a 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1565,11 +1565,16 @@ func (fc *funcContext) formatExprInternal(format string, a []interface{}, parens out.WriteString(strconv.FormatInt(d, 10)) return } - if is64Bit(fc.pkgCtx.TypeOf(e).Underlying().(*types.Basic)) { + if t, ok := fc.pkgCtx.TypeOf(e).Underlying().(*types.Basic); ok && is64Bit(t) { out.WriteString("$flatten64(") writeExpr("") out.WriteString(")") return + } else if t, ok := fc.pkgCtx.TypeOf(e).(*types.TypeParam); ok { + out.WriteString("$flatten64(") + writeExpr("") + fmt.Fprintf(out, ", %s)", fc.typeName(t)) + return } writeExpr("") case 'h': diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 57a1d3943..168c0a63b 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -155,20 +155,18 @@ var knownFails = map[string]failReason{ // Failures related to the lack of generics support. Ideally, this section // should be emptied once https://github.com/gopherjs/gopherjs/issues/1013 is // fixed. - "typeparam/absdiff2.go": {category: generics, desc: "missing support for unary minus operator"}, - "typeparam/absdiff3.go": {category: generics, desc: "missing support for unary minus operator"}, - "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, - "typeparam/dictionaryCapture.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, - "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, - "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, - "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, - "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, - "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, - "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, - "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, - "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, + "typeparam/absdiff2.go": {category: generics, desc: "missing support for unary minus operator"}, + "typeparam/absdiff3.go": {category: generics, desc: "missing support for unary minus operator"}, + "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, + "typeparam/double.go": {category: generics, desc: "missing support for range over type parameter"}, + "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, + "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, + "typeparam/issue48453.go": {category: generics, desc: "missing support for range over type parameter"}, + "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, + "typeparam/nested.go": {category: generics, desc: "missing comparison operator support for generic types"}, + "typeparam/typeswitch2.go": {category: generics, desc: "complex types have different print() format"}, + "typeparam/typeswitch3.go": {category: generics, desc: "missing support for type switching on generic types"}, + "typeparam/typeswitch5.go": {category: generics, desc: "different print() format for floating point types"}, } type failCategory uint8 From bfa92fe085423464a7f5125a77537d2e130bc66e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 12 Nov 2023 17:46:23 +0000 Subject: [PATCH 80/83] Implement cap() builtin for type params. Similar to len(), to maintain performance when used in loops I've kept compile-time specializations for when the exact type is known. Otherwise, a type-specific implementation is called at runtime. --- compiler/expressions.go | 8 +++++-- compiler/prelude/types.js | 4 ++++ tests/typeparams/builtins_test.go | 39 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 80830671a..d77d2ce13 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1084,9 +1084,13 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args return fc.formatExpr("%e.$capacity", args[0]) case *types.Pointer: return fc.formatExpr("(%e, %d)", args[0], argType.Elem().(*types.Array).Len()) - // capacity of array is constant + case *types.Array: + // This should never happen™ + panic(fmt.Errorf("array capacity should have been inlined as constant")) + case *types.Interface: // *types.TypeParam has interface as underlying type. + return fc.formatExpr("%s.$cap(%e)", fc.typeName(fc.pkgCtx.TypeOf(args[0])), args[0]) default: - panic(fmt.Sprintf("Unhandled cap type: %T\n", argType)) + panic(fmt.Errorf("unhandled cap type: %T", argType)) } case "panic": return fc.formatExpr("$panic(%s)", fc.translateImplicitConversion(args[0], types.NewInterface(nil, nil))) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 072607338..08f75892a 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -165,6 +165,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError }); }; typ.$len = (v) => typ.len; + typ.$cap = (v) => typ.len; break; case $kindChan: @@ -179,6 +180,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { }; typ.$make = (bufsize) => new $Chan(typ.elem, bufsize ? bufsize : 0); typ.$len = (v) => v.$buffer.length; + typ.$cap = (v) => v.$capacity; break; case $kindFunc: @@ -241,6 +243,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.nil = new typ($throwNilPointerError, $throwNilPointerError); }; typ.$len = (v) => typ.elem.$len(typ.wrapped ? v : v.$get()); + typ.$cap = (v) => typ.elem.$cap(typ.wrapped ? v : v.$get()); break; case $kindSlice: @@ -263,6 +266,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { }; typ.$make = (size, capacity) => $makeSlice(typ, size, capacity); typ.$len = (v) => v.$length; + typ.$cap = (v) => v.$capacity; break; case $kindStruct: diff --git a/tests/typeparams/builtins_test.go b/tests/typeparams/builtins_test.go index 08e09a0e2..2c9a1b83a 100644 --- a/tests/typeparams/builtins_test.go +++ b/tests/typeparams/builtins_test.go @@ -124,3 +124,42 @@ func TestLen(t *testing.T) { }) } } + +func _cap[T []int | *[3]int | [3]int | chan int](x T) int { + return cap(x) +} + +func TestCap(t *testing.T) { + ch := make(chan int, 2) + ch <- 1 + + tests := []struct { + desc string + got int + want int + }{{ + desc: "[]int", + got: _cap([]int{1, 2, 3}), + want: 3, + }, { + desc: "*[3]int", + got: _cap(&[3]int{1}), + want: 3, + }, { + desc: "[3]int", + got: _cap([3]int{1}), + want: 3, + }, { + desc: "chan int", + got: _cap(ch), + want: 2, + }} + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + if test.got != test.want { + t.Errorf("Got: len() = %d. Want: %d.", test.got, test.want) + } + }) + } +} From cc4773e5a66837c1b1a2ba3014665b3c04dc5bf2 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 12 Nov 2023 17:57:54 +0000 Subject: [PATCH 81/83] Support new() built-in for type params. For the most part it already worked through the $newDataPointer(), I only had to update it to handle pointers to array, which are a wrapped type. --- compiler/prelude/types.js | 2 +- tests/typeparams/builtins_test.go | 35 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 08f75892a..c3c8915c3 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -855,7 +855,7 @@ var $ptrType = elem => { }; var $newDataPointer = (data, constructor) => { - if (constructor.elem.kind === $kindStruct) { + if (constructor.elem.kind === $kindStruct || constructor.elem.kind === $kindArray) { return data; } return new constructor(() => { return data; }, v => { data = v; }); diff --git a/tests/typeparams/builtins_test.go b/tests/typeparams/builtins_test.go index 2c9a1b83a..9f423a1ff 100644 --- a/tests/typeparams/builtins_test.go +++ b/tests/typeparams/builtins_test.go @@ -2,6 +2,7 @@ package typeparams_test import ( "fmt" + "reflect" "testing" ) @@ -163,3 +164,37 @@ func TestCap(t *testing.T) { }) } } + +func _new[T any]() *T { + return new(T) +} + +func TestNew(t *testing.T) { + type S struct{ i int } + + tests := []struct { + desc string + got any + want any + }{{ + desc: "struct S", + got: *_new[S](), + want: S{}, + }, { + desc: "[3]int", + got: *_new[[3]int](), + want: [3]int{}, + }, { + desc: "int", + got: *_new[int](), + want: int(0), + }} + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + if !reflect.DeepEqual(test.got, test.want) { + t.Errorf("Got: new(%T) = %#v. Want: %#v.", test.want, test.got, test.want) + } + }) + } +} From c0dc2983591bedbf02f8a7731d4bd2658086c603 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 12 Nov 2023 18:41:29 +0000 Subject: [PATCH 82/83] Support append() and delete() built-ins for type params. Instead of interrogating the type of the slice or map expression passed to the built-in, we interrogate types of the computed built-in signature. In case of append(), it will be a slice of element types due to variadic nature of the built-in. For map it will be the plain key type. We can then use that type to translate expressing with implicit conversion. --- compiler/expressions.go | 6 +++--- tests/typeparams/builtins_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index d77d2ce13..3cd3080d0 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1099,11 +1099,11 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args argStr := fc.translateArgs(sig, args, ellipsis) return fc.formatExpr("$appendSlice(%s, %s)", argStr[0], argStr[1]) } - sliceType := sig.Results().At(0).Type().Underlying().(*types.Slice) - return fc.formatExpr("$append(%e, %s)", args[0], strings.Join(fc.translateExprSlice(args[1:], sliceType.Elem()), ", ")) + elType := sig.Params().At(1).Type().(*types.Slice).Elem() + return fc.formatExpr("$append(%e, %s)", args[0], strings.Join(fc.translateExprSlice(args[1:], elType), ", ")) case "delete": args = fc.expandTupleArgs(args) - keyType := fc.pkgCtx.TypeOf(args[0]).Underlying().(*types.Map).Key() + keyType := sig.Params().At(1).Type() return fc.formatExpr( `$mapDelete(%1e, %2s.keyFor(%3s))`, args[0], diff --git a/tests/typeparams/builtins_test.go b/tests/typeparams/builtins_test.go index 9f423a1ff..6f30ca77d 100644 --- a/tests/typeparams/builtins_test.go +++ b/tests/typeparams/builtins_test.go @@ -198,3 +198,30 @@ func TestNew(t *testing.T) { }) } } + +func _append[E any, S []E](s S, e E) S { + return append(s, e) +} + +func TestAppend(t *testing.T) { + got := _append([]int{1, 2}, 3) + want := []int{1, 2, 3} + + if !reflect.DeepEqual(got, want) { + t.Errorf("Got: append(...) = %#v. Want: %#v", got, want) + } +} + +func _delete[K comparable, M map[K]string | map[K]int](m M, k K) { + delete(m, k) +} + +func TestDelete(t *testing.T) { + got := map[int]string{1: "a", 2: "b"} + delete(got, 1) + want := map[int]string{2: "b"} + + if !reflect.DeepEqual(got, want) { + t.Errorf("Got: delete(...) = %#v. Want: %#v", got, want) + } +} From 72ba5b11bf9a34b58ab0c71beaa899c4bb124aca Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sun, 12 Nov 2023 19:59:08 +0000 Subject: [PATCH 83/83] Support copy() built-in for type params. --- compiler/expressions.go | 6 ++---- compiler/prelude/types.js | 2 ++ compiler/utils.go | 2 ++ tests/typeparams/builtins_test.go | 31 +++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 3cd3080d0..a4069bf74 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1112,10 +1112,8 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args ) case "copy": args = fc.expandTupleArgs(args) - if basic, isBasic := fc.pkgCtx.TypeOf(args[1]).Underlying().(*types.Basic); isBasic && isString(basic) { - return fc.formatExpr("$copyString(%e, %e)", args[0], args[1]) - } - return fc.formatExpr("$copySlice(%e, %e)", args[0], args[1]) + dst, src := args[0], args[1] + return fc.formatExpr("%s.$copy(%e, %e)", fc.typeName(fc.pkgCtx.TypeOf(src)), dst, src) case "print": args = fc.expandTupleArgs(args) return fc.formatExpr("$print(%s)", strings.Join(fc.translateExprSlice(args, nil), ", ")) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index c3c8915c3..e9f89f16c 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -94,6 +94,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.wrap = (v) => new typ(v); typ.keyFor = x => { return "$" + x; }; typ.$len = (v) => v.length; + typ.$copy = (dst, src) => $copyString(dst, src); break; case $kindFloat32: @@ -267,6 +268,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.$make = (size, capacity) => $makeSlice(typ, size, capacity); typ.$len = (v) => v.$length; typ.$cap = (v) => v.$capacity; + typ.$copy = (dst, src) => $copySlice(dst, src); break; case $kindStruct: diff --git a/compiler/utils.go b/compiler/utils.go index 7372aaf35..517be76a5 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -704,6 +704,8 @@ func toJavaScriptType(t *types.Basic) string { return "Int32" case types.UnsafePointer: return "UnsafePointer" + case types.UntypedString: + return "String" default: name := t.String() return strings.ToUpper(name[:1]) + name[1:] diff --git a/tests/typeparams/builtins_test.go b/tests/typeparams/builtins_test.go index 6f30ca77d..66b379935 100644 --- a/tests/typeparams/builtins_test.go +++ b/tests/typeparams/builtins_test.go @@ -225,3 +225,34 @@ func TestDelete(t *testing.T) { t.Errorf("Got: delete(...) = %#v. Want: %#v", got, want) } } + +func _copy[D []byte, S []byte | string](dst D, src S) int { + return copy(dst, src) +} + +func TestCopy(t *testing.T) { + t.Run("string", func(t *testing.T) { + src := "abc" + got := make([]byte, 3) + n := _copy(got, src) + want := []byte{'a', 'b', 'c'} + if !reflect.DeepEqual(want, got) { + t.Errorf("Got: copy result: %v. Want: %v", got, want) + } + if n != 3 { + t.Errorf("Got: copied %d elements. Want: 3", n) + } + }) + t.Run("slice", func(t *testing.T) { + src := []byte{'a', 'b', 'c', 'd'} + got := make([]byte, 3) + n := _copy(got, src) + want := []byte{'a', 'b', 'c'} + if !reflect.DeepEqual(want, got) { + t.Errorf("Got: copy result: %v. Want: %v", got, want) + } + if n != 3 { + t.Errorf("Got: copied %d elements. Want: 3", n) + } + }) +}