Before this CL, unexported fields were ignored for the purposes of determining which classes a Go struct should extend or implement. However, the field types were also ignored, resulting in the types not being generated at all. This CL adds the types of unexported fields to the set of types to be generated. Fixes golang/go#17945 Change-Id: I5c6c44b7cdfe0c3d4c4dc44863ae201dca7ae9a4 Reviewed-on: https://go-review.googlesource.com/38635 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
251 lines
6.3 KiB
Go
251 lines
6.3 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// The importers package uses go/ast to analyze Go packages or Go files
|
|
// and collect references to types whose package has a package prefix.
|
|
// It is used by the language specific importers to determine the set of
|
|
// wrapper types to be generated.
|
|
//
|
|
// For example, in the Go file
|
|
//
|
|
// package javaprogram
|
|
//
|
|
// import "Java/java/lang"
|
|
//
|
|
// func F() {
|
|
// o := lang.Object.New()
|
|
// ...
|
|
// }
|
|
//
|
|
// the java importer uses this package to determine that the "java/lang"
|
|
// package and the wrapper interface, lang.Object, needs to be generated.
|
|
// After calling AnalyzeFile or AnalyzePackages, the References result
|
|
// contains the reference to lang.Object and the names set will contain
|
|
// "New".
|
|
package importers
|
|
|
|
import (
|
|
"errors"
|
|
"go/ast"
|
|
"go/build"
|
|
"go/parser"
|
|
"go/token"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// References is the result of analyzing a Go file or set of Go packages.
|
|
//
|
|
// For example, the Go file
|
|
//
|
|
// package pkg
|
|
//
|
|
// import "Prefix/some/Package"
|
|
//
|
|
// var A = Package.Identifier
|
|
//
|
|
// Will result in a single PkgRef with the "some/Package" package and
|
|
// the Identifier name. The Names set will contain the single name,
|
|
// "Identifier".
|
|
type References struct {
|
|
// The list of references to identifiers in packages that are
|
|
// identified by a package prefix.
|
|
Refs []PkgRef
|
|
// The list of names used in at least one selector expression.
|
|
// Useful as a conservative upper bound on the set of identifiers
|
|
// referenced from a set of packages.
|
|
Names map[string]struct{}
|
|
// Embedders is a list of struct types with prefixed types
|
|
// embedded.
|
|
Embedders []Struct
|
|
}
|
|
|
|
// Struct is a representation of a struct type with embedded
|
|
// types.
|
|
type Struct struct {
|
|
Name string
|
|
Pkg string
|
|
PkgPath string
|
|
Refs []PkgRef
|
|
}
|
|
|
|
// PkgRef is a reference to an identifier in a package.
|
|
type PkgRef struct {
|
|
Name string
|
|
Pkg string
|
|
}
|
|
|
|
type refsSaver struct {
|
|
pkgPrefix string
|
|
*References
|
|
refMap map[PkgRef]struct{}
|
|
insideStruct bool
|
|
}
|
|
|
|
// AnalyzeFile scans the provided file for references to packages with the given
|
|
// package prefix. The list of unique (package, identifier) pairs is returned
|
|
func AnalyzeFile(file *ast.File, pkgPrefix string) (*References, error) {
|
|
visitor := newRefsSaver(pkgPrefix)
|
|
fset := token.NewFileSet()
|
|
files := map[string]*ast.File{file.Name.Name: file}
|
|
// Ignore errors (from unknown packages)
|
|
pkg, _ := ast.NewPackage(fset, files, visitor.importer(), nil)
|
|
ast.Walk(visitor, pkg)
|
|
visitor.findEmbeddingStructs("", pkg)
|
|
return visitor.References, nil
|
|
}
|
|
|
|
// AnalyzePackages scans the provided packages for references to packages with the given
|
|
// package prefix. The list of unique (package, identifier) pairs is returned
|
|
func AnalyzePackages(pkgs []*build.Package, pkgPrefix string) (*References, error) {
|
|
visitor := newRefsSaver(pkgPrefix)
|
|
imp := visitor.importer()
|
|
fset := token.NewFileSet()
|
|
for _, pkg := range pkgs {
|
|
fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
|
|
files := make(map[string]*ast.File)
|
|
for _, name := range fileNames {
|
|
f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
files[name] = f
|
|
}
|
|
// Ignore errors (from unknown packages)
|
|
astpkg, _ := ast.NewPackage(fset, files, imp, nil)
|
|
ast.Walk(visitor, astpkg)
|
|
visitor.findEmbeddingStructs(pkg.ImportPath, astpkg)
|
|
}
|
|
return visitor.References, nil
|
|
}
|
|
|
|
// findEmbeddingStructs finds all top level declarations embedding a prefixed type.
|
|
//
|
|
// For example:
|
|
//
|
|
// import "Prefix/some/Package"
|
|
//
|
|
// type T struct {
|
|
// Package.Class
|
|
// }
|
|
func (v *refsSaver) findEmbeddingStructs(pkgpath string, pkg *ast.Package) {
|
|
var names []string
|
|
for _, obj := range pkg.Scope.Objects {
|
|
if obj.Kind != ast.Typ || !ast.IsExported(obj.Name) {
|
|
continue
|
|
}
|
|
names = append(names, obj.Name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
obj := pkg.Scope.Objects[name]
|
|
|
|
t, ok := obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType)
|
|
if !ok {
|
|
continue
|
|
}
|
|
var refs []PkgRef
|
|
for _, f := range t.Fields.List {
|
|
sel, ok := f.Type.(*ast.SelectorExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ref, ok := v.addRef(sel)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if len(f.Names) > 0 && !f.Names[0].IsExported() {
|
|
continue
|
|
}
|
|
refs = append(refs, ref)
|
|
}
|
|
if len(refs) > 0 {
|
|
v.Embedders = append(v.Embedders, Struct{
|
|
Name: obj.Name,
|
|
Pkg: pkg.Name,
|
|
PkgPath: pkgpath,
|
|
|
|
Refs: refs,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func newRefsSaver(pkgPrefix string) *refsSaver {
|
|
s := &refsSaver{
|
|
pkgPrefix: pkgPrefix,
|
|
refMap: make(map[PkgRef]struct{}),
|
|
References: &References{},
|
|
}
|
|
s.Names = make(map[string]struct{})
|
|
return s
|
|
}
|
|
|
|
func (v *refsSaver) importer() ast.Importer {
|
|
return func(imports map[string]*ast.Object, pkgPath string) (*ast.Object, error) {
|
|
if pkg, exists := imports[pkgPath]; exists {
|
|
return pkg, nil
|
|
}
|
|
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
|
|
return nil, errors.New("ignored")
|
|
}
|
|
pkg := ast.NewObj(ast.Pkg, path.Base(pkgPath))
|
|
imports[pkgPath] = pkg
|
|
return pkg, nil
|
|
}
|
|
}
|
|
|
|
func (v *refsSaver) addRef(sel *ast.SelectorExpr) (PkgRef, bool) {
|
|
x, ok := sel.X.(*ast.Ident)
|
|
if !ok || x.Obj == nil {
|
|
return PkgRef{}, false
|
|
}
|
|
imp, ok := x.Obj.Decl.(*ast.ImportSpec)
|
|
if !ok {
|
|
return PkgRef{}, false
|
|
}
|
|
pkgPath, err := strconv.Unquote(imp.Path.Value)
|
|
if err != nil {
|
|
return PkgRef{}, false
|
|
}
|
|
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
|
|
return PkgRef{}, false
|
|
}
|
|
pkgPath = pkgPath[len(v.pkgPrefix):]
|
|
ref := PkgRef{Pkg: pkgPath, Name: sel.Sel.Name}
|
|
if _, exists := v.refMap[ref]; !exists {
|
|
v.refMap[ref] = struct{}{}
|
|
v.Refs = append(v.Refs, ref)
|
|
}
|
|
return ref, true
|
|
}
|
|
|
|
func (v *refsSaver) Visit(n ast.Node) ast.Visitor {
|
|
switch n := n.(type) {
|
|
case *ast.StructType:
|
|
// Use a copy of refsSaver that only accepts exported fields. It refers
|
|
// to the original refsSaver for collecting references.
|
|
v2 := *v
|
|
v2.insideStruct = true
|
|
return &v2
|
|
case *ast.Field:
|
|
if v.insideStruct && len(n.Names) == 1 && !n.Names[0].IsExported() {
|
|
return nil
|
|
}
|
|
case *ast.SelectorExpr:
|
|
v.Names[n.Sel.Name] = struct{}{}
|
|
if _, ok := v.addRef(n); ok {
|
|
return nil
|
|
}
|
|
case *ast.FuncDecl:
|
|
if n.Recv != nil { // Methods
|
|
v.Names[n.Name.Name] = struct{}{}
|
|
}
|
|
}
|
|
return v
|
|
}
|