-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update AST helper generation #7558
Merged
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
71482a7
it's a start
systay 4cbdb20
apply pre- and post-switch
systay 08e38d9
switch
systay 0a1992e
extracted rewriter code to own file
systay 05b0af3
visit struct fields that implement the interface
systay d72873d
support field with struct type
systay 9290991
extracted rewriter generation into its own file
systay 9c3dc09
added replacer methods
systay e2d23e2
support interface implementations that are not pointers
systay 98e4e5b
add replacement methods for array fields
systay 5c93919
added case statements for slice fields
systay 2d66e2e
Addition of panic of value type implementation
frouioui 999a36f
asthelpergen: improve integration tests
vmg 21e902c
better tests
systay 7af230c
benchmark for slice replacer
systay 7b5f171
nicer replacement methods for slices
systay 0116a12
Added replacer and cases for InterfaceSlice
GuptaManan100 c3c68be
moved code away from the generated file
systay 0fb0313
support slices of non-AST elements
systay 3f43634
use the new rewriter
systay 34c6502
Merge remote-tracking branch 'upstream/master' into visitorgen-rewrite3
systay f2d4e68
refactored and addressed review comments
systay 99b6ac0
Merge remote-tracking branch 'upstream/master' into visitorgen-rewrite3
systay ffe3527
Add deep clone capability to ASThelpergen
systay 725fcd5
added support for basic types that implement interfaces
systay e2dc629
handle interface{} fields
systay 044b550
final touch ups. produce the clone.go file for the AST
systay b42085d
code clean up; minor change for interface clone methods
systay b79366a
make the clone methods use less memory
systay 858859e
added comment to clone methods
systay 46c42d4
Added the option to excempt types from clone generation
systay dc14a65
remove the hand written clone and start using the generated one
systay 09f3f0b
inline replacer methods for the rewriter
systay fce3959
Merge remote-tracking branch 'upstream/master' into deep-clone
systay aa88caf
made nextval as reference type in ast
harshit-gangal 3e95345
dry
systay f4036ca
simplify the function used to visit slice elements
systay cd7c4b3
simplify the rewriter some more
systay f03d4b1
rewriter benchmark
systay 9af91e0
Revert "simplify the rewriter some more"
systay 5488cc6
added error to the return params of Rewrite
systay db053f7
return errors instead of panicking
systay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
/* | ||
Copyright 2021 The Vitess Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"flag" | ||
"fmt" | ||
"go/types" | ||
"io/ioutil" | ||
"log" | ||
"path" | ||
"strings" | ||
|
||
"github.com/dave/jennifer/jen" | ||
"golang.org/x/tools/go/packages" | ||
) | ||
|
||
const licenseFileHeader = `Copyright 2021 The Vitess Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License.` | ||
|
||
type generator interface { | ||
visitStruct(t types.Type, stroct *types.Struct) error | ||
visitInterface(t types.Type, iface *types.Interface) error | ||
visitSlice(t types.Type, slice *types.Slice) error | ||
createFile(pkgName string) (string, *jen.File) | ||
} | ||
|
||
// astHelperGen finds implementations of the given interface, | ||
// and uses the supplied `generator`s to produce the output code | ||
type astHelperGen struct { | ||
DebugTypes bool | ||
mod *packages.Module | ||
sizes types.Sizes | ||
namedIface *types.Named | ||
iface *types.Interface | ||
gens []generator | ||
} | ||
|
||
func newGenerator(mod *packages.Module, sizes types.Sizes, named *types.Named, generators ...generator) *astHelperGen { | ||
return &astHelperGen{ | ||
DebugTypes: true, | ||
mod: mod, | ||
sizes: sizes, | ||
namedIface: named, | ||
iface: named.Underlying().(*types.Interface), | ||
gens: generators, | ||
} | ||
} | ||
|
||
func findImplementations(scope *types.Scope, iff *types.Interface, impl func(types.Type) error) error { | ||
for _, name := range scope.Names() { | ||
obj := scope.Lookup(name) | ||
if _, ok := obj.(*types.TypeName); !ok { | ||
continue | ||
} | ||
baseType := obj.Type() | ||
if types.Implements(baseType, iff) { | ||
err := impl(baseType) | ||
if err != nil { | ||
return err | ||
} | ||
continue | ||
} | ||
pointerT := types.NewPointer(baseType) | ||
if types.Implements(pointerT, iff) { | ||
err := impl(pointerT) | ||
if err != nil { | ||
return err | ||
} | ||
continue | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (gen *astHelperGen) visitStruct(t types.Type, stroct *types.Struct) error { | ||
for _, g := range gen.gens { | ||
err := g.visitStruct(t, stroct) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (gen *astHelperGen) visitSlice(t types.Type, slice *types.Slice) error { | ||
for _, g := range gen.gens { | ||
err := g.visitSlice(t, slice) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (gen *astHelperGen) visitInterface(t types.Type, iface *types.Interface) error { | ||
for _, g := range gen.gens { | ||
err := g.visitInterface(t, iface) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// GenerateCode is the main loop where we build up the code per file. | ||
func (gen *astHelperGen) GenerateCode() (map[string]*jen.File, error) { | ||
pkg := gen.namedIface.Obj().Pkg() | ||
iface, ok := gen.iface.Underlying().(*types.Interface) | ||
if !ok { | ||
return nil, fmt.Errorf("expected interface, but got %T", gen.iface) | ||
} | ||
|
||
err := findImplementations(pkg.Scope(), iface, func(t types.Type) error { | ||
switch n := t.Underlying().(type) { | ||
case *types.Struct: | ||
return gen.visitStruct(t, n) | ||
case *types.Slice: | ||
return gen.visitSlice(t, n) | ||
case *types.Pointer: | ||
strct, isStrct := n.Elem().Underlying().(*types.Struct) | ||
if isStrct { | ||
return gen.visitStruct(t, strct) | ||
} | ||
case *types.Interface: | ||
return gen.visitInterface(t, n) | ||
default: | ||
// do nothing | ||
} | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
result := map[string]*jen.File{} | ||
for _, g := range gen.gens { | ||
file, code := g.createFile(pkg.Name()) | ||
fullPath := path.Join(gen.mod.Dir, strings.TrimPrefix(pkg.Path(), gen.mod.Path), file) | ||
result[fullPath] = code | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
type typePaths []string | ||
|
||
func (t *typePaths) String() string { | ||
return fmt.Sprintf("%v", *t) | ||
} | ||
|
||
func (t *typePaths) Set(path string) error { | ||
*t = append(*t, path) | ||
return nil | ||
} | ||
|
||
func main() { | ||
var patterns typePaths | ||
var generate, except string | ||
var verify bool | ||
|
||
flag.Var(&patterns, "in", "Go packages to load the generator") | ||
flag.StringVar(&generate, "iface", "", "Root interface generate rewriter for") | ||
flag.BoolVar(&verify, "verify", false, "ensure that the generated files are correct") | ||
flag.StringVar(&except, "except", "", "don't deep clone these types") | ||
flag.Parse() | ||
|
||
result, err := GenerateASTHelpers(patterns, generate, except) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
if verify { | ||
for _, err := range VerifyFilesOnDisk(result) { | ||
log.Fatal(err) | ||
} | ||
log.Printf("%d files OK", len(result)) | ||
} else { | ||
for fullPath, file := range result { | ||
if err := file.Save(fullPath); err != nil { | ||
log.Fatalf("failed to save file to '%s': %v", fullPath, err) | ||
} | ||
log.Printf("saved '%s'", fullPath) | ||
} | ||
} | ||
} | ||
|
||
// VerifyFilesOnDisk compares the generated results from the codegen against the files that | ||
// currently exist on disk and returns any mismatches | ||
func VerifyFilesOnDisk(result map[string]*jen.File) (errors []error) { | ||
for fullPath, file := range result { | ||
existing, err := ioutil.ReadFile(fullPath) | ||
if err != nil { | ||
errors = append(errors, fmt.Errorf("missing file on disk: %s (%w)", fullPath, err)) | ||
continue | ||
} | ||
|
||
var buf bytes.Buffer | ||
if err := file.Render(&buf); err != nil { | ||
errors = append(errors, fmt.Errorf("render error for '%s': %w", fullPath, err)) | ||
continue | ||
} | ||
|
||
if !bytes.Equal(existing, buf.Bytes()) { | ||
errors = append(errors, fmt.Errorf("'%s' has changed", fullPath)) | ||
continue | ||
} | ||
} | ||
return errors | ||
} | ||
|
||
// GenerateASTHelpers loads the input code, constructs the necessary generators, | ||
// and generates the rewriter and clone methods for the AST | ||
func GenerateASTHelpers(packagePatterns []string, rootIface, exceptCloneType string) (map[string]*jen.File, error) { | ||
loaded, err := packages.Load(&packages.Config{ | ||
Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedTypesInfo | packages.NeedDeps | packages.NeedImports | packages.NeedModule, | ||
Logf: log.Printf, | ||
}, packagePatterns...) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
scopes := make(map[string]*types.Scope) | ||
for _, pkg := range loaded { | ||
scopes[pkg.PkgPath] = pkg.Types.Scope() | ||
} | ||
|
||
pos := strings.LastIndexByte(rootIface, '.') | ||
if pos < 0 { | ||
return nil, fmt.Errorf("unexpected input type: %s", rootIface) | ||
} | ||
|
||
pkgname := rootIface[:pos] | ||
typename := rootIface[pos+1:] | ||
|
||
scope := scopes[pkgname] | ||
if scope == nil { | ||
return nil, fmt.Errorf("no scope found for type '%s'", rootIface) | ||
} | ||
|
||
tt := scope.Lookup(typename) | ||
if tt == nil { | ||
return nil, fmt.Errorf("no type called '%s' found in '%s'", typename, pkgname) | ||
} | ||
|
||
nt := tt.Type().(*types.Named) | ||
|
||
iface := nt.Underlying().(*types.Interface) | ||
|
||
interestingType := func(t types.Type) bool { | ||
return types.Implements(t, iface) | ||
} | ||
rewriter := newRewriterGen(interestingType, nt.Obj().Name()) | ||
clone := newCloneGen(iface, scope, exceptCloneType) | ||
|
||
generator := newGenerator(loaded[0].Module, loaded[0].TypesSizes, nt, rewriter, clone) | ||
it, err := generator.GenerateCode() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return it, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
Copyright 2021 The Vitess Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestFullGeneration(t *testing.T) { | ||
result, err := GenerateASTHelpers([]string{"./integration/..."}, "vitess.io/vitess/go/tools/asthelpergen/integration.AST", "*NoCloneType") | ||
require.NoError(t, err) | ||
|
||
verifyErrors := VerifyFilesOnDisk(result) | ||
require.Empty(t, verifyErrors) | ||
|
||
for _, file := range result { | ||
contents := fmt.Sprintf("%#v", file) | ||
require.Contains(t, contents, "http://www.apache.org/licenses/LICENSE-2.0") | ||
applyIdx := strings.Index(contents, "func (a *application) apply(parent, node AST, replacer replacerFunc)") | ||
cloneIdx := strings.Index(contents, "CloneAST(in AST) AST") | ||
if applyIdx == 0 && cloneIdx == 0 { | ||
t.Fatalf("file doesn't contain expected contents") | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a comment for an exported function?