зеркало из https://github.com/golang/tools.git
internal/refactor: handle qualified names in inlined assignments
Rewrite our call site processing of imports, to simplify, and so that we can re-use the import lookup logic in the assignStmts strategy for the purpose of writing out types. A large TODO is included for a hypothetical refactoring of the inlining logic that could formalize these types of interactions between call site analysis and inlining strategy. Fixes golang/go#65217 Change-Id: Ifd99ea14430deba3a03cdfb936b6edee9e81d0bf Reviewed-on: https://go-review.googlesource.com/c/tools/+/629435 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Родитель
9311800086
Коммит
3b0b264579
|
@ -202,19 +202,19 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
|
|||
objidx, ok := freeObjIndex[obj]
|
||||
if !ok {
|
||||
objidx = len(freeObjIndex)
|
||||
var pkgpath, pkgname string
|
||||
var pkgPath, pkgName string
|
||||
if pn, ok := obj.(*types.PkgName); ok {
|
||||
pkgpath = pn.Imported().Path()
|
||||
pkgname = pn.Imported().Name()
|
||||
pkgPath = pn.Imported().Path()
|
||||
pkgName = pn.Imported().Name()
|
||||
} else if obj.Pkg() != nil {
|
||||
pkgpath = obj.Pkg().Path()
|
||||
pkgname = obj.Pkg().Name()
|
||||
pkgPath = obj.Pkg().Path()
|
||||
pkgName = obj.Pkg().Name()
|
||||
}
|
||||
freeObjs = append(freeObjs, object{
|
||||
Name: obj.Name(),
|
||||
Kind: objectKind(obj),
|
||||
PkgName: pkgname,
|
||||
PkgPath: pkgpath,
|
||||
PkgName: pkgName,
|
||||
PkgPath: pkgPath,
|
||||
ValidPos: obj.Pos().IsValid(),
|
||||
})
|
||||
freeObjIndex[obj] = objidx
|
||||
|
|
|
@ -279,7 +279,8 @@ func (st *state) inline() (*Result, error) {
|
|||
// (We can't let imports.Process take care of it as it may
|
||||
// mistake obsolete imports for missing new imports when the
|
||||
// names are similar, as is common during a package migration.)
|
||||
for _, specToDelete := range res.oldImports {
|
||||
for _, oldImport := range res.oldImports {
|
||||
specToDelete := oldImport.spec
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
|
||||
decl.Specs = slicesDeleteFunc(decl.Specs, func(spec ast.Spec) bool {
|
||||
|
@ -375,17 +376,23 @@ func (st *state) inline() (*Result, error) {
|
|||
Content: newSrc,
|
||||
Literalized: literalized,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// An oldImport is an import that will be deleted from the caller file.
|
||||
type oldImport struct {
|
||||
pkgName *types.PkgName
|
||||
spec *ast.ImportSpec
|
||||
}
|
||||
|
||||
// A newImport is an import that will be added to the caller file.
|
||||
type newImport struct {
|
||||
pkgName string
|
||||
spec *ast.ImportSpec
|
||||
}
|
||||
|
||||
type inlineCallResult struct {
|
||||
newImports []newImport // to add
|
||||
oldImports []*ast.ImportSpec // to remove
|
||||
newImports []newImport // to add
|
||||
oldImports []oldImport // to remove
|
||||
|
||||
// If elideBraces is set, old is an ast.Stmt and new is an ast.BlockStmt to
|
||||
// be spliced in. This allows the inlining analysis to assert that inlining
|
||||
|
@ -412,6 +419,9 @@ type inlineCallResult struct {
|
|||
// transformation replacing the call and adding new variable
|
||||
// declarations, for example, or replacing a call statement by zero or
|
||||
// many statements.)
|
||||
// NOTE(rfindley): we've sort-of done this, with the 'elideBraces' flag that
|
||||
// allows inlining a statement list. However, due to loss of comments, more
|
||||
// sophisticated rewrites are challenging.
|
||||
//
|
||||
// TODO(adonovan): in earlier drafts, the transformation was expressed
|
||||
// by splicing substrings of the two source files because syntax
|
||||
|
@ -421,6 +431,33 @@ type inlineCallResult struct {
|
|||
// candidate for evaluating an alternative fully self-contained tree
|
||||
// representation, such as any proposed solution to #20744, or even
|
||||
// dst or some private fork of go/ast.)
|
||||
// TODO(rfindley): see if we can reduce the amount of comment lossiness by
|
||||
// using printer.CommentedNode, which has been useful elsewhere.
|
||||
//
|
||||
// TODO(rfindley): inlineCall is getting very long, and very stateful, making
|
||||
// it very hard to read. The following refactoring may improve readability and
|
||||
// maintainability:
|
||||
// - Rename 'state' to 'callsite', since that is what it encapsulates.
|
||||
// - Add results of pre-processing analysis into the callsite struct, such as
|
||||
// the effective importMap, new/old imports, arguments, etc. Essentially
|
||||
// anything that resulted from initial analysis of the call site, and which
|
||||
// may be useful to inlining strategies.
|
||||
// - Delegate this call site analysis to a constructor or initializer, such
|
||||
// as 'analyzeCallsite', so that it does not consume bandwidth in the
|
||||
// 'inlineCall' logical flow.
|
||||
// - Once analyzeCallsite returns, the callsite is immutable, much in the
|
||||
// same way as the Callee and Caller are immutable.
|
||||
// - Decide on a standard interface for strategies (and substrategies), such
|
||||
// that they may be delegated to a separate method on callsite.
|
||||
//
|
||||
// In this way, the logical flow of inline call will clearly follow the
|
||||
// following structure:
|
||||
// 1. Analyze the call site.
|
||||
// 2. Try strategies, in order, until one succeeds.
|
||||
// 3. Process the results.
|
||||
//
|
||||
// If any expensive analysis may be avoided by earlier strategies, it can be
|
||||
// encapsulated in its own type and passed to subsequent strategies.
|
||||
func (st *state) inlineCall() (*inlineCallResult, error) {
|
||||
logf, caller, callee := st.opts.Logf, st.caller, &st.callee.impl
|
||||
|
||||
|
@ -469,39 +506,83 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
|||
assign1 = func(v *types.Var) bool { return !updatedLocals[v] }
|
||||
}
|
||||
|
||||
// import map, initially populated with caller imports.
|
||||
// import map, initially populated with caller imports, and updated below
|
||||
// with new imports necessary to reference free symbols in the callee.
|
||||
//
|
||||
// For simplicity we ignore existing dot imports, so that a
|
||||
// qualified identifier (QI) in the callee is always
|
||||
// represented by a QI in the caller, allowing us to treat a
|
||||
// QI like a selection on a package name.
|
||||
// For simplicity we ignore existing dot imports, so that a qualified
|
||||
// identifier (QI) in the callee is always represented by a QI in the caller,
|
||||
// allowing us to treat a QI like a selection on a package name.
|
||||
importMap := make(map[string][]string) // maps package path to local name(s)
|
||||
var oldImports []oldImport // imports referenced only by caller.Call.Fun
|
||||
|
||||
for _, imp := range caller.File.Imports {
|
||||
if pkgname, ok := importedPkgName(caller.Info, imp); ok &&
|
||||
pkgname.Name() != "." &&
|
||||
pkgname.Name() != "_" {
|
||||
path := pkgname.Imported().Path()
|
||||
importMap[path] = append(importMap[path], pkgname.Name())
|
||||
if pkgName, ok := importedPkgName(caller.Info, imp); ok &&
|
||||
pkgName.Name() != "." &&
|
||||
pkgName.Name() != "_" {
|
||||
|
||||
// If the import's sole use is in caller.Call.Fun of the form p.F(...),
|
||||
// where p.F is a qualified identifier, the p import may not be
|
||||
// necessary.
|
||||
//
|
||||
// Only the qualified identifier case matters, as other references to
|
||||
// imported package names in the Call.Fun expression (e.g.
|
||||
// x.after(3*time.Second).f() or time.Second.String()) will remain after
|
||||
// inlining, as arguments.
|
||||
//
|
||||
// If that is the case, proactively check if any of the callee FreeObjs
|
||||
// need this import. Doing so eagerly simplifies the resulting logic.
|
||||
needed := true
|
||||
sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr)
|
||||
if ok && soleUse(caller.Info, pkgName) == sel.X {
|
||||
needed = false // no longer needed by caller
|
||||
// Check to see if any of the inlined free objects need this package.
|
||||
for _, obj := range callee.FreeObjs {
|
||||
if obj.PkgPath == pkgName.Imported().Path() && !obj.Shadow[pkgName.Name()] {
|
||||
needed = true // needed by callee
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if needed {
|
||||
path := pkgName.Imported().Path()
|
||||
importMap[path] = append(importMap[path], pkgName.Name())
|
||||
} else {
|
||||
oldImports = append(oldImports, oldImport{pkgName: pkgName, spec: imp})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var oldImports []*ast.ImportSpec // imports referenced only caller.Call.Fun
|
||||
|
||||
// localImportName returns the local name for a given imported package path.
|
||||
var newImports []newImport
|
||||
localImportName := func(obj *object) string {
|
||||
// Does an import exist?
|
||||
for _, name := range importMap[obj.PkgPath] {
|
||||
// Check that either the import preexisted,
|
||||
// or that it was newly added (no PkgName) but is not shadowed,
|
||||
// either in the callee (shadows) or caller (caller.lookup).
|
||||
if !obj.Shadow[name] {
|
||||
// importName finds an existing import name to use in a particular shadowing
|
||||
// context. It is used to determine the set of new imports in
|
||||
// getOrMakeImportName, and is also used for writing out names in inlining
|
||||
// strategies below.
|
||||
importName := func(pkgPath string, shadow map[string]bool) string {
|
||||
for _, name := range importMap[pkgPath] {
|
||||
// Check that either the import preexisted, or that it was newly added
|
||||
// (no PkgName) but is not shadowed, either in the callee (shadows) or
|
||||
// caller (caller.lookup).
|
||||
if !shadow[name] {
|
||||
found := caller.lookup(name)
|
||||
if is[*types.PkgName](found) || found == nil {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// keep track of new imports that are necessary to reference any free names
|
||||
// in the callee.
|
||||
var newImports []newImport
|
||||
|
||||
// getOrMakeImportName returns the local name for a given imported package path,
|
||||
// adding one if it doesn't exists.
|
||||
getOrMakeImportName := func(pkgPath, pkgName string, shadow map[string]bool) string {
|
||||
// Does an import already exist that works in this shadowing context?
|
||||
if name := importName(pkgPath, shadow); name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
newlyAdded := func(name string) bool {
|
||||
for _, new := range newImports {
|
||||
|
@ -515,33 +596,17 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
|||
// shadowedInCaller reports whether a candidate package name
|
||||
// already refers to a declaration in the caller.
|
||||
shadowedInCaller := func(name string) bool {
|
||||
existing := caller.lookup(name)
|
||||
|
||||
// If the candidate refers to a PkgName p whose sole use is
|
||||
// in caller.Call.Fun of the form p.F(...), where p.F is a
|
||||
// qualified identifier, the p import will be deleted,
|
||||
// so it's safe (and better) to recycle the name.
|
||||
//
|
||||
// Only the qualified identifier case matters, as other
|
||||
// references to imported package names in the Call.Fun
|
||||
// expression (e.g. x.after(3*time.Second).f()
|
||||
// or time.Second.String()) will remain after
|
||||
// inlining, as arguments.
|
||||
if pkgName, ok := existing.(*types.PkgName); ok {
|
||||
if sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr); ok {
|
||||
if sole := soleUse(caller.Info, pkgName); sole == sel.X {
|
||||
for _, spec := range caller.File.Imports {
|
||||
pkgName2, ok := importedPkgName(caller.Info, spec)
|
||||
if ok && pkgName2 == pkgName {
|
||||
oldImports = append(oldImports, spec)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
obj := caller.lookup(name)
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
// If obj will be removed, the name is available.
|
||||
for _, old := range oldImports {
|
||||
if old.pkgName == obj {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return existing != nil
|
||||
return true
|
||||
}
|
||||
|
||||
// import added by callee
|
||||
|
@ -555,29 +620,28 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
|||
// TODO(rfindley): is it worth preserving local package names for callee
|
||||
// imports? Are they likely to be better or worse than the name we choose
|
||||
// here?
|
||||
base := obj.PkgName
|
||||
base := pkgName
|
||||
name := base
|
||||
for n := 0; obj.Shadow[name] || shadowedInCaller(name) || newlyAdded(name) || name == "init"; n++ {
|
||||
for n := 0; shadow[name] || shadowedInCaller(name) || newlyAdded(name) || name == "init"; n++ {
|
||||
name = fmt.Sprintf("%s%d", base, n)
|
||||
}
|
||||
|
||||
logf("adding import %s %q", name, obj.PkgPath)
|
||||
logf("adding import %s %q", name, pkgPath)
|
||||
spec := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(obj.PkgPath),
|
||||
Value: strconv.Quote(pkgPath),
|
||||
},
|
||||
}
|
||||
// Use explicit pkgname (out of necessity) when it differs from the declared name,
|
||||
// or (for good style) when it differs from base(pkgpath).
|
||||
if name != obj.PkgName || name != pathpkg.Base(obj.PkgPath) {
|
||||
if name != pkgName || name != pathpkg.Base(pkgPath) {
|
||||
spec.Name = makeIdent(name)
|
||||
}
|
||||
newImports = append(newImports, newImport{
|
||||
pkgName: name,
|
||||
spec: spec,
|
||||
})
|
||||
importMap[obj.PkgPath] = append(importMap[obj.PkgPath], name)
|
||||
importMap[pkgPath] = append(importMap[pkgPath], name)
|
||||
return name
|
||||
}
|
||||
|
||||
|
@ -607,7 +671,8 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
|||
var newName ast.Expr
|
||||
if obj.Kind == "pkgname" {
|
||||
// Use locally appropriate import, creating as needed.
|
||||
newName = makeIdent(localImportName(&obj)) // imported package
|
||||
n := getOrMakeImportName(obj.PkgPath, obj.PkgName, obj.Shadow)
|
||||
newName = makeIdent(n) // imported package
|
||||
} else if !obj.ValidPos {
|
||||
// Built-in function, type, or value (e.g. nil, zero):
|
||||
// check not shadowed at caller.
|
||||
|
@ -651,7 +716,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
|||
|
||||
// Form a qualified identifier, pkg.Name.
|
||||
if qualify {
|
||||
pkgName := localImportName(&obj)
|
||||
pkgName := getOrMakeImportName(obj.PkgPath, obj.PkgName, obj.Shadow)
|
||||
newName = &ast.SelectorExpr{
|
||||
X: makeIdent(pkgName),
|
||||
Sel: makeIdent(obj.Name),
|
||||
|
@ -992,8 +1057,9 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
|||
(!needBindingDecl || (bindingDecl != nil && len(bindingDecl.names) == 0)) {
|
||||
|
||||
// Reduces to: { var (bindings); lhs... := rhs... }
|
||||
if newStmts, ok := st.assignStmts(stmt, results); ok {
|
||||
if newStmts, ok := st.assignStmts(stmt, results, importName); ok {
|
||||
logf("strategy: reduce assign-context call to { return exprs }")
|
||||
|
||||
clearPositions(calleeDecl.Body)
|
||||
|
||||
block := &ast.BlockStmt{
|
||||
|
@ -2890,6 +2956,15 @@ func declares(stmts []ast.Stmt) map[string]bool {
|
|||
return names
|
||||
}
|
||||
|
||||
// A importNameFunc is used to query local import names in the caller, in a
|
||||
// particular shadowing context.
|
||||
//
|
||||
// The shadow map contains additional names shadowed in the inlined code, at
|
||||
// the position the local import name is to be used. The shadow map only needs
|
||||
// to contain newly introduced names in the inlined code; names shadowed at the
|
||||
// caller are handled automatically.
|
||||
type importNameFunc = func(pkgPath string, shadow map[string]bool) string
|
||||
|
||||
// assignStmts rewrites a statement assigning the results of a call into zero
|
||||
// or more statements that assign its return operands, or (nil, false) if no
|
||||
// such rewrite is possible. The set of bindings created by the result of
|
||||
|
@ -2932,7 +3007,7 @@ func declares(stmts []ast.Stmt) map[string]bool {
|
|||
//
|
||||
// Note: assignStmts may return (nil, true) if it determines that the rewritten
|
||||
// assignment consists only of _ = nil assignments.
|
||||
func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Expr) ([]ast.Stmt, bool) {
|
||||
func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Expr, importName importNameFunc) ([]ast.Stmt, bool) {
|
||||
logf, caller, callee := st.opts.Logf, st.caller, &st.callee.impl
|
||||
|
||||
assert(len(callee.Returns) == 1, "unexpected multiple returns")
|
||||
|
@ -3035,18 +3110,23 @@ func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Ex
|
|||
// Inlining techniques below will need to write type information in order to
|
||||
// preserve the correct types of LHS identifiers.
|
||||
//
|
||||
// writeType is a simple helper to write out type expressions.
|
||||
// typeExpr is a simple helper to write out type expressions. It currently
|
||||
// handles (possibly qualified) type names.
|
||||
//
|
||||
// TODO(rfindley):
|
||||
// 1. handle qualified type names (potentially adding new imports)
|
||||
// 2. expand this to handle more type expressions.
|
||||
// 3. refactor to share logic with callee rewriting.
|
||||
// 1. expand this to handle more type expressions.
|
||||
// 2. refactor to share logic with callee rewriting.
|
||||
universeAny := types.Universe.Lookup("any")
|
||||
typeExpr := func(typ types.Type, shadows ...map[string]bool) ast.Expr {
|
||||
var typeName string
|
||||
typeExpr := func(typ types.Type, shadow map[string]bool) ast.Expr {
|
||||
var (
|
||||
typeName string
|
||||
obj *types.TypeName // nil for basic types
|
||||
)
|
||||
switch typ := typ.(type) {
|
||||
case *types.Basic:
|
||||
typeName = typ.Name()
|
||||
case interface{ Obj() *types.TypeName }: // Named, Alias, TypeParam
|
||||
obj = typ.Obj()
|
||||
typeName = typ.Obj().Name()
|
||||
}
|
||||
|
||||
|
@ -3060,15 +3140,20 @@ func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Ex
|
|||
return nil
|
||||
}
|
||||
|
||||
for _, shadow := range shadows {
|
||||
if obj == nil || obj.Pkg() == nil || obj.Pkg() == caller.Types { // local type or builtin
|
||||
if shadow[typeName] {
|
||||
logf("cannot write shadowed type name %q", typeName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
obj, _ := caller.lookup(typeName).(*types.TypeName)
|
||||
if obj != nil && types.Identical(obj.Type(), typ) {
|
||||
return ast.NewIdent(typeName)
|
||||
obj, _ := caller.lookup(typeName).(*types.TypeName)
|
||||
if obj != nil && types.Identical(obj.Type(), typ) {
|
||||
return ast.NewIdent(typeName)
|
||||
}
|
||||
} else if pkgName := importName(obj.Pkg().Path(), shadow); pkgName != "" {
|
||||
return &ast.SelectorExpr{
|
||||
X: ast.NewIdent(pkgName),
|
||||
Sel: ast.NewIdent(typeName),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -3187,7 +3272,7 @@ func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Ex
|
|||
idx := origIdxs[i]
|
||||
if nonTrivial[idx] && defs[idx] != nil {
|
||||
typ := caller.Info.TypeOf(lhs[i])
|
||||
texpr := typeExpr(typ)
|
||||
texpr := typeExpr(typ, nil)
|
||||
if texpr == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
Basic tests of inlining a call on the RHS of an assignment.
|
||||
|
||||
-- go.mod --
|
||||
module testdata
|
||||
go 1.12
|
||||
|
||||
-- a/a1.go --
|
||||
package a
|
||||
|
||||
import "testdata/b"
|
||||
|
||||
func _() {
|
||||
var y int
|
||||
x, y := b.B1() //@ inline(re"B", b1)
|
||||
_, _ = x, y
|
||||
}
|
||||
|
||||
-- a/a2.go --
|
||||
package a
|
||||
|
||||
import "testdata/b"
|
||||
|
||||
func _() {
|
||||
var y int
|
||||
x, y := b.B2() //@ inline(re"B", b2)
|
||||
_, _ = x, y
|
||||
}
|
||||
|
||||
-- a/a3.go --
|
||||
package a
|
||||
|
||||
import "testdata/b"
|
||||
|
||||
func _() {
|
||||
x, y := b.B3() //@ inline(re"B", b3)
|
||||
_, _ = x, y
|
||||
}
|
||||
|
||||
-- a/a4.go --
|
||||
package a
|
||||
|
||||
import "testdata/b"
|
||||
|
||||
func _() {
|
||||
x, y := b.B4() //@ inline(re"B", b4)
|
||||
_, _ = x, y
|
||||
}
|
||||
|
||||
-- b/b.go --
|
||||
package b
|
||||
|
||||
import (
|
||||
"testdata/c"
|
||||
)
|
||||
|
||||
func B1() (c.C, int) {
|
||||
return 0, 1
|
||||
}
|
||||
|
||||
func B2() (c.C, int) {
|
||||
return B1()
|
||||
}
|
||||
|
||||
func B3() (c.C, c.C) {
|
||||
return 0, 1
|
||||
}
|
||||
|
||||
-- b/b4.go --
|
||||
package b
|
||||
|
||||
import (
|
||||
c1 "testdata/c"
|
||||
c2 "testdata/c2"
|
||||
)
|
||||
|
||||
func B4() (c1.C, c2.C) {
|
||||
return 0, 1
|
||||
}
|
||||
|
||||
-- c/c.go --
|
||||
package c
|
||||
|
||||
type C int
|
||||
|
||||
-- c2/c.go --
|
||||
package c
|
||||
|
||||
type C int
|
||||
|
||||
-- b1 --
|
||||
package a
|
||||
|
||||
import (
|
||||
"testdata/c"
|
||||
)
|
||||
|
||||
func _() {
|
||||
var y int
|
||||
x, y := c.C(0), 1 //@ inline(re"B", b1)
|
||||
_, _ = x, y
|
||||
}
|
||||
-- b2 --
|
||||
package a
|
||||
|
||||
import (
|
||||
"testdata/b"
|
||||
"testdata/c"
|
||||
)
|
||||
|
||||
func _() {
|
||||
var y int
|
||||
var x c.C
|
||||
x, y = b.B1() //@ inline(re"B", b2)
|
||||
_, _ = x, y
|
||||
}
|
||||
-- b3 --
|
||||
package a
|
||||
|
||||
import (
|
||||
"testdata/c"
|
||||
)
|
||||
|
||||
func _() {
|
||||
x, y := c.C(0), c.C(1) //@ inline(re"B", b3)
|
||||
_, _ = x, y
|
||||
}
|
||||
|
||||
-- b4 --
|
||||
package a
|
||||
|
||||
import (
|
||||
"testdata/c"
|
||||
c0 "testdata/c2"
|
||||
)
|
||||
|
||||
func _() {
|
||||
x, y := c.C(0), c0.C(1) //@ inline(re"B", b4)
|
||||
_, _ = x, y
|
||||
}
|
Загрузка…
Ссылка в новой задаче