Skip to content

Commit 698d025

Browse files
authored
Merge pull request gopherjs#1216 from gopherjs/keep_overridden
Ability to call overridden functions from the std library
2 parents 781d5dd + 634857c commit 698d025

File tree

3 files changed

+58
-15
lines changed

3 files changed

+58
-15
lines changed

build/build.go

+29-14
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,20 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
125125
// The native packages are augmented by the contents of natives.FS in the following way.
126126
// The file names do not matter except the usual `_test` suffix. The files for
127127
// native overrides get added to the package (even if they have the same name
128-
// as an existing file from the standard library). For all identifiers that exist
129-
// in the original AND the overrides, the original identifier in the AST gets
130-
// replaced by `_`. New identifiers that don't exist in original package get added.
128+
// as an existing file from the standard library). For function identifiers that exist
129+
// in the original AND the overrides AND that include the following directive in their comment:
130+
// //gopherjs:keep-original, the original identifier in the AST gets prefixed by
131+
// `_gopherjs_original_`. For other identifiers that exist in the original AND the overrides,
132+
// the original identifier gets replaced by `_`. New identifiers that don't exist in original
133+
// package get added.
131134
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) {
132135
var files []*ast.File
133-
replacedDeclNames := make(map[string]bool)
134-
pruneOriginalFuncs := make(map[string]bool)
136+
137+
type overrideInfo struct {
138+
keepOriginal bool
139+
pruneOriginal bool
140+
}
141+
replacedDeclNames := make(map[string]overrideInfo)
135142

136143
isXTest := strings.HasSuffix(pkg.ImportPath, "_test")
137144
importPath := pkg.ImportPath
@@ -170,18 +177,20 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
170177
switch d := decl.(type) {
171178
case *ast.FuncDecl:
172179
k := astutil.FuncKey(d)
173-
replacedDeclNames[k] = true
174-
pruneOriginalFuncs[k] = astutil.PruneOriginal(d)
180+
replacedDeclNames[k] = overrideInfo{
181+
keepOriginal: astutil.KeepOriginal(d),
182+
pruneOriginal: astutil.PruneOriginal(d),
183+
}
175184
case *ast.GenDecl:
176185
switch d.Tok {
177186
case token.TYPE:
178187
for _, spec := range d.Specs {
179-
replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = true
188+
replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{}
180189
}
181190
case token.VAR, token.CONST:
182191
for _, spec := range d.Specs {
183192
for _, name := range spec.(*ast.ValueSpec).Names {
184-
replacedDeclNames[name.Name] = true
193+
replacedDeclNames[name.Name] = overrideInfo{}
185194
}
186195
}
187196
}
@@ -234,20 +243,26 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
234243
switch d := decl.(type) {
235244
case *ast.FuncDecl:
236245
k := astutil.FuncKey(d)
237-
if replacedDeclNames[k] {
238-
d.Name = ast.NewIdent("_")
239-
if pruneOriginalFuncs[k] {
246+
if info, ok := replacedDeclNames[k]; ok {
247+
if info.pruneOriginal {
240248
// Prune function bodies, since it may contain code invalid for
241249
// GopherJS and pin unwanted imports.
242250
d.Body = nil
243251
}
252+
if info.keepOriginal {
253+
// Allow overridden function calls
254+
// The standard library implementation of foo() becomes _gopherjs_original_foo()
255+
d.Name.Name = "_gopherjs_original_" + d.Name.Name
256+
} else {
257+
d.Name = ast.NewIdent("_")
258+
}
244259
}
245260
case *ast.GenDecl:
246261
switch d.Tok {
247262
case token.TYPE:
248263
for _, spec := range d.Specs {
249264
s := spec.(*ast.TypeSpec)
250-
if replacedDeclNames[s.Name.Name] {
265+
if _, ok := replacedDeclNames[s.Name.Name]; ok {
251266
s.Name = ast.NewIdent("_")
252267
s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}}
253268
s.TypeParams = nil
@@ -257,7 +272,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
257272
for _, spec := range d.Specs {
258273
s := spec.(*ast.ValueSpec)
259274
for i, name := range s.Names {
260-
if replacedDeclNames[name.Name] {
275+
if _, ok := replacedDeclNames[name.Name]; ok {
261276
s.Names[i] = ast.NewIdent("_")
262277
}
263278
}

compiler/astutil/astutil.go

+20
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,26 @@ func PruneOriginal(d *ast.FuncDecl) bool {
9393
return false
9494
}
9595

96+
// KeepOriginal returns true if gopherjs:keep-original directive is present
97+
// before a function decl.
98+
//
99+
// `//gopherjs:keep-original` is a GopherJS-specific directive, which can be
100+
// applied to functions in native overlays and will instruct the augmentation
101+
// logic to expose the original function such that it can be called. For a
102+
// function in the original called `foo`, it will be accessible by the name
103+
// `_gopherjs_original_foo`.
104+
func KeepOriginal(d *ast.FuncDecl) bool {
105+
if d.Doc == nil {
106+
return false
107+
}
108+
for _, c := range d.Doc.List {
109+
if strings.HasPrefix(c.Text, "//gopherjs:keep-original") {
110+
return true
111+
}
112+
}
113+
return false
114+
}
115+
96116
// FindLoopStmt tries to find the loop statement among the AST nodes in the
97117
// |stack| that corresponds to the break/continue statement represented by
98118
// branch.

compiler/natives/src/regexp/regexp_test.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ import (
77
"testing"
88
)
99

10+
//gopherjs:keep-original
1011
func TestOnePassCutoff(t *testing.T) {
11-
t.Skip() // "Maximum call stack size exceeded" on V8
12+
defer func() {
13+
if r := recover(); r != nil {
14+
t.Log(r)
15+
t.Skip("'Maximum call stack size exceeded' may happen on V8, skipping")
16+
}
17+
}()
18+
19+
_gopherjs_original_TestOnePassCutoff(t)
1220
}

0 commit comments

Comments
 (0)