Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c4be02f

Browse files
Merge pull request #1414 from Workiva/newOverrideDirectives
[go1.21] Adding `new` / `replace` optional override directives
2 parents c0bc7ea + affda92 commit c4be02f

File tree

5 files changed

+209
-8
lines changed

5 files changed

+209
-8
lines changed

build/build.go

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,26 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
124124
// overrideInfo is used by parseAndAugment methods to manage
125125
// directives and how the overlay and original are merged.
126126
type overrideInfo struct {
127+
// replace indicates that the original code is expected to exist.
128+
// If the original code is not present, then error to let us know
129+
// the original code has been removed or renamed.
130+
// This will be set to false once the original code has been found.
131+
replace bool
132+
133+
// new indicates that the override code is expected to be new.
134+
// If the original code is present, then error to let us know
135+
// that we are overriding something that we did not expect to replace.
136+
// This lets us know of new or renamed code in the original that happens
137+
// to collide with our overrides.
138+
new bool
139+
127140
// keepOriginal indicates that the original code should be kept
128141
// but the identifier will be prefixed by `_gopherjs_original_foo`.
129142
// If false the original code is removed.
130143
keepOriginal bool
131144

132-
// purgeMethods indicates that this info is for a type and
133-
// if a method has this type as a receiver should also be removed.
145+
// purgeMethods indicates that this info is for a type and if a method has
146+
// this type as a receiver then the method should also be removed.
134147
// If the method is defined in the overlays and therefore has its
135148
// own overrides, this will be ignored.
136149
purgeMethods bool
@@ -165,8 +178,11 @@ type overrideInfo struct {
165178
// to match the overridden function signature. This allows the receiver,
166179
// type parameters, parameter, and return values to be modified as needed.
167180
// - Otherwise for identifiers that exist in the original and the overrides,
168-
// the original is removed.
181+
// the original is removed. Use `gopherjs:replace` to ensure that the
182+
// original existed so that a replace is made.
169183
// - New identifiers that don't exist in original package get added.
184+
// Use `gopherjs:new` to ensure that the identifier is new and there was
185+
// no original code for it.
170186
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []incjs.File, error) {
171187
jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet)
172188

@@ -186,8 +202,13 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
186202
}
187203

188204
if len(overrides) > 0 {
205+
found := make(map[string]struct{}, len(overrides))
189206
for _, file := range originalFiles {
190-
augmentOriginalFile(file, overrides)
207+
augmentOriginalFile(file, overrides, found)
208+
}
209+
err := checkOverrides(overrides, found, pkg.ImportPath)
210+
if err != nil {
211+
return nil, nil, err
191212
}
192213
}
193214

@@ -284,12 +305,17 @@ func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File,
284305
func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) {
285306
anyChange := false
286307
for i, decl := range file.Decls {
308+
replaceDecl := astutil.DirectiveReplace(decl)
309+
newDecl := astutil.DirectiveNew(decl)
287310
purgeDecl := astutil.Purge(decl)
311+
288312
switch d := decl.(type) {
289313
case *ast.FuncDecl:
290314
k := astutil.FuncKey(d)
291315
oi := overrideInfo{
292316
keepOriginal: astutil.KeepOriginal(d),
317+
replace: replaceDecl,
318+
new: newDecl,
293319
}
294320
if astutil.OverrideSignature(d) {
295321
oi.overrideSignature = d
@@ -299,14 +325,21 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) {
299325
case *ast.GenDecl:
300326
for j, spec := range d.Specs {
301327
purgeSpec := purgeDecl || astutil.Purge(spec)
328+
replaceSpec := replaceDecl || astutil.DirectiveReplace(spec)
329+
newSpec := newDecl || astutil.DirectiveNew(spec)
302330
switch s := spec.(type) {
303331
case *ast.TypeSpec:
304332
overrides[s.Name.Name] = overrideInfo{
305333
purgeMethods: purgeSpec,
334+
replace: replaceSpec,
335+
new: newSpec,
306336
}
307337
case *ast.ValueSpec:
308338
for _, name := range s.Names {
309-
overrides[name.Name] = overrideInfo{}
339+
overrides[name.Name] = overrideInfo{
340+
replace: replaceSpec,
341+
new: newSpec,
342+
}
310343
}
311344
}
312345
if purgeSpec {
@@ -346,12 +379,14 @@ func augmentOriginalImports(importPath string, file *ast.File) {
346379
// augmentOriginalFile is the part of parseAndAugment that processes an
347380
// original file AST to augment the source code using the overrides from
348381
// the overlay files.
349-
func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
382+
func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, found map[string]struct{}) {
350383
anyChange := false
351384
for i, decl := range file.Decls {
352385
switch d := decl.(type) {
353386
case *ast.FuncDecl:
354-
if info, ok := overrides[astutil.FuncKey(d)]; ok {
387+
funcKey := astutil.FuncKey(d)
388+
if info, ok := overrides[funcKey]; ok {
389+
found[funcKey] = struct{}{}
355390
anyChange = true
356391
removeFunc := true
357392
if info.keepOriginal {
@@ -373,6 +408,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
373408
} else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 {
374409
// check if the receiver has been purged, if so, remove the method too.
375410
if info, ok := overrides[recvKey]; ok && info.purgeMethods {
411+
found[recvKey] = struct{}{}
376412
anyChange = true
377413
file.Decls[i] = nil
378414
}
@@ -382,6 +418,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
382418
switch s := spec.(type) {
383419
case *ast.TypeSpec:
384420
if _, ok := overrides[s.Name.Name]; ok {
421+
found[s.Name.Name] = struct{}{}
385422
anyChange = true
386423
d.Specs[j] = nil
387424
}
@@ -395,6 +432,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
395432
// to be run, add the call into the overlay.
396433
for k, name := range s.Names {
397434
if _, ok := overrides[name.Name]; ok {
435+
found[name.Name] = struct{}{}
398436
anyChange = true
399437
s.Names[k] = nil
400438
s.Values[k] = nil
@@ -410,6 +448,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
410448
nameRemoved := false
411449
for _, name := range s.Names {
412450
if _, ok := overrides[name.Name]; ok {
451+
found[name.Name] = struct{}{}
413452
nameRemoved = true
414453
name.Name = `_`
415454
}
@@ -438,6 +477,29 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
438477
}
439478
}
440479

480+
// checkOverrides performs a final check of the overrides to ensure that
481+
// all overrides that were expected to be found were found and all overrides
482+
// that were not expected to be found were not found.
483+
func checkOverrides(overrides map[string]overrideInfo, found map[string]struct{}, pkgPath string) error {
484+
el := errlist.ErrorList{}
485+
for name, info := range overrides {
486+
_, wasFound := found[name]
487+
switch {
488+
case wasFound && info.new:
489+
el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was found, but override had `new` directive", pkgPath, name))
490+
case !wasFound && info.replace:
491+
el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `replace` directive", pkgPath, name))
492+
case !wasFound && info.keepOriginal:
493+
el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `keep-original` directive", pkgPath, name))
494+
case !wasFound && info.purgeMethods:
495+
el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `purge` directive", pkgPath, name))
496+
case !wasFound && info.overrideSignature != nil:
497+
el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `override-signature` directive", pkgPath, name))
498+
}
499+
}
500+
return el.ErrOrNil()
501+
}
502+
441503
// isOnlyImports determines if this file is empty except for imports.
442504
func isOnlyImports(file *ast.File) bool {
443505
for _, decl := range file.Decls {

build/build_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,11 @@ func TestOriginalAugmentation(t *testing.T) {
724724
fileSrc := f.Parse("test.go", pkgName+test.src)
725725

726726
augmentOriginalImports(importPath, fileSrc)
727-
augmentOriginalFile(fileSrc, test.info)
727+
found := make(map[string]struct{})
728+
augmentOriginalFile(fileSrc, test.info, found)
729+
if err := checkOverrides(test.info, found, importPath); err != nil {
730+
t.Errorf("check overrides: %v", err)
731+
}
728732
pruneImports(fileSrc)
729733

730734
got := srctesting.Format(t, f.FileSet, fileSrc)

compiler/astutil/astutil.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,28 @@ func FuncReceiverKey(d *ast.FuncDecl) string {
133133
}
134134
}
135135

136+
// DirectiveReplace returns true if gopherjs:replace directive is present
137+
// on a struct, interface, type, variable, constant, or function.
138+
//
139+
// `//gopherjs:replace` is a GopherJS-specific directive, which can be
140+
// applied in native overlays and will instruct the augmentation logic to
141+
// ensure that the original code is present and has not been removed nor renamed,
142+
// otherwise an error will be raised.
143+
func DirectiveReplace(d ast.Node) bool {
144+
return hasDirective(d, `replace`)
145+
}
146+
147+
// DirectiveNew returns true if gopherjs:new directive is
148+
// present on a struct, interface, type, variable, constant, or function.
149+
//
150+
// `//gopherjs:new` is a GopherJS-specific directive, which can be
151+
// applied in native overlays and will instruct the augmentation logic to
152+
// ensure that the original code is not present so that this code does not
153+
// override any original code, otherwise an error will be raised.
154+
func DirectiveNew(d ast.Node) bool {
155+
return hasDirective(d, `new`)
156+
}
157+
136158
// KeepOriginal returns true if gopherjs:keep-original directive is present
137159
// before a function decl.
138160
//
@@ -141,6 +163,9 @@ func FuncReceiverKey(d *ast.FuncDecl) string {
141163
// logic to expose the original function such that it can be called. For a
142164
// function in the original called `foo`, it will be accessible by the name
143165
// `_gopherjs_original_foo`.
166+
//
167+
// This will also ensure that the original function exists and hasn't been
168+
// removed or renamed, otherwise an error will be raised.
144169
func KeepOriginal(d *ast.FuncDecl) bool {
145170
return hasDirective(d, `keep-original`)
146171
}
@@ -156,6 +181,9 @@ func KeepOriginal(d *ast.FuncDecl) bool {
156181
// fully supported). It should be used with caution since it may remove needed
157182
// dependencies. If a type is purged, all methods using that type as
158183
// a receiver will also be purged.
184+
//
185+
// This will also ensure that the original code exists and hasn't been
186+
// removed or renamed, otherwise an error will be raised.
159187
func Purge(d ast.Node) bool {
160188
return hasDirective(d, `purge`)
161189
}
@@ -167,13 +195,17 @@ func Purge(d ast.Node) bool {
167195
// be applied in native overlays and will instruct the augmentation logic to
168196
// replace the original function signature which has the same FuncKey with the
169197
// signature defined in the native overlays.
198+
//
170199
// This directive can be used to remove generics from a function signature or
171200
// to replace a receiver of a function with another one. The given native
172201
// overlay function will be removed, so no method body is needed in the overlay.
173202
//
174203
// The new signature may not contain types which require a new import since
175204
// the imports will not be automatically added when needed, only removed.
176205
// Use a type alias in the overlay to deal manage imports.
206+
//
207+
// This will also ensure that the original code exists and hasn't been
208+
// removed or renamed, otherwise an error will be raised.
177209
func OverrideSignature(d *ast.FuncDecl) bool {
178210
return hasDirective(d, `override-signature`)
179211
}

compiler/natives/src/internal/bytealg/bytealg.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package bytealg
44

5+
//gopherjs:replace
56
func Equal(a, b []byte) bool {
67
if len(a) != len(b) {
78
return false
@@ -14,6 +15,7 @@ func Equal(a, b []byte) bool {
1415
return true
1516
}
1617

18+
//gopherjs:replace
1719
func IndexByte(b []byte, c byte) int {
1820
for i, x := range b {
1921
if x == c {
@@ -23,6 +25,7 @@ func IndexByte(b []byte, c byte) int {
2325
return -1
2426
}
2527

28+
//gopherjs:replace
2629
func IndexByteString(s string, c byte) int {
2730
for i := 0; i < len(s); i++ {
2831
if s[i] == c {
@@ -31,3 +34,8 @@ func IndexByteString(s string, c byte) int {
3134
}
3235
return -1
3336
}
37+
38+
//gopherjs:replace
39+
func MakeNoZero(n int) []byte {
40+
return make([]byte, n)
41+
}

0 commit comments

Comments
 (0)