Skip to content

Commit fbb7047

Browse files
committed
internal/refactor/inline: extract import handling from inlineCall
Change-Id: I4102e9a11aca35daf52fbaa343d30bde50dd9fb1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/660957 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 2d8ef13 commit fbb7047

File tree

1 file changed

+157
-151
lines changed

1 file changed

+157
-151
lines changed

internal/refactor/inline/inline.go

Lines changed: 157 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,156 @@ type newImport struct {
470470
spec *ast.ImportSpec
471471
}
472472

473+
// importState tracks information about imports.
474+
type importState struct {
475+
logf func(string, ...any)
476+
caller *Caller
477+
importMap map[string][]string // from package paths in the caller's file to local names
478+
newImports []newImport // for references to free names in callee; to be added to the file
479+
oldImports []oldImport // referenced only by caller.Call.Fun; to be removed from the file
480+
}
481+
482+
// newImportState returns an importState with initial information about the caller's imports.
483+
func newImportState(logf func(string, ...any), caller *Caller, callee *gobCallee) *importState {
484+
// For simplicity we ignore existing dot imports, so that a qualified
485+
// identifier (QI) in the callee is always represented by a QI in the caller,
486+
// allowing us to treat a QI like a selection on a package name.
487+
is := &importState{
488+
logf: logf,
489+
caller: caller,
490+
importMap: make(map[string][]string),
491+
}
492+
493+
for _, imp := range caller.File.Imports {
494+
if pkgName, ok := importedPkgName(caller.Info, imp); ok &&
495+
pkgName.Name() != "." &&
496+
pkgName.Name() != "_" {
497+
498+
// If the import's sole use is in caller.Call.Fun of the form p.F(...),
499+
// where p.F is a qualified identifier, the p import may not be
500+
// necessary.
501+
//
502+
// Only the qualified identifier case matters, as other references to
503+
// imported package names in the Call.Fun expression (e.g.
504+
// x.after(3*time.Second).f() or time.Second.String()) will remain after
505+
// inlining, as arguments.
506+
//
507+
// If that is the case, proactively check if any of the callee FreeObjs
508+
// need this import. Doing so eagerly simplifies the resulting logic.
509+
needed := true
510+
sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr)
511+
if ok && soleUse(caller.Info, pkgName) == sel.X {
512+
needed = false // no longer needed by caller
513+
// Check to see if any of the inlined free objects need this package.
514+
for _, obj := range callee.FreeObjs {
515+
if obj.PkgPath == pkgName.Imported().Path() && obj.Shadow[pkgName.Name()] == 0 {
516+
needed = true // needed by callee
517+
break
518+
}
519+
}
520+
}
521+
522+
// Exclude imports not needed by the caller or callee after inlining; the second
523+
// return value holds these.
524+
if needed {
525+
path := pkgName.Imported().Path()
526+
is.importMap[path] = append(is.importMap[path], pkgName.Name())
527+
} else {
528+
is.oldImports = append(is.oldImports, oldImport{pkgName: pkgName, spec: imp})
529+
}
530+
}
531+
}
532+
return is
533+
}
534+
535+
// importName finds an existing import name to use in a particular shadowing
536+
// context. It is used to determine the set of new imports in
537+
// getOrMakeImportName, and is also used for writing out names in inlining
538+
// strategies below.
539+
func (i *importState) importName(pkgPath string, shadow shadowMap) string {
540+
for _, name := range i.importMap[pkgPath] {
541+
// Check that either the import preexisted, or that it was newly added
542+
// (no PkgName) but is not shadowed, either in the callee (shadows) or
543+
// caller (caller.lookup).
544+
if shadow[name] == 0 {
545+
found := i.caller.lookup(name)
546+
if is[*types.PkgName](found) || found == nil {
547+
return name
548+
}
549+
}
550+
}
551+
return ""
552+
}
553+
554+
// localName returns the local name for a given imported package path,
555+
// adding one if it doesn't exists.
556+
func (i *importState) localName(pkgPath, pkgName string, shadow shadowMap) string {
557+
// Does an import already exist that works in this shadowing context?
558+
if name := i.importName(pkgPath, shadow); name != "" {
559+
return name
560+
}
561+
562+
newlyAdded := func(name string) bool {
563+
for _, new := range i.newImports {
564+
if new.pkgName == name {
565+
return true
566+
}
567+
}
568+
return false
569+
}
570+
571+
// shadowedInCaller reports whether a candidate package name
572+
// already refers to a declaration in the caller.
573+
shadowedInCaller := func(name string) bool {
574+
obj := i.caller.lookup(name)
575+
if obj == nil {
576+
return false
577+
}
578+
// If obj will be removed, the name is available.
579+
for _, old := range i.oldImports {
580+
if old.pkgName == obj {
581+
return false
582+
}
583+
}
584+
return true
585+
}
586+
587+
// import added by callee
588+
//
589+
// Choose local PkgName based on last segment of
590+
// package path plus, if needed, a numeric suffix to
591+
// ensure uniqueness.
592+
//
593+
// "init" is not a legal PkgName.
594+
//
595+
// TODO(rfindley): is it worth preserving local package names for callee
596+
// imports? Are they likely to be better or worse than the name we choose
597+
// here?
598+
base := pkgName
599+
name := base
600+
for n := 0; shadow[name] != 0 || shadowedInCaller(name) || newlyAdded(name) || name == "init"; n++ {
601+
name = fmt.Sprintf("%s%d", base, n)
602+
}
603+
i.logf("adding import %s %q", name, pkgPath)
604+
spec := &ast.ImportSpec{
605+
Path: &ast.BasicLit{
606+
Kind: token.STRING,
607+
Value: strconv.Quote(pkgPath),
608+
},
609+
}
610+
// Use explicit pkgname (out of necessity) when it differs from the declared name,
611+
// or (for good style) when it differs from base(pkgpath).
612+
if name != pkgName || name != pathpkg.Base(pkgPath) {
613+
spec.Name = makeIdent(name)
614+
}
615+
i.newImports = append(i.newImports, newImport{
616+
pkgName: name,
617+
spec: spec,
618+
})
619+
i.importMap[pkgPath] = append(i.importMap[pkgPath], name)
620+
return name
621+
}
622+
473623
type inlineCallResult struct {
474624
newImports []newImport // to add
475625
oldImports []oldImport // to remove
@@ -586,102 +736,8 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
586736
assign1 = func(v *types.Var) bool { return !updatedLocals[v] }
587737
}
588738

589-
// Build a map, initially populated with caller imports, and updated below
590-
// with new imports necessary to reference free symbols in the callee.
591-
// oldImports are caller imports that won't be needed after inlining.
592-
importMap, oldImports := callerImportMap(caller, callee)
593-
594-
// importName finds an existing import name to use in a particular shadowing
595-
// context. It is used to determine the set of new imports in
596-
// getOrMakeImportName, and is also used for writing out names in inlining
597-
// strategies below.
598-
importName := func(pkgPath string, shadow shadowMap) string {
599-
for _, name := range importMap[pkgPath] {
600-
// Check that either the import preexisted, or that it was newly added
601-
// (no PkgName) but is not shadowed, either in the callee (shadows) or
602-
// caller (caller.lookup).
603-
if shadow[name] == 0 {
604-
found := caller.lookup(name)
605-
if is[*types.PkgName](found) || found == nil {
606-
return name
607-
}
608-
}
609-
}
610-
return ""
611-
}
612-
613-
// keep track of new imports that are necessary to reference any free names
614-
// in the callee.
615-
var newImports []newImport
616-
617-
// getOrMakeImportName returns the local name for a given imported package path,
618-
// adding one if it doesn't exists.
619-
getOrMakeImportName := func(pkgPath, pkgName string, shadow shadowMap) string {
620-
// Does an import already exist that works in this shadowing context?
621-
if name := importName(pkgPath, shadow); name != "" {
622-
return name
623-
}
624-
625-
newlyAdded := func(name string) bool {
626-
for _, new := range newImports {
627-
if new.pkgName == name {
628-
return true
629-
}
630-
}
631-
return false
632-
}
633-
634-
// shadowedInCaller reports whether a candidate package name
635-
// already refers to a declaration in the caller.
636-
shadowedInCaller := func(name string) bool {
637-
obj := caller.lookup(name)
638-
if obj == nil {
639-
return false
640-
}
641-
// If obj will be removed, the name is available.
642-
for _, old := range oldImports {
643-
if old.pkgName == obj {
644-
return false
645-
}
646-
}
647-
return true
648-
}
649-
650-
// import added by callee
651-
//
652-
// Choose local PkgName based on last segment of
653-
// package path plus, if needed, a numeric suffix to
654-
// ensure uniqueness.
655-
//
656-
// "init" is not a legal PkgName.
657-
//
658-
// TODO(rfindley): is it worth preserving local package names for callee
659-
// imports? Are they likely to be better or worse than the name we choose
660-
// here?
661-
base := pkgName
662-
name := base
663-
for n := 0; shadow[name] != 0 || shadowedInCaller(name) || newlyAdded(name) || name == "init"; n++ {
664-
name = fmt.Sprintf("%s%d", base, n)
665-
}
666-
logf("adding import %s %q", name, pkgPath)
667-
spec := &ast.ImportSpec{
668-
Path: &ast.BasicLit{
669-
Kind: token.STRING,
670-
Value: strconv.Quote(pkgPath),
671-
},
672-
}
673-
// Use explicit pkgname (out of necessity) when it differs from the declared name,
674-
// or (for good style) when it differs from base(pkgpath).
675-
if name != pkgName || name != pathpkg.Base(pkgPath) {
676-
spec.Name = makeIdent(name)
677-
}
678-
newImports = append(newImports, newImport{
679-
pkgName: name,
680-
spec: spec,
681-
})
682-
importMap[pkgPath] = append(importMap[pkgPath], name)
683-
return name
684-
}
739+
// Extract information about the caller's imports.
740+
istate := newImportState(logf, caller, callee)
685741

686742
// Compute the renaming of the callee's free identifiers.
687743
objRenames := make([]ast.Expr, len(callee.FreeObjs)) // nil => no change
@@ -709,7 +765,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
709765
var newName ast.Expr
710766
if obj.Kind == "pkgname" {
711767
// Use locally appropriate import, creating as needed.
712-
n := getOrMakeImportName(obj.PkgPath, obj.PkgName, obj.Shadow)
768+
n := istate.localName(obj.PkgPath, obj.PkgName, obj.Shadow)
713769
newName = makeIdent(n) // imported package
714770
} else if !obj.ValidPos {
715771
// Built-in function, type, or value (e.g. nil, zero):
@@ -754,7 +810,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
754810

755811
// Form a qualified identifier, pkg.Name.
756812
if qualify {
757-
pkgName := getOrMakeImportName(obj.PkgPath, obj.PkgName, obj.Shadow)
813+
pkgName := istate.localName(obj.PkgPath, obj.PkgName, obj.Shadow)
758814
newName = &ast.SelectorExpr{
759815
X: makeIdent(pkgName),
760816
Sel: makeIdent(obj.Name),
@@ -765,8 +821,8 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
765821
}
766822

767823
res := &inlineCallResult{
768-
newImports: newImports,
769-
oldImports: oldImports,
824+
newImports: istate.newImports,
825+
oldImports: istate.oldImports,
770826
}
771827

772828
// Parse callee function declaration.
@@ -1115,7 +1171,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
11151171
(!needBindingDecl || (bindingDecl != nil && len(bindingDecl.names) == 0)) {
11161172

11171173
// Reduces to: { var (bindings); lhs... := rhs... }
1118-
if newStmts, ok := st.assignStmts(stmt, results, importName); ok {
1174+
if newStmts, ok := st.assignStmts(stmt, results, istate.importName); ok {
11191175
logf("strategy: reduce assign-context call to { return exprs }")
11201176

11211177
clearPositions(calleeDecl.Body)
@@ -1348,56 +1404,6 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
13481404
return res, nil
13491405
}
13501406

1351-
// callerImportMap returns a map from package paths in the caller's file to local names.
1352-
// The map excludes imports not needed by the caller or callee after inlining; the second
1353-
// return value holds these.
1354-
func callerImportMap(caller *Caller, callee *gobCallee) (map[string][]string, []oldImport) {
1355-
// For simplicity we ignore existing dot imports, so that a qualified
1356-
// identifier (QI) in the callee is always represented by a QI in the caller,
1357-
// allowing us to treat a QI like a selection on a package name.
1358-
importMap := make(map[string][]string) // maps package path to local name(s)
1359-
var oldImports []oldImport // imports referenced only by caller.Call.Fun
1360-
1361-
for _, imp := range caller.File.Imports {
1362-
if pkgName, ok := importedPkgName(caller.Info, imp); ok &&
1363-
pkgName.Name() != "." &&
1364-
pkgName.Name() != "_" {
1365-
1366-
// If the import's sole use is in caller.Call.Fun of the form p.F(...),
1367-
// where p.F is a qualified identifier, the p import may not be
1368-
// necessary.
1369-
//
1370-
// Only the qualified identifier case matters, as other references to
1371-
// imported package names in the Call.Fun expression (e.g.
1372-
// x.after(3*time.Second).f() or time.Second.String()) will remain after
1373-
// inlining, as arguments.
1374-
//
1375-
// If that is the case, proactively check if any of the callee FreeObjs
1376-
// need this import. Doing so eagerly simplifies the resulting logic.
1377-
needed := true
1378-
sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr)
1379-
if ok && soleUse(caller.Info, pkgName) == sel.X {
1380-
needed = false // no longer needed by caller
1381-
// Check to see if any of the inlined free objects need this package.
1382-
for _, obj := range callee.FreeObjs {
1383-
if obj.PkgPath == pkgName.Imported().Path() && obj.Shadow[pkgName.Name()] == 0 {
1384-
needed = true // needed by callee
1385-
break
1386-
}
1387-
}
1388-
}
1389-
1390-
if needed {
1391-
path := pkgName.Imported().Path()
1392-
importMap[path] = append(importMap[path], pkgName.Name())
1393-
} else {
1394-
oldImports = append(oldImports, oldImport{pkgName: pkgName, spec: imp})
1395-
}
1396-
}
1397-
}
1398-
return importMap, oldImports
1399-
}
1400-
14011407
type argument struct {
14021408
expr ast.Expr
14031409
typ types.Type // may be tuple for sole non-receiver arg in spread call

0 commit comments

Comments
 (0)