diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 000000000..03fa75d9c --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,25 @@ +name: golangci-lint +on: + pull_request: +permissions: + contents: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "1.19.13" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53.3 + only-new-issues: true + + - name: Check go.mod + run: | + go mod tidy && git diff --exit-code diff --git a/.github/workflows/measure-size.yml b/.github/workflows/measure-size.yml index d193b0df6..ee4024e6a 100644 --- a/.github/workflows/measure-size.yml +++ b/.github/workflows/measure-size.yml @@ -11,7 +11,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '~1.18.10' + go-version: '~1.19.13' - uses: gopherjs/output-size-action/measure@main with: name: jQuery TodoMVC diff --git a/.golangci.toml b/.golangci.toml new file mode 100644 index 000000000..535ea6b50 --- /dev/null +++ b/.golangci.toml @@ -0,0 +1,15 @@ +[run] +timeout = "1m" + +[output] +format = "colored-line-number" + +[linters] +disable-all = true +enable = [ + "gofumpt", + "govet", +] + +[issues] +exclude-use-default = false diff --git a/README.md b/README.md index 63c5aabee..3804dbf0f 100644 --- a/README.md +++ b/README.md @@ -31,21 +31,21 @@ Nearly everything, including Goroutines ([compatibility documentation](https://g ### Installation and Usage -GopherJS [requires Go 1.18 or newer](https://github.com/gopherjs/gopherjs/blob/master/doc/compatibility.md#go-version-compatibility). If you need an older Go +GopherJS [requires Go 1.19 or newer](https://github.com/gopherjs/gopherjs/blob/master/doc/compatibility.md#go-version-compatibility). If you need an older Go version, you can use an [older GopherJS release](https://github.com/gopherjs/gopherjs/releases). Install GopherJS with `go install`: ``` -go install github.com/gopherjs/gopherjs@v1.18.0-beta3 # Or replace 'v1.18.0-beta3' with another version. +go install github.com/gopherjs/gopherjs@v1.19.0-beta1 # Or replace 'v1.19.0-beta1' with another version. ``` -If your local Go distribution as reported by `go version` is newer than Go 1.18, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.18 distribution. For example: +If your local Go distribution as reported by `go version` is newer than Go 1.19, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.19 distribution. For example: ``` -go install golang.org/dl/go1.18.10@latest -go1.18.10 download -export GOPHERJS_GOROOT="$(go1.18.10 env GOROOT)" # Also add this line to your .profile or equivalent. +go install golang.org/dl/go1.19.13@latest +go1.19.13 download +export GOPHERJS_GOROOT="$(go1.19.13 env GOROOT)" # Also add this line to your .profile or equivalent. ``` Now you can use `gopherjs build [package]`, `gopherjs build [files]` or `gopherjs install [package]` which behave similar to the `go` tool. For `main` packages, these commands create a `.js` file and `.js.map` source map in the current directory or in `$GOPATH/bin`. The generated JavaScript file can be used as usual in a website. Use `gopherjs help [command]` to get a list of possible command line flags, e.g. for minification and automatically watching for changes. diff --git a/build/build.go b/build/build.go index e82e8052e..def9cd313 100644 --- a/build/build.go +++ b/build/build.go @@ -72,9 +72,9 @@ func NewBuildContext(installSuffix string, buildTags []string) XContext { // In the directory containing the package, .go and .inc.js files are // considered part of the package except for: // -// - .go files in package documentation -// - files starting with _ or . (likely editor temporary files) -// - files with build constraints not satisfied by the context +// - .go files in package documentation +// - files starting with _ or . (likely editor temporary files) +// - files with build constraints not satisfied by the context // // If an error occurs, Import returns a non-nil error and a nil // *PackageData. @@ -117,6 +117,26 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } +// overrideInfo is used by parseAndAugment methods to manage +// directives and how the overlay and original are merged. +type overrideInfo struct { + // KeepOriginal indicates that the original code should be kept + // but the identifier will be prefixed by `_gopherjs_original_foo`. + // If false the original code is removed. + keepOriginal bool + + // purgeMethods indicates that this info is for a type and + // if a method has this type as a receiver should also be removed. + // If the method is defined in the overlays and therefore has its + // own overrides, this will be ignored. + purgeMethods bool + + // overrideSignature is the function definition given in the overlays + // that should be used to replace the signature in the originals. + // Only receivers, type parameters, parameters, and results will be used. + overrideSignature *ast.FuncDecl +} + // parseAndAugment parses and returns all .go files of given pkg. // Standard Go library packages are augmented with files in compiler/natives folder. // If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests. @@ -125,82 +145,111 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // The native packages are augmented by the contents of natives.FS in the following way. // The file names do not matter except the usual `_test` suffix. The files for // native overrides get added to the package (even if they have the same name -// as an existing file from the standard library). For all identifiers that exist -// in the original AND the overrides, the original identifier in the AST gets -// replaced by `_`. New identifiers that don't exist in original package get added. +// as an existing file from the standard library). +// +// - For function identifiers that exist in the original and the overrides +// and have the directive `gopherjs:keep-original`, the original identifier +// in the AST gets prefixed by `_gopherjs_original_`. +// - For identifiers that exist in the original and the overrides, and have +// the directive `gopherjs:purge`, both the original and override are +// removed. This is for completely removing something which is currently +// invalid for GopherJS. For any purged types any methods with that type as +// the receiver are also removed. +// - For function identifiers that exist in the original and the overrides, +// and have the directive `gopherjs:override-signature`, the overridden +// function is removed and the original function's signature is changed +// to match the overridden function signature. This allows the receiver, +// type parameters, parameter, and return values to be modified as needed. +// - Otherwise for identifiers that exist in the original and the overrides, +// the original is removed. +// - New identifiers that don't exist in original package get added. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) { - var files []*ast.File - replacedDeclNames := make(map[string]bool) - pruneOriginalFuncs := make(map[string]bool) + jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet) + + originalFiles, err := parserOriginalFiles(pkg, fileSet) + if err != nil { + return nil, nil, err + } + + overrides := make(map[string]overrideInfo) + for _, file := range overlayFiles { + augmentOverlayFile(file, overrides) + pruneImports(file) + } + delete(overrides, "init") + + for _, file := range originalFiles { + augmentOriginalImports(pkg.ImportPath, file) + } + + if len(overrides) > 0 { + for _, file := range originalFiles { + augmentOriginalFile(file, overrides) + } + } + + return append(overlayFiles, originalFiles...), jsFiles, nil +} +// parseOverlayFiles loads and parses overlay files +// to augment the original files with. +func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]JSFile, []*ast.File) { isXTest := strings.HasSuffix(pkg.ImportPath, "_test") importPath := pkg.ImportPath if isXTest { importPath = importPath[:len(importPath)-5] } - jsFiles := []JSFile{} - nativesContext := overlayCtx(xctx.Env()) + nativesPkg, err := nativesContext.Import(importPath, "", 0) + if err != nil { + return nil, nil + } - if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil { - jsFiles = nativesPkg.JSFiles - names := nativesPkg.GoFiles - if isTest { - names = append(names, nativesPkg.TestGoFiles...) - } - if isXTest { - names = nativesPkg.XTestGoFiles + jsFiles := nativesPkg.JSFiles + var files []*ast.File + names := nativesPkg.GoFiles + if isTest { + names = append(names, nativesPkg.TestGoFiles...) + } + if isXTest { + names = nativesPkg.XTestGoFiles + } + + for _, name := range names { + fullPath := path.Join(nativesPkg.Dir, name) + r, err := nativesContext.bctx.OpenFile(fullPath) + if err != nil { + panic(err) } - for _, name := range names { - fullPath := path.Join(nativesPkg.Dir, name) - r, err := nativesContext.bctx.OpenFile(fullPath) - if err != nil { - panic(err) - } - // Files should be uniquely named and in the original package directory in order to be - // ordered correctly - newPath := path.Join(pkg.Dir, "gopherjs__"+name) - file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments) - if err != nil { - panic(err) - } - r.Close() - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - k := astutil.FuncKey(d) - replacedDeclNames[k] = true - pruneOriginalFuncs[k] = astutil.PruneOriginal(d) - case *ast.GenDecl: - switch d.Tok { - case token.TYPE: - for _, spec := range d.Specs { - replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = true - } - case token.VAR, token.CONST: - for _, spec := range d.Specs { - for _, name := range spec.(*ast.ValueSpec).Names { - replacedDeclNames[name.Name] = true - } - } - } - } - } - files = append(files, file) + // Files should be uniquely named and in the original package directory in order to be + // ordered correctly + newPath := path.Join(pkg.Dir, "gopherjs__"+name) + file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments) + if err != nil { + panic(err) } + r.Close() + + files = append(files, file) } - delete(replacedDeclNames, "init") + return jsFiles, files +} +// parserOriginalFiles loads and parses the original files to augment. +func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, error) { + var files []*ast.File var errList compiler.ErrorList for _, name := range pkg.GoFiles { if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`. name = filepath.Join(pkg.Dir, name) } + r, err := buildutil.OpenFile(pkg.bctx, name) if err != nil { - return nil, nil, err + return nil, err } + file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments) r.Close() if err != nil { @@ -217,62 +266,331 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke continue } - switch pkg.ImportPath { - case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time": - for _, spec := range file.Imports { - path, _ := strconv.Unquote(spec.Path.Value) - if path == "sync" { - if spec.Name == nil { - spec.Name = ast.NewIdent("sync") + files = append(files, file) + } + + if errList != nil { + return nil, errList + } + return files, nil +} + +// augmentOverlayFile is the part of parseAndAugment that processes +// an overlay file AST to collect information such as compiler directives +// and perform any initial augmentation needed to the overlay. +func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { + anyChange := false + for i, decl := range file.Decls { + purgeDecl := astutil.Purge(decl) + switch d := decl.(type) { + case *ast.FuncDecl: + k := astutil.FuncKey(d) + oi := overrideInfo{ + keepOriginal: astutil.KeepOriginal(d), + } + if astutil.OverrideSignature(d) { + oi.overrideSignature = d + purgeDecl = true + } + overrides[k] = oi + case *ast.GenDecl: + for j, spec := range d.Specs { + purgeSpec := purgeDecl || astutil.Purge(spec) + switch s := spec.(type) { + case *ast.TypeSpec: + overrides[s.Name.Name] = overrideInfo{ + purgeMethods: purgeSpec, + } + case *ast.ValueSpec: + for _, name := range s.Names { + overrides[name.Name] = overrideInfo{} } - spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"` + } + if purgeSpec { + anyChange = true + d.Specs[j] = nil } } } + if purgeDecl { + anyChange = true + file.Decls[i] = nil + } + } + if anyChange { + finalizeRemovals(file) + pruneImports(file) + } +} - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - k := astutil.FuncKey(d) - if replacedDeclNames[k] { - d.Name = ast.NewIdent("_") - if pruneOriginalFuncs[k] { - // Prune function bodies, since it may contain code invalid for - // GopherJS and pin unwanted imports. - d.Body = nil - } +// augmentOriginalImports is the part of parseAndAugment that processes +// an original file AST to modify the imports for that file. +func augmentOriginalImports(importPath string, file *ast.File) { + switch importPath { + case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time": + for _, spec := range file.Imports { + path, _ := strconv.Unquote(spec.Path.Value) + if path == "sync" { + if spec.Name == nil { + spec.Name = ast.NewIdent("sync") } - case *ast.GenDecl: - switch d.Tok { - case token.TYPE: - for _, spec := range d.Specs { - s := spec.(*ast.TypeSpec) - if replacedDeclNames[s.Name.Name] { - s.Name = ast.NewIdent("_") - s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}} - s.TypeParams = nil - } + spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"` + } + } + } +} + +// augmentOriginalFile is the part of parseAndAugment that processes an +// original file AST to augment the source code using the overrides from +// the overlay files. +func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { + anyChange := false + for i, decl := range file.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + if info, ok := overrides[astutil.FuncKey(d)]; ok { + anyChange = true + removeFunc := true + if info.keepOriginal { + // Allow overridden function calls + // The standard library implementation of foo() becomes _gopherjs_original_foo() + d.Name.Name = "_gopherjs_original_" + d.Name.Name + removeFunc = false + } + if overSig := info.overrideSignature; overSig != nil { + d.Recv = overSig.Recv + d.Type.TypeParams = overSig.Type.TypeParams + d.Type.Params = overSig.Type.Params + d.Type.Results = overSig.Type.Results + removeFunc = false + } + if removeFunc { + file.Decls[i] = nil + } + } else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 { + // check if the receiver has been purged, if so, remove the method too. + if info, ok := overrides[recvKey]; ok && info.purgeMethods { + anyChange = true + file.Decls[i] = nil + } + } + case *ast.GenDecl: + for j, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + if _, ok := overrides[s.Name.Name]; ok { + anyChange = true + d.Specs[j] = nil } - case token.VAR, token.CONST: - for _, spec := range d.Specs { - s := spec.(*ast.ValueSpec) - for i, name := range s.Names { - if replacedDeclNames[name.Name] { - s.Names[i] = ast.NewIdent("_") + case *ast.ValueSpec: + if len(s.Names) == len(s.Values) { + // multi-value context + // e.g. var a, b = 2, foo[int]() + // A removal will also remove the value which may be from a + // function call. This allows us to remove unwanted statements. + // However, if that call has a side effect which still needs + // to be run, add the call into the overlay. + for k, name := range s.Names { + if _, ok := overrides[name.Name]; ok { + anyChange = true + s.Names[k] = nil + s.Values[k] = nil + } + } + } else { + // single-value context + // e.g. var a, b = foo[int]() + // If a removal from the overlays makes all returned values unused, + // then remove the function call as well. This allows us to stop + // unwanted calls if needed. If that call has a side effect which + // still needs to be run, add the call into the overlay. + nameRemoved := false + for _, name := range s.Names { + if _, ok := overrides[name.Name]; ok { + nameRemoved = true + name.Name = `_` + } + } + if nameRemoved { + removeSpec := true + for _, name := range s.Names { + if name.Name != `_` { + removeSpec = false + break + } + } + if removeSpec { + anyChange = true + d.Specs[j] = nil } } } } } } + } + if anyChange { + finalizeRemovals(file) + pruneImports(file) + } +} + +// isOnlyImports determines if this file is empty except for imports. +func isOnlyImports(file *ast.File) bool { + for _, decl := range file.Decls { + if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.IMPORT { + continue + } - files = append(files, file) + // The decl was either a FuncDecl or a non-import GenDecl. + return false } + return true +} - if errList != nil { - return nil, nil, errList +// pruneImports will remove any unused imports from the file. +// +// This will not remove any dot (`.`) or blank (`_`) imports, unless +// there are no declarations or directives meaning that all the imports +// should be cleared. +// If the removal of code causes an import to be removed, the init's from that +// import may not be run anymore. If we still need to run an init for an import +// which is no longer used, add it to the overlay as a blank (`_`) import. +// +// This uses the given name or guesses at the name using the import path, +// meaning this doesn't work for packages which have a different package name +// from the path, including those paths which are versioned +// (e.g. `github.com/foo/bar/v2` where the package name is `bar`) +// or if the import is defined using a relative path (e.g. `./..`). +// Those cases don't exist in the native for Go, so we should only run +// this pruning when we have native overlays, but not for unknown packages. +func pruneImports(file *ast.File) { + if isOnlyImports(file) && !astutil.HasDirectivePrefix(file, `//go:linkname `) { + // The file is empty, remove all imports including any `.` or `_` imports. + file.Imports = nil + file.Decls = nil + return + } + + unused := make(map[string]int, len(file.Imports)) + for i, in := range file.Imports { + if name := astutil.ImportName(in); len(name) > 0 { + unused[name] = i + } + } + + // Remove "unused imports" for any import which is used. + ast.Inspect(file, func(n ast.Node) bool { + if sel, ok := n.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Obj == nil { + delete(unused, id.Name) + } + } + return len(unused) > 0 + }) + if len(unused) == 0 { + return + } + + // Remove "unused imports" for any import used for a directive. + directiveImports := map[string]string{ + `unsafe`: `//go:linkname `, + `embed`: `//go:embed `, + } + for name, index := range unused { + in := file.Imports[index] + path, _ := strconv.Unquote(in.Path.Value) + directivePrefix, hasPath := directiveImports[path] + if hasPath && astutil.HasDirectivePrefix(file, directivePrefix) { + // since the import is otherwise unused set the name to blank. + in.Name = ast.NewIdent(`_`) + delete(unused, name) + } + } + if len(unused) == 0 { + return + } + + // Remove all unused import specifications + isUnusedSpec := map[*ast.ImportSpec]bool{} + for _, index := range unused { + isUnusedSpec[file.Imports[index]] = true + } + for _, decl := range file.Decls { + if d, ok := decl.(*ast.GenDecl); ok { + for i, spec := range d.Specs { + if other, ok := spec.(*ast.ImportSpec); ok && isUnusedSpec[other] { + d.Specs[i] = nil + } + } + } + } + + // Remove the unused import copies in the file + for _, index := range unused { + file.Imports[index] = nil + } + + finalizeRemovals(file) +} + +// finalizeRemovals fully removes any declaration, specification, imports +// that have been set to nil. This will also remove any unassociated comment +// groups, including the comments from removed code. +func finalizeRemovals(file *ast.File) { + fileChanged := false + for i, decl := range file.Decls { + switch d := decl.(type) { + case nil: + fileChanged = true + case *ast.GenDecl: + declChanged := false + for j, spec := range d.Specs { + switch s := spec.(type) { + case nil: + declChanged = true + case *ast.ValueSpec: + specChanged := false + for _, name := range s.Names { + if name == nil { + specChanged = true + break + } + } + if specChanged { + s.Names = astutil.Squeeze(s.Names) + s.Values = astutil.Squeeze(s.Values) + if len(s.Names) == 0 { + declChanged = true + d.Specs[j] = nil + } + } + } + } + if declChanged { + d.Specs = astutil.Squeeze(d.Specs) + if len(d.Specs) == 0 { + fileChanged = true + file.Decls[i] = nil + } + } + } + } + if fileChanged { + file.Decls = astutil.Squeeze(file.Decls) } - return files, jsFiles, nil + + file.Imports = astutil.Squeeze(file.Imports) + + file.Comments = nil // clear this first so ast.Inspect doesn't walk it. + remComments := []*ast.CommentGroup{} + ast.Inspect(file, func(n ast.Node) bool { + if cg, ok := n.(*ast.CommentGroup); ok { + remComments = append(remComments, cg) + } + return true + }) + file.Comments = remComments } // Options controls build process behavior. @@ -663,7 +981,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) { archive := s.buildCache.LoadArchive(pkg.ImportPath) if archive != nil && !pkg.SrcModTime.After(archive.BuildTime) { if err := archive.RegisterTypes(s.Types); err != nil { - panic(fmt.Errorf("Failed to load type information from %v: %w", archive, err)) + panic(fmt.Errorf("failed to load type information from %v: %w", archive, err)) } s.UpToDateArchives[pkg.ImportPath] = archive // Existing archive is up to date, no need to build it from scratch. @@ -730,7 +1048,7 @@ func (s *Session) SourceMappingCallback(m *sourcemap.Map) func(generatedLine, ge // WriteCommandPackage writes the final JavaScript output file at pkgObj path. func (s *Session) WriteCommandPackage(archive *compiler.Archive, pkgObj string) error { - if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(pkgObj), 0o777); err != nil { return err } codeFile, err := os.Create(pkgObj) diff --git a/build/build_test.go b/build/build_test.go index 2fa17e2c5..343e8b933 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -7,6 +7,7 @@ import ( "strconv" "testing" + "github.com/gopherjs/gopherjs/internal/srctesting" "github.com/shurcooL/go/importgraphutil" ) @@ -127,3 +128,615 @@ func (m stringSet) String() string { } return fmt.Sprintf("%q", s) } + +func TestOverlayAugmentation(t *testing.T) { + tests := []struct { + desc string + src string + noCodeChange bool + want string + expInfo map[string]overrideInfo + }{ + { + desc: `remove function`, + src: `func Foo(a, b int) int { + return a + b + }`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + }, + }, { + desc: `keep function`, + src: `//gopherjs:keep-original + func Foo(a, b int) int { + return a + b + }`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `Foo`: {keepOriginal: true}, + }, + }, { + desc: `remove constants and values`, + src: `import "time" + + const ( + foo = 42 + bar = "gopherjs" + ) + + var now = time.Now`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `now`: {}, + }, + }, { + desc: `remove types`, + src: `type ( + foo struct {} + bar int + ) + + type bob interface {}`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `bob`: {}, + }, + }, { + desc: `remove methods`, + src: `type Foo struct { + bar int + } + + func (x *Foo) GetBar() int { return x.bar } + func (x *Foo) SetBar(bar int) { x.bar = bar }`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + `Foo.GetBar`: {}, + `Foo.SetBar`: {}, + }, + }, { + desc: `remove generics`, + src: `import "cmp" + + type Pointer[T any] struct {} + + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // this is a stub for "func Equal[S ~[]E, E any](s1, s2 S) bool {}" + func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + noCodeChange: true, + expInfo: map[string]overrideInfo{ + `Pointer`: {}, + `Sort`: {}, + `Equal`: {}, + }, + }, { + desc: `prune an unused import`, + src: `import foo "some/other/bar"`, + want: ``, + expInfo: map[string]overrideInfo{}, + }, { + desc: `purge function`, + src: `//gopherjs:purge + func Foo(a, b int) int { + return a + b + }`, + want: ``, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + }, + }, { + desc: `purge struct removes an import`, + src: `import "bytes" + import "math" + + //gopherjs:purge + type Foo struct { + bar *bytes.Buffer + } + + const Tau = math.Pi * 2.0`, + want: `import "math" + + const Tau = math.Pi * 2.0`, + expInfo: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + `Tau`: {}, + }, + }, { + desc: `purge whole type decl`, + src: `//gopherjs:purge + type ( + Foo struct {} + bar interface{} + bob int + )`, + want: ``, + expInfo: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + `bar`: {purgeMethods: true}, + `bob`: {purgeMethods: true}, + }, + }, { + desc: `purge part of type decl`, + src: `type ( + Foo struct {} + + //gopherjs:purge + bar interface{} + + //gopherjs:purge + bob int + )`, + want: `type ( + Foo struct {} + )`, + expInfo: map[string]overrideInfo{ + `Foo`: {}, + `bar`: {purgeMethods: true}, + `bob`: {purgeMethods: true}, + }, + }, { + desc: `purge all of a type decl`, + src: `type ( + //gopherjs:purge + Foo struct {} + )`, + want: ``, + expInfo: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + }, + }, { + desc: `remove and purge values`, + src: `import "time" + + const ( + foo = 42 + //gopherjs:purge + bar = "gopherjs" + ) + + //gopherjs:purge + var now = time.Now`, + want: `const ( + foo = 42 + )`, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `now`: {}, + }, + }, { + desc: `purge all value names`, + src: `//gopherjs:purge + var foo, bar int + + //gopherjs:purge + const bob, sal = 12, 42`, + want: ``, + expInfo: map[string]overrideInfo{ + `foo`: {}, + `bar`: {}, + `bob`: {}, + `sal`: {}, + }, + }, { + desc: `imports not confused by local variables`, + src: `import ( + "cmp" + "time" + ) + + //gopherjs:purge + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + func SecondsSince(start time.Time) int { + cmp := time.Now().Sub(start) + return int(cmp.Second()) + }`, + want: `import ( + "time" + ) + + func SecondsSince(start time.Time) int { + cmp := time.Now().Sub(start) + return int(cmp.Second()) + }`, + expInfo: map[string]overrideInfo{ + `Sort`: {}, + `SecondsSince`: {}, + }, + }, { + desc: `purge generics`, + src: `import "cmp" + + //gopherjs:purge + type Pointer[T any] struct {} + + //gopherjs:purge + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // stub for "func Equal[S ~[]E, E any](s1, s2 S) bool" + func Equal() {}`, + want: `// stub for "func Equal[S ~[]E, E any](s1, s2 S) bool" + func Equal() {}`, + expInfo: map[string]overrideInfo{ + `Pointer`: {purgeMethods: true}, + `Sort`: {}, + `Equal`: {}, + }, + }, { + desc: `remove unsafe and embed if not needed`, + src: `import "unsafe" + import "embed" + + //gopherjs:purge + var eFile embed.FS + + //gopherjs:purge + func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)`, + want: ``, + expInfo: map[string]overrideInfo{ + `SwapPointer`: {}, + `eFile`: {}, + }, + }, { + desc: `keep unsafe and embed for directives`, + src: `import "unsafe" + import "embed" + + //go:embed hello.txt + var eFile embed.FS + + //go:linkname runtimeNano runtime.nanotime + func runtimeNano() int64`, + want: `import _ "unsafe" + import "embed" + + //go:embed hello.txt + var eFile embed.FS + + //go:linkname runtimeNano runtime.nanotime + func runtimeNano() int64`, + expInfo: map[string]overrideInfo{ + `eFile`: {}, + `runtimeNano`: {}, + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const pkgName = "package testpackage\n\n" + if test.noCodeChange { + test.want = test.src + } + + fsetSrc := token.NewFileSet() + fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) + + overrides := map[string]overrideInfo{} + augmentOverlayFile(fileSrc, overrides) + pruneImports(fileSrc) + + got := srctesting.Format(t, fsetSrc, fileSrc) + + fsetWant := token.NewFileSet() + fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) + want := srctesting.Format(t, fsetWant, fileWant) + + if got != want { + t.Errorf("augmentOverlayFile and pruneImports got unexpected code:\n"+ + "returned:\n\t%q\nwant:\n\t%q", got, want) + } + + for key, expInfo := range test.expInfo { + if gotInfo, ok := overrides[key]; !ok { + t.Errorf(`%q was expected but not gotten`, key) + } else if expInfo != gotInfo { + t.Errorf(`%q had wrong info, got %+v`, key, gotInfo) + } + } + for key, gotInfo := range overrides { + if _, ok := test.expInfo[key]; !ok { + t.Errorf(`%q with %+v was not expected`, key, gotInfo) + } + } + }) + } +} + +func TestOriginalAugmentation(t *testing.T) { + tests := []struct { + desc string + info map[string]overrideInfo + src string + want string + }{ + { + desc: `do not affect function`, + info: map[string]overrideInfo{}, + src: `func Foo(a, b int) int { + return a + b + }`, + want: `func Foo(a, b int) int { + return a + b + }`, + }, { + desc: `change unnamed sync import`, + info: map[string]overrideInfo{}, + src: `import "sync" + + var _ = &sync.Mutex{}`, + want: `import sync "github.com/gopherjs/gopherjs/nosync" + + var _ = &sync.Mutex{}`, + }, { + desc: `change named sync import`, + info: map[string]overrideInfo{}, + src: `import foo "sync" + + var _ = &foo.Mutex{}`, + want: `import foo "github.com/gopherjs/gopherjs/nosync" + + var _ = &foo.Mutex{}`, + }, { + desc: `remove function`, + info: map[string]overrideInfo{ + `Foo`: {}, + }, + src: `func Foo(a, b int) int { + return a + b + }`, + want: ``, + }, { + desc: `keep original function`, + info: map[string]overrideInfo{ + `Foo`: {keepOriginal: true}, + }, + src: `func Foo(a, b int) int { + return a + b + }`, + want: `func _gopherjs_original_Foo(a, b int) int { + return a + b + }`, + }, { + desc: `remove types and values`, + info: map[string]overrideInfo{ + `Foo`: {}, + `now`: {}, + `bar1`: {}, + }, + src: `import "time" + + type Foo interface{ + bob(a, b string) string + } + + var now = time.Now + const bar1, bar2 = 21, 42`, + want: `const bar2 = 42`, + }, { + desc: `remove in multi-value context`, + info: map[string]overrideInfo{ + `bar`: {}, + }, + src: `const foo, bar = func() (int, int) { + return 24, 12 + }()`, + want: `const foo, _ = func() (int, int) { + return 24, 12 + }()`, + }, { + desc: `full remove in multi-value context`, + info: map[string]overrideInfo{ + `bar`: {}, + }, + src: `const _, bar = func() (int, int) { + return 24, 12 + }()`, + want: ``, + }, { + desc: `remove methods`, + info: map[string]overrideInfo{ + `Foo.GetBar`: {}, + `Foo.SetBar`: {}, + }, + src: ` + func (x Foo) GetBar() int { return x.bar } + func (x *Foo) SetBar(bar int) { x.bar = bar }`, + want: ``, + }, { + desc: `purge struct and methods`, + info: map[string]overrideInfo{ + `Foo`: {purgeMethods: true}, + }, + src: `type Foo struct{ + bar int + } + + func (f Foo) GetBar() int { return f.bar } + func (f *Foo) SetBar(bar int) { f.bar = bar } + + func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`, + // NewFoo is not removed automatically since + // only functions with Foo as a receiver are removed. + want: `func NewFoo(bar int) *Foo { return &Foo{bar: bar} }`, + }, { + desc: `remove generics`, + info: map[string]overrideInfo{ + `Pointer`: {}, + `Sort`: {}, + `Equal`: {}, + }, + src: `import "cmp" + + // keeps the isOnlyImports from skipping what is being tested. + func foo() {} + + type Pointer[T any] struct {} + + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // overlay had stub "func Equal() {}" + func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + want: `// keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, + }, { + desc: `purge generics`, + info: map[string]overrideInfo{ + `Pointer`: {purgeMethods: true}, + `Sort`: {}, + `Equal`: {}, + }, + src: `import "cmp" + + // keeps the isOnlyImports from skipping what is being tested. + func foo() {} + + type Pointer[T any] struct {} + func (x *Pointer[T]) Load() *T {} + func (x *Pointer[T]) Store(val *T) {} + + func Sort[S ~[]E, E cmp.Ordered](x S) {} + + // overlay had stub "func Equal() {}" + func Equal[S ~[]E, E any](s1, s2 S) bool {}`, + want: `// keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, + }, { + desc: `prune an unused import`, + info: map[string]overrideInfo{}, + src: `import foo "some/other/bar" + + // keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, + want: `// keeps the isOnlyImports from skipping what is being tested. + func foo() {}`, + }, { + desc: `override signature of function`, + info: map[string]overrideInfo{ + `Foo`: { + overrideSignature: srctesting.ParseFuncDecl(t, + `package whatever + func Foo(a, b any) (any, bool) {}`), + }, + }, + src: `func Foo[T comparable](a, b T) (T, bool) { + if a == b { + return a, true + } + return b, false + }`, + want: `func Foo(a, b any) (any, bool) { + if a == b { + return a, true + } + return b, false + }`, + }, { + desc: `override signature of method`, + info: map[string]overrideInfo{ + `Foo.Bar`: { + overrideSignature: srctesting.ParseFuncDecl(t, + `package whatever + func (r *Foo) Bar(a, b any) (any, bool) {}`), + }, + }, + src: `func (r *Foo[T]) Bar(a, b T) (T, bool) { + if r.isSame(a, b) { + return a, true + } + return b, false + }`, + want: `func (r *Foo) Bar(a, b any) (any, bool) { + if r.isSame(a, b) { + return a, true + } + return b, false + }`, + }, { + desc: `empty file removes all imports`, + info: map[string]overrideInfo{ + `foo`: {}, + }, + src: `import . "math/rand" + func foo() int { + return Int() + }`, + want: ``, + }, { + desc: `empty file with directive`, + info: map[string]overrideInfo{ + `foo`: {}, + }, + src: `//go:linkname foo bar + import _ "unsafe"`, + want: `//go:linkname foo bar + import _ "unsafe"`, + }, { + desc: `multiple imports for directives`, + info: map[string]overrideInfo{ + `A`: {}, + `C`: {}, + }, + src: `import "unsafe" + import "embed" + + //go:embed hello.txt + var A embed.FS + + //go:embed goodbye.txt + var B string + + var C unsafe.Pointer + + // override Now with hardcoded time for testing + //go:linkname timeNow time.Now + func timeNow() time.Time { + return time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC) + }`, + want: `import _ "unsafe" + import _ "embed" + + //go:embed goodbye.txt + var B string + + // override Now with hardcoded time for testing + //go:linkname timeNow time.Now + func timeNow() time.Time { + return time.Date(2012, 8, 6, 0, 0, 0, 0, time.UTC) + }`, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + pkgName := "package testpackage\n\n" + importPath := `math/rand` + fsetSrc := token.NewFileSet() + fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) + + augmentOriginalImports(importPath, fileSrc) + augmentOriginalFile(fileSrc, test.info) + pruneImports(fileSrc) + + got := srctesting.Format(t, fsetSrc, fileSrc) + + fsetWant := token.NewFileSet() + fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) + want := srctesting.Format(t, fsetWant, fileWant) + + if got != want { + t.Errorf("augmentOriginalImports, augmentOriginalFile, and pruneImports got unexpected code:\n"+ + "returned:\n\t%q\nwant:\n\t%q", got, want) + } + }) + } +} diff --git a/build/cache/cache.go b/build/cache/cache.go index 79e0471cf..14257ef9e 100644 --- a/build/cache/cache.go +++ b/build/cache/cache.go @@ -95,7 +95,7 @@ func (bc *BuildCache) StoreArchive(a *compiler.Archive) { return // Caching is disabled. } path := cachedPath(bc.archiveKey(a.ImportPath)) - if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { log.Warningf("Failed to create build cache directory: %v", err) return } diff --git a/build/context.go b/build/context.go index 111f313ed..316bfb2bb 100644 --- a/build/context.go +++ b/build/context.go @@ -179,7 +179,7 @@ func (sc simpleCtx) gotool(subcommand string, args ...string) (string, error) { panic(fmt.Errorf("can't use go tool with a virtual build context")) } args = append([]string{subcommand}, args...) - cmd := exec.Command("go", args...) + cmd := exec.Command(filepath.Join(sc.bctx.GOROOT, "bin", "go"), args...) if sc.bctx.Dir != "" { cmd.Dir = sc.bctx.Dir diff --git a/build/embed.go b/build/embed.go index 2b5661f6c..c749eeb50 100644 --- a/build/embed.go +++ b/build/embed.go @@ -55,17 +55,21 @@ func embedFiles(pkg *PackageData, fset *token.FileSet, files []*ast.File) (*ast. &ast.CallExpr{ Fun: em.Spec.Type, Args: []ast.Expr{ - &ast.Ident{Name: buildIdent(fs[0].Name), - NamePos: em.Spec.Names[0].NamePos}, + &ast.Ident{ + Name: buildIdent(fs[0].Name), + NamePos: em.Spec.Names[0].NamePos, + }, }, - }} + }, + } case goembed.EmbedBytes: // value = []byte(data) em.Spec.Values = []ast.Expr{ &ast.CallExpr{ Fun: em.Spec.Type, Args: []ast.Expr{ast.NewIdent(buildIdent(fs[0].Name))}, - }} + }, + } case goembed.EmbedString: // value = data em.Spec.Values = []ast.Expr{ast.NewIdent(buildIdent(fs[0].Name))} @@ -115,15 +119,15 @@ func embedFiles(pkg *PackageData, fset *token.FileSet, files []*ast.File) (*ast. Elt: &ast.StructType{ Fields: &ast.FieldList{ List: []*ast.Field{ - &ast.Field{ + { Names: []*ast.Ident{ast.NewIdent("name")}, Type: ast.NewIdent("string"), }, - &ast.Field{ + { Names: []*ast.Ident{ast.NewIdent("data")}, Type: ast.NewIdent("string"), }, - &ast.Field{ + { Names: []*ast.Ident{ast.NewIdent("hash")}, Type: &ast.ArrayType{ Len: &ast.BasicLit{Kind: token.INT, Value: "16"}, diff --git a/circle.yml b/circle.yml index 068f9d5e2..2c3095c8b 100644 --- a/circle.yml +++ b/circle.yml @@ -54,7 +54,11 @@ workflows: parameters: go_version: type: string - default: "1.18.10" + default: "1.19.13" + chocolatey_go_version: + type: string + # Chocolatey doesn't have 1.19.13, closest is 1.19.9 + default: "1.19.9" nvm_version: type: string default: "0.38.0" @@ -72,23 +76,6 @@ jobs: executor: gopherjs steps: - setup_and_install_gopherjs - - run: - name: Check minified prelude - command: | - set -e - go generate github.com/gopherjs/gopherjs/compiler/prelude - diff -u <(echo -n) <(git status --porcelain) - - run: - name: Check gofmt - command: diff -u <(echo -n) <(gofmt -d .) - - run: - name: Check go vet - command: | - set -e - # Go package in root directory. - go vet . - # All subdirectories except "doc", "tests", "node*". - for d in */; do echo ./$d...; done | grep -v ./doc | grep -v ./tests | grep -v ./node | xargs go vet - run: name: Check natives build tags command: diff -u <(echo -n) <(go list ./compiler/natives/src/...) # All those packages should have // +build js. @@ -116,7 +103,8 @@ jobs: set -e export GO111MODULE=off export GOPATH=/tmp/gopath - mkdir -p $GOPATH/src + mkdir -p $GOPATH/src/github.com/gopherjs/gopherjs + cp -r -p . $GOPATH/src/github.com/gopherjs/gopherjs/ go get -v github.com/gopherjs/todomvc gopherjs build -v -o /tmp/todomvc_gopath.js github.com/gopherjs/todomvc gopherjs test -v github.com/gopherjs/todomvc/... @@ -187,7 +175,7 @@ jobs: - run: name: Install Go command: | - choco install golang --version="<< pipeline.parameters.go_version >>" -my + choco install golang --version="<< pipeline.parameters.chocolatey_go_version >>" -my --force -y go version (Get-Command go).Path [Environment]::SetEnvironmentVariable( @@ -215,7 +203,7 @@ jobs: darwin_smoke: macos: - xcode: 13.3.0 # Mac OS 12.2.1 + xcode: 13.4.1 # Mac OS 12.6.1, see https://circleci.com/docs/using-macos/ steps: - checkout - setup_environment diff --git a/compiler/analysis/escape.go b/compiler/analysis/escape.go index 2807ecf64..4209fca6c 100644 --- a/compiler/analysis/escape.go +++ b/compiler/analysis/escape.go @@ -14,16 +14,13 @@ func EscapingObjects(n ast.Node, info *types.Info) []*types.Var { bottomScopes: make(map[*types.Scope]bool), } ast.Walk(&v, n) - var list []*types.Var - for obj := range v.escaping { - list = append(list, obj) - } - return list + return v.ordered } type escapeAnalysis struct { info *types.Info escaping map[*types.Var]bool + ordered []*types.Var topScope *types.Scope bottomScopes map[*types.Scope]bool } @@ -57,7 +54,10 @@ func (v *escapingObjectCollector) Visit(node ast.Node) (w ast.Visitor) { if obj, ok := v.analysis.info.Uses[id].(*types.Var); ok { for s := obj.Parent(); s != nil; s = s.Parent() { if s == v.analysis.topScope { - v.analysis.escaping[obj] = true + if !v.analysis.escaping[obj] { + v.analysis.escaping[obj] = true + v.analysis.ordered = append(v.analysis.ordered, obj) + } break } if v.analysis.bottomScopes[s] { diff --git a/compiler/analysis/info.go b/compiler/analysis/info.go index c984b726f..304c8808a 100644 --- a/compiler/analysis/info.go +++ b/compiler/analysis/info.go @@ -93,7 +93,10 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { } func (info *Info) IsBlocking(fun *types.Func) bool { - return len(info.FuncDeclInfos[fun].Blocking) > 0 + if funInfo := info.FuncDeclInfos[fun]; funInfo != nil { + return len(funInfo.Blocking) > 0 + } + panic(fmt.Errorf(`info did not have function declaration for %s`, fun.FullName())) } func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info { diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index b9c4b54c8..5cfe2dbd3 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -5,6 +5,10 @@ import ( "go/ast" "go/token" "go/types" + "path" + "reflect" + "regexp" + "strconv" "strings" ) @@ -59,35 +63,149 @@ func ImportsUnsafe(file *ast.File) bool { return false } +// ImportName tries to determine the package name for an import. +// +// If the package name isn't specified then this will make a best +// make a best guess using the import path. +// If the import name is dot (`.`), blank (`_`), or there +// was an issue determining the package name then empty is returned. +func ImportName(spec *ast.ImportSpec) string { + var name string + if spec.Name != nil { + name = spec.Name.Name + } else { + importPath, _ := strconv.Unquote(spec.Path.Value) + name = path.Base(importPath) + } + + switch name { + case `_`, `.`, `/`: + return `` + default: + return name + } +} + // FuncKey returns a string, which uniquely identifies a top-level function or // method in a package. func FuncKey(d *ast.FuncDecl) string { - if d.Recv == nil || len(d.Recv.List) == 0 { - return d.Name.Name + if recvKey := FuncReceiverKey(d); len(recvKey) > 0 { + return recvKey + "." + d.Name.Name + } + return d.Name.Name +} + +// FuncReceiverKey returns a string that uniquely identifies the receiver +// struct of the function or an empty string if there is no receiver. +// This name will match the name of the struct in the struct's type spec. +func FuncReceiverKey(d *ast.FuncDecl) string { + if d == nil || d.Recv == nil || len(d.Recv.List) == 0 { + return `` } recv := d.Recv.List[0].Type - if star, ok := recv.(*ast.StarExpr); ok { - recv = star.X + for { + switch r := recv.(type) { + case *ast.IndexListExpr: + recv = r.X + continue + case *ast.IndexExpr: + recv = r.X + continue + case *ast.StarExpr: + recv = r.X + continue + case *ast.Ident: + return r.Name + default: + panic(fmt.Errorf(`unexpected type %T in receiver of function: %v`, recv, d)) + } } - return recv.(*ast.Ident).Name + "." + d.Name.Name } -// PruneOriginal returns true if gopherjs:prune-original directive is present +// KeepOriginal returns true if gopherjs:keep-original directive is present // before a function decl. // -// `//gopherjs:prune-original` is a GopherJS-specific directive, which can be +// `//gopherjs:keep-original` is a GopherJS-specific directive, which can be // applied to functions in native overlays and will instruct the augmentation -// logic to delete the body of a standard library function that was replaced. -// This directive can be used to remove code that would be invalid in GopherJS, -// such as code expecting ints to be 64-bit. It should be used with caution -// since it may create unused imports in the original source file. -func PruneOriginal(d *ast.FuncDecl) bool { - if d.Doc == nil { - return false - } - for _, c := range d.Doc.List { - if strings.HasPrefix(c.Text, "//gopherjs:prune-original") { - return true +// logic to expose the original function such that it can be called. For a +// function in the original called `foo`, it will be accessible by the name +// `_gopherjs_original_foo`. +func KeepOriginal(d *ast.FuncDecl) bool { + return hasDirective(d, `keep-original`) +} + +// Purge returns true if gopherjs:purge directive is present +// on a struct, interface, type, variable, constant, or function. +// +// `//gopherjs:purge` is a GopherJS-specific directive, which can be +// applied in native overlays and will instruct the augmentation logic to +// delete part of the standard library without a replacement. This directive +// can be used to remove code that would be invalid in GopherJS, such as code +// using unsupported features (e.g. generic interfaces before generics were +// fully supported). It should be used with caution since it may remove needed +// dependencies. If a type is purged, all methods using that type as +// a receiver will also be purged. +func Purge(d ast.Node) bool { + return hasDirective(d, `purge`) +} + +// OverrideSignature returns true if gopherjs:override-signature directive is +// present on a function. +// +// `//gopherjs:override-signature` is a GopherJS-specific directive, which can +// be applied in native overlays and will instruct the augmentation logic to +// replace the original function signature which has the same FuncKey with the +// signature defined in the native overlays. +// This directive can be used to remove generics from a function signature or +// to replace a receiver of a function with another one. The given native +// overlay function will be removed, so no method body is needed in the overlay. +// +// The new signature may not contain types which require a new import since +// the imports will not be automatically added when needed, only removed. +// Use a type alias in the overlay to deal manage imports. +func OverrideSignature(d *ast.FuncDecl) bool { + return hasDirective(d, `override-signature`) +} + +// directiveMatcher is a regex which matches a GopherJS directive +// and finds the directive action. +var directiveMatcher = regexp.MustCompile(`^\/(?:\/|\*)gopherjs:([\w-]+)`) + +// hasDirective returns true if the associated documentation +// or line comments for the given node have the given directive action. +// +// All GopherJS-specific directives must start with `//gopherjs:` or +// `/*gopherjs:` and followed by an action without any whitespace. The action +// must be one or more letter, decimal, underscore, or hyphen. +// +// see https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives +func hasDirective(node ast.Node, directiveAction string) bool { + foundDirective := false + ast.Inspect(node, func(n ast.Node) bool { + switch a := n.(type) { + case *ast.Comment: + m := directiveMatcher.FindStringSubmatch(a.Text) + if len(m) == 2 && m[1] == directiveAction { + foundDirective = true + } + return false + case *ast.CommentGroup: + return !foundDirective + default: + return n == node + } + }) + return foundDirective +} + +// HasDirectivePrefix determines if any line in the given file +// has the given directive prefix in it. +func HasDirectivePrefix(file *ast.File, prefix string) bool { + for _, cg := range file.Comments { + for _, c := range cg.List { + if strings.HasPrefix(c.Text, prefix) { + return true + } } } return false @@ -147,3 +265,21 @@ func EndsWithReturn(stmts []ast.Stmt) bool { return false } } + +// Squeeze removes all nil nodes from the slice. +// +// The given slice will be modified. This is designed for squeezing +// declaration, specification, imports, and identifier lists. +func Squeeze[E ast.Node, S ~[]E](s S) S { + var zero E + count, dest := len(s), 0 + for src := 0; src < count; src++ { + if !reflect.DeepEqual(s[src], zero) { + // Swap the values, this will put the nil values to the end + // of the slice so that the tail isn't holding onto pointers. + s[dest], s[src] = s[src], s[dest] + dest++ + } + } + return s[:dest] +} diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index a996ae73f..28528a2b3 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -1,7 +1,9 @@ package astutil import ( + "go/ast" "go/token" + "strconv" "testing" "github.com/gopherjs/gopherjs/internal/srctesting" @@ -52,6 +54,47 @@ func TestImportsUnsafe(t *testing.T) { } } +func TestImportName(t *testing.T) { + tests := []struct { + desc string + src string + want string + }{ + { + desc: `named import`, + src: `import foo "some/other/bar"`, + want: `foo`, + }, { + desc: `unnamed import`, + src: `import "some/other/bar"`, + want: `bar`, + }, { + desc: `dot import`, + src: `import . "some/other/bar"`, + want: ``, + }, { + desc: `blank import`, + src: `import _ "some/other/bar"`, + want: ``, + }, + } + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + src := "package testpackage\n\n" + test.src + fset := token.NewFileSet() + file := srctesting.Parse(t, fset, src) + if len(file.Imports) != 1 { + t.Fatal(`expected one and only one import`) + } + importSpec := file.Imports[0] + got := ImportName(importSpec) + if got != test.want { + t.Fatalf(`ImportName() returned %q, want %q`, got, test.want) + } + }) + } +} + func TestFuncKey(t *testing.T) { tests := []struct { desc string @@ -59,72 +102,382 @@ func TestFuncKey(t *testing.T) { want string }{ { - desc: "top-level function", - src: `package testpackage; func foo() {}`, - want: "foo", + desc: `top-level function`, + src: `func foo() {}`, + want: `foo`, + }, { + desc: `top-level exported function`, + src: `func Foo() {}`, + want: `Foo`, + }, { + desc: `method on reference`, + src: `func (_ myType) bar() {}`, + want: `myType.bar`, }, { - desc: "top-level exported function", - src: `package testpackage; func Foo() {}`, - want: "Foo", + desc: `method on pointer`, + src: ` func (_ *myType) bar() {}`, + want: `myType.bar`, }, { - desc: "method", - src: `package testpackage; func (_ myType) bar() {}`, - want: "myType.bar", + desc: `method on generic reference`, + src: ` func (_ myType[T]) bar() {}`, + want: `myType.bar`, + }, { + desc: `method on generic pointer`, + src: ` func (_ *myType[T]) bar() {}`, + want: `myType.bar`, + }, { + desc: `method on struct with multiple generics`, + src: ` func (_ *myType[T1, T2, T3, T4]) bar() {}`, + want: `myType.bar`, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - fdecl := srctesting.ParseFuncDecl(t, test.src) + src := `package testpackage; ` + test.src + fdecl := srctesting.ParseFuncDecl(t, src) if got := FuncKey(fdecl); got != test.want { - t.Errorf("Got %q, want %q", got, test.want) + t.Errorf(`Got %q, want %q`, got, test.want) } }) } } -func TestPruneOriginal(t *testing.T) { +func TestHasDirectiveOnDecl(t *testing.T) { tests := []struct { desc string src string want bool }{ { - desc: "no comment", + desc: `no comment on function`, src: `package testpackage; - func foo() {}`, + func foo() {}`, want: false, }, { - desc: "regular godoc", + desc: `no directive on function with comment`, src: `package testpackage; - // foo does something - func foo() {}`, + // foo has no directive + func foo() {}`, want: false, }, { - desc: "only directive", + desc: `wrong directive on function`, src: `package testpackage; - //gopherjs:prune-original - func foo() {}`, + //gopherjs:wrong-directive + func foo() {}`, + want: false, + }, { + desc: `correct directive on function`, + src: `package testpackage; + //gopherjs:do-stuff + // foo has a directive to do stuff + func foo() {}`, want: true, }, { - desc: "directive with explanation", + desc: `correct directive in multiline comment on function`, src: `package testpackage; - //gopherjs:prune-original because reasons - func foo() {}`, + /*gopherjs:do-stuff + foo has a directive to do stuff + */ + func foo() {}`, want: true, }, { - desc: "directive in godoc", + desc: `invalid directive in multiline comment on function`, src: `package testpackage; - // foo does something - //gopherjs:prune-original - func foo() {}`, + /* + gopherjs:do-stuff + */ + func foo() {}`, + want: false, + }, { + desc: `prefix directive on function`, + src: `package testpackage; + //gopherjs:do-stuffs + func foo() {}`, + want: false, + }, { + desc: `multiple directives on function`, + src: `package testpackage; + //gopherjs:wrong-directive + //gopherjs:do-stuff + //gopherjs:another-directive + func foo() {}`, + want: true, + }, { + desc: `directive with explanation on function`, + src: `package testpackage; + //gopherjs:do-stuff 'cause we can + func foo() {}`, + want: true, + }, { + desc: `no directive on type declaration`, + src: `package testpackage; + // Foo has a comment + type Foo int`, + want: false, + }, { + desc: `directive on type declaration`, + src: `package testpackage; + //gopherjs:do-stuff + type Foo int`, + want: true, + }, { + desc: `directive on specification, not on declaration`, + src: `package testpackage; + type ( + Foo int + + //gopherjs:do-stuff + Bar struct{} + )`, + want: false, + }, { + desc: `no directive on const declaration`, + src: `package testpackage; + const foo = 42`, + want: false, + }, { + desc: `directive on const documentation`, + src: `package testpackage; + //gopherjs:do-stuff + const foo = 42`, + want: true, + }, { + desc: `no directive on var declaration`, + src: `package testpackage; + var foo = 42`, + want: false, + }, { + desc: `directive on var documentation`, + src: `package testpackage; + //gopherjs:do-stuff + var foo = 42`, + want: true, + }, { + desc: `no directive on var declaration`, + src: `package testpackage; + import _ "embed"`, + want: false, + }, { + desc: `directive on var documentation`, + src: `package testpackage; + //gopherjs:do-stuff + import _ "embed"`, want: true, }, } + for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - fdecl := srctesting.ParseFuncDecl(t, test.src) - if got := PruneOriginal(fdecl); got != test.want { - t.Errorf("PruneOriginal() returned %t, want %t", got, test.want) + const action = `do-stuff` + decl := srctesting.ParseDecl(t, test.src) + if got := hasDirective(decl, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, decl, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveOnSpec(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no directive on type specification`, + src: `package testpackage; + type Foo int`, + want: false, + }, { + desc: `directive on declaration, not on specification`, + src: `package testpackage; + //gopherjs:do-stuff + type Foo int`, + want: false, + }, { + desc: `directive in doc on type specification`, + src: `package testpackage; + type ( + //gopherjs:do-stuff + Foo int + )`, + want: true, + }, { + desc: `directive in line on type specification`, + src: `package testpackage; + type Foo int //gopherjs:do-stuff`, + want: true, + }, { + desc: `no directive on const specification`, + src: `package testpackage; + const foo = 42`, + want: false, + }, { + desc: `directive in doc on const specification`, + src: `package testpackage; + const ( + //gopherjs:do-stuff + foo = 42 + )`, + want: true, + }, { + desc: `directive in line on const specification`, + src: `package testpackage; + const foo = 42 //gopherjs:do-stuff`, + want: true, + }, { + desc: `no directive on var specification`, + src: `package testpackage; + var foo = 42`, + want: false, + }, { + desc: `directive in doc on var specification`, + src: `package testpackage; + var ( + //gopherjs:do-stuff + foo = 42 + )`, + want: true, + }, { + desc: `directive in line on var specification`, + src: `package testpackage; + var foo = 42 //gopherjs:do-stuff`, + want: true, + }, { + desc: `no directive on import specification`, + src: `package testpackage; + import _ "embed"`, + want: false, + }, { + desc: `directive in doc on import specification`, + src: `package testpackage; + import ( + //gopherjs:do-stuff + _ "embed" + )`, + want: true, + }, { + desc: `directive in line on import specification`, + src: `package testpackage; + import _ "embed" //gopherjs:do-stuff`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + spec := srctesting.ParseSpec(t, test.src) + if got := hasDirective(spec, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, spec, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveOnFile(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no directive on file`, + src: `package testpackage; + //gopherjs:do-stuff + type Foo int`, + want: false, + }, { + desc: `directive on file`, + src: `//gopherjs:do-stuff + package testpackage; + type Foo int`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + fset := token.NewFileSet() + file := srctesting.Parse(t, fset, test.src) + if got := hasDirective(file, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, file, action, got, test.want) + } + }) + } +} + +func TestHasDirectiveOnField(t *testing.T) { + tests := []struct { + desc string + src string + want bool + }{ + { + desc: `no directive on struct field`, + src: `package testpackage; + type Foo struct { + bar int + }`, + want: false, + }, { + desc: `directive in doc on struct field`, + src: `package testpackage; + type Foo struct { + //gopherjs:do-stuff + bar int + }`, + want: true, + }, { + desc: `directive in line on struct field`, + src: `package testpackage; + type Foo struct { + bar int //gopherjs:do-stuff + }`, + want: true, + }, { + desc: `no directive on interface method`, + src: `package testpackage; + type Foo interface { + Bar(a int) int + }`, + want: false, + }, { + desc: `directive in doc on interface method`, + src: `package testpackage; + type Foo interface { + //gopherjs:do-stuff + Bar(a int) int + }`, + want: true, + }, { + desc: `directive in line on interface method`, + src: `package testpackage; + type Foo interface { + Bar(a int) int //gopherjs:do-stuff + }`, + want: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + const action = `do-stuff` + spec := srctesting.ParseSpec(t, test.src) + tspec := spec.(*ast.TypeSpec) + var field *ast.Field + switch typeNode := tspec.Type.(type) { + case *ast.StructType: + field = typeNode.Fields.List[0] + case *ast.InterfaceType: + field = typeNode.Methods.List[0] + default: + t.Errorf(`unexpected node type, %T, when finding field`, typeNode) + return + } + if got := hasDirective(field, action); got != test.want { + t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, field, action, got, test.want) } }) } @@ -181,3 +534,61 @@ func TestEndsWithReturn(t *testing.T) { }) } } + +func TestSqueezeIdents(t *testing.T) { + tests := []struct { + desc string + count int + assign []int + }{ + { + desc: `no squeezing`, + count: 5, + assign: []int{0, 1, 2, 3, 4}, + }, { + desc: `missing front`, + count: 5, + assign: []int{3, 4}, + }, { + desc: `missing back`, + count: 5, + assign: []int{0, 1, 2}, + }, { + desc: `missing several`, + count: 10, + assign: []int{1, 2, 3, 6, 8}, + }, { + desc: `empty`, + count: 0, + assign: []int{}, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + input := make([]*ast.Ident, test.count) + for _, i := range test.assign { + input[i] = ast.NewIdent(strconv.Itoa(i)) + } + + result := Squeeze(input) + if len(result) != len(test.assign) { + t.Errorf("Squeeze() returned a slice %d long, want %d", len(result), len(test.assign)) + } + for i, id := range input { + if i < len(result) { + if id == nil { + t.Errorf(`Squeeze() returned a nil in result at %d`, i) + } else { + value, err := strconv.Atoi(id.Name) + if err != nil || value != test.assign[i] { + t.Errorf(`Squeeze() returned %s at %d instead of %d`, id.Name, i, test.assign[i]) + } + } + } else if id != nil { + t.Errorf(`Squeeze() didn't clear out tail of slice, want %d nil`, i) + } + } + }) + } +} diff --git a/compiler/compiler.go b/compiler/compiler.go index e660b1bee..0588a923c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -21,8 +21,10 @@ import ( "golang.org/x/tools/go/gcexportdata" ) -var sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8} -var reservedKeywords = make(map[string]bool) +var ( + sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8} + reservedKeywords = make(map[string]bool) +) func init() { for _, keyword := range []string{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "undefined", "var", "void", "volatile", "while", "with", "yield"} { @@ -266,7 +268,7 @@ func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) err preludeJS := prelude.Prelude if minify { - preludeJS = prelude.Minified + preludeJS = prelude.Minified() } if _, err := io.WriteString(w, preludeJS); err != nil { return err diff --git a/compiler/expressions.go b/compiler/expressions.go index 578c3f09d..21971ab5f 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -100,8 +100,34 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { switch e := expr.(type) { case *ast.CompositeLit: - if ptrType, isPointer := exprType.(*types.Pointer); isPointer { - exprType = ptrType.Elem() + 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.setType(&ast.UnaryExpr{ + OpPos: e.Pos(), + Op: token.AND, + X: fc.setType(&ast.CompositeLit{ + Elts: e.Elts, + }, ptrType.Elem()), + }, ptrType) + + if exprType, ok := exprType.(*types.Named); ok { + // Handle a special case when the pointer type is named, e.g.: + // type PS *S + // _ = []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.newTypeIdent(exprType.String(), exprType.Obj()), + Lparen: e.Lbrace, + Args: []ast.Expr{rewritten}, + Rparen: e.Rbrace, + }, exprType) + rewritten = typeCast + } + return fc.translateExpr(rewritten) } collectIndexedElements := func(elementType types.Type) []string { @@ -173,7 +199,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { } return fc.formatExpr("new %s.ptr(%s)", fc.typeName(exprType), strings.Join(elements, ", ")) default: - panic(fmt.Sprintf("Unhandled CompositeLit type: %T\n", t)) + panic(fmt.Sprintf("Unhandled CompositeLit type: %[1]T %[1]v\n", t)) } case *ast.FuncLit: @@ -241,7 +267,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case token.ARROW: call := &ast.CallExpr{ - Fun: fc.newIdent("$recv", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), + Fun: fc.newIdent("$recv", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), Args: []ast.Expr{e.X}, } fc.Blocking[call] = true @@ -494,8 +520,11 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { ) case *types.Basic: return fc.formatExpr("%e.charCodeAt(%f)", e.X, e.Index) + case *types.Signature: + err := bailout(fmt.Errorf(`unsupported type parameters used at %s`, fc.pkgCtx.fileSet.Position(e.Pos()))) + panic(err) default: - panic(fmt.Sprintf("Unhandled IndexExpr: %T\n", t)) + panic(fmt.Errorf(`unhandled IndexExpr: %T`, t)) } case *ast.SliceExpr: @@ -677,7 +706,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case "Float": return fc.internalize(recv, types.Typ[types.Float64]) case "Interface": - return fc.internalize(recv, types.NewInterface(nil, nil)) + return fc.internalize(recv, types.NewInterfaceType(nil, nil)) case "Unsafe": return recv default: @@ -972,7 +1001,7 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args panic(fmt.Sprintf("Unhandled cap type: %T\n", argType)) } case "panic": - return fc.formatExpr("$panic(%s)", fc.translateImplicitConversion(args[0], types.NewInterface(nil, nil))) + return fc.formatExpr("$panic(%s)", fc.translateImplicitConversion(args[0], types.NewInterfaceType(nil, nil))) case "append": if ellipsis || len(args) == 1 { argStr := fc.translateArgs(sig, args, ellipsis) diff --git a/compiler/linkname.go b/compiler/linkname.go index 5e1782462..ae1e3ea2b 100644 --- a/compiler/linkname.go +++ b/compiler/linkname.go @@ -26,10 +26,10 @@ type GoLinkname struct { // This is a logical equivalent of a symbol name used by traditional linkers. // The following properties should hold true: // -// - Each named symbol within a program has a unique SymName. -// - Similarly named methods of different types will have different symbol names. -// - The string representation is opaque and should not be attempted to reversed -// to a struct form. +// - Each named symbol within a program has a unique SymName. +// - Similarly named methods of different types will have different symbol names. +// - The string representation is opaque and should not be attempted to reversed +// to a struct form. type SymName struct { PkgPath string // Full package import path. Name string // Symbol name. @@ -85,12 +85,12 @@ func (n SymName) IsMethod() (recv string, method string, ok bool) { // // GopherJS directive support has the following limitations: // -// - External linkname must be specified. -// - The directive must be applied to a package-level function or method (variables -// are not supported). -// - The local function referenced by the directive must have no body (in other -// words, it can only "import" an external function implementation into the -// local scope). +// - External linkname must be specified. +// - The directive must be applied to a package-level function or method (variables +// are not supported). +// - The local function referenced by the directive must have no body (in other +// words, it can only "import" an external function implementation into the +// local scope). func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]GoLinkname, error) { var errs ErrorList = nil var directives []GoLinkname diff --git a/compiler/natives/src/crypto/elliptic/nistec.go b/compiler/natives/src/crypto/elliptic/nistec.go new file mode 100644 index 000000000..326c602d5 --- /dev/null +++ b/compiler/natives/src/crypto/elliptic/nistec.go @@ -0,0 +1,81 @@ +//go:build js +// +build js + +package elliptic + +import ( + "crypto/internal/nistec" + "math/big" +) + +// nistPoint uses generics so must be removed for generic-less GopherJS. +// All the following code changes in this file are to make p224, p256, +// p521, and p384 still function correctly without this generic struct. +// +//gopherjs:purge for go1.19 without generics +type nistPoint[T any] interface{} + +// nistCurve replaces the generics with a version using the wrappedPoint +// interface, then update all the method signatures to also use wrappedPoint. +type nistCurve struct { + newPoint func() nistec.WrappedPoint + params *CurveParams +} + +//gopherjs:override-signature +func (curve *nistCurve) Params() *CurveParams + +//gopherjs:override-signature +func (curve *nistCurve) IsOnCurve(x, y *big.Int) bool + +//gopherjs:override-signature +func (curve *nistCurve) pointFromAffine(x, y *big.Int) (p nistec.WrappedPoint, err error) + +//gopherjs:override-signature +func (curve *nistCurve) pointToAffine(p nistec.WrappedPoint) (x, y *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) normalizeScalar(scalar []byte) []byte + +//gopherjs:override-signature +func (curve *nistCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) ScalarBaseMult(scalar []byte) (*big.Int, *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) CombinedMult(Px, Py *big.Int, s1, s2 []byte) (x, y *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) Unmarshal(data []byte) (x, y *big.Int) + +//gopherjs:override-signature +func (curve *nistCurve) UnmarshalCompressed(data []byte) (x, y *big.Int) + +var p224 = &nistCurve{ + newPoint: nistec.NewP224WrappedPoint, +} + +type p256Curve struct { + nistCurve +} + +var p256 = &p256Curve{ + nistCurve: nistCurve{ + newPoint: nistec.NewP256WrappedPoint, + }, +} + +var p521 = &nistCurve{ + newPoint: nistec.NewP521WrappedPoint, +} + +var p384 = &nistCurve{ + newPoint: nistec.NewP384WrappedPoint, +} diff --git a/compiler/natives/src/crypto/internal/boring/bbig/big.go b/compiler/natives/src/crypto/internal/boring/bbig/big.go new file mode 100644 index 000000000..3a726ba3c --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/bbig/big.go @@ -0,0 +1,42 @@ +//go:build js +// +build js + +package bbig + +import ( + "crypto/internal/boring" + "math/big" +) + +func Enc(b *big.Int) boring.BigInt { + if b == nil { + return nil + } + x := b.Bits() + if len(x) == 0 { + return boring.BigInt{} + } + // Replacing original which uses unsafe: + // return unsafe.Slice((*uint)(&x[0]), len(x)) + b2 := make(boring.BigInt, len(x)) + for i, w := range x { + b2[i] = uint(w) + } + return b2 +} + +func Dec(b boring.BigInt) *big.Int { + if b == nil { + return nil + } + if len(b) == 0 { + return new(big.Int) + } + // Replacing original which uses unsafe: + // x := unsafe.Slice((*big.Word)(&b[0]), len(b)) + x := make([]big.Word, len(b)) + for i, w := range b { + x[i] = big.Word(w) + } + return new(big.Int).SetBits(x) +} diff --git a/compiler/natives/src/crypto/internal/boring/bcache/cache.go b/compiler/natives/src/crypto/internal/boring/bcache/cache.go new file mode 100644 index 000000000..afff404ce --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/bcache/cache.go @@ -0,0 +1,30 @@ +//go:build js +// +build js + +package bcache + +import "unsafe" + +// Cache relies on GC to periodically clear the cache. +// Since GopherJS doesn't have the same GC hooks, it currently can not +// register this cache with the GC. +// Without this cache Boring crypto, in particular public and private +// RSA and ECDSA keys, will be slower because the cache will always miss. +type Cache struct{} + +func (c *Cache) Register() {} +func (c *Cache) Clear() {} +func (c *Cache) Get(k unsafe.Pointer) unsafe.Pointer { return nil } +func (c *Cache) Put(k, v unsafe.Pointer) {} + +//gopherjs:purge +func (c *Cache) table() *[cacheSize]unsafe.Pointer + +//gopherjs:purge +type cacheEntry struct{} + +//gopherjs:purge +func registerCache(unsafe.Pointer) + +//gopherjs:purge +const cacheSize = 1021 diff --git a/compiler/natives/src/crypto/internal/boring/bcache/cache_test.go b/compiler/natives/src/crypto/internal/boring/bcache/cache_test.go new file mode 100644 index 000000000..12f2c4da4 --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/bcache/cache_test.go @@ -0,0 +1,10 @@ +//go:build js +// +build js + +package bcache + +import "testing" + +func TestCache(t *testing.T) { + t.Skip(`This test uses runtime.GC(), which GopherJS doesn't support`) +} diff --git a/compiler/natives/src/crypto/internal/boring/sig/sig.go b/compiler/natives/src/crypto/internal/boring/sig/sig.go new file mode 100644 index 000000000..3eb2454aa --- /dev/null +++ b/compiler/natives/src/crypto/internal/boring/sig/sig.go @@ -0,0 +1,13 @@ +//go:build js +// +build js + +package sig + +// Setting to no-op +func BoringCrypto() {} + +// Setting to no-op +func FIPSOnly() {} + +// Setting to no-op +func StandardCrypto() {} diff --git a/compiler/natives/src/crypto/internal/nistec/nistec_test.go b/compiler/natives/src/crypto/internal/nistec/nistec_test.go new file mode 100644 index 000000000..d755e7ec3 --- /dev/null +++ b/compiler/natives/src/crypto/internal/nistec/nistec_test.go @@ -0,0 +1,89 @@ +//go:build js +// +build js + +package nistec_test + +import ( + "crypto/elliptic" + "crypto/internal/nistec" + "testing" +) + +func TestAllocations(t *testing.T) { + t.Skip("testing.AllocsPerRun not supported in GopherJS") +} + +//gopherjs:purge +type nistPoint[T any] interface{} + +func TestEquivalents(t *testing.T) { + t.Run("P224", func(t *testing.T) { + testEquivalents(t, nistec.NewP224WrappedPoint, nistec.NewP224WrappedGenerator, elliptic.P224()) + }) + t.Run("P256", func(t *testing.T) { + testEquivalents(t, nistec.NewP256WrappedPoint, nistec.NewP256WrappedGenerator, elliptic.P256()) + }) + t.Run("P384", func(t *testing.T) { + testEquivalents(t, nistec.NewP384WrappedPoint, nistec.NewP384WrappedGenerator, elliptic.P384()) + }) + t.Run("P521", func(t *testing.T) { + testEquivalents(t, nistec.NewP521WrappedPoint, nistec.NewP521WrappedGenerator, elliptic.P521()) + }) +} + +//gopherjs:override-signature +func testEquivalents(t *testing.T, newPoint, newGenerator func() nistec.WrappedPoint, c elliptic.Curve) + +func TestScalarMult(t *testing.T) { + t.Run("P224", func(t *testing.T) { + testScalarMult(t, nistec.NewP224WrappedPoint, nistec.NewP224WrappedGenerator, elliptic.P224()) + }) + t.Run("P256", func(t *testing.T) { + testScalarMult(t, nistec.NewP256WrappedPoint, nistec.NewP256WrappedGenerator, elliptic.P256()) + }) + t.Run("P384", func(t *testing.T) { + testScalarMult(t, nistec.NewP384WrappedPoint, nistec.NewP384WrappedGenerator, elliptic.P384()) + }) + t.Run("P521", func(t *testing.T) { + testScalarMult(t, nistec.NewP521WrappedPoint, nistec.NewP521WrappedGenerator, elliptic.P521()) + }) +} + +//gopherjs:override-signature +func testScalarMult(t *testing.T, newPoint, newGenerator func() nistec.WrappedPoint, c elliptic.Curve) + +func BenchmarkScalarMult(b *testing.B) { + b.Run("P224", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP224WrappedGenerator(), 28) + }) + b.Run("P256", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP256WrappedGenerator(), 32) + }) + b.Run("P384", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP384WrappedGenerator(), 48) + }) + b.Run("P521", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP521WrappedGenerator(), 66) + }) +} + +//gopherjs:override-signature +func benchmarkScalarMult(b *testing.B, p nistec.WrappedPoint, scalarSize int) + +func BenchmarkScalarBaseMult(b *testing.B) { + b.Run("P224", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP224WrappedGenerator(), 28) + }) + b.Run("P256", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP256WrappedGenerator(), 32) + }) + b.Run("P384", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP384WrappedGenerator(), 48) + }) + b.Run("P521", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP521WrappedGenerator(), 66) + }) +} + +//gopherjs:override-signature +func benchmarkScalarBaseMult(b *testing.B, p nistec.WrappedPoint, scalarSize int) diff --git a/compiler/natives/src/crypto/internal/nistec/wrapper.go b/compiler/natives/src/crypto/internal/nistec/wrapper.go new file mode 100644 index 000000000..0d6706b52 --- /dev/null +++ b/compiler/natives/src/crypto/internal/nistec/wrapper.go @@ -0,0 +1,185 @@ +//go:build js +// +build js + +package nistec + +type WrappedPoint interface { + Bytes() []byte + SetBytes(b []byte) (WrappedPoint, error) + Add(w1, w2 WrappedPoint) WrappedPoint + Double(w1 WrappedPoint) WrappedPoint + ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) + ScalarBaseMult(scalar []byte) (WrappedPoint, error) +} + +type p224Wrapper struct { + point *P224Point +} + +func wrapP224(point *P224Point) WrappedPoint { + return p224Wrapper{point: point} +} + +func NewP224WrappedPoint() WrappedPoint { + return wrapP224(NewP224Point()) +} + +func NewP224WrappedGenerator() WrappedPoint { + return wrapP224(NewP224Generator()) +} + +func (w p224Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p224Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP224(p), err +} + +func (w p224Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP224(w.point.Add(w1.(p224Wrapper).point, w2.(p224Wrapper).point)) +} + +func (w p224Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP224(w.point.Double(w1.(p224Wrapper).point)) +} + +func (w p224Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p224Wrapper).point, scalar) + return wrapP224(p), err +} + +func (w p224Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP224(p), err +} + +type p256Wrapper struct { + point *P256Point +} + +func wrapP256(point *P256Point) WrappedPoint { + return p256Wrapper{point: point} +} + +func NewP256WrappedPoint() WrappedPoint { + return wrapP256(NewP256Point()) +} + +func NewP256WrappedGenerator() WrappedPoint { + return wrapP256(NewP256Generator()) +} + +func (w p256Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p256Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP256(p), err +} + +func (w p256Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP256(w.point.Add(w1.(p256Wrapper).point, w2.(p256Wrapper).point)) +} + +func (w p256Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP256(w.point.Double(w1.(p256Wrapper).point)) +} + +func (w p256Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p256Wrapper).point, scalar) + return wrapP256(p), err +} + +func (w p256Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP256(p), err +} + +type p521Wrapper struct { + point *P521Point +} + +func wrapP521(point *P521Point) WrappedPoint { + return p521Wrapper{point: point} +} + +func NewP521WrappedPoint() WrappedPoint { + return wrapP521(NewP521Point()) +} + +func NewP521WrappedGenerator() WrappedPoint { + return wrapP521(NewP521Generator()) +} + +func (w p521Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p521Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP521(p), err +} + +func (w p521Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP521(w.point.Add(w1.(p521Wrapper).point, w2.(p521Wrapper).point)) +} + +func (w p521Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP521(w.point.Double(w1.(p521Wrapper).point)) +} + +func (w p521Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p521Wrapper).point, scalar) + return wrapP521(p), err +} + +func (w p521Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP521(p), err +} + +type p384Wrapper struct { + point *P384Point +} + +func wrapP384(point *P384Point) WrappedPoint { + return p384Wrapper{point: point} +} + +func NewP384WrappedPoint() WrappedPoint { + return wrapP384(NewP384Point()) +} + +func NewP384WrappedGenerator() WrappedPoint { + return wrapP384(NewP384Generator()) +} + +func (w p384Wrapper) Bytes() []byte { + return w.point.Bytes() +} + +func (w p384Wrapper) SetBytes(b []byte) (WrappedPoint, error) { + p, err := w.point.SetBytes(b) + return wrapP384(p), err +} + +func (w p384Wrapper) Add(w1, w2 WrappedPoint) WrappedPoint { + return wrapP384(w.point.Add(w1.(p384Wrapper).point, w2.(p384Wrapper).point)) +} + +func (w p384Wrapper) Double(w1 WrappedPoint) WrappedPoint { + return wrapP384(w.point.Double(w1.(p384Wrapper).point)) +} + +func (w p384Wrapper) ScalarMult(w1 WrappedPoint, scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarMult(w1.(p384Wrapper).point, scalar) + return wrapP384(p), err +} + +func (w p384Wrapper) ScalarBaseMult(scalar []byte) (WrappedPoint, error) { + p, err := w.point.ScalarBaseMult(scalar) + return wrapP384(p), err +} diff --git a/compiler/natives/src/crypto/internal/subtle/aliasing.go b/compiler/natives/src/crypto/internal/subtle/aliasing.go index 104ac82bb..145687d59 100644 --- a/compiler/natives/src/crypto/internal/subtle/aliasing.go +++ b/compiler/natives/src/crypto/internal/subtle/aliasing.go @@ -4,8 +4,9 @@ package subtle // This file duplicated is these two locations: -// - src/crypto/internal/subtle/ -// - src/golang.org/x/crypto/internal/subtle/ +// - src/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/alias/alias.go import "github.com/gopherjs/gopherjs/js" diff --git a/compiler/natives/src/debug/pe/symbol.go b/compiler/natives/src/debug/pe/symbol.go new file mode 100644 index 000000000..798502ce3 --- /dev/null +++ b/compiler/natives/src/debug/pe/symbol.go @@ -0,0 +1,119 @@ +//go:build js +// +build js + +package pe + +import ( + "encoding/binary" + "fmt" + "io" +) + +// bytesBufferLite is a simplified bytes.Buffer to avoid +// including `bytes` as a new import into the pe package. +type bytesBufferLite struct { + data []byte + off int +} + +func (buf *bytesBufferLite) Write(p []byte) (int, error) { + buf.data = append(buf.data, p...) + return len(p), nil +} + +func (buf *bytesBufferLite) Read(p []byte) (int, error) { + n := copy(p, buf.data[buf.off:]) + buf.off += n + return n, nil +} + +func copyToAuxFormat5(sym *COFFSymbol) (*COFFSymbolAuxFormat5, error) { + buf := &bytesBufferLite{data: make([]byte, 0, 20)} + if err := binary.Write(buf, binary.LittleEndian, sym); err != nil { + return nil, err + } + aux := &COFFSymbolAuxFormat5{} + if err := binary.Read(buf, binary.LittleEndian, aux); err != nil { + return nil, err + } + return aux, nil +} + +func copyFromAuxFormat5(aux *COFFSymbolAuxFormat5) (*COFFSymbol, error) { + buf := &bytesBufferLite{data: make([]byte, 0, 20)} + if err := binary.Write(buf, binary.LittleEndian, aux); err != nil { + return nil, err + } + sym := &COFFSymbol{} + if err := binary.Read(buf, binary.LittleEndian, sym); err != nil { + return nil, err + } + return sym, nil +} + +func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) { + if fh.PointerToSymbolTable == 0 { + return nil, nil + } + if fh.NumberOfSymbols <= 0 { + return nil, nil + } + _, err := r.Seek(int64(fh.PointerToSymbolTable), seekStart) + if err != nil { + return nil, fmt.Errorf("fail to seek to symbol table: %v", err) + } + syms := make([]COFFSymbol, fh.NumberOfSymbols) + naux := 0 + for k := range syms { + if naux == 0 { + err = binary.Read(r, binary.LittleEndian, &syms[k]) + if err != nil { + return nil, fmt.Errorf("fail to read symbol table: %v", err) + } + naux = int(syms[k].NumberOfAuxSymbols) + } else { + naux-- + // The following was reading into one struct with the same memory + // footprint as another struck. This doesn't work in JS so the + // `syms` value is left with a bunch of defaults. So replace + // aux := (*COFFSymbolAuxFormat5)(unsafe.Pointer(&syms[k])) + // (an in memory remap) with the following read and then copy. + aux := &COFFSymbolAuxFormat5{} + err = binary.Read(r, binary.LittleEndian, aux) + if err != nil { + return nil, fmt.Errorf("fail to read symbol table: %v", err) + } + pesymn, err := copyFromAuxFormat5(aux) + if err != nil { + return nil, err + } + syms[k] = *pesymn + } + } + if naux != 0 { + return nil, fmt.Errorf("fail to read symbol table: %d aux symbols unread", naux) + } + return syms, nil +} + +func (f *File) COFFSymbolReadSectionDefAux(idx int) (*COFFSymbolAuxFormat5, error) { + var rv *COFFSymbolAuxFormat5 + if idx < 0 || idx >= len(f.COFFSymbols) { + return rv, fmt.Errorf("invalid symbol index") + } + pesym := &f.COFFSymbols[idx] + const IMAGE_SYM_CLASS_STATIC = 3 + if pesym.StorageClass != uint8(IMAGE_SYM_CLASS_STATIC) { + return rv, fmt.Errorf("incorrect symbol storage class") + } + if pesym.NumberOfAuxSymbols == 0 || idx+1 >= len(f.COFFSymbols) { + return rv, fmt.Errorf("aux symbol unavailable") + } + pesymn := &f.COFFSymbols[idx+1] + // The following was reading one struct as another struct with + // the same memory footprint. This doesn't work in JS so the + // `rv` value is left with a bunch of `undefined`s. So replace + // rv = (*COFFSymbolAuxFormat5)(unsafe.Pointer(pesymn)) + // (an in memory remap) with the following copy. + return copyToAuxFormat5(pesymn) +} diff --git a/compiler/natives/src/embed/embed.go b/compiler/natives/src/embed/embed.go index 18cb7001f..bb9738546 100644 --- a/compiler/natives/src/embed/embed.go +++ b/compiler/natives/src/embed/embed.go @@ -7,7 +7,8 @@ func buildFS(list []struct { name string data string hash [16]byte -}) (f FS) { +}, +) (f FS) { n := len(list) files := make([]file, n) for i := 0; i < n; i++ { diff --git a/compiler/natives/src/encoding/gob/gob_test.go b/compiler/natives/src/encoding/gob/gob_test.go index 94a999ccd..823b572ac 100644 --- a/compiler/natives/src/encoding/gob/gob_test.go +++ b/compiler/natives/src/encoding/gob/gob_test.go @@ -68,7 +68,7 @@ func TestEndToEnd(t *testing.T) { // TODO: Fix this problem: // TypeError: dst.$set is not a function // at typedmemmove (/github.com/gopherjs/gopherjs/reflect.go:487:3) - //Marr: map[[2]string][2]*float64{arr1: floatArr1, arr2: floatArr2}, + // Marr: map[[2]string][2]*float64{arr1: floatArr1, arr2: floatArr2}, EmptyMap: make(map[string]int), N: &[3]float64{1.5, 2.5, 3.5}, Strs: &[2]string{s1, s2}, diff --git a/compiler/natives/src/go/token/position.go b/compiler/natives/src/go/token/position.go new file mode 100644 index 000000000..6a1ee0c15 --- /dev/null +++ b/compiler/natives/src/go/token/position.go @@ -0,0 +1,22 @@ +//go:build js +// +build js + +package token + +import "sync" + +type FileSet struct { + mutex sync.RWMutex + base int + files []*File + + // replaced atomic.Pointer[File] for go1.19 without generics. + last atomicFilePointer +} + +type atomicFilePointer struct { + v *File +} + +func (x *atomicFilePointer) Load() *File { return x.v } +func (x *atomicFilePointer) Store(val *File) { x.v = val } diff --git a/compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go b/compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go new file mode 100644 index 000000000..a3e1e7f79 --- /dev/null +++ b/compiler/natives/src/golang.org/x/crypto/internal/alias/alias.go @@ -0,0 +1,21 @@ +//go:build js +// +build js + +package alias + +// This file duplicated is these two locations: +// - src/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/alias/alias.go + +import "github.com/gopherjs/gopherjs/js" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + // GopherJS: We can't rely on pointer arithmetic, so use GopherJS slice internals. + return len(x) > 0 && len(y) > 0 && + js.InternalObject(x).Get("$array") == js.InternalObject(y).Get("$array") && + js.InternalObject(x).Get("$offset").Int() <= js.InternalObject(y).Get("$offset").Int()+len(y)-1 && + js.InternalObject(y).Get("$offset").Int() <= js.InternalObject(x).Get("$offset").Int()+len(x)-1 +} diff --git a/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go b/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go index 104ac82bb..145687d59 100644 --- a/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go +++ b/compiler/natives/src/golang.org/x/crypto/internal/subtle/aliasing.go @@ -4,8 +4,9 @@ package subtle // This file duplicated is these two locations: -// - src/crypto/internal/subtle/ -// - src/golang.org/x/crypto/internal/subtle/ +// - src/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/subtle/aliasing.go +// - src/golang.org/x/crypto/internal/alias/alias.go import "github.com/gopherjs/gopherjs/js" diff --git a/compiler/natives/src/hash/maphash/maphash.go b/compiler/natives/src/hash/maphash/maphash.go index 877366f04..5c982404f 100644 --- a/compiler/natives/src/hash/maphash/maphash.go +++ b/compiler/natives/src/hash/maphash/maphash.go @@ -3,31 +3,58 @@ package maphash -// used in hash{32,64}.go to seed the hash function -var hashkey [4]uint32 +import ( + _ "unsafe" // for linkname +) + +// hashkey is similar how it is defined in runtime/alg.go for Go 1.19 +// to be used in hash{32,64}.go to seed the hash function as part of +// runtime_memhash. We're using locally defined memhash so it got moved here. +var hashkey [3]uint32 func init() { for i := range hashkey { - hashkey[i] = runtime_fastrand() + hashkey[i] = runtime_fastrand() | 1 + // The `| 1` is to make sure these numbers are odd } - hashkey[0] |= 1 // make sure these numbers are odd - hashkey[1] |= 1 - hashkey[2] |= 1 - hashkey[3] |= 1 } -func _rthash(b []byte, seed uint64) uint64 { +//go:linkname runtime_fastrand runtime.fastrand +func runtime_fastrand() uint32 + +// Bytes uses less efficient equivalent to avoid using unsafe. +func Bytes(seed Seed, b []byte) uint64 { + var h Hash + h.SetSeed(seed) + _, _ = h.Write(b) + return h.Sum64() +} + +// String uses less efficient equivalent to avoid using unsafe. +func String(seed Seed, s string) uint64 { + var h Hash + h.SetSeed(seed) + _, _ = h.WriteString(s) + return h.Sum64() +} + +// rthash is similar to the Go 1.19.13 version +// with the call to memhash changed to not use unsafe pointers. +func rthash(b []byte, seed uint64) uint64 { if len(b) == 0 { return seed } // The runtime hasher only works on uintptr. Since GopherJS implements a // 32-bit environment, we use two parallel hashers on the lower and upper 32 // bits. - lo := memhash(b, uint32(seed), uint32(len(b))) - hi := memhash(b, uint32(seed>>32), uint32(len(b))) + lo := memhash(b, uint32(seed)) + hi := memhash(b, uint32(seed>>32)) return uint64(hi)<<32 | uint64(lo) } +//gopherjs:purge to remove link using unsafe pointers, use memhash instead. +func runtime_memhash() + // The implementation below is adapted from the upstream runtime/hash32.go // and avoids use of unsafe, which GopherJS doesn't support well and leads to // worse performance. @@ -38,8 +65,9 @@ func _rthash(b []byte, seed uint64) uint64 { // // Hashing algorithm inspired by wyhash: // https://github.com/wangyi-fudan/wyhash/blob/ceb019b530e2c1c14d70b79bfa2bc49de7d95bc1/Modern%20Non-Cryptographic%20Hash%20Function%20and%20Pseudorandom%20Number%20Generator.pdf -func memhash(p []byte, seed uint32, s uint32) uintptr { - a, b := mix32(uint32(seed), uint32(s^hashkey[0])) +func memhash(p []byte, seed uint32) uintptr { + s := len(p) + a, b := mix32(uint32(seed), uint32(s)^hashkey[0]) if s == 0 { return uintptr(a ^ b) } @@ -63,7 +91,7 @@ func memhash(p []byte, seed uint32, s uint32) uintptr { return uintptr(a ^ b) } -func add(p []byte, x uint32) []byte { +func add(p []byte, x int) []byte { return p[x:] } @@ -80,51 +108,59 @@ func mix32(a, b uint32) (uint32, uint32) { /* The following functions were modified in Go 1.17 to improve performance, but at the expense of being unsafe, and thus incompatible with GopherJS. - To compensate, we have reverted these to the unoptimized Go 1.16 versions - for now. + See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/hash/maphash/maphash.go; + To compensate, we use a simplified version of each method from Go 1.19.13, + similar to Go 1.16's versions, with the call to rthash changed to not use unsafe pointers. See upstream issue https://github.com/golang/go/issues/47342 to implement a purego version of this package, which should render this hack (and likely this entire file) obsolete. */ -// Write is borrowed from Go 1.16. +// Write is a simplification from Go 1.19 changed to not use unsafe. func (h *Hash) Write(b []byte) (int, error) { size := len(b) - for h.n+len(b) > len(h.buf) { - k := copy(h.buf[h.n:], b) - h.n = len(h.buf) - b = b[k:] - h.flush() + if h.n+len(b) > bufSize { + h.initSeed() + for h.n+len(b) > bufSize { + k := copy(h.buf[h.n:], b) + h.state.s = rthash(h.buf[:], h.state.s) + b = b[k:] + h.n = 0 + } } h.n += copy(h.buf[h.n:], b) return size, nil } -// WriteString is borrowed from Go 1.16. +// WriteString is a simplification from Go 1.19 changed to not use unsafe. func (h *Hash) WriteString(s string) (int, error) { size := len(s) - for h.n+len(s) > len(h.buf) { - k := copy(h.buf[h.n:], s) - h.n = len(h.buf) - s = s[k:] - h.flush() + if h.n+len(s) > bufSize { + h.initSeed() + for h.n+len(s) > bufSize { + k := copy(h.buf[h.n:], s) + h.state.s = rthash(h.buf[:], h.state.s) + s = s[k:] + h.n = 0 + } } h.n += copy(h.buf[h.n:], s) return size, nil } +// flush is the Go 1.19 version changed to not use unsafe. func (h *Hash) flush() { if h.n != len(h.buf) { panic("maphash: flush of partially full buffer") } h.initSeed() - h.state.s = _rthash(h.buf[:], h.state.s) + h.state.s = rthash(h.buf[:], h.state.s) h.n = 0 } -// Sum64 is borrowed from Go 1.16. +// Sum64 is the Go 1.19 version changed to not use unsafe. func (h *Hash) Sum64() uint64 { h.initSeed() - return _rthash(h.buf[:h.n], h.state.s) + return rthash(h.buf[:h.n], h.state.s) } diff --git a/compiler/natives/src/internal/reflectlite/all_test.go b/compiler/natives/src/internal/reflectlite/all_test.go index 55639d276..4445189a0 100644 --- a/compiler/natives/src/internal/reflectlite/all_test.go +++ b/compiler/natives/src/internal/reflectlite/all_test.go @@ -4,8 +4,9 @@ package reflectlite_test import ( - . "internal/reflectlite" "testing" + + . "internal/reflectlite" ) func TestTypes(t *testing.T) { @@ -20,3 +21,27 @@ func TestTypes(t *testing.T) { func TestNameBytesAreAligned(t *testing.T) { t.Skip("TestNameBytesAreAligned") } + +// `A` is used with `B[T any]` and is otherwise not needed. +// +//gopherjs:purge for go1.19 without generics +type ( + A struct{} + B[T any] struct{} +) + +// removing the name tests using `B[T any]` for go1.19 without generics +var nameTests = []nameTest{ + {(*int32)(nil), "int32"}, + {(*D1)(nil), "D1"}, + {(*[]D1)(nil), ""}, + {(*chan D1)(nil), ""}, + {(*func() D1)(nil), ""}, + {(*<-chan D1)(nil), ""}, + {(*chan<- D1)(nil), ""}, + {(*any)(nil), ""}, + {(*interface { + F() + })(nil), ""}, + {(*TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678)(nil), "TheNameOfThisTypeIsExactly255BytesLongSoWhenTheCompilerPrependsTheReflectTestPackageNameAndExtraStarTheLinkerRuntimeAndReflectPackagesWillHaveToCorrectlyDecodeTheSecondLengthByte0123456789_0123456789_0123456789_0123456789_0123456789_012345678"}, +} diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index e4c03bed5..d48f15987 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -32,9 +32,7 @@ func init() { uint8Type = TypeOf(uint8(0)).(*rtype) // set for real } -var ( - uint8Type *rtype -) +var uint8Type *rtype var ( idJsType = "_jsType" @@ -52,7 +50,7 @@ func reflectType(typ *js.Object) *rtype { rt := &rtype{ size: uintptr(typ.Get("size").Int()), kind: uint8(typ.Get("kind").Int()), - str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool())), + str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(rt).Set(idJsType, typ) typ.Set(idReflectType, js.InternalObject(rt)) @@ -71,7 +69,7 @@ func reflectType(typ *js.Object) *rtype { continue } reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported)), + name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), mtyp: newTypeOff(reflectType(m.Get("typ"))), }) } @@ -83,12 +81,12 @@ func reflectType(typ *js.Object) *rtype { continue } reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported)), + name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), mtyp: newTypeOff(reflectType(m.Get("typ"))), }) } ut := &uncommonType{ - pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false)), + pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false, false)), mcount: uint16(methodSet.Length()), xcount: xcount, _methods: reflectMethods, @@ -143,13 +141,13 @@ func reflectType(typ *js.Object) *rtype { for i := range imethods { m := methods.Index(i) imethods[i] = imethod{ - name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "")), + name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), typ: newTypeOff(reflectType(m.Get("typ"))), } } setKindType(rt, &interfaceType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false), + pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), methods: imethods, }) case Map: @@ -170,19 +168,15 @@ func reflectType(typ *js.Object) *rtype { reflectFields := make([]structField, fields.Length()) for i := range reflectFields { f := fields.Index(i) - offsetEmbed := uintptr(i) << 1 - if f.Get("embedded").Bool() { - offsetEmbed |= 1 - } reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool()), - typ: reflectType(f.Get("typ")), - offsetEmbed: offsetEmbed, + name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + typ: reflectType(f.Get("typ")), + offset: uintptr(i), } } setKindType(rt, &structType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false), + pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), fields: reflectFields, }) } @@ -244,6 +238,7 @@ type nameData struct { name string tag string exported bool + embedded bool } var nameMap = make(map[*byte]*nameData) @@ -252,13 +247,15 @@ func (n name) name() (s string) { return nameMap[n.bytes].name } func (n name) tag() (s string) { return nameMap[n.bytes].tag } func (n name) pkgPath() string { return "" } func (n name) isExported() bool { return nameMap[n.bytes].exported } +func (n name) embedded() bool { return nameMap[n.bytes].embedded } -func newName(n, tag string, exported bool) name { +func newName(n, tag string, exported, embedded bool) name { b := new(byte) nameMap[b] = &nameData{ name: n, tag: tag, exported: exported, + embedded: embedded, } return name{ bytes: b, @@ -578,7 +575,7 @@ func maplen(m unsafe.Pointer) int { } func cvtDirect(v Value, typ Type) Value { - var srcVal = v.object() + srcVal := v.object() if srcVal == jsType(v.typ).Get("nil") { return makeValue(typ, jsType(typ).Get("nil"), v.flag) } @@ -914,7 +911,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return true } } - var n = v1.Len() + n := v1.Len() if n != v2.Len() { return false } @@ -932,7 +929,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { case Ptr: return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) case Struct: - var n = v1.NumField() + n := v1.NumField() for i := 0; i < n; i++ { if !deepValueEqualJs(v1.Field(i), v2.Field(i), visited) { return false @@ -946,7 +943,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if v1.object() == v2.object() { return true } - var keys = v1.MapKeys() + keys := v1.MapKeys() if len(keys) != v2.Len() { return false } diff --git a/compiler/natives/src/internal/reflectlite/utils.go b/compiler/natives/src/internal/reflectlite/utils.go index f74182476..1941f0d0e 100644 --- a/compiler/natives/src/internal/reflectlite/utils.go +++ b/compiler/natives/src/internal/reflectlite/utils.go @@ -23,9 +23,7 @@ func (e *errorString) Error() string { return e.s } -var ( - ErrSyntax = &errorString{"invalid syntax"} -) +var ErrSyntax = &errorString{"invalid syntax"} func unquote(s string) (string, error) { if len(s) < 2 { diff --git a/compiler/natives/src/math/big/big.go b/compiler/natives/src/math/big/big.go index 9e068d5cd..25512db31 100644 --- a/compiler/natives/src/math/big/big.go +++ b/compiler/natives/src/math/big/big.go @@ -4,5 +4,6 @@ package big // TODO: This is a workaround for https://github.com/gopherjs/gopherjs/issues/652. -// Remove after that issue is resolved. +// +// Remove after that issue is resolved. type Word uintptr diff --git a/compiler/natives/src/math/math.go b/compiler/natives/src/math/math.go index d1ed66edb..b0ed2da0d 100644 --- a/compiler/natives/src/math/math.go +++ b/compiler/natives/src/math/math.go @@ -7,10 +7,12 @@ import ( "github.com/gopherjs/gopherjs/js" ) -var math = js.Global.Get("Math") -var _zero float64 = 0 -var posInf = 1 / _zero -var negInf = -1 / _zero +var ( + math = js.Global.Get("Math") + _zero float64 = 0 + posInf = 1 / _zero + negInf = -1 / _zero +) // Usually, NaN can be obtained in JavaScript with `0 / 0` operation. However, // in V8, `0 / _zero` yields a bitwise-different value of NaN compared to the diff --git a/compiler/natives/src/net/fastrand.go b/compiler/natives/src/net/fastrand.go index 861217a9d..8feafc78f 100644 --- a/compiler/natives/src/net/fastrand.go +++ b/compiler/natives/src/net/fastrand.go @@ -7,5 +7,5 @@ import ( _ "unsafe" // For go:linkname ) -//go:linkname fastrand runtime.fastrand -func fastrand() uint32 +//go:linkname fastrandu runtime.fastrandu +func fastrandu() uint diff --git a/compiler/natives/src/net/http/http.go b/compiler/natives/src/net/http/http.go index 7843235b2..8fd607c4d 100644 --- a/compiler/natives/src/net/http/http.go +++ b/compiler/natives/src/net/http/http.go @@ -7,7 +7,7 @@ import ( "bufio" "bytes" "errors" - "io/ioutil" + "io" "net/textproto" "strconv" @@ -68,7 +68,7 @@ func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { StatusCode: xhr.Get("status").Int(), Header: Header(header), ContentLength: contentLength, - Body: ioutil.NopCloser(bytes.NewReader(body)), + Body: io.NopCloser(bytes.NewReader(body)), Request: req, } }) @@ -91,7 +91,7 @@ func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { if req.Body == nil { xhr.Call("send") } else { - body, err := ioutil.ReadAll(req.Body) + body, err := io.ReadAll(req.Body) if err != nil { req.Body.Close() // RoundTrip must always close the body, including on errors. return nil, err diff --git a/compiler/natives/src/net/netip/export_test.go b/compiler/natives/src/net/netip/export_test.go index b8748228f..03b7cbe1b 100644 --- a/compiler/natives/src/net/netip/export_test.go +++ b/compiler/natives/src/net/netip/export_test.go @@ -1,13 +1,14 @@ //go:build js +// +build js package netip import ( "fmt" + "internal/intern" ) -//gopherjs:prune-original func MkAddr(u Uint128, z any) Addr { switch z := z.(type) { case *intern.Value: diff --git a/compiler/natives/src/net/netip/fuzz_test.go b/compiler/natives/src/net/netip/fuzz_test.go index 574201d2d..f7359c5bb 100644 --- a/compiler/natives/src/net/netip/fuzz_test.go +++ b/compiler/natives/src/net/netip/fuzz_test.go @@ -1,4 +1,5 @@ //go:build js +// +build js package netip_test diff --git a/compiler/natives/src/net/netip/netip.go b/compiler/natives/src/net/netip/netip.go index 92a61900a..9d2b8b2d6 100644 --- a/compiler/natives/src/net/netip/netip.go +++ b/compiler/natives/src/net/netip/netip.go @@ -1,4 +1,5 @@ //go:build js +// +build js package netip @@ -17,7 +18,6 @@ var ( z6noz = "\x00ipv6noz" ) -//gopherjs:prune-original func (ip Addr) Zone() string { if ip.z == z4 || ip.z == z6noz { return "" @@ -25,7 +25,6 @@ func (ip Addr) Zone() string { return ip.z } -//gopherjs:prune-original func (ip Addr) WithZone(zone string) Addr { if !ip.Is6() { return ip diff --git a/compiler/natives/src/net/netip/netip_test.go b/compiler/natives/src/net/netip/netip_test.go new file mode 100644 index 000000000..46b116c00 --- /dev/null +++ b/compiler/natives/src/net/netip/netip_test.go @@ -0,0 +1,10 @@ +//go:build js +// +build js + +package netip_test + +import "testing" + +func TestAddrStringAllocs(t *testing.T) { + t.Skip("testing.AllocsPerRun not supported in GopherJS") +} diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index 7ed8b5f06..47b93662e 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -63,7 +63,7 @@ func reflectType(typ *js.Object) *rtype { rt := &rtype{ size: uintptr(typ.Get("size").Int()), kind: uint8(typ.Get("kind").Int()), - str: resolveReflectName(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool())), + str: resolveReflectName(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), } js.InternalObject(rt).Set("jsType", typ) typ.Set("reflectType", js.InternalObject(rt)) @@ -99,7 +99,7 @@ func reflectType(typ *js.Object) *rtype { }) } ut := &uncommonType{ - pkgPath: resolveReflectName(newName(internalStr(typ.Get("pkg")), "", false)), + pkgPath: resolveReflectName(newName(internalStr(typ.Get("pkg")), "", false, false)), mcount: uint16(methodSet.Length()), xcount: xcount, _methods: reflectMethods, @@ -160,7 +160,7 @@ func reflectType(typ *js.Object) *rtype { } setKindType(rt, &interfaceType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false), + pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), methods: imethods, }) case Map: @@ -181,19 +181,15 @@ func reflectType(typ *js.Object) *rtype { reflectFields := make([]structField, fields.Length()) for i := range reflectFields { f := fields.Index(i) - offsetEmbed := uintptr(i) << 1 - if f.Get("embedded").Bool() { - offsetEmbed |= 1 - } reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool()), - typ: reflectType(f.Get("typ")), - offsetEmbed: offsetEmbed, + name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + typ: reflectType(f.Get("typ")), + offset: uintptr(i), } } setKindType(rt, &structType{ rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false), + pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), fields: reflectFields, }) } @@ -257,6 +253,7 @@ type nameData struct { name string tag string exported bool + embedded bool pkgPath string } @@ -266,13 +263,18 @@ func (n name) name() (s string) { return nameMap[n.bytes].name } func (n name) tag() (s string) { return nameMap[n.bytes].tag } func (n name) pkgPath() string { return nameMap[n.bytes].pkgPath } func (n name) isExported() bool { return nameMap[n.bytes].exported } +func (n name) embedded() bool { return nameMap[n.bytes].embedded } +func (n name) setPkgPath(pkgpath string) { + nameMap[n.bytes].pkgPath = pkgpath +} -func newName(n, tag string, exported bool) name { +func newName(n, tag string, exported, embedded bool) name { b := new(byte) nameMap[b] = &nameData{ name: n, tag: tag, exported: exported, + embedded: embedded, } return name{ bytes: b, @@ -1177,6 +1179,11 @@ func (v Value) Cap() int { return v.typ.Len() case Chan, Slice: return v.object().Get("$capacity").Int() + case Ptr: + if v.typ.Elem().Kind() == Array { + return v.typ.Elem().Len() + } + panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") } panic(&ValueError{"reflect.Value.Cap", k}) } @@ -1403,6 +1410,11 @@ func (v Value) Len() int { return v.object().Get("$buffer").Get("length").Int() case Map: return v.object().Get("size").Int() + case Ptr: + if v.typ.Elem().Kind() == Array { + return v.typ.Elem().Len() + } + panic("reflect: call of reflect.Value.Len on ptr to non-array Value") default: panic(&ValueError{"reflect.Value.Len", k}) } @@ -1448,6 +1460,29 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } +func (v Value) bytesSlow() []byte { + switch v.kind() { + case Slice: + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.Bytes of non-byte slice") + } + return *(*[]byte)(v.ptr) + case Array: + if v.typ.Elem().Kind() != Uint8 { + panic("reflect.Value.Bytes of non-byte array") + } + if !v.CanAddr() { + panic("reflect.Value.Bytes of unaddressable byte array") + } + // Replace the following with JS to avoid using unsafe pointers. + // p := (*byte)(v.ptr) + // n := int((*arrayType)(unsafe.Pointer(v.typ)).len) + // return unsafe.Slice(p, n) + return js.InternalObject(v.ptr).Interface().([]byte) + } + panic(&ValueError{"reflect.Value.Bytes", v.kind()}) +} + func (v Value) SetBytes(x []byte) { v.mustBeAssignable() v.mustBe(Slice) @@ -1726,29 +1761,47 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1, false)), js.InternalObject(valueInterface(v2, false))).Bool() } -func methodNameSkip() string { - pc, _, _, _ := runtime.Caller(3) - f := runtime.FuncForPC(pc) - if f == nil { - return "unknown method" - } - // 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". - // 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] == '.' { - break +func stringsLastIndex(s string, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i } - idx-- } - if idx < 0 { - return name + return -1 +} + +func stringsHasPrefix(s, prefix string) bool { + return len(s) >= len(prefix) && s[:len(prefix)] == prefix +} + +func valueMethodName() string { + var pc [5]uintptr + n := runtime.Callers(1, pc[:]) + frames := runtime.CallersFrames(pc[:n]) + var frame runtime.Frame + for more := true; more; { + frame, more = frames.Next() + name := frame.Function + + // Function name extracted from the call stack can be different from + // vanilla Go, so is not prefixed by "reflect.Value." as needed by the original. + // See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/reflect/value.go;l=173-191 + // Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey" + // into "reflect.Value.SetIterKey". + // This workaround may become obsolete after + // https://github.com/gopherjs/gopherjs/issues/1085 is resolved. + + const prefix = `Object.$packages.reflect.` + if stringsHasPrefix(name, prefix) { + if idx := stringsLastIndex(name, '.'); idx >= 0 { + methodName := name[idx+1:] + if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' { + return `reflect.Value.` + methodName + } + } + } } - return "Value" + name[idx:] + return "unknown method" } func verifyNotInHeapPtr(p uintptr) bool { diff --git a/compiler/natives/src/reflect/reflect_test.go b/compiler/natives/src/reflect/reflect_test.go index 0ba5f29d5..79bbe5385 100644 --- a/compiler/natives/src/reflect/reflect_test.go +++ b/compiler/natives/src/reflect/reflect_test.go @@ -157,7 +157,7 @@ func TestIssue22073(t *testing.T) { // TypeError: Cannot read property 'apply' of undefined // Shouldn't panic. - //m.Call(nil) + // m.Call(nil) } func TestCallReturnsEmpty(t *testing.T) { @@ -285,9 +285,16 @@ func TestMethodCallValueCodePtr(t *testing.T) { t.Skip("methodValueCallCodePtr() is not applicable in GopherJS") } -type B struct{} +//gopherjs:purge for go1.19 without generics +type ( + A struct{} + B[T any] struct{} +) -//gopherjs:prune-original func TestIssue50208(t *testing.T) { t.Skip("This test required generics, which are not yet supported: https://github.com/gopherjs/gopherjs/issues/1013") } + +func TestStructOfTooLarge(t *testing.T) { + t.Skip("This test is dependent on field alignment to determine if a struct size would exceed virtual address space.") +} diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index d5fa9211d..3a2d58d32 100644 --- a/compiler/natives/src/regexp/regexp_test.go +++ b/compiler/natives/src/regexp/regexp_test.go @@ -7,6 +7,14 @@ import ( "testing" ) +//gopherjs:keep-original func TestOnePassCutoff(t *testing.T) { - t.Skip() // "Maximum call stack size exceeded" on V8 + defer func() { + if r := recover(); r != nil { + t.Log(r) + t.Skip("'Maximum call stack size exceeded' may happen on V8, skipping") + } + }() + + _gopherjs_original_TestOnePassCutoff(t) } diff --git a/compiler/natives/src/runtime/fastrand.go b/compiler/natives/src/runtime/fastrand.go index 8f6ab6292..a5f2bdbb8 100644 --- a/compiler/natives/src/runtime/fastrand.go +++ b/compiler/natives/src/runtime/fastrand.go @@ -13,3 +13,15 @@ func fastrand() uint32 { // similar distribution. return uint32(js.Global.Get("Math").Call("random").Float() * (1<<32 - 1)) } + +func fastrandn(n uint32) uint32 { + return fastrand() % n +} + +func fastrand64() uint64 { + return uint64(fastrand())<<32 | uint64(fastrand()) +} + +func fastrandu() uint { + return uint(fastrand()) +} diff --git a/compiler/natives/src/runtime/runtime.go b/compiler/natives/src/runtime/runtime.go index bcea9ad8e..41c60876c 100644 --- a/compiler/natives/src/runtime/runtime.go +++ b/compiler/natives/src/runtime/runtime.go @@ -7,9 +7,11 @@ import ( "github.com/gopherjs/gopherjs/js" ) -const GOOS = "js" -const GOARCH = "ecmascript" -const Compiler = "gopherjs" +const ( + GOOS = "js" + GOARCH = "ecmascript" + Compiler = "gopherjs" +) // The Error interface identifies a run time error. type Error interface { @@ -487,5 +489,6 @@ func throw(s string) { } func nanotime() int64 { - return js.Global.Get("Date").New().Call("getTime").Int64() * int64(1000_000) + const millisecond = 1_000_000 + return js.Global.Get("Date").New().Call("getTime").Int64() * millisecond } diff --git a/compiler/natives/src/sync/atomic/atomic.go b/compiler/natives/src/sync/atomic/atomic.go index ebc98e910..1cbfe65f9 100644 --- a/compiler/natives/src/sync/atomic/atomic.go +++ b/compiler/natives/src/sync/atomic/atomic.go @@ -220,3 +220,6 @@ func sameType(x, y interface{}) bool { // existing and differing for different types. return js.InternalObject(x).Get("constructor") == js.InternalObject(y).Get("constructor") } + +//gopherjs:purge for go1.19 without generics +type Pointer[T any] struct{} diff --git a/compiler/natives/src/sync/atomic/atomic_test.go b/compiler/natives/src/sync/atomic/atomic_test.go index f4450cc67..e1ec6086c 100644 --- a/compiler/natives/src/sync/atomic/atomic_test.go +++ b/compiler/natives/src/sync/atomic/atomic_test.go @@ -3,7 +3,51 @@ package atomic_test -import "testing" +import ( + "testing" + "unsafe" +) + +//gopherjs:purge for go1.19 without generics +func testPointers() []unsafe.Pointer {} + +func TestSwapPointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestSwapPointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestCompareAndSwapPointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestCompareAndSwapPointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestLoadPointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestLoadPointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestStorePointer(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +func TestStorePointerMethod(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +//gopherjs:purge for go1.19 without generics +func hammerStoreLoadPointer(t *testing.T, paddr unsafe.Pointer) {} + +//gopherjs:purge for go1.19 without generics +func hammerStoreLoadPointerMethod(t *testing.T, paddr unsafe.Pointer) {} func TestHammerStoreLoad(t *testing.T) { t.Skip("use of unsafe") @@ -12,3 +56,22 @@ func TestHammerStoreLoad(t *testing.T) { func TestUnaligned64(t *testing.T) { t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") } + +func TestAutoAligned64(t *testing.T) { + t.Skip("GopherJS emulates atomics, which makes alignment irrelevant.") +} + +func TestNilDeref(t *testing.T) { + t.Skip("GopherJS does not support generics yet.") +} + +//gopherjs:purge for go1.19 without generics +type List struct{} + +func TestHammer32(t *testing.T) { + t.Skip("use of unsafe") +} + +func TestHammer64(t *testing.T) { + t.Skip("use of unsafe") +} diff --git a/compiler/natives/src/sync/pool_test.go b/compiler/natives/src/sync/pool_test.go index 218852658..ea35fd136 100644 --- a/compiler/natives/src/sync/pool_test.go +++ b/compiler/natives/src/sync/pool_test.go @@ -24,7 +24,6 @@ func TestPool(t *testing.T) { t.Fatalf("Got: p.Get() returned: %s. Want: %s.", got, want) } } - } func TestPoolGC(t *testing.T) { diff --git a/compiler/natives/src/sync/sync.go b/compiler/natives/src/sync/sync.go index 588537751..294b0b109 100644 --- a/compiler/natives/src/sync/sync.go +++ b/compiler/natives/src/sync/sync.go @@ -3,7 +3,11 @@ package sync -import "github.com/gopherjs/gopherjs/js" +import ( + _ "unsafe" // For go:linkname + + "github.com/gopherjs/gopherjs/js" +) var semWaiters = make(map[*uint32][]chan bool) @@ -69,11 +73,8 @@ func runtime_canSpin(i int) bool { return false } -// Copy of time.runtimeNano. -func runtime_nanotime() int64 { - const millisecond = 1000000 - return js.Global.Get("Date").New().Call("getTime").Int64() * millisecond -} +//go:linkname runtime_nanotime runtime.nanotime +func runtime_nanotime() int64 // Implemented in runtime. func throw(s string) { diff --git a/compiler/natives/src/syscall/js/export_test.go b/compiler/natives/src/syscall/js/export_test.go index 25f6f6833..8f030c4d7 100644 --- a/compiler/natives/src/syscall/js/export_test.go +++ b/compiler/natives/src/syscall/js/export_test.go @@ -4,6 +4,5 @@ package js // Defined to avoid a compile error in the original TestGarbageCollection() -// body. Can't use gopherjs:prune-original on it, since it causes an unused -// import error. +// body. var JSGo Value diff --git a/compiler/natives/src/syscall/js/js_test.go b/compiler/natives/src/syscall/js/js_test.go index c95c2e764..999266da2 100644 --- a/compiler/natives/src/syscall/js/js_test.go +++ b/compiler/natives/src/syscall/js/js_test.go @@ -5,7 +5,6 @@ package js_test import "testing" -//gopherjs:prune-original func TestIntConversion(t *testing.T) { // Same as upstream, but only test cases appropriate for a 32-bit environment. testIntConversion(t, 0) diff --git a/compiler/natives/src/syscall/legacy.go b/compiler/natives/src/syscall/legacy.go index 239ba388c..beb99eb78 100644 --- a/compiler/natives/src/syscall/legacy.go +++ b/compiler/natives/src/syscall/legacy.go @@ -6,9 +6,11 @@ import ( "github.com/gopherjs/gopherjs/js" ) -var syscallModule *js.Object -var alreadyTriedToLoad = false -var minusOne = -1 +var ( + syscallModule *js.Object + alreadyTriedToLoad = false + minusOne = -1 +) var warningPrinted = false diff --git a/compiler/natives/src/testing/helper_test.go b/compiler/natives/src/testing/helper_test.go index b277fa31f..6815fd651 100644 --- a/compiler/natives/src/testing/helper_test.go +++ b/compiler/natives/src/testing/helper_test.go @@ -2,3 +2,7 @@ // +build js package testing + +func TestTBHelper(t *T) { + t.Skip("GopherJS does not support generics yet.") +} diff --git a/compiler/natives/src/testing/helperfuncs_test.go b/compiler/natives/src/testing/helperfuncs_test.go new file mode 100644 index 000000000..54a1ee737 --- /dev/null +++ b/compiler/natives/src/testing/helperfuncs_test.go @@ -0,0 +1,13 @@ +//go:build js +// +build js + +package testing + +//gopherjs:purge for go1.19 without generics +func genericHelper[G any](t *T, msg string) + +//gopherjs:purge for go1.19 without generics +var genericIntHelper = genericHelper[int] + +//gopherjs:purge for go1.19 without generics (uses genericHelper) +func testHelper(t *T) diff --git a/compiler/package.go b/compiler/package.go index 93c22f1c5..ad918ba3e 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -131,6 +131,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } if fe, ok := bailingOut(e); ok { // Orderly bailout, return whatever clues we already have. + fmt.Fprintf(fe, `building package %q`, importPath) err = fe return } @@ -269,7 +270,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } sort.Strings(importedPaths) for _, impPath := range importedPaths { - id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignature(nil, nil, nil, false)) + id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignatureType(nil, nil, nil, nil, nil, false)) call := &ast.CallExpr{Fun: id} funcCtx.Blocking[call] = true funcCtx.Flattened[call] = true @@ -287,13 +288,6 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor 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 } @@ -421,7 +415,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor d.DceObjectFilter = "" case "init": d.InitCode = funcCtx.CatchOutput(1, func() { - id := funcCtx.newIdent("", types.NewSignature(nil, nil, nil, false)) + id := funcCtx.newIdent("", types.NewSignatureType(nil, nil, nil, nil, nil, false)) funcCtx.pkgCtx.Uses[id] = o call := &ast.CallExpr{Fun: id} if len(funcCtx.pkgCtx.FuncDeclInfos[o].Blocking) != 0 { @@ -438,7 +432,14 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor if isPointer { namedRecvType = ptr.Elem().(*types.Named) } - d.NamedRecvType = funcCtx.objectName(namedRecvType.Obj()) + if namedRecvType.TypeParams() != nil { + 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.FullName()), + } + } + name := funcCtx.objectName(namedRecvType.Obj()) + d.NamedRecvType = name d.DceObjectFilter = namedRecvType.Obj().Name() if !fun.Name.IsExported() { d.DceMethodFilter = o.Name() + "~" @@ -454,7 +455,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor if mainFunc == nil { return nil, fmt.Errorf("missing main function") } - id := funcCtx.newIdent("", types.NewSignature(nil, nil, nil, false)) + id := funcCtx.newIdent("", types.NewSignatureType(nil, nil, nil, nil, nil, false)) funcCtx.pkgCtx.Uses[id] = mainFunc call := &ast.CallExpr{Fun: id} ifStmt := &ast.IfStmt{ @@ -636,9 +637,9 @@ func (fc *funcContext) initArgs(ty types.Type) string { case *types.Map: return fmt.Sprintf("%s, %s", fc.typeName(t.Key()), fc.typeName(t.Elem())) case *types.Pointer: - return fmt.Sprintf("%s", fc.typeName(t.Elem())) + return fc.typeName(t.Elem()) case *types.Slice: - return fmt.Sprintf("%s", fc.typeName(t.Elem())) + return fc.typeName(t.Elem()) case *types.Signature: params := make([]string, t.Params().Len()) for i := range params { @@ -660,6 +661,9 @@ func (fc *funcContext) initArgs(ty types.Type) string { fields[i] = fmt.Sprintf(`{prop: "%s", name: %s, embedded: %t, exported: %t, typ: %s, tag: %s}`, fieldName(t, i), encodeString(field.Name()), field.Anonymous(), field.Exported(), fc.typeName(field.Type()), encodeString(t.Tag(i))) } return fmt.Sprintf(`"%s", [%s]`, pkgPath, strings.Join(fields, ", ")) + case *types.TypeParam: + err := bailout(fmt.Errorf(`%v has unexpected generic type parameter %T`, ty, ty)) + panic(err) default: err := bailout(fmt.Errorf("%v has unexpected type %T", ty, ty)) panic(err) diff --git a/compiler/prelude/genmin.go b/compiler/prelude/genmin.go deleted file mode 100644 index 6e46e058e..000000000 --- a/compiler/prelude/genmin.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build ignore -// +build ignore - -package main - -import ( - "bytes" - "fmt" - "go/build" - "io/ioutil" - "log" - "os/exec" - "path/filepath" - "strings" - - "github.com/gopherjs/gopherjs/compiler/prelude" -) - -func main() { - if err := run(); err != nil { - log.Fatalln(err) - } -} - -func run() error { - bpkg, err := build.Import("github.com/gopherjs/gopherjs", "", build.FindOnly) - if err != nil { - return fmt.Errorf("failed to locate path for github.com/gopherjs/gopherjs: %v", err) - } - - preludeDir := filepath.Join(bpkg.Dir, "compiler", "prelude") - - args := []string{ - filepath.Join(bpkg.Dir, "node_modules", ".bin", "uglifyjs"), - "--config-file", - filepath.Join(preludeDir, "uglifyjs_options.json"), - } - - stderr := new(bytes.Buffer) - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdin = strings.NewReader(prelude.Prelude) - cmd.Stderr = stderr - - out, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to run %v: %v\n%s", strings.Join(args, " "), err, stderr.String()) - } - - fn := "prelude_min.go" - - outStr := fmt.Sprintf(`// Code generated by genmin; DO NOT EDIT. - -package prelude - -// Minified is an uglifyjs-minified version of Prelude. -const Minified = %q -`, out) - - if err := ioutil.WriteFile(fn, []byte(outStr), 0644); err != nil { - return fmt.Errorf("failed to write to %v: %v", fn, err) - } - - return nil -} diff --git a/compiler/prelude/goroutines.go b/compiler/prelude/goroutines.go deleted file mode 100644 index 6478aba7a..000000000 --- a/compiler/prelude/goroutines.go +++ /dev/null @@ -1,393 +0,0 @@ -package prelude - -const goroutines = ` -var $stackDepthOffset = 0; -var $getStackDepth = function() { - var err = new Error(); - if (err.stack === undefined) { - return undefined; - } - return $stackDepthOffset + err.stack.split("\n").length; -}; - -var $panicStackDepth = null, $panicValue; -var $callDeferred = function(deferred, jsErr, fromPanic) { - if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) { - throw jsErr; - } - if (jsErr !== null) { - var newErr = null; - try { - $panic(new $jsErrorPtr(jsErr)); - } catch (err) { - newErr = err; - } - $callDeferred(deferred, newErr); - return; - } - if ($curGoroutine.asleep) { - return; - } - - $stackDepthOffset--; - var outerPanicStackDepth = $panicStackDepth; - var outerPanicValue = $panicValue; - - var localPanicValue = $curGoroutine.panicStack.pop(); - if (localPanicValue !== undefined) { - $panicStackDepth = $getStackDepth(); - $panicValue = localPanicValue; - } - - try { - while (true) { - if (deferred === null) { - deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1]; - if (deferred === undefined) { - /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */ - $panicStackDepth = null; - if (localPanicValue.Object instanceof Error) { - throw localPanicValue.Object; - } - var msg; - if (localPanicValue.constructor === $String) { - msg = localPanicValue.$val; - } else if (localPanicValue.Error !== undefined) { - msg = localPanicValue.Error(); - } else if (localPanicValue.String !== undefined) { - msg = localPanicValue.String(); - } else { - msg = localPanicValue; - } - throw new Error(msg); - } - } - var call = deferred.pop(); - if (call === undefined) { - $curGoroutine.deferStack.pop(); - if (localPanicValue !== undefined) { - deferred = null; - continue; - } - return; - } - var r = call[0].apply(call[2], call[1]); - if (r && r.$blk !== undefined) { - deferred.push([r.$blk, [], r]); - if (fromPanic) { - throw null; - } - return; - } - - if (localPanicValue !== undefined && $panicStackDepth === null) { - /* error was recovered */ - if (fromPanic) { - throw null; - } - return; - } - } - } catch(e) { - // Deferred function threw a JavaScript exception or tries to unwind stack - // to the point where a panic was handled. - if (fromPanic) { - // Re-throw the exception to reach deferral execution call at the end - // of the function. - throw e; - } - // We are at the end of the function, handle the error or re-throw to - // continue unwinding if necessary, or simply stop unwinding if we got far - // enough. - $callDeferred(deferred, e, fromPanic); - } finally { - if (localPanicValue !== undefined) { - if ($panicStackDepth !== null) { - $curGoroutine.panicStack.push(localPanicValue); - } - $panicStackDepth = outerPanicStackDepth; - $panicValue = outerPanicValue; - } - $stackDepthOffset++; - } -}; - -var $panic = function(value) { - $curGoroutine.panicStack.push(value); - $callDeferred(null, null, true); -}; -var $recover = function() { - if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) { - return $ifaceNil; - } - $panicStackDepth = null; - return $panicValue; -}; -var $throw = function(err) { throw err; }; - -var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] }; -var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0; -var $mainFinished = false; -var $go = function(fun, args) { - $totalGoroutines++; - $awakeGoroutines++; - var $goroutine = function() { - try { - $curGoroutine = $goroutine; - var r = fun.apply(undefined, args); - if (r && r.$blk !== undefined) { - fun = function() { return r.$blk(); }; - args = []; - return; - } - $goroutine.exit = true; - } catch (err) { - if (!$goroutine.exit) { - throw err; - } - } finally { - $curGoroutine = $noGoroutine; - if ($goroutine.exit) { /* also set by runtime.Goexit() */ - $totalGoroutines--; - $goroutine.asleep = true; - } - if ($goroutine.asleep) { - $awakeGoroutines--; - if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock && $exportedFunctions === 0) { - console.error("fatal error: all goroutines are asleep - deadlock!"); - if ($global.process !== undefined) { - $global.process.exit(2); - } - } - } - } - }; - $goroutine.asleep = false; - $goroutine.exit = false; - $goroutine.deferStack = []; - $goroutine.panicStack = []; - $schedule($goroutine); -}; - -var $scheduled = []; -var $runScheduled = function() { - // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize - // the effect of this penalty by queueing the timer preemptively before we run - // the goroutines, and later cancelling it if it turns out unneeded. See: - // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#nested_timeouts - var nextRun = setTimeout($runScheduled); - try { - var start = Date.now(); - var r; - while ((r = $scheduled.shift()) !== undefined) { - r(); - // We need to interrupt this loop in order to allow the event loop to - // process timers, IO, etc. However, invoking scheduling through - // setTimeout is ~1000 times more expensive, so we amortize this cost by - // looping until the 4ms minimal delay has elapsed (assuming there are - // scheduled goroutines to run), and then yield to the event loop. - var elapsed = Date.now() - start; - if (elapsed > 4 || elapsed < 0) { break; } - } - } finally { - if ($scheduled.length == 0) { - // Cancel scheduling pass if there's nothing to run. - clearTimeout(nextRun); - } - } -}; - -var $schedule = function(goroutine) { - if (goroutine.asleep) { - goroutine.asleep = false; - $awakeGoroutines++; - } - $scheduled.push(goroutine); - if ($curGoroutine === $noGoroutine) { - $runScheduled(); - } -}; - -var $setTimeout = function(f, t) { - $awakeGoroutines++; - return setTimeout(function() { - $awakeGoroutines--; - f(); - }, t); -}; - -var $block = function() { - if ($curGoroutine === $noGoroutine) { - $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"); - } - $curGoroutine.asleep = true; -}; - -var $restore = function(context, params) { - if (context !== undefined && context.$blk !== undefined) { - return context; - } - return params; -} - -var $send = function(chan, value) { - if (chan.$closed) { - $throwRuntimeError("send on closed channel"); - } - var queuedRecv = chan.$recvQueue.shift(); - if (queuedRecv !== undefined) { - queuedRecv([value, true]); - return; - } - if (chan.$buffer.length < chan.$capacity) { - chan.$buffer.push(value); - return; - } - - var thisGoroutine = $curGoroutine; - var closedDuringSend; - chan.$sendQueue.push(function(closed) { - closedDuringSend = closed; - $schedule(thisGoroutine); - return value; - }); - $block(); - return { - $blk: function() { - if (closedDuringSend) { - $throwRuntimeError("send on closed channel"); - } - } - }; -}; -var $recv = function(chan) { - var queuedSend = chan.$sendQueue.shift(); - if (queuedSend !== undefined) { - chan.$buffer.push(queuedSend(false)); - } - var bufferedValue = chan.$buffer.shift(); - if (bufferedValue !== undefined) { - return [bufferedValue, true]; - } - if (chan.$closed) { - return [chan.$elem.zero(), false]; - } - - var thisGoroutine = $curGoroutine; - var f = { $blk: function() { return this.value; } }; - var queueEntry = function(v) { - f.value = v; - $schedule(thisGoroutine); - }; - chan.$recvQueue.push(queueEntry); - $block(); - return f; -}; -var $close = function(chan) { - if (chan.$closed) { - $throwRuntimeError("close of closed channel"); - } - chan.$closed = true; - while (true) { - var queuedSend = chan.$sendQueue.shift(); - if (queuedSend === undefined) { - break; - } - queuedSend(true); /* will panic */ - } - while (true) { - var queuedRecv = chan.$recvQueue.shift(); - if (queuedRecv === undefined) { - break; - } - queuedRecv([chan.$elem.zero(), false]); - } -}; -var $select = function(comms) { - var ready = []; - var selection = -1; - for (var i = 0; i < comms.length; i++) { - var comm = comms[i]; - var chan = comm[0]; - switch (comm.length) { - case 0: /* default */ - selection = i; - break; - case 1: /* recv */ - if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) { - ready.push(i); - } - break; - case 2: /* send */ - if (chan.$closed) { - $throwRuntimeError("send on closed channel"); - } - if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) { - ready.push(i); - } - break; - } - } - - if (ready.length !== 0) { - selection = ready[Math.floor(Math.random() * ready.length)]; - } - if (selection !== -1) { - var comm = comms[selection]; - switch (comm.length) { - case 0: /* default */ - return [selection]; - case 1: /* recv */ - return [selection, $recv(comm[0])]; - case 2: /* send */ - $send(comm[0], comm[1]); - return [selection]; - } - } - - var entries = []; - var thisGoroutine = $curGoroutine; - var f = { $blk: function() { return this.selection; } }; - var removeFromQueues = function() { - for (var i = 0; i < entries.length; i++) { - var entry = entries[i]; - var queue = entry[0]; - var index = queue.indexOf(entry[1]); - if (index !== -1) { - queue.splice(index, 1); - } - } - }; - for (var i = 0; i < comms.length; i++) { - (function(i) { - var comm = comms[i]; - switch (comm.length) { - case 1: /* recv */ - var queueEntry = function(value) { - f.selection = [i, value]; - removeFromQueues(); - $schedule(thisGoroutine); - }; - entries.push([comm[0].$recvQueue, queueEntry]); - comm[0].$recvQueue.push(queueEntry); - break; - case 2: /* send */ - var queueEntry = function() { - if (comm[0].$closed) { - $throwRuntimeError("send on closed channel"); - } - f.selection = [i]; - removeFromQueues(); - $schedule(thisGoroutine); - return comm[1]; - }; - entries.push([comm[0].$sendQueue, queueEntry]); - comm[0].$sendQueue.push(queueEntry); - break; - } - })(i); - } - $block(); - return f; -}; -` diff --git a/compiler/prelude/goroutines.js b/compiler/prelude/goroutines.js new file mode 100644 index 000000000..65dabe36e --- /dev/null +++ b/compiler/prelude/goroutines.js @@ -0,0 +1,389 @@ +var $stackDepthOffset = 0; +var $getStackDepth = () => { + var err = new Error(); + if (err.stack === undefined) { + return undefined; + } + return $stackDepthOffset + err.stack.split("\n").length; +}; + +var $panicStackDepth = null, $panicValue; +var $callDeferred = (deferred, jsErr, fromPanic) => { + if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) { + throw jsErr; + } + if (jsErr !== null) { + var newErr = null; + try { + $panic(new $jsErrorPtr(jsErr)); + } catch (err) { + newErr = err; + } + $callDeferred(deferred, newErr); + return; + } + if ($curGoroutine.asleep) { + return; + } + + $stackDepthOffset--; + var outerPanicStackDepth = $panicStackDepth; + var outerPanicValue = $panicValue; + + var localPanicValue = $curGoroutine.panicStack.pop(); + if (localPanicValue !== undefined) { + $panicStackDepth = $getStackDepth(); + $panicValue = localPanicValue; + } + + try { + while (true) { + if (deferred === null) { + deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1]; + if (deferred === undefined) { + /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */ + $panicStackDepth = null; + if (localPanicValue.Object instanceof Error) { + throw localPanicValue.Object; + } + var msg; + if (localPanicValue.constructor === $String) { + msg = localPanicValue.$val; + } else if (localPanicValue.Error !== undefined) { + msg = localPanicValue.Error(); + } else if (localPanicValue.String !== undefined) { + msg = localPanicValue.String(); + } else { + msg = localPanicValue; + } + throw new Error(msg); + } + } + var call = deferred.pop(); + if (call === undefined) { + $curGoroutine.deferStack.pop(); + if (localPanicValue !== undefined) { + deferred = null; + continue; + } + return; + } + var r = call[0].apply(call[2], call[1]); + if (r && r.$blk !== undefined) { + deferred.push([r.$blk, [], r]); + if (fromPanic) { + throw null; + } + return; + } + + if (localPanicValue !== undefined && $panicStackDepth === null) { + /* error was recovered */ + if (fromPanic) { + throw null; + } + return; + } + } + } catch (e) { + // Deferred function threw a JavaScript exception or tries to unwind stack + // to the point where a panic was handled. + if (fromPanic) { + // Re-throw the exception to reach deferral execution call at the end + // of the function. + throw e; + } + // We are at the end of the function, handle the error or re-throw to + // continue unwinding if necessary, or simply stop unwinding if we got far + // enough. + $callDeferred(deferred, e, fromPanic); + } finally { + if (localPanicValue !== undefined) { + if ($panicStackDepth !== null) { + $curGoroutine.panicStack.push(localPanicValue); + } + $panicStackDepth = outerPanicStackDepth; + $panicValue = outerPanicValue; + } + $stackDepthOffset++; + } +}; + +var $panic = value => { + $curGoroutine.panicStack.push(value); + $callDeferred(null, null, true); +}; +var $recover = () => { + if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) { + return $ifaceNil; + } + $panicStackDepth = null; + return $panicValue; +}; +var $throw = err => { throw err; }; + +var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] }; +var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0; +var $mainFinished = false; +var $go = (fun, args) => { + $totalGoroutines++; + $awakeGoroutines++; + var $goroutine = () => { + try { + $curGoroutine = $goroutine; + var r = fun(...args); + if (r && r.$blk !== undefined) { + fun = () => { return r.$blk(); }; + args = []; + return; + } + $goroutine.exit = true; + } catch (err) { + if (!$goroutine.exit) { + throw err; + } + } finally { + $curGoroutine = $noGoroutine; + if ($goroutine.exit) { /* also set by runtime.Goexit() */ + $totalGoroutines--; + $goroutine.asleep = true; + } + if ($goroutine.asleep) { + $awakeGoroutines--; + if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock && $exportedFunctions === 0) { + console.error("fatal error: all goroutines are asleep - deadlock!"); + if ($global.process !== undefined) { + $global.process.exit(2); + } + } + } + } + }; + $goroutine.asleep = false; + $goroutine.exit = false; + $goroutine.deferStack = []; + $goroutine.panicStack = []; + $schedule($goroutine); +}; + +var $scheduled = []; +var $runScheduled = () => { + // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize + // the effect of this penalty by queueing the timer preemptively before we run + // the goroutines, and later cancelling it if it turns out unneeded. See: + // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#nested_timeouts + var nextRun = setTimeout($runScheduled); + try { + var start = Date.now(); + var r; + while ((r = $scheduled.shift()) !== undefined) { + r(); + // We need to interrupt this loop in order to allow the event loop to + // process timers, IO, etc. However, invoking scheduling through + // setTimeout is ~1000 times more expensive, so we amortize this cost by + // looping until the 4ms minimal delay has elapsed (assuming there are + // scheduled goroutines to run), and then yield to the event loop. + var elapsed = Date.now() - start; + if (elapsed > 4 || elapsed < 0) { break; } + } + } finally { + if ($scheduled.length == 0) { + // Cancel scheduling pass if there's nothing to run. + clearTimeout(nextRun); + } + } +}; + +var $schedule = goroutine => { + if (goroutine.asleep) { + goroutine.asleep = false; + $awakeGoroutines++; + } + $scheduled.push(goroutine); + if ($curGoroutine === $noGoroutine) { + $runScheduled(); + } +}; + +var $setTimeout = (f, t) => { + $awakeGoroutines++; + return setTimeout(() => { + $awakeGoroutines--; + f(); + }, t); +}; + +var $block = () => { + if ($curGoroutine === $noGoroutine) { + $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"); + } + $curGoroutine.asleep = true; +}; + +var $restore = (context, params) => { + if (context !== undefined && context.$blk !== undefined) { + return context; + } + return params; +} + +var $send = (chan, value) => { + if (chan.$closed) { + $throwRuntimeError("send on closed channel"); + } + var queuedRecv = chan.$recvQueue.shift(); + if (queuedRecv !== undefined) { + queuedRecv([value, true]); + return; + } + if (chan.$buffer.length < chan.$capacity) { + chan.$buffer.push(value); + return; + } + + var thisGoroutine = $curGoroutine; + var closedDuringSend; + chan.$sendQueue.push(closed => { + closedDuringSend = closed; + $schedule(thisGoroutine); + return value; + }); + $block(); + return { + $blk() { + if (closedDuringSend) { + $throwRuntimeError("send on closed channel"); + } + } + }; +}; +var $recv = chan => { + var queuedSend = chan.$sendQueue.shift(); + if (queuedSend !== undefined) { + chan.$buffer.push(queuedSend(false)); + } + var bufferedValue = chan.$buffer.shift(); + if (bufferedValue !== undefined) { + return [bufferedValue, true]; + } + if (chan.$closed) { + return [chan.$elem.zero(), false]; + } + + var thisGoroutine = $curGoroutine; + var f = { $blk() { return this.value; } }; + var queueEntry = v => { + f.value = v; + $schedule(thisGoroutine); + }; + chan.$recvQueue.push(queueEntry); + $block(); + return f; +}; +var $close = chan => { + if (chan.$closed) { + $throwRuntimeError("close of closed channel"); + } + chan.$closed = true; + while (true) { + var queuedSend = chan.$sendQueue.shift(); + if (queuedSend === undefined) { + break; + } + queuedSend(true); /* will panic */ + } + while (true) { + var queuedRecv = chan.$recvQueue.shift(); + if (queuedRecv === undefined) { + break; + } + queuedRecv([chan.$elem.zero(), false]); + } +}; +var $select = comms => { + var ready = []; + var selection = -1; + for (var i = 0; i < comms.length; i++) { + var comm = comms[i]; + var chan = comm[0]; + switch (comm.length) { + case 0: /* default */ + selection = i; + break; + case 1: /* recv */ + if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) { + ready.push(i); + } + break; + case 2: /* send */ + if (chan.$closed) { + $throwRuntimeError("send on closed channel"); + } + if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) { + ready.push(i); + } + break; + } + } + + if (ready.length !== 0) { + selection = ready[Math.floor(Math.random() * ready.length)]; + } + if (selection !== -1) { + var comm = comms[selection]; + switch (comm.length) { + case 0: /* default */ + return [selection]; + case 1: /* recv */ + return [selection, $recv(comm[0])]; + case 2: /* send */ + $send(comm[0], comm[1]); + return [selection]; + } + } + + var entries = []; + var thisGoroutine = $curGoroutine; + var f = { $blk() { return this.selection; } }; + var removeFromQueues = () => { + for (var i = 0; i < entries.length; i++) { + var entry = entries[i]; + var queue = entry[0]; + var index = queue.indexOf(entry[1]); + if (index !== -1) { + queue.splice(index, 1); + } + } + }; + for (var i = 0; i < comms.length; i++) { + (i => { + var comm = comms[i]; + switch (comm.length) { + case 1: /* recv */ + var queueEntry = value => { + f.selection = [i, value]; + removeFromQueues(); + $schedule(thisGoroutine); + }; + entries.push([comm[0].$recvQueue, queueEntry]); + comm[0].$recvQueue.push(queueEntry); + break; + case 2: /* send */ + var queueEntry = () => { + if (comm[0].$closed) { + $throwRuntimeError("send on closed channel"); + } + f.selection = [i]; + removeFromQueues(); + $schedule(thisGoroutine); + return comm[1]; + }; + entries.push([comm[0].$sendQueue, queueEntry]); + comm[0].$sendQueue.push(queueEntry); + break; + } + })(i); + } + $block(); + return f; +}; diff --git a/compiler/prelude/jsmapping.go b/compiler/prelude/jsmapping.go deleted file mode 100644 index 5cb3c14f5..000000000 --- a/compiler/prelude/jsmapping.go +++ /dev/null @@ -1,417 +0,0 @@ -package prelude - -const jsmapping = ` -var $jsObjectPtr, $jsErrorPtr; - -var $needsExternalization = function(t) { - switch (t.kind) { - case $kindBool: - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8: - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindFloat32: - case $kindFloat64: - return false; - default: - return t !== $jsObjectPtr; - } -}; - -var $externalize = function(v, t, makeWrapper) { - if (t === $jsObjectPtr) { - return v; - } - switch (t.kind) { - case $kindBool: - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8: - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindFloat32: - case $kindFloat64: - return v; - case $kindInt64: - case $kindUint64: - return $flatten64(v); - case $kindArray: - if ($needsExternalization(t.elem)) { - return $mapArray(v, function(e) { return $externalize(e, t.elem, makeWrapper); }); - } - return v; - case $kindFunc: - return $externalizeFunction(v, t, false, makeWrapper); - case $kindInterface: - if (v === $ifaceNil) { - return null; - } - if (v.constructor === $jsObjectPtr) { - return v.$val.object; - } - return $externalize(v.$val, v.constructor, makeWrapper); - case $kindMap: - if (v.keys === undefined) { - return null; - } - var m = {}; - var keys = Array.from(v.keys()); - for (var i = 0; i < keys.length; i++) { - var entry = v.get(keys[i]); - m[$externalize(entry.k, t.key, makeWrapper)] = $externalize(entry.v, t.elem, makeWrapper); - } - return m; - case $kindPtr: - if (v === t.nil) { - return null; - } - return $externalize(v.$get(), t.elem, makeWrapper); - case $kindSlice: - if (v === v.constructor.nil) { - return null; - } - if ($needsExternalization(t.elem)) { - return $mapArray($sliceToNativeArray(v), function(e) { return $externalize(e, t.elem, makeWrapper); }); - } - return $sliceToNativeArray(v); - case $kindString: - if ($isASCII(v)) { - return v; - } - var s = "", r; - for (var i = 0; i < v.length; i += r[1]) { - r = $decodeRune(v, i); - var c = r[0]; - if (c > 0xFFFF) { - var h = Math.floor((c - 0x10000) / 0x400) + 0xD800; - var l = (c - 0x10000) % 0x400 + 0xDC00; - s += String.fromCharCode(h, l); - continue; - } - s += String.fromCharCode(c); - } - return s; - case $kindStruct: - var timePkg = $packages["time"]; - if (timePkg !== undefined && v.constructor === timePkg.Time.ptr) { - var milli = $div64(v.UnixNano(), new $Int64(0, 1000000)); - return new Date($flatten64(milli)); - } - - var noJsObject = {}; - var searchJsObject = function(v, t) { - if (t === $jsObjectPtr) { - return v; - } - switch (t.kind) { - case $kindPtr: - if (v === t.nil) { - return noJsObject; - } - return searchJsObject(v.$get(), t.elem); - case $kindStruct: - if (t.fields.length === 0) { - return noJsObject; - } - var f = t.fields[0]; - return searchJsObject(v[f.prop], f.typ); - case $kindInterface: - return searchJsObject(v.$val, v.constructor); - default: - return noJsObject; - } - }; - var o = searchJsObject(v, t); - if (o !== noJsObject) { - return o; - } - - if (makeWrapper !== undefined) { - return makeWrapper(v); - } - - o = {}; - for (var i = 0; i < t.fields.length; i++) { - var f = t.fields[i]; - if (!f.exported) { - continue; - } - o[f.name] = $externalize(v[f.prop], f.typ, makeWrapper); - } - return o; - } - $throwRuntimeError("cannot externalize " + t.string); -}; - -var $externalizeFunction = function(v, t, passThis, makeWrapper) { - if (v === $throwNilPointerError) { - return null; - } - if (v.$externalizeWrapper === undefined) { - $checkForDeadlock = false; - v.$externalizeWrapper = function() { - var args = []; - for (var i = 0; i < t.params.length; i++) { - if (t.variadic && i === t.params.length - 1) { - var vt = t.params[i].elem, varargs = []; - for (var j = i; j < arguments.length; j++) { - varargs.push($internalize(arguments[j], vt, makeWrapper)); - } - args.push(new (t.params[i])(varargs)); - break; - } - args.push($internalize(arguments[i], t.params[i], makeWrapper)); - } - var result = v.apply(passThis ? this : undefined, args); - switch (t.results.length) { - case 0: - return; - case 1: - return $externalize($copyIfRequired(result, t.results[0]), t.results[0], makeWrapper); - default: - for (var i = 0; i < t.results.length; i++) { - result[i] = $externalize($copyIfRequired(result[i], t.results[i]), t.results[i], makeWrapper); - } - return result; - } - }; - } - return v.$externalizeWrapper; -}; - -var $internalize = function(v, t, recv, seen, makeWrapper) { - if (t === $jsObjectPtr) { - return v; - } - if (t === $jsObjectPtr.elem) { - $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); - } - if (v && v.__internal_object__ !== undefined) { - return $assertType(v.__internal_object__, t, false); - } - var timePkg = $packages["time"]; - if (timePkg !== undefined && t === timePkg.Time) { - if (!(v !== null && v !== undefined && v.constructor === Date)) { - $throwRuntimeError("cannot internalize time.Time from " + typeof v + ", must be Date"); - } - return timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)); - } - - // Cache for values we've already internalized in order to deal with circular - // references. - if (seen === undefined) { seen = new Map(); } - if (!seen.has(t)) { seen.set(t, new Map()); } - if (seen.get(t).has(v)) { return seen.get(t).get(v); } - - switch (t.kind) { - case $kindBool: - return !!v; - case $kindInt: - return parseInt(v); - case $kindInt8: - return parseInt(v) << 24 >> 24; - case $kindInt16: - return parseInt(v) << 16 >> 16; - case $kindInt32: - return parseInt(v) >> 0; - case $kindUint: - return parseInt(v); - case $kindUint8: - return parseInt(v) << 24 >>> 24; - case $kindUint16: - return parseInt(v) << 16 >>> 16; - case $kindUint32: - case $kindUintptr: - return parseInt(v) >>> 0; - case $kindInt64: - case $kindUint64: - return new t(0, v); - case $kindFloat32: - case $kindFloat64: - return parseFloat(v); - case $kindArray: - if (v.length !== t.len) { - $throwRuntimeError("got array with wrong size from JavaScript native"); - } - return $mapArray(v, function(e) { return $internalize(e, t.elem, makeWrapper); }); - case $kindFunc: - return function() { - var args = []; - for (var i = 0; i < t.params.length; i++) { - if (t.variadic && i === t.params.length - 1) { - var vt = t.params[i].elem, varargs = arguments[i]; - for (var j = 0; j < varargs.$length; j++) { - args.push($externalize(varargs.$array[varargs.$offset + j], vt, makeWrapper)); - } - break; - } - args.push($externalize(arguments[i], t.params[i], makeWrapper)); - } - var result = v.apply(recv, args); - switch (t.results.length) { - case 0: - return; - case 1: - return $internalize(result, t.results[0], makeWrapper); - default: - for (var i = 0; i < t.results.length; i++) { - result[i] = $internalize(result[i], t.results[i], makeWrapper); - } - return result; - } - }; - case $kindInterface: - if (t.methods.length !== 0) { - $throwRuntimeError("cannot internalize " + t.string); - } - if (v === null) { - return $ifaceNil; - } - if (v === undefined) { - return new $jsObjectPtr(undefined); - } - switch (v.constructor) { - case Int8Array: - return new ($sliceType($Int8))(v); - case Int16Array: - return new ($sliceType($Int16))(v); - case Int32Array: - return new ($sliceType($Int))(v); - case Uint8Array: - return new ($sliceType($Uint8))(v); - case Uint16Array: - return new ($sliceType($Uint16))(v); - case Uint32Array: - return new ($sliceType($Uint))(v); - case Float32Array: - return new ($sliceType($Float32))(v); - case Float64Array: - return new ($sliceType($Float64))(v); - case Array: - return $internalize(v, $sliceType($emptyInterface), makeWrapper); - case Boolean: - return new $Bool(!!v); - case Date: - if (timePkg === undefined) { - /* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */ - return new $jsObjectPtr(v); - } - return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper)); - case (function () { }).constructor: // is usually Function, but in Chrome extensions it is something else - var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true); - return new funcType($internalize(v, funcType, makeWrapper)); - case Number: - return new $Float64(parseFloat(v)); - case String: - return new $String($internalize(v, $String, makeWrapper)); - default: - if ($global.Node && v instanceof $global.Node) { - return new $jsObjectPtr(v); - } - var mapType = $mapType($String, $emptyInterface); - return new mapType($internalize(v, mapType, recv, seen, makeWrapper)); - } - case $kindMap: - var m = new Map(); - seen.get(t).set(v, m); - var keys = $keys(v); - for (var i = 0; i < keys.length; i++) { - var k = $internalize(keys[i], t.key, recv, seen, makeWrapper); - m.set(t.key.keyFor(k), { k: k, v: $internalize(v[keys[i]], t.elem, recv, seen, makeWrapper) }); - } - return m; - case $kindPtr: - if (t.elem.kind === $kindStruct) { - return $internalize(v, t.elem, makeWrapper); - } - case $kindSlice: - return new t($mapArray(v, function(e) { return $internalize(e, t.elem, makeWrapper); })); - case $kindString: - v = String(v); - if ($isASCII(v)) { - return v; - } - var s = ""; - var i = 0; - while (i < v.length) { - var h = v.charCodeAt(i); - if (0xD800 <= h && h <= 0xDBFF) { - var l = v.charCodeAt(i + 1); - var c = (h - 0xD800) * 0x400 + l - 0xDC00 + 0x10000; - s += $encodeRune(c); - i += 2; - continue; - } - s += $encodeRune(h); - i++; - } - return s; - case $kindStruct: - var noJsObject = {}; - var searchJsObject = function(t) { - if (t === $jsObjectPtr) { - return v; - } - if (t === $jsObjectPtr.elem) { - $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); - } - switch (t.kind) { - case $kindPtr: - return searchJsObject(t.elem); - case $kindStruct: - if (t.fields.length === 0) { - return noJsObject; - } - var f = t.fields[0]; - var o = searchJsObject(f.typ); - if (o !== noJsObject) { - var n = new t.ptr(); - n[f.prop] = o; - return n; - } - return noJsObject; - default: - return noJsObject; - } - }; - var o = searchJsObject(t); - if (o !== noJsObject) { - return o; - } - } - $throwRuntimeError("cannot internalize " + t.string); -}; - -var $copyIfRequired = function(v, typ) { - // interface values - if (v && v.constructor && v.constructor.copy) { - return new v.constructor($clone(v.$val, v.constructor)) - } - // array and struct values - if (typ.copy) { - var clone = typ.zero(); - typ.copy(clone, v); - return clone; - } - return v; -} - -/* $isASCII reports whether string s contains only ASCII characters. */ -var $isASCII = function(s) { - for (var i = 0; i < s.length; i++) { - if (s.charCodeAt(i) >= 128) { - return false; - } - } - return true; -}; -` diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js new file mode 100644 index 000000000..b22454bc3 --- /dev/null +++ b/compiler/prelude/jsmapping.js @@ -0,0 +1,426 @@ +var $jsObjectPtr, $jsErrorPtr; + +var $needsExternalization = t => { + switch (t.kind) { + case $kindBool: + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindFloat32: + case $kindFloat64: + return false; + default: + return t !== $jsObjectPtr; + } +}; + +var $externalize = (v, t, makeWrapper) => { + if (t === $jsObjectPtr) { + return v; + } + switch (t.kind) { + case $kindBool: + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindFloat32: + case $kindFloat64: + return v; + case $kindInt64: + case $kindUint64: + return $flatten64(v); + case $kindArray: + if ($needsExternalization(t.elem)) { + return $mapArray(v, e => { return $externalize(e, t.elem, makeWrapper); }); + } + return v; + case $kindFunc: + return $externalizeFunction(v, t, false, makeWrapper); + case $kindInterface: + if (v === $ifaceNil) { + return null; + } + if (v.constructor === $jsObjectPtr) { + return v.$val.object; + } + return $externalize(v.$val, v.constructor, makeWrapper); + case $kindMap: + if (v.keys === undefined) { + return null; + } + var m = {}; + var keys = Array.from(v.keys()); + for (var i = 0; i < keys.length; i++) { + var entry = v.get(keys[i]); + m[$externalize(entry.k, t.key, makeWrapper)] = $externalize(entry.v, t.elem, makeWrapper); + } + return m; + case $kindPtr: + if (v === t.nil) { + return null; + } + return $externalize(v.$get(), t.elem, makeWrapper); + case $kindSlice: + if (v === v.constructor.nil) { + return null; + } + if ($needsExternalization(t.elem)) { + return $mapArray($sliceToNativeArray(v), e => { return $externalize(e, t.elem, makeWrapper); }); + } + return $sliceToNativeArray(v); + case $kindString: + if ($isASCII(v)) { + return v; + } + var s = "", r; + for (var i = 0; i < v.length; i += r[1]) { + r = $decodeRune(v, i); + var c = r[0]; + if (c > 0xFFFF) { + var h = Math.floor((c - 0x10000) / 0x400) + 0xD800; + var l = (c - 0x10000) % 0x400 + 0xDC00; + s += String.fromCharCode(h, l); + continue; + } + s += String.fromCharCode(c); + } + return s; + case $kindStruct: + var timePkg = $packages["time"]; + if (timePkg !== undefined && v.constructor === timePkg.Time.ptr) { + var milli = $div64(v.UnixNano(), new $Int64(0, 1000000)); + return new Date($flatten64(milli)); + } + + var noJsObject = {}; + var searchJsObject = (v, t) => { + if (t === $jsObjectPtr) { + return v; + } + switch (t.kind) { + case $kindPtr: + if (v === t.nil) { + return noJsObject; + } + return searchJsObject(v.$get(), t.elem); + case $kindStruct: + if (t.fields.length === 0) { + return noJsObject; + } + var f = t.fields[0]; + return searchJsObject(v[f.prop], f.typ); + case $kindInterface: + return searchJsObject(v.$val, v.constructor); + default: + return noJsObject; + } + }; + var o = searchJsObject(v, t); + if (o !== noJsObject) { + return o; + } + + if (makeWrapper !== undefined) { + return makeWrapper(v); + } + + o = {}; + for (var i = 0; i < t.fields.length; i++) { + var f = t.fields[i]; + if (!f.exported) { + continue; + } + o[f.name] = $externalize(v[f.prop], f.typ, makeWrapper); + } + return o; + } + $throwRuntimeError("cannot externalize " + t.string); +}; + +var $externalizeFunction = (v, t, passThis, makeWrapper) => { + if (v === $throwNilPointerError) { + return null; + } + if (v.$externalizeWrapper === undefined) { + $checkForDeadlock = false; + v.$externalizeWrapper = function () { + var args = []; + for (var i = 0; i < t.params.length; i++) { + if (t.variadic && i === t.params.length - 1) { + var vt = t.params[i].elem, varargs = []; + for (var j = i; j < arguments.length; j++) { + varargs.push($internalize(arguments[j], vt, makeWrapper)); + } + args.push(new (t.params[i])(varargs)); + break; + } + args.push($internalize(arguments[i], t.params[i], makeWrapper)); + } + var result = v.apply(passThis ? this : undefined, args); + switch (t.results.length) { + case 0: + return; + case 1: + return $externalize($copyIfRequired(result, t.results[0]), t.results[0], makeWrapper); + default: + for (var i = 0; i < t.results.length; i++) { + result[i] = $externalize($copyIfRequired(result[i], t.results[i]), t.results[i], makeWrapper); + } + return result; + } + }; + } + return v.$externalizeWrapper; +}; + +var $internalize = (v, t, recv, seen, makeWrapper) => { + if (t === $jsObjectPtr) { + return v; + } + if (t === $jsObjectPtr.elem) { + $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); + } + if (v && v.__internal_object__ !== undefined) { + return $assertType(v.__internal_object__, t, false); + } + var timePkg = $packages["time"]; + if (timePkg !== undefined && t === timePkg.Time) { + if (!(v !== null && v !== undefined && v.constructor === Date)) { + $throwRuntimeError("cannot internalize time.Time from " + typeof v + ", must be Date"); + } + return timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)); + } + + // Cache for values we've already internalized in order to deal with circular + // references. + if (seen === undefined) { seen = new Map(); } + if (!seen.has(t)) { seen.set(t, new Map()); } + if (seen.get(t).has(v)) { return seen.get(t).get(v); } + + switch (t.kind) { + case $kindBool: + return !!v; + case $kindInt: + return parseInt(v); + case $kindInt8: + return parseInt(v) << 24 >> 24; + case $kindInt16: + return parseInt(v) << 16 >> 16; + case $kindInt32: + return parseInt(v) >> 0; + case $kindUint: + return parseInt(v); + case $kindUint8: + return parseInt(v) << 24 >>> 24; + case $kindUint16: + return parseInt(v) << 16 >>> 16; + case $kindUint32: + case $kindUintptr: + return parseInt(v) >>> 0; + case $kindInt64: + case $kindUint64: + return new t(0, v); + case $kindFloat32: + case $kindFloat64: + return parseFloat(v); + case $kindArray: + if (v.length !== t.len) { + $throwRuntimeError("got array with wrong size from JavaScript native"); + } + return $mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); }); + case $kindFunc: + return function () { + var args = []; + for (var i = 0; i < t.params.length; i++) { + if (t.variadic && i === t.params.length - 1) { + var vt = t.params[i].elem, varargs = arguments[i]; + for (var j = 0; j < varargs.$length; j++) { + args.push($externalize(varargs.$array[varargs.$offset + j], vt, makeWrapper)); + } + break; + } + args.push($externalize(arguments[i], t.params[i], makeWrapper)); + } + var result = v.apply(recv, args); + switch (t.results.length) { + case 0: + return; + case 1: + return $internalize(result, t.results[0], makeWrapper); + default: + for (var i = 0; i < t.results.length; i++) { + result[i] = $internalize(result[i], t.results[i], makeWrapper); + } + return result; + } + }; + case $kindInterface: + if (t.methods.length !== 0) { + $throwRuntimeError("cannot internalize " + t.string); + } + if (v === null) { + return $ifaceNil; + } + if (v === undefined) { + return new $jsObjectPtr(undefined); + } + switch (v.constructor) { + case Int8Array: + return new ($sliceType($Int8))(v); + case Int16Array: + return new ($sliceType($Int16))(v); + case Int32Array: + return new ($sliceType($Int))(v); + case Uint8Array: + return new ($sliceType($Uint8))(v); + case Uint16Array: + return new ($sliceType($Uint16))(v); + case Uint32Array: + return new ($sliceType($Uint))(v); + case Float32Array: + return new ($sliceType($Float32))(v); + case Float64Array: + return new ($sliceType($Float64))(v); + case Array: + return $internalize(v, $sliceType($emptyInterface), makeWrapper); + case Boolean: + return new $Bool(!!v); + case Date: + if (timePkg === undefined) { + /* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */ + return new $jsObjectPtr(v); + } + return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper)); + case ((() => { })).constructor: // is usually Function, but in Chrome extensions it is something else + var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true); + return new funcType($internalize(v, funcType, makeWrapper)); + case Number: + return new $Float64(parseFloat(v)); + case String: + return new $String($internalize(v, $String, makeWrapper)); + default: + if ($global.Node && v instanceof $global.Node) { + return new $jsObjectPtr(v); + } + var mapType = $mapType($String, $emptyInterface); + return new mapType($internalize(v, mapType, recv, seen, makeWrapper)); + } + case $kindMap: + var m = new Map(); + seen.get(t).set(v, m); + var keys = $keys(v); + for (var i = 0; i < keys.length; i++) { + var k = $internalize(keys[i], t.key, recv, seen, makeWrapper); + m.set(t.key.keyFor(k), { k, v: $internalize(v[keys[i]], t.elem, recv, seen, makeWrapper) }); + } + return m; + case $kindPtr: + if (t.elem.kind === $kindStruct) { + return $internalize(v, t.elem, makeWrapper); + } + case $kindSlice: + return new t($mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); })); + case $kindString: + v = String(v); + if ($isASCII(v)) { + return v; + } + var s = ""; + var i = 0; + while (i < v.length) { + var h = v.charCodeAt(i); + if (0xD800 <= h && h <= 0xDBFF) { + var l = v.charCodeAt(i + 1); + var c = (h - 0xD800) * 0x400 + l - 0xDC00 + 0x10000; + s += $encodeRune(c); + i += 2; + continue; + } + s += $encodeRune(h); + i++; + } + return s; + case $kindStruct: + var noJsObject = {}; + var searchJsObject = t => { + if (t === $jsObjectPtr) { + return v; + } + if (t === $jsObjectPtr.elem) { + $throwRuntimeError("cannot internalize js.Object, use *js.Object instead"); + } + switch (t.kind) { + case $kindPtr: + return searchJsObject(t.elem); + case $kindStruct: + if (t.fields.length === 0) { + return noJsObject; + } + var f = t.fields[0]; + var o = searchJsObject(f.typ); + if (o !== noJsObject) { + var n = new t.ptr(); + n[f.prop] = o; + return n; + } + return noJsObject; + default: + return noJsObject; + } + }; + var o = searchJsObject(t); + if (o !== noJsObject) { + return o; + } + var n = new t.ptr(); + for (var i = 0; i < t.fields.length; i++) { + var f = t.fields[i]; + + if (!f.exported) { + continue; + } + var jsProp = v[f.name]; + + n[f.prop] = $internalize(jsProp, f.typ, recv, seen, makeWrapper); + } + + return n; + } + $throwRuntimeError("cannot internalize " + t.string); +}; + +var $copyIfRequired = (v, typ) => { + // interface values + if (v && v.constructor && v.constructor.copy) { + return new v.constructor($clone(v.$val, v.constructor)) + } + // array and struct values + if (typ.copy) { + var clone = typ.zero(); + typ.copy(clone, v); + return clone; + } + return v; +} + +/* $isASCII reports whether string s contains only ASCII characters. */ +var $isASCII = s => { + for (var i = 0; i < s.length; i++) { + if (s.charCodeAt(i) >= 128) { + return false; + } + } + return true; +}; diff --git a/compiler/prelude/numeric.go b/compiler/prelude/numeric.go deleted file mode 100644 index 76bc30a2d..000000000 --- a/compiler/prelude/numeric.go +++ /dev/null @@ -1,216 +0,0 @@ -package prelude - -const numeric = ` -var $min = Math.min; -var $mod = function(x, y) { return x % y; }; -var $parseInt = parseInt; -var $parseFloat = function(f) { - if (f !== undefined && f !== null && f.constructor === Number) { - return f; - } - return parseFloat(f); -}; - -var $froundBuf = new Float32Array(1); -var $fround = Math.fround || function(f) { - $froundBuf[0] = f; - return $froundBuf[0]; -}; - -var $imul = Math.imul || function(a, b) { - var ah = (a >>> 16) & 0xffff; - var al = a & 0xffff; - var bh = (b >>> 16) & 0xffff; - var bl = b & 0xffff; - return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) >> 0); -}; - -var $floatKey = function(f) { - if (f !== f) { - $idCounter++; - return "NaN$" + $idCounter; - } - return String(f); -}; - -var $flatten64 = function(x) { - return x.$high * 4294967296 + x.$low; -}; - -var $shiftLeft64 = function(x, y) { - if (y === 0) { - return x; - } - if (y < 32) { - return new x.constructor(x.$high << y | x.$low >>> (32 - y), (x.$low << y) >>> 0); - } - if (y < 64) { - return new x.constructor(x.$low << (y - 32), 0); - } - return new x.constructor(0, 0); -}; - -var $shiftRightInt64 = function(x, y) { - if (y === 0) { - return x; - } - if (y < 32) { - return new x.constructor(x.$high >> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); - } - if (y < 64) { - return new x.constructor(x.$high >> 31, (x.$high >> (y - 32)) >>> 0); - } - if (x.$high < 0) { - return new x.constructor(-1, 4294967295); - } - return new x.constructor(0, 0); -}; - -var $shiftRightUint64 = function(x, y) { - if (y === 0) { - return x; - } - if (y < 32) { - return new x.constructor(x.$high >>> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); - } - if (y < 64) { - return new x.constructor(0, x.$high >>> (y - 32)); - } - return new x.constructor(0, 0); -}; - -var $mul64 = function(x, y) { - var x48 = x.$high >>> 16; - var x32 = x.$high & 0xFFFF; - var x16 = x.$low >>> 16; - var x00 = x.$low & 0xFFFF; - - var y48 = y.$high >>> 16; - var y32 = y.$high & 0xFFFF; - var y16 = y.$low >>> 16; - var y00 = y.$low & 0xFFFF; - - var z48 = 0, z32 = 0, z16 = 0, z00 = 0; - z00 += x00 * y00; - z16 += z00 >>> 16; - z00 &= 0xFFFF; - z16 += x16 * y00; - z32 += z16 >>> 16; - z16 &= 0xFFFF; - z16 += x00 * y16; - z32 += z16 >>> 16; - z16 &= 0xFFFF; - z32 += x32 * y00; - z48 += z32 >>> 16; - z32 &= 0xFFFF; - z32 += x16 * y16; - z48 += z32 >>> 16; - z32 &= 0xFFFF; - z32 += x00 * y32; - z48 += z32 >>> 16; - z32 &= 0xFFFF; - z48 += x48 * y00 + x32 * y16 + x16 * y32 + x00 * y48; - z48 &= 0xFFFF; - - var hi = ((z48 << 16) | z32) >>> 0; - var lo = ((z16 << 16) | z00) >>> 0; - - var r = new x.constructor(hi, lo); - return r; -}; - -var $div64 = function(x, y, returnRemainder) { - if (y.$high === 0 && y.$low === 0) { - $throwRuntimeError("integer divide by zero"); - } - - var s = 1; - var rs = 1; - - var xHigh = x.$high; - var xLow = x.$low; - if (xHigh < 0) { - s = -1; - rs = -1; - xHigh = -xHigh; - if (xLow !== 0) { - xHigh--; - xLow = 4294967296 - xLow; - } - } - - var yHigh = y.$high; - var yLow = y.$low; - if (y.$high < 0) { - s *= -1; - yHigh = -yHigh; - if (yLow !== 0) { - yHigh--; - yLow = 4294967296 - yLow; - } - } - - var high = 0, low = 0, n = 0; - while (yHigh < 2147483648 && ((xHigh > yHigh) || (xHigh === yHigh && xLow > yLow))) { - yHigh = (yHigh << 1 | yLow >>> 31) >>> 0; - yLow = (yLow << 1) >>> 0; - n++; - } - for (var i = 0; i <= n; i++) { - high = high << 1 | low >>> 31; - low = (low << 1) >>> 0; - if ((xHigh > yHigh) || (xHigh === yHigh && xLow >= yLow)) { - xHigh = xHigh - yHigh; - xLow = xLow - yLow; - if (xLow < 0) { - xHigh--; - xLow += 4294967296; - } - low++; - if (low === 4294967296) { - high++; - low = 0; - } - } - yLow = (yLow >>> 1 | yHigh << (32 - 1)) >>> 0; - yHigh = yHigh >>> 1; - } - - if (returnRemainder) { - return new x.constructor(xHigh * rs, xLow * rs); - } - return new x.constructor(high * s, low * s); -}; - -var $divComplex = function(n, d) { - var ninf = n.$real === Infinity || n.$real === -Infinity || n.$imag === Infinity || n.$imag === -Infinity; - var dinf = d.$real === Infinity || d.$real === -Infinity || d.$imag === Infinity || d.$imag === -Infinity; - var nnan = !ninf && (n.$real !== n.$real || n.$imag !== n.$imag); - var dnan = !dinf && (d.$real !== d.$real || d.$imag !== d.$imag); - if(nnan || dnan) { - return new n.constructor(NaN, NaN); - } - if (ninf && !dinf) { - return new n.constructor(Infinity, Infinity); - } - if (!ninf && dinf) { - return new n.constructor(0, 0); - } - if (d.$real === 0 && d.$imag === 0) { - if (n.$real === 0 && n.$imag === 0) { - return new n.constructor(NaN, NaN); - } - return new n.constructor(Infinity, Infinity); - } - var a = Math.abs(d.$real); - var b = Math.abs(d.$imag); - if (a <= b) { - var ratio = d.$real / d.$imag; - var denom = d.$real * ratio + d.$imag; - return new n.constructor((n.$real * ratio + n.$imag) / denom, (n.$imag * ratio - n.$real) / denom); - } - var ratio = d.$imag / d.$real; - var denom = d.$imag * ratio + d.$real; - return new n.constructor((n.$imag * ratio + n.$real) / denom, (n.$imag - n.$real * ratio) / denom); -}; -` diff --git a/compiler/prelude/numeric.js b/compiler/prelude/numeric.js new file mode 100644 index 000000000..5cfd7644d --- /dev/null +++ b/compiler/prelude/numeric.js @@ -0,0 +1,212 @@ +var $min = Math.min; +var $mod = (x, y) => { return x % y; }; +var $parseInt = parseInt; +var $parseFloat = f => { + if (f !== undefined && f !== null && f.constructor === Number) { + return f; + } + return parseFloat(f); +}; + +var $froundBuf = new Float32Array(1); +var $fround = Math.fround || (f => { + $froundBuf[0] = f; + return $froundBuf[0]; +}); + +var $imul = Math.imul || ((a, b) => { + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) >> 0); +}); + +var $floatKey = f => { + if (f !== f) { + $idCounter++; + return "NaN$" + $idCounter; + } + return String(f); +}; + +var $flatten64 = x => { + return x.$high * 4294967296 + x.$low; +}; + +var $shiftLeft64 = (x, y) => { + if (y === 0) { + return x; + } + if (y < 32) { + return new x.constructor(x.$high << y | x.$low >>> (32 - y), (x.$low << y) >>> 0); + } + if (y < 64) { + return new x.constructor(x.$low << (y - 32), 0); + } + return new x.constructor(0, 0); +}; + +var $shiftRightInt64 = (x, y) => { + if (y === 0) { + return x; + } + if (y < 32) { + return new x.constructor(x.$high >> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); + } + if (y < 64) { + return new x.constructor(x.$high >> 31, (x.$high >> (y - 32)) >>> 0); + } + if (x.$high < 0) { + return new x.constructor(-1, 4294967295); + } + return new x.constructor(0, 0); +}; + +var $shiftRightUint64 = (x, y) => { + if (y === 0) { + return x; + } + if (y < 32) { + return new x.constructor(x.$high >>> y, (x.$low >>> y | x.$high << (32 - y)) >>> 0); + } + if (y < 64) { + return new x.constructor(0, x.$high >>> (y - 32)); + } + return new x.constructor(0, 0); +}; + +var $mul64 = (x, y) => { + var x48 = x.$high >>> 16; + var x32 = x.$high & 0xFFFF; + var x16 = x.$low >>> 16; + var x00 = x.$low & 0xFFFF; + + var y48 = y.$high >>> 16; + var y32 = y.$high & 0xFFFF; + var y16 = y.$low >>> 16; + var y00 = y.$low & 0xFFFF; + + var z48 = 0, z32 = 0, z16 = 0, z00 = 0; + z00 += x00 * y00; + z16 += z00 >>> 16; + z00 &= 0xFFFF; + z16 += x16 * y00; + z32 += z16 >>> 16; + z16 &= 0xFFFF; + z16 += x00 * y16; + z32 += z16 >>> 16; + z16 &= 0xFFFF; + z32 += x32 * y00; + z48 += z32 >>> 16; + z32 &= 0xFFFF; + z32 += x16 * y16; + z48 += z32 >>> 16; + z32 &= 0xFFFF; + z32 += x00 * y32; + z48 += z32 >>> 16; + z32 &= 0xFFFF; + z48 += x48 * y00 + x32 * y16 + x16 * y32 + x00 * y48; + z48 &= 0xFFFF; + + var hi = ((z48 << 16) | z32) >>> 0; + var lo = ((z16 << 16) | z00) >>> 0; + + var r = new x.constructor(hi, lo); + return r; +}; + +var $div64 = (x, y, returnRemainder) => { + if (y.$high === 0 && y.$low === 0) { + $throwRuntimeError("integer divide by zero"); + } + + var s = 1; + var rs = 1; + + var xHigh = x.$high; + var xLow = x.$low; + if (xHigh < 0) { + s = -1; + rs = -1; + xHigh = -xHigh; + if (xLow !== 0) { + xHigh--; + xLow = 4294967296 - xLow; + } + } + + var yHigh = y.$high; + var yLow = y.$low; + if (y.$high < 0) { + s *= -1; + yHigh = -yHigh; + if (yLow !== 0) { + yHigh--; + yLow = 4294967296 - yLow; + } + } + + var high = 0, low = 0, n = 0; + while (yHigh < 2147483648 && ((xHigh > yHigh) || (xHigh === yHigh && xLow > yLow))) { + yHigh = (yHigh << 1 | yLow >>> 31) >>> 0; + yLow = (yLow << 1) >>> 0; + n++; + } + for (var i = 0; i <= n; i++) { + high = high << 1 | low >>> 31; + low = (low << 1) >>> 0; + if ((xHigh > yHigh) || (xHigh === yHigh && xLow >= yLow)) { + xHigh = xHigh - yHigh; + xLow = xLow - yLow; + if (xLow < 0) { + xHigh--; + xLow += 4294967296; + } + low++; + if (low === 4294967296) { + high++; + low = 0; + } + } + yLow = (yLow >>> 1 | yHigh << (32 - 1)) >>> 0; + yHigh = yHigh >>> 1; + } + + if (returnRemainder) { + return new x.constructor(xHigh * rs, xLow * rs); + } + return new x.constructor(high * s, low * s); +}; + +var $divComplex = (n, d) => { + var ninf = n.$real === Infinity || n.$real === -Infinity || n.$imag === Infinity || n.$imag === -Infinity; + var dinf = d.$real === Infinity || d.$real === -Infinity || d.$imag === Infinity || d.$imag === -Infinity; + var nnan = !ninf && (n.$real !== n.$real || n.$imag !== n.$imag); + var dnan = !dinf && (d.$real !== d.$real || d.$imag !== d.$imag); + if (nnan || dnan) { + return new n.constructor(NaN, NaN); + } + if (ninf && !dinf) { + return new n.constructor(Infinity, Infinity); + } + if (!ninf && dinf) { + return new n.constructor(0, 0); + } + if (d.$real === 0 && d.$imag === 0) { + if (n.$real === 0 && n.$imag === 0) { + return new n.constructor(NaN, NaN); + } + return new n.constructor(Infinity, Infinity); + } + var a = Math.abs(d.$real); + var b = Math.abs(d.$imag); + if (a <= b) { + var ratio = d.$real / d.$imag; + var denom = d.$real * ratio + d.$imag; + return new n.constructor((n.$real * ratio + n.$imag) / denom, (n.$imag * ratio - n.$real) / denom); + } + var ratio = d.$imag / d.$real; + var denom = d.$imag * ratio + d.$real; + return new n.constructor((n.$imag * ratio + n.$real) / denom, (n.$imag - n.$real * ratio) / denom); +}; diff --git a/compiler/prelude/prelude.go b/compiler/prelude/prelude.go index ad5ceeb28..0b605b697 100644 --- a/compiler/prelude/prelude.go +++ b/compiler/prelude/prelude.go @@ -1,579 +1,48 @@ package prelude -//go:generate go run genmin.go +import ( + _ "embed" -// Prelude is the GopherJS JavaScript interop layer. -const Prelude = prelude + numeric + types + goroutines + jsmapping - -const prelude = `Error.stackTraceLimit = Infinity; - -var $NaN = NaN; -var $global, $module; -if (typeof window !== "undefined") { /* web page */ - $global = window; -} else if (typeof self !== "undefined") { /* web worker */ - $global = self; -} else if (typeof global !== "undefined") { /* Node.js */ - $global = global; - $global.require = require; -} else { /* others (e.g. Nashorn) */ - $global = this; -} - -if ($global === undefined || $global.Array === undefined) { - throw new Error("no global object found"); -} -if (typeof module !== "undefined") { - $module = module; -} - -if (!$global.fs && $global.require) { - try { - var fs = $global.require('fs'); - if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { - $global.fs = fs; - } - } catch(e) { /* Ignore if the module couldn't be loaded. */ } -} - -if (!$global.fs) { - var outputBuf = ""; - var 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 }, // unused - writeSync: function writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - var nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); - } - return buf.length; - }, - write: function write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - callback(enosys()); - return; - } - var n = this.writeSync(fd, buf); - callback(null, n); - } - }; -} - -var $linknames = {} // Collection of functions referenced by a go:linkname directive. -var $packages = {}, $idCounter = 0; -var $keys = function(m) { return m ? Object.keys(m) : []; }; -var $flushConsole = function() {}; -var $throwRuntimeError; /* set by package "runtime" */ -var $throwNilPointerError = function() { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; -var $call = function(fn, rcvr, args) { return fn.apply(rcvr, args); }; -var $makeFunc = function(fn) { return function() { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments, []))), $emptyInterface); }; }; -var $unused = function(v) {}; -var $print = console.log; -// Under Node we can emulate print() more closely by avoiding a newline. -if (($global.process !== undefined) && $global.require) { - try { - var util = $global.require('util'); - $print = function() { $global.process.stderr.write(util.format.apply(this, arguments)); }; - } catch (e) { - // Failed to require util module, keep using console.log(). - } -} -var $println = console.log - -var $initAllLinknames = function() { - var names = $keys($packages); - for (var i = 0; i < names.length; i++) { - var f = $packages[names[i]]["$initLinknames"]; - if (typeof f == 'function') { - f(); - } - } -} - -var $mapArray = function(array, f) { - var newArray = new array.constructor(array.length); - for (var i = 0; i < array.length; i++) { - newArray[i] = f(array[i]); - } - return newArray; -}; + "github.com/evanw/esbuild/pkg/api" + log "github.com/sirupsen/logrus" +) -// $mapIndex returns the value of the given key in m, or undefined if m is nil/undefined or not a map -var $mapIndex = function(m, key) { - return typeof m.get === "function" ? m.get(key) : undefined; -}; -// $mapDelete deletes the key and associated value from m. If m is nil/undefined or not a map, $mapDelete is a no-op -var $mapDelete = function(m, key) { - typeof m.delete === "function" && m.delete(key) -}; -// Returns a method bound to the receiver instance, safe to invoke as a -// standalone function. Bound function is cached for later reuse. -var $methodVal = function(recv, name) { - var vals = recv.$methodVals || {}; - recv.$methodVals = vals; /* noop for primitives */ - var f = vals[name]; - if (f !== undefined) { - return f; - } - var method = recv[name]; - f = method.bind(recv); - vals[name] = f; - return f; -}; - -var $methodExpr = function(typ, name) { - var method = typ.prototype[name]; - if (method.$expr === undefined) { - method.$expr = function() { - $stackDepthOffset--; - try { - if (typ.wrapped) { - arguments[0] = new typ(arguments[0]); - } - return Function.call.apply(method, arguments); - } finally { - $stackDepthOffset++; - } - }; - } - return method.$expr; -}; - -var $ifaceMethodExprs = {}; -var $ifaceMethodExpr = function(name) { - var expr = $ifaceMethodExprs["$" + name]; - if (expr === undefined) { - expr = $ifaceMethodExprs["$" + name] = function() { - $stackDepthOffset--; - try { - return Function.call.apply(arguments[0][name], arguments); - } finally { - $stackDepthOffset++; - } - }; - } - return expr; -}; - -var $subslice = function(slice, low, high, max) { - if (high === undefined) { - high = slice.$length; - } - if (max === undefined) { - max = slice.$capacity; - } - if (low < 0 || high < low || max < high || high > slice.$capacity || max > slice.$capacity) { - $throwRuntimeError("slice bounds out of range"); - } - if (slice === slice.constructor.nil) { - return slice; - } - var s = new slice.constructor(slice.$array); - s.$offset = slice.$offset + low; - s.$length = high - low; - s.$capacity = max - low; - return s; -}; - -var $substring = function(str, low, high) { - if (low < 0 || high < low || high > str.length) { - $throwRuntimeError("slice bounds out of range"); - } - return str.substring(low, high); -}; - -// Convert Go slice to an equivalent JS array type. -var $sliceToNativeArray = function(slice) { - if (slice.$array.constructor !== Array) { - return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length); - } - return slice.$array.slice(slice.$offset, slice.$offset + slice.$length); -}; - -// Convert Go slice to a pointer to an underlying Go array. -// -// Note that an array pointer can be represented by an "unwrapped" native array -// type, and it will be wrapped back into its Go type when necessary. -var $sliceToGoArray = function(slice, arrayPtrType) { - var arrayType = arrayPtrType.elem; - if (arrayType !== undefined && slice.$length < arrayType.len) { - $throwRuntimeError("cannot convert slice with length " + slice.$length + " to pointer to array with length " + arrayType.len); - } - if (slice == slice.constructor.nil) { - return arrayPtrType.nil; // Nil slice converts to nil array pointer. - } - if (slice.$array.constructor !== Array) { - return slice.$array.subarray(slice.$offset, slice.$offset + arrayType.len); - } - if (slice.$offset == 0 && slice.$length == slice.$capacity && slice.$length == arrayType.len) { - return slice.$array; - } - if (arrayType.len == 0) { - return new arrayType([]); - } - - // Array.slice (unlike TypedArray.subarray) returns a copy of an array range, - // which is not sharing memory with the original one, which violates the spec - // for slice to array conversion. This is incompatible with the Go spec, in - // particular that the assignments to the array elements would be visible in - // the slice. Prefer to fail explicitly instead of creating subtle bugs. - $throwRuntimeError("gopherjs: non-numeric slice to underlying array conversion is not supported for subslices"); -}; - -// Convert between compatible slice types (e.g. native and names). -var $convertSliceType = function(slice, desiredType) { - if (slice == slice.constructor.nil) { - return desiredType.nil; // Preserve nil value. - } - - return $subslice(new desiredType(slice.$array), slice.$offset, slice.$offset + slice.$length); +// Prelude is the GopherJS JavaScript interop layer. +var Prelude = prelude + numeric + types + goroutines + jsmapping + +//go:embed prelude.js +var prelude string + +//go:embed types.js +var types string + +//go:embed numeric.js +var numeric string + +//go:embed jsmapping.js +var jsmapping string + +//go:embed goroutines.js +var goroutines string + +func Minified() string { + result := api.Transform(Prelude, api.TransformOptions{ + Target: api.ES2015, + MinifyWhitespace: true, + MinifyIdentifiers: true, + MinifySyntax: true, + KeepNames: true, + Charset: api.CharsetUTF8, + LegalComments: api.LegalCommentsEndOfFile, + }) + for _, w := range result.Warnings { + log.Warnf("%d:%d: %s\n%s\n", w.Location.Line, w.Location.Column, w.Text, w.Location.LineText) + } + if errCount := len(result.Errors); errCount > 0 { + for _, e := range result.Errors { + log.Errorf("%d:%d: %s\n%s\n", e.Location.Line, e.Location.Column, e.Text, e.Location.LineText) + } + log.Fatalf("Prelude minification failed with %d errors", errCount) + } + return string(result.Code) } - -var $decodeRune = function(str, pos) { - var c0 = str.charCodeAt(pos); - - if (c0 < 0x80) { - return [c0, 1]; - } - - if (c0 !== c0 || c0 < 0xC0) { - return [0xFFFD, 1]; - } - - var c1 = str.charCodeAt(pos + 1); - if (c1 !== c1 || c1 < 0x80 || 0xC0 <= c1) { - return [0xFFFD, 1]; - } - - if (c0 < 0xE0) { - var r = (c0 & 0x1F) << 6 | (c1 & 0x3F); - if (r <= 0x7F) { - return [0xFFFD, 1]; - } - return [r, 2]; - } - - var c2 = str.charCodeAt(pos + 2); - if (c2 !== c2 || c2 < 0x80 || 0xC0 <= c2) { - return [0xFFFD, 1]; - } - - if (c0 < 0xF0) { - var r = (c0 & 0x0F) << 12 | (c1 & 0x3F) << 6 | (c2 & 0x3F); - if (r <= 0x7FF) { - return [0xFFFD, 1]; - } - if (0xD800 <= r && r <= 0xDFFF) { - return [0xFFFD, 1]; - } - return [r, 3]; - } - - var c3 = str.charCodeAt(pos + 3); - if (c3 !== c3 || c3 < 0x80 || 0xC0 <= c3) { - return [0xFFFD, 1]; - } - - if (c0 < 0xF8) { - var r = (c0 & 0x07) << 18 | (c1 & 0x3F) << 12 | (c2 & 0x3F) << 6 | (c3 & 0x3F); - if (r <= 0xFFFF || 0x10FFFF < r) { - return [0xFFFD, 1]; - } - return [r, 4]; - } - - return [0xFFFD, 1]; -}; - -var $encodeRune = function(r) { - if (r < 0 || r > 0x10FFFF || (0xD800 <= r && r <= 0xDFFF)) { - r = 0xFFFD; - } - if (r <= 0x7F) { - return String.fromCharCode(r); - } - if (r <= 0x7FF) { - return String.fromCharCode(0xC0 | r >> 6, 0x80 | (r & 0x3F)); - } - if (r <= 0xFFFF) { - return String.fromCharCode(0xE0 | r >> 12, 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); - } - return String.fromCharCode(0xF0 | r >> 18, 0x80 | (r >> 12 & 0x3F), 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); -}; - -var $stringToBytes = function(str) { - var array = new Uint8Array(str.length); - for (var i = 0; i < str.length; i++) { - array[i] = str.charCodeAt(i); - } - return array; -}; - -var $bytesToString = function(slice) { - if (slice.$length === 0) { - return ""; - } - var str = ""; - for (var i = 0; i < slice.$length; i += 10000) { - str += String.fromCharCode.apply(undefined, slice.$array.subarray(slice.$offset + i, slice.$offset + Math.min(slice.$length, i + 10000))); - } - return str; -}; - -var $stringToRunes = function(str) { - var array = new Int32Array(str.length); - var rune, j = 0; - for (var i = 0; i < str.length; i += rune[1], j++) { - rune = $decodeRune(str, i); - array[j] = rune[0]; - } - return array.subarray(0, j); -}; - -var $runesToString = function(slice) { - if (slice.$length === 0) { - return ""; - } - var str = ""; - for (var i = 0; i < slice.$length; i++) { - str += $encodeRune(slice.$array[slice.$offset + i]); - } - return str; -}; - -var $copyString = function(dst, src) { - var n = Math.min(src.length, dst.$length); - for (var i = 0; i < n; i++) { - dst.$array[dst.$offset + i] = src.charCodeAt(i); - } - return n; -}; - -var $copySlice = function(dst, src) { - var n = Math.min(src.$length, dst.$length); - $copyArray(dst.$array, src.$array, dst.$offset, src.$offset, n, dst.constructor.elem); - return n; -}; - -var $copyArray = function(dst, src, dstOffset, srcOffset, n, elem) { - if (n === 0 || (dst === src && dstOffset === srcOffset)) { - return; - } - - if (src.subarray) { - dst.set(src.subarray(srcOffset, srcOffset + n), dstOffset); - return; - } - - switch (elem.kind) { - case $kindArray: - case $kindStruct: - if (dst === src && dstOffset > srcOffset) { - for (var i = n - 1; i >= 0; i--) { - elem.copy(dst[dstOffset + i], src[srcOffset + i]); - } - return; - } - for (var i = 0; i < n; i++) { - elem.copy(dst[dstOffset + i], src[srcOffset + i]); - } - return; - } - - if (dst === src && dstOffset > srcOffset) { - for (var i = n - 1; i >= 0; i--) { - dst[dstOffset + i] = src[srcOffset + i]; - } - return; - } - for (var i = 0; i < n; i++) { - dst[dstOffset + i] = src[srcOffset + i]; - } -}; - -var $clone = function(src, type) { - var clone = type.zero(); - type.copy(clone, src); - return clone; -}; - -var $pointerOfStructConversion = function(obj, type) { - if(obj.$proxies === undefined) { - obj.$proxies = {}; - obj.$proxies[obj.constructor.string] = obj; - } - var proxy = obj.$proxies[type.string]; - if (proxy === undefined) { - var properties = {}; - for (var i = 0; i < type.elem.fields.length; i++) { - (function(fieldProp) { - properties[fieldProp] = { - get: function() { return obj[fieldProp]; }, - set: function(value) { obj[fieldProp] = value; } - }; - })(type.elem.fields[i].prop); - } - proxy = Object.create(type.prototype, properties); - proxy.$val = proxy; - obj.$proxies[type.string] = proxy; - proxy.$proxies = obj.$proxies; - } - return proxy; -}; - -var $append = function(slice) { - return $internalAppend(slice, arguments, 1, arguments.length - 1); -}; - -var $appendSlice = function(slice, toAppend) { - if (toAppend.constructor === String) { - var bytes = $stringToBytes(toAppend); - return $internalAppend(slice, bytes, 0, bytes.length); - } - return $internalAppend(slice, toAppend.$array, toAppend.$offset, toAppend.$length); -}; - -var $internalAppend = function(slice, array, offset, length) { - if (length === 0) { - return slice; - } - - var newArray = slice.$array; - var newOffset = slice.$offset; - var newLength = slice.$length + length; - var newCapacity = slice.$capacity; - - if (newLength > newCapacity) { - newOffset = 0; - newCapacity = Math.max(newLength, slice.$capacity < 1024 ? slice.$capacity * 2 : Math.floor(slice.$capacity * 5 / 4)); - - if (slice.$array.constructor === Array) { - newArray = slice.$array.slice(slice.$offset, slice.$offset + slice.$length); - newArray.length = newCapacity; - var zero = slice.constructor.elem.zero; - for (var i = slice.$length; i < newCapacity; i++) { - newArray[i] = zero(); - } - } else { - newArray = new slice.$array.constructor(newCapacity); - newArray.set(slice.$array.subarray(slice.$offset, slice.$offset + slice.$length)); - } - } - - $copyArray(newArray, array, newOffset + slice.$length, offset, length, slice.constructor.elem); - - var newSlice = new slice.constructor(newArray); - newSlice.$offset = newOffset; - newSlice.$length = newLength; - newSlice.$capacity = newCapacity; - return newSlice; -}; - -var $equal = function(a, b, type) { - if (type === $jsObjectPtr) { - return a === b; - } - switch (type.kind) { - case $kindComplex64: - case $kindComplex128: - return a.$real === b.$real && a.$imag === b.$imag; - case $kindInt64: - case $kindUint64: - return a.$high === b.$high && a.$low === b.$low; - case $kindArray: - if (a.length !== b.length) { - return false; - } - for (var i = 0; i < a.length; i++) { - if (!$equal(a[i], b[i], type.elem)) { - return false; - } - } - return true; - case $kindStruct: - for (var i = 0; i < type.fields.length; i++) { - var f = type.fields[i]; - if (!$equal(a[f.prop], b[f.prop], f.typ)) { - return false; - } - } - return true; - case $kindInterface: - return $interfaceIsEqual(a, b); - default: - return a === b; - } -}; - -var $interfaceIsEqual = function(a, b) { - if (a === $ifaceNil || b === $ifaceNil) { - return a === b; - } - if (a.constructor !== b.constructor) { - return false; - } - if (a.constructor === $jsObjectPtr) { - return a.object === b.object; - } - if (!a.constructor.comparable) { - $throwRuntimeError("comparing uncomparable type " + a.constructor.string); - } - return $equal(a.$val, b.$val, a.constructor); -}; - -var $unsafeMethodToFunction = function(typ, name, isPtr) { - if (isPtr) { - return function(r, ...args) { - var ptrType = $ptrType(typ); - if (r.constructor != ptrType) { - switch (typ.kind) { - case $kindStruct: - r = $pointerOfStructConversion(r, ptrType); - break; - case $kindArray: - r = new ptrType(r); - break; - default: - r = new ptrType(r.$get,r.$set,r.$target); - } - } - return r[name](...args); - } - } else { - return function(r, ...args) { - var ptrType = $ptrType(typ); - if (r.constructor != ptrType) { - switch (typ.kind) { - case $kindStruct: - r = $clone(r, typ); - break; - case $kindSlice: - r = $convertSliceType(r, typ); - break; - case $kindComplex64: - case $kindComplex128: - r = new typ(r.$real, r.$imag); - break; - default: - r = new typ(r); - } - } - return r[name](...args); - } - } -}; - -var $id = function(x) { - return x; -}; - -var $instanceOf = function(x, y) { - return x instanceof y; -}; - -var $typeOf = function(x) { - return typeof(x); -}; -` diff --git a/compiler/prelude/prelude.js b/compiler/prelude/prelude.js new file mode 100644 index 000000000..d35de6b01 --- /dev/null +++ b/compiler/prelude/prelude.js @@ -0,0 +1,571 @@ +Error.stackTraceLimit = Infinity; + +var $NaN = NaN; +var $global, $module; +if (typeof window !== "undefined") { /* web page */ + $global = window; +} else if (typeof self !== "undefined") { /* web worker */ + $global = self; +} else if (typeof global !== "undefined") { /* Node.js */ + $global = global; + $global.require = require; +} else { /* others (e.g. Nashorn) */ + $global = this; +} + +if ($global === undefined || $global.Array === undefined) { + throw new Error("no global object found"); +} +if (typeof module !== "undefined") { + $module = module; +} + +if (!$global.fs && $global.require) { + try { + var fs = $global.require('fs'); + if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { + $global.fs = fs; + } + } catch (e) { /* Ignore if the module couldn't be loaded. */ } +} + +if (!$global.fs) { + var outputBuf = ""; + var 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 }, // unused + writeSync: function writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + var nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write: function write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + var n = this.writeSync(fd, buf); + callback(null, n); + } + }; +} + +var $linknames = {} // Collection of functions referenced by a go:linkname directive. +var $packages = {}, $idCounter = 0; +var $keys = m => { return m ? Object.keys(m) : []; }; +var $flushConsole = () => { }; +var $throwRuntimeError; /* set by package "runtime" */ +var $throwNilPointerError = () => { $throwRuntimeError("invalid memory address or nil pointer dereference"); }; +var $call = (fn, rcvr, args) => { return fn.apply(rcvr, args); }; +var $makeFunc = fn => { return function(...args) { return $externalize(fn(this, new ($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(args, []))), $emptyInterface); }; }; +var $unused = v => { }; +var $print = console.log; +// Under Node we can emulate print() more closely by avoiding a newline. +if (($global.process !== undefined) && $global.require) { + try { + var util = $global.require('util'); + $print = function(...args) { $global.process.stderr.write(util.format.apply(this, args)); }; + } catch (e) { + // Failed to require util module, keep using console.log(). + } +} +var $println = console.log + +var $initAllLinknames = () => { + var names = $keys($packages); + for (var i = 0; i < names.length; i++) { + var f = $packages[names[i]]["$initLinknames"]; + if (typeof f == 'function') { + f(); + } + } +} + +var $mapArray = (array, f) => { + var newArray = new array.constructor(array.length); + for (var i = 0; i < array.length; i++) { + newArray[i] = f(array[i]); + } + return newArray; +}; + +// $mapIndex returns the value of the given key in m, or undefined if m is nil/undefined or not a map +var $mapIndex = (m, key) => { + return typeof m.get === "function" ? m.get(key) : undefined; +}; +// $mapDelete deletes the key and associated value from m. If m is nil/undefined or not a map, $mapDelete is a no-op +var $mapDelete = (m, key) => { + typeof m.delete === "function" && m.delete(key) +}; +// Returns a method bound to the receiver instance, safe to invoke as a +// standalone function. Bound function is cached for later reuse. +var $methodVal = (recv, name) => { + var vals = recv.$methodVals || {}; + recv.$methodVals = vals; /* noop for primitives */ + var f = vals[name]; + if (f !== undefined) { + return f; + } + var method = recv[name]; + f = method.bind(recv); + vals[name] = f; + return f; +}; + +var $methodExpr = (typ, name) => { + var method = typ.prototype[name]; + if (method.$expr === undefined) { + method.$expr = (...args) => { + $stackDepthOffset--; + try { + if (typ.wrapped) { + args[0] = new typ(args[0]); + } + return Function.call.apply(method, args); + } finally { + $stackDepthOffset++; + } + }; + } + return method.$expr; +}; + +var $ifaceMethodExprs = {}; +var $ifaceMethodExpr = name => { + var expr = $ifaceMethodExprs["$" + name]; + if (expr === undefined) { + expr = $ifaceMethodExprs["$" + name] = (...args) => { + $stackDepthOffset--; + try { + return Function.call.apply(args[0][name], args); + } finally { + $stackDepthOffset++; + } + }; + } + return expr; +}; + +var $subslice = (slice, low, high, max) => { + if (high === undefined) { + high = slice.$length; + } + if (max === undefined) { + max = slice.$capacity; + } + if (low < 0 || high < low || max < high || high > slice.$capacity || max > slice.$capacity) { + $throwRuntimeError("slice bounds out of range"); + } + if (slice === slice.constructor.nil) { + return slice; + } + var s = new slice.constructor(slice.$array); + s.$offset = slice.$offset + low; + s.$length = high - low; + s.$capacity = max - low; + return s; +}; + +var $substring = (str, low, high) => { + if (low < 0 || high < low || high > str.length) { + $throwRuntimeError("slice bounds out of range"); + } + return str.substring(low, high); +}; + +// Convert Go slice to an equivalent JS array type. +var $sliceToNativeArray = slice => { + if (slice.$array.constructor !== Array) { + return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length); + } + return slice.$array.slice(slice.$offset, slice.$offset + slice.$length); +}; + +// Convert Go slice to a pointer to an underlying Go array. +// +// Note that an array pointer can be represented by an "unwrapped" native array +// type, and it will be wrapped back into its Go type when necessary. +var $sliceToGoArray = (slice, arrayPtrType) => { + var arrayType = arrayPtrType.elem; + if (arrayType !== undefined && slice.$length < arrayType.len) { + $throwRuntimeError("cannot convert slice with length " + slice.$length + " to pointer to array with length " + arrayType.len); + } + if (slice == slice.constructor.nil) { + return arrayPtrType.nil; // Nil slice converts to nil array pointer. + } + if (slice.$array.constructor !== Array) { + return slice.$array.subarray(slice.$offset, slice.$offset + arrayType.len); + } + if (slice.$offset == 0 && slice.$length == slice.$capacity && slice.$length == arrayType.len) { + return slice.$array; + } + if (arrayType.len == 0) { + return new arrayType([]); + } + + // Array.slice (unlike TypedArray.subarray) returns a copy of an array range, + // which is not sharing memory with the original one, which violates the spec + // for slice to array conversion. This is incompatible with the Go spec, in + // particular that the assignments to the array elements would be visible in + // the slice. Prefer to fail explicitly instead of creating subtle bugs. + $throwRuntimeError("gopherjs: non-numeric slice to underlying array conversion is not supported for subslices"); +}; + +// Convert between compatible slice types (e.g. native and names). +var $convertSliceType = (slice, desiredType) => { + if (slice == slice.constructor.nil) { + return desiredType.nil; // Preserve nil value. + } + + return $subslice(new desiredType(slice.$array), slice.$offset, slice.$offset + slice.$length); +} + +var $decodeRune = (str, pos) => { + var c0 = str.charCodeAt(pos); + + if (c0 < 0x80) { + return [c0, 1]; + } + + if (c0 !== c0 || c0 < 0xC0) { + return [0xFFFD, 1]; + } + + var c1 = str.charCodeAt(pos + 1); + if (c1 !== c1 || c1 < 0x80 || 0xC0 <= c1) { + return [0xFFFD, 1]; + } + + if (c0 < 0xE0) { + var r = (c0 & 0x1F) << 6 | (c1 & 0x3F); + if (r <= 0x7F) { + return [0xFFFD, 1]; + } + return [r, 2]; + } + + var c2 = str.charCodeAt(pos + 2); + if (c2 !== c2 || c2 < 0x80 || 0xC0 <= c2) { + return [0xFFFD, 1]; + } + + if (c0 < 0xF0) { + var r = (c0 & 0x0F) << 12 | (c1 & 0x3F) << 6 | (c2 & 0x3F); + if (r <= 0x7FF) { + return [0xFFFD, 1]; + } + if (0xD800 <= r && r <= 0xDFFF) { + return [0xFFFD, 1]; + } + return [r, 3]; + } + + var c3 = str.charCodeAt(pos + 3); + if (c3 !== c3 || c3 < 0x80 || 0xC0 <= c3) { + return [0xFFFD, 1]; + } + + if (c0 < 0xF8) { + var r = (c0 & 0x07) << 18 | (c1 & 0x3F) << 12 | (c2 & 0x3F) << 6 | (c3 & 0x3F); + if (r <= 0xFFFF || 0x10FFFF < r) { + return [0xFFFD, 1]; + } + return [r, 4]; + } + + return [0xFFFD, 1]; +}; + +var $encodeRune = r => { + if (r < 0 || r > 0x10FFFF || (0xD800 <= r && r <= 0xDFFF)) { + r = 0xFFFD; + } + if (r <= 0x7F) { + return String.fromCharCode(r); + } + if (r <= 0x7FF) { + return String.fromCharCode(0xC0 | r >> 6, 0x80 | (r & 0x3F)); + } + if (r <= 0xFFFF) { + return String.fromCharCode(0xE0 | r >> 12, 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); + } + return String.fromCharCode(0xF0 | r >> 18, 0x80 | (r >> 12 & 0x3F), 0x80 | (r >> 6 & 0x3F), 0x80 | (r & 0x3F)); +}; + +var $stringToBytes = str => { + var array = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + array[i] = str.charCodeAt(i); + } + return array; +}; + +var $bytesToString = slice => { + if (slice.$length === 0) { + return ""; + } + var str = ""; + for (var i = 0; i < slice.$length; i += 10000) { + str += String.fromCharCode.apply(undefined, slice.$array.subarray(slice.$offset + i, slice.$offset + Math.min(slice.$length, i + 10000))); + } + return str; +}; + +var $stringToRunes = str => { + var array = new Int32Array(str.length); + var rune, j = 0; + for (var i = 0; i < str.length; i += rune[1], j++) { + rune = $decodeRune(str, i); + array[j] = rune[0]; + } + return array.subarray(0, j); +}; + +var $runesToString = slice => { + if (slice.$length === 0) { + return ""; + } + var str = ""; + for (var i = 0; i < slice.$length; i++) { + str += $encodeRune(slice.$array[slice.$offset + i]); + } + return str; +}; + +var $copyString = (dst, src) => { + var n = Math.min(src.length, dst.$length); + for (var i = 0; i < n; i++) { + dst.$array[dst.$offset + i] = src.charCodeAt(i); + } + return n; +}; + +var $copySlice = (dst, src) => { + var n = Math.min(src.$length, dst.$length); + $copyArray(dst.$array, src.$array, dst.$offset, src.$offset, n, dst.constructor.elem); + return n; +}; + +var $copyArray = (dst, src, dstOffset, srcOffset, n, elem) => { + if (n === 0 || (dst === src && dstOffset === srcOffset)) { + return; + } + + if (src.subarray) { + dst.set(src.subarray(srcOffset, srcOffset + n), dstOffset); + return; + } + + switch (elem.kind) { + case $kindArray: + case $kindStruct: + if (dst === src && dstOffset > srcOffset) { + for (var i = n - 1; i >= 0; i--) { + elem.copy(dst[dstOffset + i], src[srcOffset + i]); + } + return; + } + for (var i = 0; i < n; i++) { + elem.copy(dst[dstOffset + i], src[srcOffset + i]); + } + return; + } + + if (dst === src && dstOffset > srcOffset) { + for (var i = n - 1; i >= 0; i--) { + dst[dstOffset + i] = src[srcOffset + i]; + } + return; + } + for (var i = 0; i < n; i++) { + dst[dstOffset + i] = src[srcOffset + i]; + } +}; + +var $clone = (src, type) => { + var clone = type.zero(); + type.copy(clone, src); + return clone; +}; + +var $pointerOfStructConversion = (obj, type) => { + if (obj.$proxies === undefined) { + obj.$proxies = {}; + obj.$proxies[obj.constructor.string] = obj; + } + var proxy = obj.$proxies[type.string]; + if (proxy === undefined) { + var properties = {}; + for (var i = 0; i < type.elem.fields.length; i++) { + (fieldProp => { + properties[fieldProp] = { + get() { return obj[fieldProp]; }, + set(value) { obj[fieldProp] = value; } + }; + })(type.elem.fields[i].prop); + } + proxy = Object.create(type.prototype, properties); + proxy.$val = proxy; + obj.$proxies[type.string] = proxy; + proxy.$proxies = obj.$proxies; + } + return proxy; +}; + +var $append = function (slice) { + return $internalAppend(slice, arguments, 1, arguments.length - 1); +}; + +var $appendSlice = (slice, toAppend) => { + if (toAppend.constructor === String) { + var bytes = $stringToBytes(toAppend); + return $internalAppend(slice, bytes, 0, bytes.length); + } + return $internalAppend(slice, toAppend.$array, toAppend.$offset, toAppend.$length); +}; + +var $internalAppend = (slice, array, offset, length) => { + if (length === 0) { + return slice; + } + + var newArray = slice.$array; + var newOffset = slice.$offset; + var newLength = slice.$length + length; + var newCapacity = slice.$capacity; + + if (newLength > newCapacity) { + newOffset = 0; + newCapacity = Math.max(newLength, slice.$capacity < 1024 ? slice.$capacity * 2 : Math.floor(slice.$capacity * 5 / 4)); + + if (slice.$array.constructor === Array) { + newArray = slice.$array.slice(slice.$offset, slice.$offset + slice.$length); + newArray.length = newCapacity; + var zero = slice.constructor.elem.zero; + for (var i = slice.$length; i < newCapacity; i++) { + newArray[i] = zero(); + } + } else { + newArray = new slice.$array.constructor(newCapacity); + newArray.set(slice.$array.subarray(slice.$offset, slice.$offset + slice.$length)); + } + } + + $copyArray(newArray, array, newOffset + slice.$length, offset, length, slice.constructor.elem); + + var newSlice = new slice.constructor(newArray); + newSlice.$offset = newOffset; + newSlice.$length = newLength; + newSlice.$capacity = newCapacity; + return newSlice; +}; + +var $equal = (a, b, type) => { + if (type === $jsObjectPtr) { + return a === b; + } + switch (type.kind) { + case $kindComplex64: + case $kindComplex128: + return a.$real === b.$real && a.$imag === b.$imag; + case $kindInt64: + case $kindUint64: + return a.$high === b.$high && a.$low === b.$low; + case $kindArray: + if (a.length !== b.length) { + return false; + } + for (var i = 0; i < a.length; i++) { + if (!$equal(a[i], b[i], type.elem)) { + return false; + } + } + return true; + case $kindStruct: + for (var i = 0; i < type.fields.length; i++) { + var f = type.fields[i]; + if (!$equal(a[f.prop], b[f.prop], f.typ)) { + return false; + } + } + return true; + case $kindInterface: + return $interfaceIsEqual(a, b); + default: + return a === b; + } +}; + +var $interfaceIsEqual = (a, b) => { + if (a === $ifaceNil || b === $ifaceNil) { + return a === b; + } + if (a.constructor !== b.constructor) { + return false; + } + if (a.constructor === $jsObjectPtr) { + return a.object === b.object; + } + if (!a.constructor.comparable) { + $throwRuntimeError("comparing uncomparable type " + a.constructor.string); + } + return $equal(a.$val, b.$val, a.constructor); +}; + +var $unsafeMethodToFunction = (typ, name, isPtr) => { + if (isPtr) { + return (r, ...args) => { + var ptrType = $ptrType(typ); + if (r.constructor != ptrType) { + switch (typ.kind) { + case $kindStruct: + r = $pointerOfStructConversion(r, ptrType); + break; + case $kindArray: + r = new ptrType(r); + break; + default: + r = new ptrType(r.$get, r.$set, r.$target); + } + } + return r[name](...args); + }; + } else { + return (r, ...args) => { + var ptrType = $ptrType(typ); + if (r.constructor != ptrType) { + switch (typ.kind) { + case $kindStruct: + r = $clone(r, typ); + break; + case $kindSlice: + r = $convertSliceType(r, typ); + break; + case $kindComplex64: + case $kindComplex128: + r = new typ(r.$real, r.$imag); + break; + default: + r = new typ(r); + } + } + return r[name](...args); + }; + } +}; + +var $id = x => { + return x; +}; + +var $instanceOf = (x, y) => { + return x instanceof y; +}; + +var $typeOf = x => { + return typeof (x); +}; diff --git a/compiler/prelude/prelude_min.go b/compiler/prelude/prelude_min.go deleted file mode 100644 index 9cd74945c..000000000 --- a/compiler/prelude/prelude_min.go +++ /dev/null @@ -1,6 +0,0 @@ -// Code generated by genmin; DO NOT EDIT. - -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:if(0===n.fields.length)return p;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" diff --git a/compiler/prelude/types.go b/compiler/prelude/types.go deleted file mode 100644 index 69afe8af1..000000000 --- a/compiler/prelude/types.go +++ /dev/null @@ -1,774 +0,0 @@ -package prelude - -const types = ` -var $kindBool = 1; -var $kindInt = 2; -var $kindInt8 = 3; -var $kindInt16 = 4; -var $kindInt32 = 5; -var $kindInt64 = 6; -var $kindUint = 7; -var $kindUint8 = 8; -var $kindUint16 = 9; -var $kindUint32 = 10; -var $kindUint64 = 11; -var $kindUintptr = 12; -var $kindFloat32 = 13; -var $kindFloat64 = 14; -var $kindComplex64 = 15; -var $kindComplex128 = 16; -var $kindArray = 17; -var $kindChan = 18; -var $kindFunc = 19; -var $kindInterface = 20; -var $kindMap = 21; -var $kindPtr = 22; -var $kindSlice = 23; -var $kindString = 24; -var $kindStruct = 25; -var $kindUnsafePointer = 26; - -var $methodSynthesizers = []; -var $addMethodSynthesizer = function(f) { - if ($methodSynthesizers === null) { - f(); - return; - } - $methodSynthesizers.push(f); -}; -var $synthesizeMethods = function() { - $methodSynthesizers.forEach(function(f) { f(); }); - $methodSynthesizers = null; -}; - -var $ifaceKeyFor = function(x) { - if (x === $ifaceNil) { - return 'nil'; - } - var c = x.constructor; - return c.string + '$' + c.keyFor(x.$val); -}; - -var $identity = function(x) { return x; }; - -var $typeIDCounter = 0; - -var $idKey = function(x) { - if (x.$id === undefined) { - $idCounter++; - x.$id = $idCounter; - } - return String(x.$id); -}; - -// Creates constructor functions for array pointer types. Returns a new function -// instace each time to make sure each type is independent of the other. -var $arrayPtrCtor = function() { - return function(array) { - this.$get = function() { return array; }; - this.$set = function(v) { typ.copy(this, v); }; - this.$val = array; - } -} - -var $newType = function(size, kind, string, named, pkg, exported, constructor) { - var typ; - switch(kind) { - case $kindBool: - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8: - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindUnsafePointer: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = $identity; - break; - - case $kindString: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = function(x) { return "$" + x; }; - break; - - case $kindFloat32: - case $kindFloat64: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = function(x) { return $floatKey(x); }; - break; - - case $kindInt64: - typ = function(high, low) { - this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >> 0; - this.$low = low >>> 0; - this.$val = this; - }; - typ.keyFor = function(x) { return x.$high + "$" + x.$low; }; - break; - - case $kindUint64: - typ = function(high, low) { - this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >>> 0; - this.$low = low >>> 0; - this.$val = this; - }; - typ.keyFor = function(x) { return x.$high + "$" + x.$low; }; - break; - - case $kindComplex64: - typ = function(real, imag) { - this.$real = $fround(real); - this.$imag = $fround(imag); - this.$val = this; - }; - typ.keyFor = function(x) { return x.$real + "$" + x.$imag; }; - break; - - case $kindComplex128: - typ = function(real, imag) { - this.$real = real; - this.$imag = imag; - this.$val = this; - }; - typ.keyFor = function(x) { return x.$real + "$" + x.$imag; }; - break; - - case $kindArray: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor()); - typ.init = function(elem, len) { - typ.elem = elem; - typ.len = len; - typ.comparable = elem.comparable; - typ.keyFor = function(x) { - return Array.prototype.join.call($mapArray(x, function(e) { - return String(elem.keyFor(e)).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); - }), "$"); - }; - typ.copy = function(dst, src) { - $copyArray(dst, src, 0, 0, src.length, elem); - }; - typ.ptr.init(typ); - Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError }); - }; - break; - - case $kindChan: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.keyFor = $idKey; - typ.init = function(elem, sendOnly, recvOnly) { - typ.elem = elem; - typ.sendOnly = sendOnly; - typ.recvOnly = recvOnly; - }; - break; - - case $kindFunc: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.init = function(params, results, variadic) { - typ.params = params; - typ.results = results; - typ.variadic = variadic; - typ.comparable = false; - }; - break; - - case $kindInterface: - typ = { implementedBy: {}, missingMethodFor: {} }; - typ.keyFor = $ifaceKeyFor; - typ.init = function(methods) { - typ.methods = methods; - methods.forEach(function(m) { - $ifaceNil[m.prop] = $throwNilPointerError; - }); - }; - break; - - case $kindMap: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.init = function(key, elem) { - typ.key = key; - typ.elem = elem; - typ.comparable = false; - }; - break; - - case $kindPtr: - typ = constructor || function(getter, setter, target) { - this.$get = getter; - this.$set = setter; - this.$target = target; - this.$val = this; - }; - typ.keyFor = $idKey; - typ.init = function(elem) { - typ.elem = elem; - typ.wrapped = (elem.kind === $kindArray); - typ.nil = new typ($throwNilPointerError, $throwNilPointerError); - }; - break; - - case $kindSlice: - typ = function(array) { - if (array.constructor !== typ.nativeArray) { - array = new typ.nativeArray(array); - } - this.$array = array; - this.$offset = 0; - this.$length = array.length; - this.$capacity = array.length; - this.$val = this; - }; - typ.init = function(elem) { - typ.elem = elem; - typ.comparable = false; - typ.nativeArray = $nativeArray(elem.kind); - typ.nil = new typ([]); - }; - break; - - case $kindStruct: - typ = function(v) { this.$val = v; }; - typ.wrapped = true; - typ.ptr = $newType(4, $kindPtr, "*" + string, false, pkg, exported, constructor); - typ.ptr.elem = typ; - typ.ptr.prototype.$get = function() { return this; }; - typ.ptr.prototype.$set = function(v) { typ.copy(this, v); }; - typ.init = function(pkgPath, fields) { - typ.pkgPath = pkgPath; - typ.fields = fields; - fields.forEach(function(f) { - if (!f.typ.comparable) { - typ.comparable = false; - } - }); - typ.keyFor = function(x) { - var val = x.$val; - return $mapArray(fields, function(f) { - return String(f.typ.keyFor(val[f.prop])).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); - }).join("$"); - }; - typ.copy = function(dst, src) { - for (var i = 0; i < fields.length; i++) { - var f = fields[i]; - switch (f.typ.kind) { - case $kindArray: - case $kindStruct: - f.typ.copy(dst[f.prop], src[f.prop]); - continue; - default: - dst[f.prop] = src[f.prop]; - continue; - } - } - }; - /* nil value */ - var properties = {}; - fields.forEach(function(f) { - properties[f.prop] = { get: $throwNilPointerError, set: $throwNilPointerError }; - }); - typ.ptr.nil = Object.create(constructor.prototype, properties); - typ.ptr.nil.$val = typ.ptr.nil; - /* methods for embedded fields */ - $addMethodSynthesizer(function() { - var synthesizeMethod = function(target, m, f) { - if (target.prototype[m.prop] !== undefined) { return; } - target.prototype[m.prop] = function() { - var v = this.$val[f.prop]; - if (f.typ === $jsObjectPtr) { - v = new $jsObjectPtr(v); - } - if (v.$val === undefined) { - v = new f.typ(v); - } - return v[m.prop].apply(v, arguments); - }; - }; - fields.forEach(function(f) { - if (f.embedded) { - $methodSet(f.typ).forEach(function(m) { - synthesizeMethod(typ, m, f); - synthesizeMethod(typ.ptr, m, f); - }); - $methodSet($ptrType(f.typ)).forEach(function(m) { - synthesizeMethod(typ.ptr, m, f); - }); - } - }); - }); - }; - break; - - default: - $panic(new $String("invalid kind: " + kind)); - } - - switch (kind) { - case $kindBool: - case $kindMap: - typ.zero = function() { return false; }; - break; - - case $kindInt: - case $kindInt8: - case $kindInt16: - case $kindInt32: - case $kindUint: - case $kindUint8 : - case $kindUint16: - case $kindUint32: - case $kindUintptr: - case $kindUnsafePointer: - case $kindFloat32: - case $kindFloat64: - typ.zero = function() { return 0; }; - break; - - case $kindString: - typ.zero = function() { return ""; }; - break; - - case $kindInt64: - case $kindUint64: - case $kindComplex64: - case $kindComplex128: - var zero = new typ(0, 0); - typ.zero = function() { return zero; }; - break; - - case $kindPtr: - case $kindSlice: - typ.zero = function() { return typ.nil; }; - break; - - case $kindChan: - typ.zero = function() { return $chanNil; }; - break; - - case $kindFunc: - typ.zero = function() { return $throwNilPointerError; }; - break; - - case $kindInterface: - typ.zero = function() { return $ifaceNil; }; - break; - - case $kindArray: - typ.zero = function() { - var arrayClass = $nativeArray(typ.elem.kind); - if (arrayClass !== Array) { - return new arrayClass(typ.len); - } - var array = new Array(typ.len); - for (var i = 0; i < typ.len; i++) { - array[i] = typ.elem.zero(); - } - return array; - }; - break; - - case $kindStruct: - typ.zero = function() { return new typ.ptr(); }; - break; - - default: - $panic(new $String("invalid kind: " + kind)); - } - - typ.id = $typeIDCounter; - $typeIDCounter++; - typ.size = size; - typ.kind = kind; - typ.string = string; - typ.named = named; - typ.pkg = pkg; - typ.exported = exported; - typ.methods = []; - typ.methodSetCache = null; - typ.comparable = true; - return typ; -}; - -var $methodSet = function(typ) { - if (typ.methodSetCache !== null) { - return typ.methodSetCache; - } - var base = {}; - - var isPtr = (typ.kind === $kindPtr); - if (isPtr && typ.elem.kind === $kindInterface) { - typ.methodSetCache = []; - return []; - } - - var current = [{typ: isPtr ? typ.elem : typ, indirect: isPtr}]; - - var seen = {}; - - while (current.length > 0) { - var next = []; - var mset = []; - - current.forEach(function(e) { - if (seen[e.typ.string]) { - return; - } - seen[e.typ.string] = true; - - if (e.typ.named) { - mset = mset.concat(e.typ.methods); - if (e.indirect) { - mset = mset.concat($ptrType(e.typ).methods); - } - } - - switch (e.typ.kind) { - case $kindStruct: - e.typ.fields.forEach(function(f) { - if (f.embedded) { - var fTyp = f.typ; - var fIsPtr = (fTyp.kind === $kindPtr); - next.push({typ: fIsPtr ? fTyp.elem : fTyp, indirect: e.indirect || fIsPtr}); - } - }); - break; - - case $kindInterface: - mset = mset.concat(e.typ.methods); - break; - } - }); - - mset.forEach(function(m) { - if (base[m.name] === undefined) { - base[m.name] = m; - } - }); - - current = next; - } - - typ.methodSetCache = []; - Object.keys(base).sort().forEach(function(name) { - typ.methodSetCache.push(base[name]); - }); - return typ.methodSetCache; -}; - -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); -var $Int16 = $newType( 2, $kindInt16, "int16", true, "", false, null); -var $Int32 = $newType( 4, $kindInt32, "int32", true, "", false, null); -var $Int64 = $newType( 8, $kindInt64, "int64", true, "", false, null); -var $Uint = $newType( 4, $kindUint, "uint", true, "", false, null); -var $Uint8 = $newType( 1, $kindUint8, "uint8", true, "", false, null); -var $Uint16 = $newType( 2, $kindUint16, "uint16", true, "", false, null); -var $Uint32 = $newType( 4, $kindUint32, "uint32", true, "", false, null); -var $Uint64 = $newType( 8, $kindUint64, "uint64", true, "", false, null); -var $Uintptr = $newType( 4, $kindUintptr, "uintptr", true, "", false, null); -var $Float32 = $newType( 4, $kindFloat32, "float32", true, "", false, null); -var $Float64 = $newType( 8, $kindFloat64, "float64", true, "", false, null); -var $Complex64 = $newType( 8, $kindComplex64, "complex64", true, "", false, null); -var $Complex128 = $newType(16, $kindComplex128, "complex128", true, "", false, null); -var $String = $newType( 8, $kindString, "string", true, "", false, null); -var $UnsafePointer = $newType( 4, $kindUnsafePointer, "unsafe.Pointer", true, "unsafe", false, null); - -var $nativeArray = function(elemKind) { - switch (elemKind) { - 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: - return Uint32Array; - case $kindUintptr: - return Uint32Array; - case $kindFloat32: - return Float32Array; - case $kindFloat64: - return Float64Array; - default: - return Array; - } -}; -var $toNativeArray = function(elemKind, array) { - var nativeArray = $nativeArray(elemKind); - if (nativeArray === Array) { - return array; - } - return new nativeArray(array); -}; -var $arrayTypes = {}; -var $arrayType = function(elem, len) { - var typeKey = elem.id + "$" + len; - var typ = $arrayTypes[typeKey]; - if (typ === undefined) { - typ = $newType(elem.size*len, $kindArray, "[" + len + "]" + elem.string, false, "", false, null); - $arrayTypes[typeKey] = typ; - typ.init(elem, len); - } - return typ; -}; - -var $chanType = function(elem, sendOnly, recvOnly) { - var string = (recvOnly ? "<-" : "") + "chan" + (sendOnly ? "<- " : " "); - if (!sendOnly && !recvOnly && (elem.string[0] == "<")) { - string += "(" + elem.string + ")"; - } else { - string += elem.string; - } - var field = sendOnly ? "SendChan" : (recvOnly ? "RecvChan" : "Chan"); - var typ = elem[field]; - if (typ === undefined) { - typ = $newType(4, $kindChan, string, false, "", false, null); - elem[field] = typ; - typ.init(elem, sendOnly, recvOnly); - } - return typ; -}; -var $Chan = function(elem, capacity) { - if (capacity < 0 || capacity > 2147483647) { - $throwRuntimeError("makechan: size out of range"); - } - this.$elem = elem; - this.$capacity = capacity; - this.$buffer = []; - this.$sendQueue = []; - this.$recvQueue = []; - this.$closed = false; -}; -var $chanNil = new $Chan(null, 0); -$chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push: function() {}, shift: function() { return undefined; }, indexOf: function() { return -1; } }; - -var $funcTypes = {}; -var $funcType = function(params, results, variadic) { - var typeKey = $mapArray(params, function(p) { return p.id; }).join(",") + "$" + $mapArray(results, function(r) { return r.id; }).join(",") + "$" + variadic; - var typ = $funcTypes[typeKey]; - if (typ === undefined) { - var paramTypes = $mapArray(params, function(p) { return p.string; }); - if (variadic) { - paramTypes[paramTypes.length - 1] = "..." + paramTypes[paramTypes.length - 1].substr(2); - } - var string = "func(" + paramTypes.join(", ") + ")"; - if (results.length === 1) { - string += " " + results[0].string; - } else if (results.length > 1) { - string += " (" + $mapArray(results, function(r) { return r.string; }).join(", ") + ")"; - } - typ = $newType(4, $kindFunc, string, false, "", false, null); - $funcTypes[typeKey] = typ; - typ.init(params, results, variadic); - } - return typ; -}; - -var $interfaceTypes = {}; -var $interfaceType = function(methods) { - var typeKey = $mapArray(methods, function(m) { return m.pkg + "," + m.name + "," + m.typ.id; }).join("$"); - var typ = $interfaceTypes[typeKey]; - if (typ === undefined) { - var string = "interface {}"; - if (methods.length !== 0) { - string = "interface { " + $mapArray(methods, function(m) { - return (m.pkg !== "" ? m.pkg + "." : "") + m.name + m.typ.string.substr(4); - }).join("; ") + " }"; - } - typ = $newType(8, $kindInterface, string, false, "", false, null); - $interfaceTypes[typeKey] = typ; - typ.init(methods); - } - return typ; -}; -var $emptyInterface = $interfaceType([]); -var $ifaceNil = {}; -var $error = $newType(8, $kindInterface, "error", true, "", false, null); -$error.init([{prop: "Error", name: "Error", pkg: "", typ: $funcType([], [$String], false)}]); - -var $mapTypes = {}; -var $mapType = function(key, elem) { - var typeKey = key.id + "$" + elem.id; - var typ = $mapTypes[typeKey]; - if (typ === undefined) { - typ = $newType(4, $kindMap, "map[" + key.string + "]" + elem.string, false, "", false, null); - $mapTypes[typeKey] = typ; - typ.init(key, elem); - } - return typ; -}; -var $makeMap = function(keyForFunc, entries) { - var m = new Map(); - for (var i = 0; i < entries.length; i++) { - var e = entries[i]; - m.set(keyForFunc(e.k), e); - } - return m; -}; - -var $ptrType = function(elem) { - var typ = elem.ptr; - if (typ === undefined) { - typ = $newType(4, $kindPtr, "*" + elem.string, false, "", elem.exported, null); - elem.ptr = typ; - typ.init(elem); - } - return typ; -}; - -var $newDataPointer = function(data, constructor) { - if (constructor.elem.kind === $kindStruct) { - return data; - } - return new constructor(function() { return data; }, function(v) { data = v; }); -}; - -var $indexPtr = function(array, index, constructor) { - if (array.buffer) { - // Pointers to the same underlying ArrayBuffer share cache. - var cache = array.buffer.$ptr = array.buffer.$ptr || {}; - // Pointers of different primitive types are non-comparable and stored in different caches. - var typeCache = cache[array.name] = cache[array.name] || {}; - var cacheIdx = array.BYTES_PER_ELEMENT * index + array.byteOffset; - return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; })); - } else { - array.$ptr = array.$ptr || {}; - return array.$ptr[index] || (array.$ptr[index] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; })); - } -}; - -var $sliceType = function(elem) { - var typ = elem.slice; - if (typ === undefined) { - typ = $newType(12, $kindSlice, "[]" + elem.string, false, "", false, null); - elem.slice = typ; - typ.init(elem); - } - return typ; -}; -var $makeSlice = function(typ, length, capacity) { - capacity = capacity || length; - if (length < 0 || length > 2147483647) { - $throwRuntimeError("makeslice: len out of range"); - } - if (capacity < 0 || capacity < length || capacity > 2147483647) { - $throwRuntimeError("makeslice: cap out of range"); - } - var array = new typ.nativeArray(capacity); - if (typ.nativeArray === Array) { - for (var i = 0; i < capacity; i++) { - array[i] = typ.elem.zero(); - } - } - var slice = new typ(array); - slice.$length = length; - return slice; -}; - -var $structTypes = {}; -var $structType = function(pkgPath, fields) { - var typeKey = $mapArray(fields, function(f) { return f.name + "," + f.typ.id + "," + f.tag; }).join("$"); - var typ = $structTypes[typeKey]; - if (typ === undefined) { - var string = "struct { " + $mapArray(fields, function(f) { - var str = f.typ.string + (f.tag !== "" ? (" \"" + f.tag.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"") : ""); - if (f.embedded) { - return str; - } - return f.name + " " + str; - }).join("; ") + " }"; - if (fields.length === 0) { - string = "struct {}"; - } - typ = $newType(0, $kindStruct, string, false, "", false, function() { - this.$val = this; - for (var i = 0; i < fields.length; i++) { - var f = fields[i]; - if (f.name == '_') { - continue; - } - var arg = arguments[i]; - this[f.prop] = arg !== undefined ? arg : f.typ.zero(); - } - }); - $structTypes[typeKey] = typ; - typ.init(pkgPath, fields); - } - return typ; -}; - -var $assertType = function(value, type, returnTuple) { - var isInterface = (type.kind === $kindInterface), ok, missingMethod = ""; - if (value === $ifaceNil) { - ok = false; - } else if (!isInterface) { - ok = value.constructor === type; - } else { - var valueTypeString = value.constructor.string; - ok = type.implementedBy[valueTypeString]; - if (ok === undefined) { - ok = true; - var valueMethodSet = $methodSet(value.constructor); - var interfaceMethods = type.methods; - for (var i = 0; i < interfaceMethods.length; i++) { - var tm = interfaceMethods[i]; - var found = false; - for (var j = 0; j < valueMethodSet.length; j++) { - var vm = valueMethodSet[j]; - if (vm.name === tm.name && vm.pkg === tm.pkg && vm.typ === tm.typ) { - found = true; - break; - } - } - if (!found) { - ok = false; - type.missingMethodFor[valueTypeString] = tm.name; - break; - } - } - type.implementedBy[valueTypeString] = ok; - } - if (!ok) { - missingMethod = type.missingMethodFor[valueTypeString]; - } - } - - if (!ok) { - if (returnTuple) { - return [type.zero(), false]; - } - $panic(new $packages["runtime"].TypeAssertionError.ptr( - $packages["runtime"]._type.ptr.nil, - (value === $ifaceNil ? $packages["runtime"]._type.ptr.nil : new $packages["runtime"]._type.ptr(value.constructor.string)), - new $packages["runtime"]._type.ptr(type.string), - missingMethod)); - } - - if (!isInterface) { - value = value.$val; - } - if (type === $jsObjectPtr) { - value = value.object; - } - return returnTuple ? [value, true] : value; -}; -` diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js new file mode 100644 index 000000000..61475454e --- /dev/null +++ b/compiler/prelude/types.js @@ -0,0 +1,769 @@ +var $kindBool = 1; +var $kindInt = 2; +var $kindInt8 = 3; +var $kindInt16 = 4; +var $kindInt32 = 5; +var $kindInt64 = 6; +var $kindUint = 7; +var $kindUint8 = 8; +var $kindUint16 = 9; +var $kindUint32 = 10; +var $kindUint64 = 11; +var $kindUintptr = 12; +var $kindFloat32 = 13; +var $kindFloat64 = 14; +var $kindComplex64 = 15; +var $kindComplex128 = 16; +var $kindArray = 17; +var $kindChan = 18; +var $kindFunc = 19; +var $kindInterface = 20; +var $kindMap = 21; +var $kindPtr = 22; +var $kindSlice = 23; +var $kindString = 24; +var $kindStruct = 25; +var $kindUnsafePointer = 26; + +var $methodSynthesizers = []; +var $addMethodSynthesizer = f => { + if ($methodSynthesizers === null) { + f(); + return; + } + $methodSynthesizers.push(f); +}; +var $synthesizeMethods = () => { + $methodSynthesizers.forEach(f => { f(); }); + $methodSynthesizers = null; +}; + +var $ifaceKeyFor = x => { + if (x === $ifaceNil) { + return 'nil'; + } + var c = x.constructor; + return c.string + '$' + c.keyFor(x.$val); +}; + +var $identity = x => { return x; }; + +var $typeIDCounter = 0; + +var $idKey = x => { + if (x.$id === undefined) { + $idCounter++; + x.$id = $idCounter; + } + return String(x.$id); +}; + +// Creates constructor functions for array pointer types. Returns a new function +// instace each time to make sure each type is independent of the other. +var $arrayPtrCtor = () => { + return function (array) { + this.$get = () => { return array; }; + this.$set = function (v) { typ.copy(this, v); }; + this.$val = array; + }; +} + +var $newType = (size, kind, string, named, pkg, exported, constructor) => { + var typ; + switch (kind) { + case $kindBool: + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindUnsafePointer: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = $identity; + break; + + case $kindString: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = x => { return "$" + x; }; + break; + + case $kindFloat32: + case $kindFloat64: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = x => { return $floatKey(x); }; + break; + + case $kindInt64: + typ = function (high, low) { + this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >> 0; + this.$low = low >>> 0; + this.$val = this; + }; + typ.keyFor = x => { return x.$high + "$" + x.$low; }; + break; + + case $kindUint64: + typ = function (high, low) { + this.$high = (high + Math.floor(Math.ceil(low) / 4294967296)) >>> 0; + this.$low = low >>> 0; + this.$val = this; + }; + typ.keyFor = x => { return x.$high + "$" + x.$low; }; + break; + + case $kindComplex64: + typ = function (real, imag) { + this.$real = $fround(real); + this.$imag = $fround(imag); + this.$val = this; + }; + typ.keyFor = x => { return x.$real + "$" + x.$imag; }; + break; + + case $kindComplex128: + typ = function (real, imag) { + this.$real = real; + this.$imag = imag; + this.$val = this; + }; + typ.keyFor = x => { return x.$real + "$" + x.$imag; }; + break; + + case $kindArray: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor()); + typ.init = (elem, len) => { + typ.elem = elem; + typ.len = len; + typ.comparable = elem.comparable; + typ.keyFor = x => { + return Array.prototype.join.call($mapArray(x, e => { + return String(elem.keyFor(e)).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); + }), "$"); + }; + typ.copy = (dst, src) => { + $copyArray(dst, src, 0, 0, src.length, elem); + }; + typ.ptr.init(typ); + Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError }); + }; + break; + + case $kindChan: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.keyFor = $idKey; + typ.init = (elem, sendOnly, recvOnly) => { + typ.elem = elem; + typ.sendOnly = sendOnly; + typ.recvOnly = recvOnly; + }; + break; + + case $kindFunc: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.init = (params, results, variadic) => { + typ.params = params; + typ.results = results; + typ.variadic = variadic; + typ.comparable = false; + }; + break; + + case $kindInterface: + typ = { implementedBy: {}, missingMethodFor: {} }; + typ.keyFor = $ifaceKeyFor; + typ.init = methods => { + typ.methods = methods; + methods.forEach(m => { + $ifaceNil[m.prop] = $throwNilPointerError; + }); + }; + break; + + case $kindMap: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.init = (key, elem) => { + typ.key = key; + typ.elem = elem; + typ.comparable = false; + }; + break; + + case $kindPtr: + typ = constructor || function (getter, setter, target) { + this.$get = getter; + this.$set = setter; + this.$target = target; + this.$val = this; + }; + typ.keyFor = $idKey; + typ.init = elem => { + typ.elem = elem; + typ.wrapped = (elem.kind === $kindArray); + typ.nil = new typ($throwNilPointerError, $throwNilPointerError); + }; + break; + + case $kindSlice: + typ = function (array) { + if (array.constructor !== typ.nativeArray) { + array = new typ.nativeArray(array); + } + this.$array = array; + this.$offset = 0; + this.$length = array.length; + this.$capacity = array.length; + this.$val = this; + }; + typ.init = elem => { + typ.elem = elem; + typ.comparable = false; + typ.nativeArray = $nativeArray(elem.kind); + typ.nil = new typ([]); + }; + break; + + case $kindStruct: + typ = function (v) { this.$val = v; }; + typ.wrapped = true; + typ.ptr = $newType(4, $kindPtr, "*" + string, false, pkg, exported, constructor); + typ.ptr.elem = typ; + typ.ptr.prototype.$get = function () { return this; }; + typ.ptr.prototype.$set = function (v) { typ.copy(this, v); }; + typ.init = (pkgPath, fields) => { + typ.pkgPath = pkgPath; + typ.fields = fields; + fields.forEach(f => { + if (!f.typ.comparable) { + typ.comparable = false; + } + }); + typ.keyFor = x => { + var val = x.$val; + return $mapArray(fields, f => { + return String(f.typ.keyFor(val[f.prop])).replace(/\\/g, "\\\\").replace(/\$/g, "\\$"); + }).join("$"); + }; + typ.copy = (dst, src) => { + for (var i = 0; i < fields.length; i++) { + var f = fields[i]; + switch (f.typ.kind) { + case $kindArray: + case $kindStruct: + f.typ.copy(dst[f.prop], src[f.prop]); + continue; + default: + dst[f.prop] = src[f.prop]; + continue; + } + } + }; + /* nil value */ + var properties = {}; + fields.forEach(f => { + properties[f.prop] = { get: $throwNilPointerError, set: $throwNilPointerError }; + }); + typ.ptr.nil = Object.create(constructor.prototype, properties); + typ.ptr.nil.$val = typ.ptr.nil; + /* methods for embedded fields */ + $addMethodSynthesizer(() => { + var synthesizeMethod = (target, m, f) => { + if (target.prototype[m.prop] !== undefined) { return; } + target.prototype[m.prop] = function(...args) { + var v = this.$val[f.prop]; + if (f.typ === $jsObjectPtr) { + v = new $jsObjectPtr(v); + } + if (v.$val === undefined) { + v = new f.typ(v); + } + return v[m.prop](...args); + }; + }; + fields.forEach(f => { + if (f.embedded) { + $methodSet(f.typ).forEach(m => { + synthesizeMethod(typ, m, f); + synthesizeMethod(typ.ptr, m, f); + }); + $methodSet($ptrType(f.typ)).forEach(m => { + synthesizeMethod(typ.ptr, m, f); + }); + } + }); + }); + }; + break; + + default: + $panic(new $String("invalid kind: " + kind)); + } + + switch (kind) { + case $kindBool: + case $kindMap: + typ.zero = () => { return false; }; + break; + + case $kindInt: + case $kindInt8: + case $kindInt16: + case $kindInt32: + case $kindUint: + case $kindUint8: + case $kindUint16: + case $kindUint32: + case $kindUintptr: + case $kindUnsafePointer: + case $kindFloat32: + case $kindFloat64: + typ.zero = () => { return 0; }; + break; + + case $kindString: + typ.zero = () => { return ""; }; + break; + + case $kindInt64: + case $kindUint64: + case $kindComplex64: + case $kindComplex128: + var zero = new typ(0, 0); + typ.zero = () => { return zero; }; + break; + + case $kindPtr: + case $kindSlice: + typ.zero = () => { return typ.nil; }; + break; + + case $kindChan: + typ.zero = () => { return $chanNil; }; + break; + + case $kindFunc: + typ.zero = () => { return $throwNilPointerError; }; + break; + + case $kindInterface: + typ.zero = () => { return $ifaceNil; }; + break; + + case $kindArray: + typ.zero = () => { + var arrayClass = $nativeArray(typ.elem.kind); + if (arrayClass !== Array) { + return new arrayClass(typ.len); + } + var array = new Array(typ.len); + for (var i = 0; i < typ.len; i++) { + array[i] = typ.elem.zero(); + } + return array; + }; + break; + + case $kindStruct: + typ.zero = () => { return new typ.ptr(); }; + break; + + default: + $panic(new $String("invalid kind: " + kind)); + } + + typ.id = $typeIDCounter; + $typeIDCounter++; + typ.size = size; + typ.kind = kind; + typ.string = string; + typ.named = named; + typ.pkg = pkg; + typ.exported = exported; + typ.methods = []; + typ.methodSetCache = null; + typ.comparable = true; + return typ; +}; + +var $methodSet = typ => { + if (typ.methodSetCache !== null) { + return typ.methodSetCache; + } + var base = {}; + + var isPtr = (typ.kind === $kindPtr); + if (isPtr && typ.elem.kind === $kindInterface) { + typ.methodSetCache = []; + return []; + } + + var current = [{ typ: isPtr ? typ.elem : typ, indirect: isPtr }]; + + var seen = {}; + + while (current.length > 0) { + var next = []; + var mset = []; + + current.forEach(e => { + if (seen[e.typ.string]) { + return; + } + seen[e.typ.string] = true; + + if (e.typ.named) { + mset = mset.concat(e.typ.methods); + if (e.indirect) { + mset = mset.concat($ptrType(e.typ).methods); + } + } + + switch (e.typ.kind) { + case $kindStruct: + e.typ.fields.forEach(f => { + if (f.embedded) { + var fTyp = f.typ; + var fIsPtr = (fTyp.kind === $kindPtr); + next.push({ typ: fIsPtr ? fTyp.elem : fTyp, indirect: e.indirect || fIsPtr }); + } + }); + break; + + case $kindInterface: + mset = mset.concat(e.typ.methods); + break; + } + }); + + mset.forEach(m => { + if (base[m.name] === undefined) { + base[m.name] = m; + } + }); + + current = next; + } + + typ.methodSetCache = []; + Object.keys(base).sort().forEach(name => { + typ.methodSetCache.push(base[name]); + }); + return typ.methodSetCache; +}; + +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); +var $Int16 = $newType(2, $kindInt16, "int16", true, "", false, null); +var $Int32 = $newType(4, $kindInt32, "int32", true, "", false, null); +var $Int64 = $newType(8, $kindInt64, "int64", true, "", false, null); +var $Uint = $newType(4, $kindUint, "uint", true, "", false, null); +var $Uint8 = $newType(1, $kindUint8, "uint8", true, "", false, null); +var $Uint16 = $newType(2, $kindUint16, "uint16", true, "", false, null); +var $Uint32 = $newType(4, $kindUint32, "uint32", true, "", false, null); +var $Uint64 = $newType(8, $kindUint64, "uint64", true, "", false, null); +var $Uintptr = $newType(4, $kindUintptr, "uintptr", true, "", false, null); +var $Float32 = $newType(4, $kindFloat32, "float32", true, "", false, null); +var $Float64 = $newType(8, $kindFloat64, "float64", true, "", false, null); +var $Complex64 = $newType(8, $kindComplex64, "complex64", true, "", false, null); +var $Complex128 = $newType(16, $kindComplex128, "complex128", true, "", false, null); +var $String = $newType(8, $kindString, "string", true, "", false, null); +var $UnsafePointer = $newType(4, $kindUnsafePointer, "unsafe.Pointer", true, "unsafe", false, null); + +var $nativeArray = elemKind => { + switch (elemKind) { + 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: + return Uint32Array; + case $kindUintptr: + return Uint32Array; + case $kindFloat32: + return Float32Array; + case $kindFloat64: + return Float64Array; + default: + return Array; + } +}; +var $toNativeArray = (elemKind, array) => { + var nativeArray = $nativeArray(elemKind); + if (nativeArray === Array) { + return array; + } + return new nativeArray(array); +}; +var $arrayTypes = {}; +var $arrayType = (elem, len) => { + var typeKey = elem.id + "$" + len; + var typ = $arrayTypes[typeKey]; + if (typ === undefined) { + typ = $newType(elem.size * len, $kindArray, "[" + len + "]" + elem.string, false, "", false, null); + $arrayTypes[typeKey] = typ; + typ.init(elem, len); + } + return typ; +}; + +var $chanType = (elem, sendOnly, recvOnly) => { + var string = (recvOnly ? "<-" : "") + "chan" + (sendOnly ? "<- " : " "); + if (!sendOnly && !recvOnly && (elem.string[0] == "<")) { + string += "(" + elem.string + ")"; + } else { + string += elem.string; + } + var field = sendOnly ? "SendChan" : (recvOnly ? "RecvChan" : "Chan"); + var typ = elem[field]; + if (typ === undefined) { + typ = $newType(4, $kindChan, string, false, "", false, null); + elem[field] = typ; + typ.init(elem, sendOnly, recvOnly); + } + return typ; +}; +var $Chan = function (elem, capacity) { + if (capacity < 0 || capacity > 2147483647) { + $throwRuntimeError("makechan: size out of range"); + } + this.$elem = elem; + this.$capacity = capacity; + this.$buffer = []; + this.$sendQueue = []; + this.$recvQueue = []; + this.$closed = false; +}; +var $chanNil = new $Chan(null, 0); +$chanNil.$sendQueue = $chanNil.$recvQueue = { length: 0, push() { }, shift() { return undefined; }, indexOf() { return -1; } }; + +var $funcTypes = {}; +var $funcType = (params, results, variadic) => { + var typeKey = $mapArray(params, p => { return p.id; }).join(",") + "$" + $mapArray(results, r => { return r.id; }).join(",") + "$" + variadic; + var typ = $funcTypes[typeKey]; + if (typ === undefined) { + var paramTypes = $mapArray(params, p => { return p.string; }); + if (variadic) { + paramTypes[paramTypes.length - 1] = "..." + paramTypes[paramTypes.length - 1].substr(2); + } + var string = "func(" + paramTypes.join(", ") + ")"; + if (results.length === 1) { + string += " " + results[0].string; + } else if (results.length > 1) { + string += " (" + $mapArray(results, r => { return r.string; }).join(", ") + ")"; + } + typ = $newType(4, $kindFunc, string, false, "", false, null); + $funcTypes[typeKey] = typ; + typ.init(params, results, variadic); + } + return typ; +}; + +var $interfaceTypes = {}; +var $interfaceType = methods => { + var typeKey = $mapArray(methods, m => { return m.pkg + "," + m.name + "," + m.typ.id; }).join("$"); + var typ = $interfaceTypes[typeKey]; + if (typ === undefined) { + var string = "interface {}"; + if (methods.length !== 0) { + string = "interface { " + $mapArray(methods, m => { + return (m.pkg !== "" ? m.pkg + "." : "") + m.name + m.typ.string.substr(4); + }).join("; ") + " }"; + } + typ = $newType(8, $kindInterface, string, false, "", false, null); + $interfaceTypes[typeKey] = typ; + typ.init(methods); + } + return typ; +}; +var $emptyInterface = $interfaceType([]); +var $ifaceNil = {}; +var $error = $newType(8, $kindInterface, "error", true, "", false, null); +$error.init([{ prop: "Error", name: "Error", pkg: "", typ: $funcType([], [$String], false) }]); + +var $mapTypes = {}; +var $mapType = (key, elem) => { + var typeKey = key.id + "$" + elem.id; + var typ = $mapTypes[typeKey]; + if (typ === undefined) { + typ = $newType(4, $kindMap, "map[" + key.string + "]" + elem.string, false, "", false, null); + $mapTypes[typeKey] = typ; + typ.init(key, elem); + } + return typ; +}; +var $makeMap = (keyForFunc, entries) => { + var m = new Map(); + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + m.set(keyForFunc(e.k), e); + } + return m; +}; + +var $ptrType = elem => { + var typ = elem.ptr; + if (typ === undefined) { + typ = $newType(4, $kindPtr, "*" + elem.string, false, "", elem.exported, null); + elem.ptr = typ; + typ.init(elem); + } + return typ; +}; + +var $newDataPointer = (data, constructor) => { + if (constructor.elem.kind === $kindStruct) { + return data; + } + return new constructor(() => { return data; }, v => { data = v; }); +}; + +var $indexPtr = (array, index, constructor) => { + if (array.buffer) { + // Pointers to the same underlying ArrayBuffer share cache. + var cache = array.buffer.$ptr = array.buffer.$ptr || {}; + // Pointers of different primitive types are non-comparable and stored in different caches. + var typeCache = cache[array.name] = cache[array.name] || {}; + var cacheIdx = array.BYTES_PER_ELEMENT * index + array.byteOffset; + return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(() => { return array[index]; }, v => { array[index] = v; })); + } else { + array.$ptr = array.$ptr || {}; + return array.$ptr[index] || (array.$ptr[index] = new constructor(() => { return array[index]; }, v => { array[index] = v; })); + } +}; + +var $sliceType = elem => { + var typ = elem.slice; + if (typ === undefined) { + typ = $newType(12, $kindSlice, "[]" + elem.string, false, "", false, null); + elem.slice = typ; + typ.init(elem); + } + return typ; +}; +var $makeSlice = (typ, length, capacity = length) => { + if (length < 0 || length > 2147483647) { + $throwRuntimeError("makeslice: len out of range"); + } + if (capacity < 0 || capacity < length || capacity > 2147483647) { + $throwRuntimeError("makeslice: cap out of range"); + } + var array = new typ.nativeArray(capacity); + if (typ.nativeArray === Array) { + for (var i = 0; i < capacity; i++) { + array[i] = typ.elem.zero(); + } + } + var slice = new typ(array); + slice.$length = length; + return slice; +}; + +var $structTypes = {}; +var $structType = (pkgPath, fields) => { + var typeKey = $mapArray(fields, f => { return f.name + "," + f.typ.id + "," + f.tag; }).join("$"); + var typ = $structTypes[typeKey]; + if (typ === undefined) { + var string = "struct { " + $mapArray(fields, f => { + var str = f.typ.string + (f.tag !== "" ? (" \"" + f.tag.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"") : ""); + if (f.embedded) { + return str; + } + return f.name + " " + str; + }).join("; ") + " }"; + if (fields.length === 0) { + string = "struct {}"; + } + typ = $newType(0, $kindStruct, string, false, "", false, function(...args) { + this.$val = this; + for (var i = 0; i < fields.length; i++) { + var f = fields[i]; + if (f.name == '_') { + continue; + } + var arg = args[i]; + this[f.prop] = arg !== undefined ? arg : f.typ.zero(); + } + }); + $structTypes[typeKey] = typ; + typ.init(pkgPath, fields); + } + return typ; +}; + +var $assertType = (value, type, returnTuple) => { + var isInterface = (type.kind === $kindInterface), ok, missingMethod = ""; + if (value === $ifaceNil) { + ok = false; + } else if (!isInterface) { + ok = value.constructor === type; + } else { + var valueTypeString = value.constructor.string; + ok = type.implementedBy[valueTypeString]; + if (ok === undefined) { + ok = true; + var valueMethodSet = $methodSet(value.constructor); + var interfaceMethods = type.methods; + for (var i = 0; i < interfaceMethods.length; i++) { + var tm = interfaceMethods[i]; + var found = false; + for (var j = 0; j < valueMethodSet.length; j++) { + var vm = valueMethodSet[j]; + if (vm.name === tm.name && vm.pkg === tm.pkg && vm.typ === tm.typ) { + found = true; + break; + } + } + if (!found) { + ok = false; + type.missingMethodFor[valueTypeString] = tm.name; + break; + } + } + type.implementedBy[valueTypeString] = ok; + } + if (!ok) { + missingMethod = type.missingMethodFor[valueTypeString]; + } + } + + if (!ok) { + if (returnTuple) { + return [type.zero(), false]; + } + $panic(new $packages["runtime"].TypeAssertionError.ptr( + $packages["runtime"]._type.ptr.nil, + (value === $ifaceNil ? $packages["runtime"]._type.ptr.nil : new $packages["runtime"]._type.ptr(value.constructor.string)), + new $packages["runtime"]._type.ptr(type.string), + missingMethod)); + } + + if (!isInterface) { + value = value.$val; + } + if (type === $jsObjectPtr) { + value = value.object; + } + return returnTuple ? [value, true] : value; +}; diff --git a/compiler/statements.go b/compiler/statements.go index f8d791948..8518f9b71 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -32,7 +32,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { panic(err) // Continue orderly bailout. } - // Oh noes, we've tried to compile something so bad that compiler paniced + // Oh noes, we've tried to compile something so bad that compiler panicked // and ran away. Let's gather some debugging clues. bail := bailout(err) pos := stmt.Pos() @@ -471,7 +471,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { case *ast.SendStmt: chanType := fc.pkgCtx.TypeOf(s.Chan).Underlying().(*types.Chan) call := &ast.CallExpr{ - Fun: fc.newIdent("$send", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), + Fun: fc.newIdent("$send", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, fc.newIdent(fc.translateImplicitConversionWithCloning(s.Value, chanType.Elem()).String(), chanType.Elem())}, } fc.Blocking[call] = true @@ -522,8 +522,8 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { } selectCall := fc.setType(&ast.CallExpr{ - Fun: fc.newIdent("$select", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterface(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), - Args: []ast.Expr{fc.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))}, + Fun: fc.newIdent("$select", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterfaceType(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), + Args: []ast.Expr{fc.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterfaceType(nil, nil))}, }, types.Typ[types.Int]) if !hasDefault { fc.Blocking[selectCall] = true diff --git a/compiler/utils.go b/compiler/utils.go index 5cde7b3c7..058437f67 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -84,13 +84,18 @@ func (fc *funcContext) Delayed(f func()) { // tuple elements. // // For example, for functions defined as: -// func a() (int, string) {return 42, "foo"} -// func b(a1 int, a2 string) {} +// +// func a() (int, string) {return 42, "foo"} +// func b(a1 int, a2 string) {} +// // ...the following statement: -// b(a()) +// +// b(a()) +// // ...will be transformed into: -// _tuple := a() -// b(_tuple[0], _tuple[1]) +// +// _tuple := a() +// b(_tuple[0], _tuple[1]) func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr { if len(argExprs) != 1 { return argExprs @@ -282,6 +287,12 @@ func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident { return ident } +func (fc *funcContext) newTypeIdent(name string, obj types.Object) *ast.Ident { + ident := ast.NewIdent(name) + fc.pkgCtx.Info.Uses[ident] = obj + return ident +} + func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr { fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t} return e @@ -397,12 +408,6 @@ func (fc *funcContext) handleEscapingVars(n ast.Node) { var names []string objs := analysis.EscapingObjects(n, fc.pkgCtx.Info.Info) - sort.Slice(objs, func(i, j int) bool { - if objs[i].Name() == objs[j].Name() { - return objs[i].Pos() < objs[j].Pos() - } - return objs[i].Name() < objs[j].Name() - }) for _, obj := range objs { names = append(names, fc.objectName(obj)) fc.pkgCtx.escapingVars[obj] = true @@ -513,24 +518,24 @@ func isBlank(expr ast.Expr) bool { // // For example, consider a Go type: // -// type SecretInt int -// func (_ SecretInt) String() string { return "" } +// type SecretInt int +// func (_ SecretInt) String() string { return "" } // -// func main() { -// var i SecretInt = 1 -// println(i.String()) -// } +// func main() { +// var i SecretInt = 1 +// println(i.String()) +// } // // For this example the compiler will generate code similar to the snippet below: // -// SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null); -// SecretInt.prototype.String = function() { -// return ""; -// }; -// main = function() { -// var i = 1; -// console.log(new SecretInt(i).String()); -// }; +// SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null); +// SecretInt.prototype.String = function() { +// return ""; +// }; +// main = function() { +// var i = 1; +// console.log(new SecretInt(i).String()); +// }; // // Note that the generated code assigns a primitive "number" value into i, and // only boxes it into an object when it's necessary to access its methods. @@ -699,13 +704,12 @@ func encodeIdent(name string) string { // // For example: // -// "my_name" -> ".my_name" -// "my name" -> `["my name"]` +// "my_name" -> ".my_name" +// "my name" -> `["my name"]` // // For more information about JavaScript property accessors and identifiers, see // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors and // https://developer.mozilla.org/en-US/docs/Glossary/Identifier. -// func formatJSStructTagVal(jsTag string) string { for i, r := range jsTag { ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_' @@ -761,7 +765,7 @@ func (st signatureTypes) Param(i int, ellipsis bool) types.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)) + panic(fmt.Errorf("tried to access parameter %d of a non-variadic signature %s", i, st.Sig)) } if ellipsis { return st.VariadicType() diff --git a/compiler/version_check.go b/compiler/version_check.go index 10f10e537..d672fa45a 100644 --- a/compiler/version_check.go +++ b/compiler/version_check.go @@ -1,5 +1,4 @@ -//go:build go1.18 -// +build go1.18 +//go:build go1.19 package compiler @@ -13,10 +12,10 @@ import ( ) // Version is the GopherJS compiler version string. -const Version = "1.18.0-beta3+go1.18.10" +const Version = "1.19.0-beta1+go1.19.13" // GoVersion is the current Go 1.x version that GopherJS is compatible with. -const GoVersion = 18 +const GoVersion = 19 // CheckGoVersion checks the version of the Go distribution // at goroot, and reports an error if it's not compatible @@ -35,13 +34,13 @@ func CheckGoVersion(goroot string) error { return nil } -// goRootVersion defermines Go release for the given GOROOT installation. +// goRootVersion determines the Go release for the given GOROOT installation. func goRootVersion(goroot string) (string, error) { - v, err := os.ReadFile(filepath.Join(goroot, "VERSION")) - if err == nil { - // Standard Go distribution has VERSION file inside its GOROOT, checking it - // is the most efficient option. - return string(v), nil + if b, err := os.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { + // Standard Go distribution has a VERSION file inside its GOROOT, + // checking its first line is the most efficient option. + v, _, _ := strings.Cut(string(b), "\n") + return v, nil } // Fall back to the "go version" command. @@ -58,8 +57,8 @@ func goRootVersion(goroot string) (string, error) { return parts[2], nil } -// GoRelease does a best-effort to identify Go release we are building with. -// If unable to determin the precise version for the given GOROOT, falls back +// GoRelease does a best-effort to identify the Go release we are building with. +// If unable to determine the precise version for the given GOROOT, falls back // to the best guess available. func GoRelease(goroot string) string { v, err := goRootVersion(goroot) diff --git a/doc/pargma.md b/doc/pargma.md index a13c64ce6..2bcf71f93 100644 --- a/doc/pargma.md +++ b/doc/pargma.md @@ -7,6 +7,12 @@ issues, so it is recommended to avoid using them if possible. GopherJS compiler supports the following directives: +- [go:linkname](#golinkname) +- [go:embed](#goembed) +- [gopherjs:keep-original](#gopherjskeep-original) +- [gopherjs:purge](#gopherjspurge) +- [gopherjs:override-signature](#gopherjsoverride-signature) + ## `go:linkname` This is a limited version of the `go:linkname` directive the upstream Go @@ -25,16 +31,166 @@ Signatures of `remotename` and `localname` must be identical. Since this directive can subvert package incapsulation, the source file that uses the directive must also import `unsafe`. -The following directive format is supported: -//go:linkname . -//go:linkname .. -//go:linkname .<(*type)>. +The following directive formats are supported: + +- `//go:linkname .` +- `//go:linkname ..` +- `//go:linkname .<(*type)>.` Compared to the upstream Go, the following limitations exist in GopherJS: - - The directive only works on package-level functions or methods (variables - are not supported). - - The directive can only be used to "import" implementation from another - package, and not to "provide" local implementation to another package. +- The directive only works on package-level functions or methods (variables + are not supported). +- The directive can only be used to "import" implementation from another + package, and not to "provide" local implementation to another package. + +See [gopherjs/issues/1000](https://github.com/gopherjs/gopherjs/issues/1000) +for details. + +## `go:embed` + +This is a very similar version of the `go:embed` directive the upstream Go +compiler implements. +GopherJS leverages [goembed](https://github.com/visualfc/goembed) +to parse this directive and provide support reading embedded content. Usage: + +```go +import _ "embed" // for go:embed + +//go:embed externalText +var embeddedText string + +//go:embed externalContent +var embeddedContent []byte + +//go:embed file1 +//go:embed file2 +// ... +//go:embed image/* blobs/* +var embeddedFiles embed.FS +``` + +This directive affects the variable specification (e.g. `embeddedText`) +that the comment containing the directive is associated with. +There may be one embed directives associated with `string` or `[]byte` +variables. There may be one or more embed directives associated with +`embed.FS` variables and each directive may contain one or more +file matching patterns. The effect is that the variable will be assigned to +the content (e.g. `externalText`) given in the directive. In the case +of `embed.FS`, several embedded files will be accessible. + +See [pkg.go.dev/embed](https://pkg.go.dev/embed#hdr-Directives) +for more information. + +## `gopherjs:keep-original` + +This directive is custom to GopherJS. This directive can be added to a +function declaration in the native file overrides as part of the build step. + +This will keep the original function by the same name as the function +in the overrides, however it will prepend `_gopherjs_original_` to the original +function's name. This allows the original function to be called by functions +in the overrides and the overridden function to be called instead of the +original. This is useful when wanting to augment the original behavior without +having to rewrite the entire original function. Usage: + +```go +//gopherjs:keep-original +func foo(a, b int) int { + return _gopherjs_original_foo(a+1, b+1) - 1 +} +``` + +## `gopherjs:purge` + +This directive is custom to GopherJS. This directive can be added +to most declarations and specification in the native file overrides as +part of the build step. +This can be added to structures, interfaces, methods, functions, +variables, or constants, but are not supported for imports, structure fields, +nor interface function signatures. -See https://github.com/gopherjs/gopherjs/issues/1000 for details. +This will remove the original structure, interface, etc from both the override +files and the original files. +If this is added to a structure, then all functions in the original files +that use that structure as a receiver will also be removed. +This is useful for removing all the code that is invalid in GopherJS, +such as code using unsupported features (e.g. generic interfaces before +generics were fully supported). In many cases the overrides to replace +the original code may not have use of all the original functions and +variables or the original code is not intended to be replaced yet. +Usage: + +```go +//gopherjs:purge +var data string + +//gopherjs:purge +// This will also purge any function starting with `dataType` as the receiver. +type dataType struct {} + +//gopherjs:purge +type interfaceType interface{} + +//gopherjs:purge +func doThing[T ~string](value T) +``` + +## `gopherjs:override-signature` + +This directive is custom to GopherJS. This directive can be added to a +function declaration in the native file overrides as part of the build step. + +This will remove the function from the overrides but record the signature +used in the overrides, then update the original function with that signature +provided in the overrides. +The affect is to change the receiver, type parameters, +parameters, or return types of the original function. The original function +and override function must have the same function key name so that they can +be associated, meaning the identifier of the receiver, if there is one, must +match and the identifier of the function must match. + +This allows the signature to be modified without modifying the body of a +function thus allowing the types to be adjusted to work in GopherJS. +The signature may need to be replaced because it uses a parameter type +that is invalid in GopherJS or the signature uses unsupported features +(e.g. generic interfaces before generics were fully supported). +Usage: + +```go +// -- in original file -- +func Foo[T comparable](a, b T) (T, bool) { + if a == b { + return a, true + } + return b, false +} + +// -- in override file -- +//gopherjs:override-signature +func Foo(a, b any) (any, bool) + +// -- result in augmented original -- +func Foo(a, b any) (any, bool) { + if a == b { + return a, true + } + return b, false +} +``` + +```go +// -- in original file -- +func (f *Foo[A, B, C]) Bar(a int, b *A) (*A, error) { + //... +} + +// -- in override file -- +//gopherjs:override-signature +func (f *Foo) Bar(a int, b jsTypeA) (jsTypeA, error) + +// -- result in augmented original -- +func (f *Foo) Bar(a int, b jsTypeA) (jsTypeA, error) { + //... +} +``` diff --git a/go.mod b/go.mod index f94ccc3ff..8edafd89b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gopherjs/gopherjs 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/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 @@ -12,14 +13,13 @@ require ( github.com/spf13/cobra v1.2.1 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/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad - golang.org/x/tools v0.1.10 + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.10.0 + golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 + golang.org/x/tools v0.11.0 ) require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect - 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 4efe9da8b..349d599ba 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanw/esbuild v0.18.0 h1:zJrquhC5ZiricRVQxMQTWqO8zYcV7F7OfUXstB9Ucbg= +github.com/evanw/esbuild v0.18.0/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -258,8 +260,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -295,7 +295,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.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 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= @@ -354,8 +354,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 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/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= @@ -399,8 +400,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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/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= @@ -465,8 +467,8 @@ 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.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= 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= diff --git a/internal/srctesting/srctesting.go b/internal/srctesting/srctesting.go index 1d9cecd20..4e374845e 100644 --- a/internal/srctesting/srctesting.go +++ b/internal/srctesting/srctesting.go @@ -53,17 +53,45 @@ func Check(t *testing.T, fset *token.FileSet, files ...*ast.File) (*types.Info, // // Fails the test if there isn't exactly one function declared in the source. func ParseFuncDecl(t *testing.T, src string) *ast.FuncDecl { + t.Helper() + decl := ParseDecl(t, src) + fdecl, ok := decl.(*ast.FuncDecl) + if !ok { + t.Fatalf("Got %T decl, expected *ast.FuncDecl", decl) + } + return fdecl +} + +// ParseDecl parses source with a single declaration and +// returns that declaration AST. +// +// Fails the test if there isn't exactly one declaration in the source. +func ParseDecl(t *testing.T, src string) ast.Decl { t.Helper() fset := token.NewFileSet() file := Parse(t, fset, src) if l := len(file.Decls); l != 1 { - t.Fatalf("Got %d decls in the sources, expected exactly 1", l) + t.Fatalf(`Got %d decls in the sources, expected exactly 1`, l) } - fdecl, ok := file.Decls[0].(*ast.FuncDecl) + return file.Decls[0] +} + +// ParseSpec parses source with a single declaration containing +// a single specification and returns that specification AST. +// +// Fails the test if there isn't exactly one declaration and +// one specification in the source. +func ParseSpec(t *testing.T, src string) ast.Spec { + t.Helper() + decl := ParseDecl(t, src) + gdecl, ok := decl.(*ast.GenDecl) if !ok { - t.Fatalf("Got %T decl, expected *ast.FuncDecl", file.Decls[0]) + t.Fatalf("Got %T decl, expected *ast.GenDecl", decl) } - return fdecl + if l := len(gdecl.Specs); l != 1 { + t.Fatalf(`Got %d spec in the sources, expected exactly 1`, l) + } + return gdecl.Specs[0] } // Format AST node into a string. diff --git a/js/js.go b/js/js.go index a1ded9eeb..e4b523275 100644 --- a/js/js.go +++ b/js/js.go @@ -2,25 +2,25 @@ // // Use MakeWrapper to expose methods to JavaScript. Use MakeFullWrapper to expose methods AND fields to JavaScript. When passing values directly, the following type conversions are performed: // -// | Go type | JavaScript type | Conversions back to interface{} | -// | --------------------- | --------------------- | ------------------------------- | -// | bool | Boolean | bool | -// | integers and floats | Number | float64 | -// | string | String | string | -// | []int8 | Int8Array | []int8 | -// | []int16 | Int16Array | []int16 | -// | []int32, []int | Int32Array | []int | -// | []uint8 | Uint8Array | []uint8 | -// | []uint16 | Uint16Array | []uint16 | -// | []uint32, []uint | Uint32Array | []uint | -// | []float32 | Float32Array | []float32 | -// | []float64 | Float64Array | []float64 | -// | all other slices | Array | []interface{} | -// | arrays | see slice type | see slice type | -// | functions | Function | func(...interface{}) *js.Object | -// | time.Time | Date | time.Time | -// | - | instanceof Node | *js.Object | -// | maps, structs | instanceof Object | map[string]interface{} | +// | Go type | JavaScript type | Conversions back to interface{} | +// | --------------------- | --------------------- | ------------------------------- | +// | bool | Boolean | bool | +// | integers and floats | Number | float64 | +// | string | String | string | +// | []int8 | Int8Array | []int8 | +// | []int16 | Int16Array | []int16 | +// | []int32, []int | Int32Array | []int | +// | []uint8 | Uint8Array | []uint8 | +// | []uint16 | Uint16Array | []uint16 | +// | []uint32, []uint | Uint32Array | []uint | +// | []float32 | Float32Array | []float32 | +// | []float64 | Float64Array | []float64 | +// | all other slices | Array | []interface{} | +// | arrays | see slice type | see slice type | +// | functions | Function | func(...interface{}) *js.Object | +// | time.Time | Date | time.Time | +// | - | instanceof Node | *js.Object | +// | maps, structs | instanceof Object | map[string]interface{} | // // Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa. package js diff --git a/node-syscall/package-lock.json b/node-syscall/package-lock.json index 41781ff3b..a92b06df6 100644 --- a/node-syscall/package-lock.json +++ b/node-syscall/package-lock.json @@ -555,9 +555,9 @@ "optional": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "requires": { "lru-cache": "^6.0.0" } diff --git a/nosync/mutex.go b/nosync/mutex.go index 03f20dc40..d988e8ffa 100644 --- a/nosync/mutex.go +++ b/nosync/mutex.go @@ -3,6 +3,10 @@ package nosync // Mutex is a dummy which is non-blocking. type Mutex struct { locked bool + _ bool + _ bool + _ bool + _ uint32 } // Lock locks m. It is a run-time error if m is already locked. @@ -23,8 +27,14 @@ func (m *Mutex) Unlock() { // RWMutex is a dummy which is non-blocking. type RWMutex struct { + _ Mutex writeLocked bool - readLockCounter int + _ bool + _ bool + _ bool + readLockCounter int32 + _ int32 + _ int32 } // Lock locks m for writing. It is a run-time error if rw is already locked for reading or writing. diff --git a/nosync/once.go b/nosync/once.go index f4cb69540..2af58f873 100644 --- a/nosync/once.go +++ b/nosync/once.go @@ -8,7 +8,9 @@ type Once struct { // Do calls the function f if and only if Do is being called for the // first time for this instance of Once. In other words, given -// var once Once +// +// var once Once +// // if once.Do(f) is called multiple times, only the first call will invoke f, // even if f has a different value in each invocation. A new instance of // Once is required for each function to execute. @@ -16,13 +18,13 @@ type Once struct { // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: -// config.once.Do(func() { config.init(filename) }) +// +// config.once.Do(func() { config.init(filename) }) // // If f causes Do to be called, it will panic. // // If f panics, Do considers it to have returned; future calls of Do return // without calling f. -// func (o *Once) Do(f func()) { if o.done { return diff --git a/nosync/pool.go b/nosync/pool.go index 3d448e0fc..ae792b642 100644 --- a/nosync/pool.go +++ b/nosync/pool.go @@ -28,7 +28,6 @@ package nosync // not a suitable use for a Pool, since the overhead does not amortize well in // that scenario. It is more efficient to have such objects implement their own // free list. -// type Pool struct { store []interface{} New func() interface{} diff --git a/tests/arrays_test.go b/tests/arrays_test.go index c202f2e9e..e79989991 100644 --- a/tests/arrays_test.go +++ b/tests/arrays_test.go @@ -74,7 +74,6 @@ func TestArrayPointer(t *testing.T) { }) t.Run("reflect.IsNil", func(t *testing.T) { - }) } diff --git a/tests/copy_test.go b/tests/copy_test.go index b0ea8e09f..866b554b4 100644 --- a/tests/copy_test.go +++ b/tests/copy_test.go @@ -125,7 +125,7 @@ func (t T) M() int { } func TestExplicitConversion(t *testing.T) { - var coolGuy = S{x: 42} + coolGuy := S{x: 42} var i I i = T(coolGuy) if i.M() != 42 { @@ -138,7 +138,7 @@ func TestCopyStructByReflect(t *testing.T) { type Info struct { name string } - a := []Info{Info{"A"}, Info{"B"}, Info{"C"}} + a := []Info{{"A"}, {"B"}, {"C"}} v := reflect.ValueOf(a) i := v.Index(0).Interface() a[0] = Info{"X"} diff --git a/tests/deferblock_test.go b/tests/deferblock_test.go index 7667f6d88..3443b936f 100644 --- a/tests/deferblock_test.go +++ b/tests/deferblock_test.go @@ -45,7 +45,7 @@ func TestBlockingInDefer(t *testing.T) { func TestIssue1083(t *testing.T) { // https://github.com/gopherjs/gopherjs/issues/1083 - var block = make(chan bool) + block := make(chan bool) recoverCompleted := false diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index dd6b392f8..d58968ada 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -11,7 +11,7 @@ // // To run manually with summary, verbose output, and full stack traces of of known failures: // -// go run run.go -summary -v -show_known_fails +// go run run.go -summary -v -show_known_fails // // TODO(bradfitz): docs of some sort, once we figure out how we're changing // headers of files @@ -24,7 +24,6 @@ import ( "fmt" "hash/fnv" "io" - "io/ioutil" "log" "os" "os/exec" @@ -45,7 +44,6 @@ import ( // GOPHERJS: Known test fails for GopherJS compiler. // // TODO: Reduce these to zero or as close as possible. -// var knownFails = map[string]failReason{ "fixedbugs/bug114.go": {desc: "fixedbugs/bug114.go:15:27: B32 (untyped int constant 4294967295) overflows int"}, "fixedbugs/bug242.go": {desc: "bad map check 13 false false Error: fail"}, @@ -153,10 +151,16 @@ 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"}, + + // These are new tests in Go 1.19 + "fixedbugs/issue50672.go": {category: usesUnsupportedGenerics, desc: "Checking function nesting with one function having a type parameter."}, + "fixedbugs/issue53137.go": {category: usesUnsupportedGenerics, desc: "Checking setting type parameter of struct in parameter of a generic function."}, + "fixedbugs/issue53309.go": {category: usesUnsupportedGenerics, desc: "Checking unused type parameter in method call to interface"}, + "fixedbugs/issue53635.go": {category: usesUnsupportedGenerics, desc: "Checking switch type against nil type with unsupported type parameters"}, + "fixedbugs/issue53653.go": {category: lowLevelRuntimeDifference, desc: "GopherJS println format of int64 is different from Go's"}, } type failCategory uint8 @@ -166,6 +170,7 @@ const ( neverTerminates // Test never terminates (so avoid starting it). usesUnsupportedPackage // Test fails because it imports an unsupported package, e.g., "unsafe". requiresSourceMapSupport // Test fails without source map support (as configured in CI), because it tries to check filename/line number via runtime.Caller. + usesUnsupportedGenerics // Test uses generics (type parameters) that are not currently supported. compilerPanic unsureIfGopherJSSupportsThisFeature lowLevelRuntimeDifference // JavaScript runtime behaves differently from Go in ways that are difficult to work around. @@ -458,8 +463,8 @@ func (t *test) goDirName() string { return filepath.Join(t.dir, strings.Replace(t.gofile, ".go", ".dir", -1)) } -func goDirFiles(longdir string) (filter []os.FileInfo, err error) { - files, dirErr := ioutil.ReadDir(longdir) +func goDirFiles(longdir string) (filter []os.DirEntry, err error) { + files, dirErr := os.ReadDir(longdir) if dirErr != nil { return nil, dirErr } @@ -482,7 +487,7 @@ func goDirPackages(longdir string) ([][]string, error) { m := make(map[string]int) for _, file := range files { name := file.Name() - data, err := ioutil.ReadFile(filepath.Join(longdir, name)) + data, err := os.ReadFile(filepath.Join(longdir, name)) if err != nil { return nil, err } @@ -594,7 +599,7 @@ func (t *test) run() { return } - srcBytes, err := ioutil.ReadFile(t.goFileName()) + srcBytes, err := os.ReadFile(t.goFileName()) if err != nil { t.err = err return @@ -683,7 +688,7 @@ func (t *test) run() { t.makeTempDir() defer os.RemoveAll(t.tempDir) - err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) + err = os.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0o644) check(err) // A few tests (of things like the environment) require these to be set. @@ -855,7 +860,7 @@ func (t *test) run() { return } tfile := filepath.Join(t.tempDir, "tmp__.go") - if err := ioutil.WriteFile(tfile, out, 0666); err != nil { + if err := os.WriteFile(tfile, out, 0o666); err != nil { t.err = fmt.Errorf("write tempfile:%s", err) return } @@ -876,7 +881,7 @@ func (t *test) run() { return } tfile := filepath.Join(t.tempDir, "tmp__.go") - err = ioutil.WriteFile(tfile, out, 0666) + err = os.WriteFile(tfile, out, 0o666) if err != nil { t.err = fmt.Errorf("write tempfile:%s", err) return @@ -924,7 +929,7 @@ func (t *test) String() string { func (t *test) makeTempDir() { var err error - t.tempDir, err = ioutil.TempDir("", "") + t.tempDir, err = os.MkdirTemp("", "") check(err) } @@ -932,7 +937,7 @@ func (t *test) expectedOutput() string { filename := filepath.Join(t.dir, t.gofile) filename = filename[:len(filename)-len(".go")] filename += ".out" - b, _ := ioutil.ReadFile(filename) + b, _ := os.ReadFile(filename) return string(b) } @@ -1024,7 +1029,7 @@ func (t *test) errorCheck(outStr string, fullshort ...string) (err error) { func (t *test) updateErrors(out string, file string) { // Read in source file. - src, err := ioutil.ReadFile(file) + src, err := os.ReadFile(file) if err != nil { fmt.Fprintln(os.Stderr, err) return @@ -1079,7 +1084,7 @@ func (t *test) updateErrors(out string, file string) { } } // Write new file. - err = ioutil.WriteFile(file, []byte(strings.Join(lines, "\n")), 0640) + err = os.WriteFile(file, []byte(strings.Join(lines, "\n")), 0o640) if err != nil { fmt.Fprintln(os.Stderr, err) return @@ -1136,7 +1141,7 @@ var ( func (t *test) wantedErrors(file, short string) (errs []wantedError) { cache := make(map[string]*regexp.Regexp) - src, _ := ioutil.ReadFile(file) + src, _ := os.ReadFile(file) for i, line := range strings.Split(string(src), "\n") { lineNum := i + 1 if strings.Contains(line, "////") { diff --git a/tests/goroutine_test.go b/tests/goroutine_test.go index 011e3e8af..a1d387263 100644 --- a/tests/goroutine_test.go +++ b/tests/goroutine_test.go @@ -73,7 +73,7 @@ func testPanic2(t *testing.T) { time.Sleep(0) checkI(t, 4) panic(7) - checkI(t, -3) + checkI(t, -3) //nolint:govet // Unreachable code is intentional for panic test } func TestPanicAdvanced(t *testing.T) { diff --git a/tests/js_test.go b/tests/js_test.go index 18dbf0c03..2ce43865f 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/gopherjs/gopherjs/js" ) @@ -294,7 +295,7 @@ func TestDate(t *testing.T) { // https://github.com/gopherjs/gopherjs/issues/287 func TestInternalizeDate(t *testing.T) { - var a = time.Unix(0, (123 * time.Millisecond).Nanoseconds()) + a := time.Unix(0, (123 * time.Millisecond).Nanoseconds()) var b time.Time js.Global.Set("internalizeDate", func(t time.Time) { b = t }) js.Global.Call("eval", "(internalizeDate(new Date(123)))") @@ -303,6 +304,100 @@ func TestInternalizeDate(t *testing.T) { } } +func TestInternalizeStruct(t *testing.T) { + type Person struct { + Name string + Age int + } + var a, expected Person + expected = Person{Name: "foo", Age: 952} + + js.Global.Set("return_person", func(p *Person) *Person { + if p == nil { + t.Fail() + return nil + } + a = *p + return p + }) + + js.Global.Call("eval", "return_person({Name: 'foo', Age: 952})") + if diff := cmp.Diff(a, expected); diff != "" { + t.Errorf("Mismatch (-want +got):\n%s", diff) + } +} + +func TestInternalizeStructUnexportedFields(t *testing.T) { + type Person struct { + Name string + age int + } + var a, expected Person + expected = Person{Name: "foo", age: 0} + js.Global.Set("return_person", func(p *Person) *Person { + a = *p + return p + }) + + js.Global.Call("eval", "return_person({Name: 'foo', age: 952})") + + // Manually check unexported fields + if a.age != expected.age { + t.Errorf("Mismatch in age: got %v, want %v", a.age, expected.age) + } + + // Check exported fields using cmp.Diff + if diff := cmp.Diff(a.Name, expected.Name); diff != "" { + t.Errorf("Mismatch in Name (-want +got):\n%s", diff) + } +} + +func TestInternalizeStructNested(t *testing.T) { + type FullName struct { + FirstName string + LastName string + } + type Person struct { + Name string + Age int + F FullName + } + var a, expected Person + expected = Person{Name: "foo", Age: 952, F: FullName{FirstName: "John", LastName: "Doe"}} + + js.Global.Set("return_person", func(p *Person) *Person { + a = *p + return p + }) + + js.Global.Call("eval", "return_person({Name: 'foo', Age: 952, F: {FirstName: 'John', LastName: 'Doe'}})") + if diff := cmp.Diff(a, expected); diff != "" { + t.Errorf("Mismatch (-want +got):\n%s", diff) + } +} + +func TestInternalizeArrayOfStructs(t *testing.T) { + type Person struct { + Name string + Age int + } + type ArrayOfStructs struct { + People []Person + } + var a, expected ArrayOfStructs + expected = ArrayOfStructs{People: []Person{{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}}} + + js.Global.Set("return_people_array", func(p ArrayOfStructs) ArrayOfStructs { + a = p + return p + }) + + js.Global.Call("eval", `return_people_array({People: [{Name: "Alice", Age: 30}, {Name: "Bob", Age: 40}]})`) + if diff := cmp.Diff(a, expected); diff != "" { + t.Errorf("Mismatch (-want +got):\n%s", diff) + } +} + func TestEquality(t *testing.T) { if js.Global.Get("Array") != js.Global.Get("Array") || js.Global.Get("Array") == js.Global.Get("String") { t.Fail() @@ -478,7 +573,6 @@ func TestMakeWrapper(t *testing.T) { if js.MakeWrapper(m).Interface() != m { t.Fail() } - } func TestMakeFullWrapperType(t *testing.T) { @@ -502,7 +596,7 @@ func TestMakeFullWrapperGettersAndSetters(t *testing.T) { Name: "Gopher", Struct: F{Field: 42}, Pointer: f, - Array: [1]F{F{Field: 42}}, + Array: [1]F{{Field: 42}}, Slice: []*F{f}, } @@ -731,7 +825,6 @@ func TestExternalize(t *testing.T) { if result != tt.want { t.Errorf("Unexpected result %q != %q", result, tt.want) } - }) } } diff --git a/tests/linkname_test.go b/tests/linkname_test.go index 9fb34ad03..e6e9e90f9 100644 --- a/tests/linkname_test.go +++ b/tests/linkname_test.go @@ -38,9 +38,11 @@ func TestLinknameMethods(t *testing.T) { method.TestLinkname(t) } -type name struct{ bytes *byte } -type nameOff int32 -type rtype struct{} +type ( + name struct{ bytes *byte } + nameOff int32 + rtype struct{} +) //go:linkname rtype_nameOff reflect.(*rtype).nameOff func rtype_nameOff(r *rtype, off nameOff) name diff --git a/tests/lowlevel_test.go b/tests/lowlevel_test.go index e966eba7a..d25c63709 100644 --- a/tests/lowlevel_test.go +++ b/tests/lowlevel_test.go @@ -1,7 +1,7 @@ package tests_test import ( - "io/ioutil" + "os" "os/exec" "path/filepath" "runtime" @@ -26,7 +26,7 @@ func TestTimeInternalizationExternalization(t *testing.T) { t.Fatalf("%v:\n%s", err, got) } - wantb, err := ioutil.ReadFile(filepath.Join("testdata", "time_inexternalization.out")) + wantb, err := os.ReadFile(filepath.Join("testdata", "time_inexternalization.out")) want := string(wantb) if err != nil { t.Fatalf("error reading .out file: %v", err) diff --git a/tests/map_js_test.go b/tests/map_js_test.go index 26d55e20c..64cc8e6f0 100644 --- a/tests/map_js_test.go +++ b/tests/map_js_test.go @@ -4,8 +4,9 @@ package tests import ( - "github.com/gopherjs/gopherjs/js" "testing" + + "github.com/gopherjs/gopherjs/js" ) func Test_MapWrapper(t *testing.T) { @@ -43,7 +44,6 @@ func Test_MapWrapper(t *testing.T) { } if got := swmWrapper.Get("DummyMap").Get("key").Get("Msg").String(); got != swm.DummyMap["key"].Msg { t.Errorf("DummyMap Got: %s, Want: %s", got, swm.DummyMap["key"].Msg) - } if got := swmWrapper.Call("MapFunc", mapFuncArg).Get("key2").String(); got != mapFuncArg["key2"] { t.Errorf("MapFunc Got: %s, Want: %s", got, mapFuncArg["key2"]) @@ -111,5 +111,4 @@ func Test_MapEmbeddedObject(t *testing.T) { if d.Props["two"] != 2 { t.Errorf("key 'two' value Got: %d, Want: %d", d.Props["two"], 2) } - } diff --git a/tests/misc_test.go b/tests/misc_test.go index 3608d126b..a38d91c81 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -1,12 +1,15 @@ package tests import ( + "go/token" "math" "reflect" "runtime" "strings" + "sync" "testing" "time" + "unsafe" "github.com/gopherjs/gopherjs/tests/otherpkg" ) @@ -430,7 +433,7 @@ func TestEmptySelectCase(t *testing.T) { ch := make(chan int, 1) ch <- 42 - var v = 0 + v := 0 select { case v = <-ch: } @@ -439,17 +442,21 @@ func TestEmptySelectCase(t *testing.T) { } } -var a int -var b int -var C int -var D int +var ( + a int + b int + C int + D int +) -var a1 = &a -var a2 = &a -var b1 = &b -var C1 = &C -var C2 = &C -var D1 = &D +var ( + a1 = &a + a2 = &a + b1 = &b + C1 = &C + C2 = &C + D1 = &D +) func TestPkgVarPointers(t *testing.T) { if a1 != a2 || a1 == b1 || C1 != C2 || C1 == D1 { @@ -532,7 +539,7 @@ func TestTrivialSwitch(t *testing.T) { } return } - t.Fail() + t.Fail() //nolint:govet // unreachable code intentional for test } func TestTupleFnReturnImplicitCast(t *testing.T) { @@ -554,10 +561,12 @@ var tuple2called = 0 func tuple1() (interface{}, error) { return tuple2() } + func tuple2() (int, error) { tuple2called++ return 14, nil } + func TestTupleReturnImplicitCast(t *testing.T) { x, _ := tuple1() if x != 14 || tuple2called != 1 { @@ -664,7 +673,7 @@ func TestSlicingNilSlice(t *testing.T) { s = s[5:10] }) t.Run("DoesNotBecomeNil", func(t *testing.T) { - var s = []int{} + s := []int{} s = s[:] if s == nil { t.Errorf("non-nil slice became nil after slicing: %#v, want []int{}", s) @@ -815,12 +824,12 @@ func TestUntypedNil(t *testing.T) { _ = x == nil ) } - var _ = (*int)(nil) - var _ = (func())(nil) - var _ = ([]byte)(nil) - var _ = (map[int]int)(nil) - var _ = (chan int)(nil) - var _ = (interface{})(nil) + _ = (*int)(nil) + _ = (func())(nil) + _ = ([]byte)(nil) + _ = (map[int]int)(nil) + _ = (chan int)(nil) + _ = (interface{})(nil) { f := func(*int) {} f(nil) @@ -920,3 +929,33 @@ func TestAssignImplicitConversion(t *testing.T) { } }) } + +func TestCompositeLiterals(t *testing.T) { + type S struct{} + type SP *S + + s1 := []*S{{}} + if got := reflect.TypeOf(s1[0]); got.String() != "*tests.S" { + t.Errorf("Got: reflect.TypeOf(s1[0]) = %v. Want: *tests.S", got) + } + + s2 := []SP{{}} + if got := reflect.TypeOf(s2[0]); got.String() != "tests.SP" { + t.Errorf("Got: reflect.TypeOf(s2[0]) = %v. Want: tests.SP", got) + } +} + +func TestFileSetSize(t *testing.T) { + type tokenFileSet struct { + // This type remained essentially consistent from go1.16 to go1.21. + mutex sync.RWMutex + base int + files []*token.File + _ *token.File // changed to atomic.Pointer[token.File] in go1.19 + } + n1 := unsafe.Sizeof(tokenFileSet{}) + n2 := unsafe.Sizeof(token.FileSet{}) + if n1 != n2 { + t.Errorf("Got: unsafe.Sizeof(token.FileSet{}) %v, Want: %v", n2, n1) + } +} diff --git a/tests/runtime_test.go b/tests/runtime_test.go index 1d5b97d02..12f0b34c3 100644 --- a/tests/runtime_test.go +++ b/tests/runtime_test.go @@ -23,14 +23,14 @@ func Test_parseCallFrame(t *testing.T) { want: "foo https://gopherjs.github.io/playground/playground.js 102 11836", }, { - name: "Chrome 96, anonymous eval", + name: "Chrome 96, anonymous eval", input: " at eval ()", - want: "eval 0 0", + want: "eval 0 0", }, { - name: "Chrome 96, anonymous Array.forEach", + name: "Chrome 96, anonymous Array.forEach", input: " at Array.forEach ()", - want: "Array.forEach 0 0", + want: "Array.forEach 0 0", }, { name: "Chrome 96, file location only", diff --git a/tests/syscall_test.go b/tests/syscall_test.go index 13631ad24..104800df7 100644 --- a/tests/syscall_test.go +++ b/tests/syscall_test.go @@ -4,7 +4,6 @@ package tests import ( - "io/ioutil" "os" "syscall" "testing" @@ -20,13 +19,13 @@ func TestGetpid(t *testing.T) { } func TestOpen(t *testing.T) { - f, err := ioutil.TempFile("", "") + f, err := os.CreateTemp("", "") if err != nil { t.Fatalf("Failed to create a temp file: %s", err) } f.Close() defer os.Remove(f.Name()) - fd, err := syscall.Open(f.Name(), syscall.O_RDONLY, 0600) + fd, err := syscall.Open(f.Name(), syscall.O_RDONLY, 0o600) if err != nil { t.Fatalf("syscall.Open() returned error: %s", err) } diff --git a/tests/testdata/defer_builtin.go b/tests/testdata/defer_builtin.go index 95d22f4a6..264b78b61 100644 --- a/tests/testdata/defer_builtin.go +++ b/tests/testdata/defer_builtin.go @@ -1,7 +1,9 @@ package main -type set map[interface{}]struct{} -type key struct{ a int } +type ( + set map[interface{}]struct{} + key struct{ a int } +) var m = set{} diff --git a/tests/testdata/linkname/three/three.go b/tests/testdata/linkname/three/three.go index 4943d3c1d..e705dc79b 100644 --- a/tests/testdata/linkname/three/three.go +++ b/tests/testdata/linkname/three/three.go @@ -7,7 +7,7 @@ func DoThree() string { func init() { // Avoid dead-code elimination. // TODO(nevkontakte): This should not be necessary. - var _ = doInternalThree + _ = doInternalThree } var threeSecret = "three secret" diff --git a/tests/testdata/linkname/two/two.go b/tests/testdata/linkname/two/two.go index 10f8f9a37..42f8362b2 100644 --- a/tests/testdata/linkname/two/two.go +++ b/tests/testdata/linkname/two/two.go @@ -5,7 +5,7 @@ import _ "unsafe" // for go:linkname func init() { // Avoid dead-code elimination. // TODO(nevkontakte): This should not be necessary. - var _ = doInternalOne + _ = doInternalOne } func DoTwo() string { diff --git a/tool.go b/tool.go index b9a47c75b..06483e96b 100644 --- a/tool.go +++ b/tool.go @@ -10,7 +10,6 @@ import ( "go/token" "go/types" "io" - "io/ioutil" "net" "net/http" "os" @@ -35,8 +34,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" - "golang.org/x/crypto/ssh/terminal" "golang.org/x/sync/errgroup" + "golang.org/x/term" ) var currentDirectory string @@ -79,7 +78,7 @@ func main() { compilerFlags := pflag.NewFlagSet("", 0) compilerFlags.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code") - compilerFlags.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") + compilerFlags.BoolVar(&options.Color, "color", term.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") compilerFlags.StringVar(&tags, "tags", "", "a list of build tags to consider satisfied during the build") compilerFlags.BoolVar(&options.MapToLocalDisk, "localmap", false, "use local paths for sourcemap") compilerFlags.BoolVarP(&options.NoCache, "no_cache", "a", false, "rebuild all packages from scratch") @@ -280,9 +279,9 @@ func main() { return fmt.Errorf("gopherjs run: no go files listed") } - tempfile, err := ioutil.TempFile(currentDirectory, filepath.Base(args[0])+".") + tempfile, err := os.CreateTemp(currentDirectory, filepath.Base(args[0])+".") if err != nil && strings.HasPrefix(currentDirectory, runtime.GOROOT()) { - tempfile, err = ioutil.TempFile("", filepath.Base(args[0])+".") + tempfile, err = os.CreateTemp("", filepath.Base(args[0])+".") } if err != nil { return err