From c47ddde90f810ea9024c6c4ed25887acb77d6438 Mon Sep 17 00:00:00 2001 From: Renaud Lehoux Date: Wed, 21 Dec 2016 16:26:06 +0100 Subject: [PATCH 1/7] build: Allow overriden functions calls Before this commit, every overriden standard library function was renamed '_'. Now, if there is the following directive in the function comment: "//gopherjs:keep_overridden", they are prefixed with "_gopherjs_overridden_" so that they can be called from the overriding function. This is typically useful to use the standard library implementation as a fallback. --- build/build.go | 52 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/build/build.go b/build/build.go index 63c89c50f..e0f19b197 100644 --- a/build/build.go +++ b/build/build.go @@ -117,6 +117,23 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } +// Test if we find the '//gopherjs:keep_overridden' comment +func findKeepOverridenComment(doc *ast.CommentGroup) bool { + if doc == nil { + return false + } + for _, comment := range doc.List { + text := comment.Text + if i := strings.Index(text, " "); i >= 0 { + text = text[:i] + } + if text == "//gopherjs:keep_overridden" { + return true + } + } + return false +} + // 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,12 +142,19 @@ 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 that include the following directive in their comment: +// //gopherjs:keep_overridden, the original identifier in the AST gets prefixed by +// `_gopherjs_overridden_`. For other identifiers that exist in the original AND the overrides, +// the original identifier gets replaced by `_`. 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) + + type overrideInfo struct { + keepOverriden bool + } + replacedDeclNames := make(map[string]overrideInfo) pruneOriginalFuncs := make(map[string]bool) isXTest := strings.HasSuffix(pkg.ImportPath, "_test") @@ -170,18 +194,18 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = true + replacedDeclNames[k] = overrideInfo{keepOverriden: findKeepOverridenComment(d.Doc)} 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 + replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{} } case token.VAR, token.CONST: for _, spec := range d.Specs { for _, name := range spec.(*ast.ValueSpec).Names { - replacedDeclNames[name.Name] = true + replacedDeclNames[name.Name] = overrideInfo{} } } } @@ -234,20 +258,26 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - if replacedDeclNames[k] { - d.Name = ast.NewIdent("_") + if info, ok := replacedDeclNames[k]; ok { if pruneOriginalFuncs[k] { // Prune function bodies, since it may contain code invalid for // GopherJS and pin unwanted imports. d.Body = nil } + if info.keepOverriden { + // Allow overridden function calls + // The standard library implementation of foo() becomes _gopherjs_overridden_foo() + d.Name.Name = "_gopherjs_overridden_" + d.Name.Name + } else { + d.Name = ast.NewIdent("_") + } } case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { s := spec.(*ast.TypeSpec) - if replacedDeclNames[s.Name.Name] { + if _, ok := replacedDeclNames[s.Name.Name]; ok { s.Name = ast.NewIdent("_") s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}} s.TypeParams = nil @@ -257,7 +287,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke for _, spec := range d.Specs { s := spec.(*ast.ValueSpec) for i, name := range s.Names { - if replacedDeclNames[name.Name] { + if _, ok := replacedDeclNames[name.Name]; ok { s.Names[i] = ast.NewIdent("_") } } From 9d91789ac258df65b55463d138839083a5f106d0 Mon Sep 17 00:00:00 2001 From: Renaud Lehoux Date: Sat, 21 Apr 2018 01:20:37 +0200 Subject: [PATCH 2/7] regexp test: added an example use for gopherjs:keep_overridden Before this commit, TestOnePassCutoff was skipped because a 'Maximum call stack size exceeded' message may occur. Now we use gopherjs:keep_overridden to try the original test, and skip only if the original test failed. --- compiler/natives/src/regexp/regexp_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index d5fa9211d..f7fc25031 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_overridden 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_overridden_TestOnePassCutoff(t) } From 06d6901afb8ad12971eec6c73808a2baf0d7bce3 Mon Sep 17 00:00:00 2001 From: Renaud Lehoux Date: Sat, 21 Apr 2018 20:06:26 +0200 Subject: [PATCH 3/7] fixed typo --- build/build.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.go b/build/build.go index e0f19b197..2ad426856 100644 --- a/build/build.go +++ b/build/build.go @@ -118,7 +118,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag } // Test if we find the '//gopherjs:keep_overridden' comment -func findKeepOverridenComment(doc *ast.CommentGroup) bool { +func findKeepOverriddenComment(doc *ast.CommentGroup) bool { if doc == nil { return false } @@ -152,7 +152,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke var files []*ast.File type overrideInfo struct { - keepOverriden bool + keepOverridden bool } replacedDeclNames := make(map[string]overrideInfo) pruneOriginalFuncs := make(map[string]bool) @@ -194,7 +194,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{keepOverriden: findKeepOverridenComment(d.Doc)} + replacedDeclNames[k] = overrideInfo{keepOverridden: findKeepOverriddenComment(d.Doc)} pruneOriginalFuncs[k] = astutil.PruneOriginal(d) case *ast.GenDecl: switch d.Tok { @@ -264,7 +264,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke // GopherJS and pin unwanted imports. d.Body = nil } - if info.keepOverriden { + if info.keepOverridden { // Allow overridden function calls // The standard library implementation of foo() becomes _gopherjs_overridden_foo() d.Name.Name = "_gopherjs_overridden_" + d.Name.Name From fe0510dc1eaf4979c9190c02622622973a7cd44e Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 14:24:25 +0200 Subject: [PATCH 4/7] Rename `gopherjs:keep_overridden` to `gopherjs:keep-original` ... as per https://github.com/gopherjs/gopherjs/pull/798#issuecomment-955238429 --- build/build.go | 6 +++--- compiler/natives/src/regexp/regexp_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.go b/build/build.go index 2ad426856..cbefb4bc6 100644 --- a/build/build.go +++ b/build/build.go @@ -117,7 +117,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } -// Test if we find the '//gopherjs:keep_overridden' comment +// Test if we find the '//gopherjs:keep-original' comment func findKeepOverriddenComment(doc *ast.CommentGroup) bool { if doc == nil { return false @@ -127,7 +127,7 @@ func findKeepOverriddenComment(doc *ast.CommentGroup) bool { if i := strings.Index(text, " "); i >= 0 { text = text[:i] } - if text == "//gopherjs:keep_overridden" { + if text == "//gopherjs:keep-original" { return true } } @@ -144,7 +144,7 @@ func findKeepOverriddenComment(doc *ast.CommentGroup) bool { // native overrides get added to the package (even if they have the same name // as an existing file from the standard library). For function identifiers that exist // in the original AND the overrides AND that include the following directive in their comment: -// //gopherjs:keep_overridden, the original identifier in the AST gets prefixed by +// //gopherjs:keep-original, the original identifier in the AST gets prefixed by // `_gopherjs_overridden_`. For other identifiers that exist in the original AND the overrides, // the original identifier gets replaced by `_`. New identifiers that don't exist in original // package get added. diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index f7fc25031..b60f72564 100644 --- a/compiler/natives/src/regexp/regexp_test.go +++ b/compiler/natives/src/regexp/regexp_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -//gopherjs:keep_overridden +//gopherjs:keep-original func TestOnePassCutoff(t *testing.T) { defer func() { if r := recover(); r != nil { From a8add9b0975474f3faf61fec274caec094930a27 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 14:37:01 +0200 Subject: [PATCH 5/7] Move findKeepOverriddenComments into astutil package for consistency with other such helpers --- build/build.go | 19 +------------------ compiler/astutil/astutil.go | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/build/build.go b/build/build.go index cbefb4bc6..d0295cabf 100644 --- a/build/build.go +++ b/build/build.go @@ -117,23 +117,6 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag return pkg, nil } -// Test if we find the '//gopherjs:keep-original' comment -func findKeepOverriddenComment(doc *ast.CommentGroup) bool { - if doc == nil { - return false - } - for _, comment := range doc.List { - text := comment.Text - if i := strings.Index(text, " "); i >= 0 { - text = text[:i] - } - if text == "//gopherjs:keep-original" { - return true - } - } - return false -} - // 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. @@ -194,7 +177,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{keepOverridden: findKeepOverriddenComment(d.Doc)} + replacedDeclNames[k] = overrideInfo{keepOverridden: astutil.KeepOriginal(d)} pruneOriginalFuncs[k] = astutil.PruneOriginal(d) case *ast.GenDecl: switch d.Tok { diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index b9c4b54c8..2f68f4c9c 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -93,6 +93,26 @@ func PruneOriginal(d *ast.FuncDecl) bool { return false } +// KeepOriginal returns true if gopherjs:keep-original directive is present +// before a function decl. +// +// `//gopherjs:keep-original` is a GopherJS-specific directive, which can be +// applied to functions in native overlays and will instruct the augmentation +// 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_overridden_foo`. +func KeepOriginal(d *ast.FuncDecl) bool { + if d.Doc == nil { + return false + } + for _, c := range d.Doc.List { + if strings.HasPrefix(c.Text, "//gopherjs:keep-original") { + return true + } + } + return false +} + // FindLoopStmt tries to find the loop statement among the AST nodes in the // |stack| that corresponds to the break/continue statement represented by // branch. From f9b66ec251851cc54763dd4f440c6d9e5a18b4ba Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Mon, 26 Jun 2023 14:40:11 +0200 Subject: [PATCH 6/7] Merge keepOriginal and pruneOriginal into a single struct --- build/build.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/build/build.go b/build/build.go index d0295cabf..86fc15014 100644 --- a/build/build.go +++ b/build/build.go @@ -135,10 +135,10 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke var files []*ast.File type overrideInfo struct { - keepOverridden bool + keepOriginal bool + pruneOriginal bool } replacedDeclNames := make(map[string]overrideInfo) - pruneOriginalFuncs := make(map[string]bool) isXTest := strings.HasSuffix(pkg.ImportPath, "_test") importPath := pkg.ImportPath @@ -177,8 +177,10 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) - replacedDeclNames[k] = overrideInfo{keepOverridden: astutil.KeepOriginal(d)} - pruneOriginalFuncs[k] = astutil.PruneOriginal(d) + replacedDeclNames[k] = overrideInfo{ + keepOriginal: astutil.KeepOriginal(d), + pruneOriginal: astutil.PruneOriginal(d), + } case *ast.GenDecl: switch d.Tok { case token.TYPE: @@ -242,12 +244,12 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke case *ast.FuncDecl: k := astutil.FuncKey(d) if info, ok := replacedDeclNames[k]; ok { - if pruneOriginalFuncs[k] { + if info.pruneOriginal { // Prune function bodies, since it may contain code invalid for // GopherJS and pin unwanted imports. d.Body = nil } - if info.keepOverridden { + if info.keepOriginal { // Allow overridden function calls // The standard library implementation of foo() becomes _gopherjs_overridden_foo() d.Name.Name = "_gopherjs_overridden_" + d.Name.Name From 634857c4a2b4819720b18cf3f48cc05ee34295a9 Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Tue, 27 Jun 2023 09:37:43 +0200 Subject: [PATCH 7/7] Rename _gopherjs_overridden_ to _gopherjs_original_ for greater consistency --- build/build.go | 6 +++--- compiler/astutil/astutil.go | 2 +- compiler/natives/src/regexp/regexp_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/build.go b/build/build.go index 86fc15014..070d05df1 100644 --- a/build/build.go +++ b/build/build.go @@ -128,7 +128,7 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // as an existing file from the standard library). For function identifiers that exist // in the original AND the overrides AND that include the following directive in their comment: // //gopherjs:keep-original, the original identifier in the AST gets prefixed by -// `_gopherjs_overridden_`. For other identifiers that exist in the original AND the overrides, +// `_gopherjs_original_`. For other identifiers that exist in the original AND the overrides, // the original identifier gets replaced by `_`. 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) { @@ -251,8 +251,8 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke } if info.keepOriginal { // Allow overridden function calls - // The standard library implementation of foo() becomes _gopherjs_overridden_foo() - d.Name.Name = "_gopherjs_overridden_" + d.Name.Name + // The standard library implementation of foo() becomes _gopherjs_original_foo() + d.Name.Name = "_gopherjs_original_" + d.Name.Name } else { d.Name = ast.NewIdent("_") } diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 2f68f4c9c..30febe1cb 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -100,7 +100,7 @@ func PruneOriginal(d *ast.FuncDecl) bool { // applied to functions in native overlays and will instruct the augmentation // 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_overridden_foo`. +// `_gopherjs_original_foo`. func KeepOriginal(d *ast.FuncDecl) bool { if d.Doc == nil { return false diff --git a/compiler/natives/src/regexp/regexp_test.go b/compiler/natives/src/regexp/regexp_test.go index b60f72564..3a2d58d32 100644 --- a/compiler/natives/src/regexp/regexp_test.go +++ b/compiler/natives/src/regexp/regexp_test.go @@ -16,5 +16,5 @@ func TestOnePassCutoff(t *testing.T) { } }() - _gopherjs_overridden_TestOnePassCutoff(t) + _gopherjs_original_TestOnePassCutoff(t) }