зеркало из https://github.com/golang/tools.git
internal/refactor: undo variadic elimination during substitution
When substituting an argument that has been synthetically packed into a composite literal expression, into the varadic argument of a call, we can undo the variadic elimination and unpack the arguments into the call. Fixes golang/go#63717 Fixes golang/go#69441 Change-Id: I8a48664b2e6486ca492e03e233b8600473e9d1a9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/629136 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:
Родитель
3b0b264579
Коммит
8c3ba8c103
|
@ -180,7 +180,7 @@ func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "refactor.rewrite.remove
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
Ellipsis2(2, []int{3}...)
|
Ellipsis2(2, 3)
|
||||||
func(_, blank0 int, rest ...int) {
|
func(_, blank0 int, rest ...int) {
|
||||||
Ellipsis2(blank0, rest...)
|
Ellipsis2(blank0, rest...)
|
||||||
}(h())
|
}(h())
|
||||||
|
|
|
@ -191,7 +191,7 @@ func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "refactor.rewrite.remove
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
Ellipsis2(2, []int{3}...)
|
Ellipsis2(2, 3)
|
||||||
func(_, blank0 int, rest ...int) {
|
func(_, blank0 int, rest ...int) {
|
||||||
Ellipsis2(blank0, rest...)
|
Ellipsis2(blank0, rest...)
|
||||||
}(h())
|
}(h())
|
||||||
|
|
|
@ -41,11 +41,13 @@ type Caller struct {
|
||||||
enclosingFunc *ast.FuncDecl // top-level function/method enclosing the call, if any
|
enclosingFunc *ast.FuncDecl // top-level function/method enclosing the call, if any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type logger = func(string, ...any)
|
||||||
|
|
||||||
// Options specifies parameters affecting the inliner algorithm.
|
// Options specifies parameters affecting the inliner algorithm.
|
||||||
// All fields are optional.
|
// All fields are optional.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Logf func(string, ...any) // log output function, records decision-making process
|
Logf logger // log output function, records decision-making process
|
||||||
IgnoreEffects bool // ignore potential side effects of arguments (unsound)
|
IgnoreEffects bool // ignore potential side effects of arguments (unsound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result holds the result of code transformation.
|
// Result holds the result of code transformation.
|
||||||
|
@ -737,11 +739,22 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
||||||
return nil, err // "can't happen"
|
return nil, err // "can't happen"
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaceCalleeID replaces an identifier in the callee.
|
// replaceCalleeID replaces an identifier in the callee. See [replacer] for
|
||||||
// The replacement tree must not belong to the caller; use cloneNode as needed.
|
// more detailed semantics.
|
||||||
replaceCalleeID := func(offset int, repl ast.Expr) {
|
replaceCalleeID := func(offset int, repl ast.Expr, unpackVariadic bool) {
|
||||||
id := findIdent(calleeDecl, calleeDecl.Pos()+token.Pos(offset))
|
path, id := findIdent(calleeDecl, calleeDecl.Pos()+token.Pos(offset))
|
||||||
logf("- replace id %q @ #%d to %q", id.Name, offset, debugFormatNode(calleeFset, repl))
|
logf("- replace id %q @ #%d to %q", id.Name, offset, debugFormatNode(calleeFset, repl))
|
||||||
|
// Replace f([]T{a, b, c}...) with f(a, b, c).
|
||||||
|
if lit, ok := repl.(*ast.CompositeLit); ok && unpackVariadic && len(path) > 0 {
|
||||||
|
if call, ok := last(path).(*ast.CallExpr); ok &&
|
||||||
|
call.Ellipsis.IsValid() &&
|
||||||
|
id == last(call.Args) {
|
||||||
|
|
||||||
|
call.Args = append(call.Args[:len(call.Args)-1], lit.Elts...)
|
||||||
|
call.Ellipsis = token.NoPos
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
replaceNode(calleeDecl, id, repl)
|
replaceNode(calleeDecl, id, repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,7 +762,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
||||||
// (The same tree may be spliced in multiple times, resulting in a DAG.)
|
// (The same tree may be spliced in multiple times, resulting in a DAG.)
|
||||||
for _, ref := range callee.FreeRefs {
|
for _, ref := range callee.FreeRefs {
|
||||||
if repl := objRenames[ref.Object]; repl != nil {
|
if repl := objRenames[ref.Object]; repl != nil {
|
||||||
replaceCalleeID(ref.Offset, repl)
|
replaceCalleeID(ref.Offset, repl, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -825,6 +838,10 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
||||||
// nop
|
// nop
|
||||||
} else {
|
} else {
|
||||||
// ordinary call: f(a1, ... aN) -> f([]T{a1, ..., aN})
|
// ordinary call: f(a1, ... aN) -> f([]T{a1, ..., aN})
|
||||||
|
//
|
||||||
|
// Substitution of []T{...} in the callee body may lead to
|
||||||
|
// g([]T{a1, ..., aN}...), which we simplify to g(a1, ..., an)
|
||||||
|
// later; see replaceCalleeID.
|
||||||
n := len(params) - 1
|
n := len(params) - 1
|
||||||
ordinary, extra := args[:n], args[n:]
|
ordinary, extra := args[:n], args[n:]
|
||||||
var elts []ast.Expr
|
var elts []ast.Expr
|
||||||
|
@ -849,6 +866,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
|
||||||
effects: effects,
|
effects: effects,
|
||||||
duplicable: false,
|
duplicable: false,
|
||||||
freevars: freevars,
|
freevars: freevars,
|
||||||
|
variadic: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1297,6 +1315,7 @@ type argument struct {
|
||||||
duplicable bool // expr may be duplicated
|
duplicable bool // expr may be duplicated
|
||||||
freevars map[string]bool // free names of expr
|
freevars map[string]bool // free names of expr
|
||||||
substitutable bool // is candidate for substitution
|
substitutable bool // is candidate for substitution
|
||||||
|
variadic bool // is explicit []T{...} for eliminated variadic
|
||||||
}
|
}
|
||||||
|
|
||||||
// arguments returns the effective arguments of the call.
|
// arguments returns the effective arguments of the call.
|
||||||
|
@ -1452,6 +1471,12 @@ type parameter struct {
|
||||||
variadic bool // (final) parameter is unsimplified ...T
|
variadic bool // (final) parameter is unsimplified ...T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A replacer replaces an identifier at the given offset in the callee.
|
||||||
|
// The replacement tree must not belong to the caller; use cloneNode as needed.
|
||||||
|
// If unpackVariadic is set, the replacement is a composite resulting from
|
||||||
|
// variadic elimination, and may be unpackeded into variadic calls.
|
||||||
|
type replacer = func(offset int, repl ast.Expr, unpackVariadic bool)
|
||||||
|
|
||||||
// substitute implements parameter elimination by substitution.
|
// substitute implements parameter elimination by substitution.
|
||||||
//
|
//
|
||||||
// It considers each parameter and its corresponding argument in turn
|
// It considers each parameter and its corresponding argument in turn
|
||||||
|
@ -1471,7 +1496,7 @@ type parameter struct {
|
||||||
// parameter, and is provided with its relative offset and replacement
|
// parameter, and is provided with its relative offset and replacement
|
||||||
// expression (argument), and the corresponding elements of params and
|
// expression (argument), and the corresponding elements of params and
|
||||||
// args are replaced by nil.
|
// args are replaced by nil.
|
||||||
func substitute(logf func(string, ...any), caller *Caller, params []*parameter, args []*argument, effects []int, falcon falconResult, replaceCalleeID func(offset int, repl ast.Expr)) {
|
func substitute(logf logger, caller *Caller, params []*parameter, args []*argument, effects []int, falcon falconResult, replace replacer) {
|
||||||
// Inv:
|
// Inv:
|
||||||
// in calls to variadic, len(args) >= len(params)-1
|
// in calls to variadic, len(args) >= len(params)-1
|
||||||
// in spread calls to non-variadic, len(args) < len(params)
|
// in spread calls to non-variadic, len(args) < len(params)
|
||||||
|
@ -1621,7 +1646,7 @@ next:
|
||||||
logf("replacing parameter %q by argument %q",
|
logf("replacing parameter %q by argument %q",
|
||||||
param.info.Name, debugFormatNode(caller.Fset, arg.expr))
|
param.info.Name, debugFormatNode(caller.Fset, arg.expr))
|
||||||
for _, ref := range param.info.Refs {
|
for _, ref := range param.info.Refs {
|
||||||
replaceCalleeID(ref, internalastutil.CloneNode(arg.expr).(ast.Expr))
|
replace(ref, internalastutil.CloneNode(arg.expr).(ast.Expr), arg.variadic)
|
||||||
}
|
}
|
||||||
params[i] = nil // substituted
|
params[i] = nil // substituted
|
||||||
args[i] = nil // substituted
|
args[i] = nil // substituted
|
||||||
|
@ -1666,7 +1691,7 @@ func isUsedOutsideCall(caller *Caller, v *types.Var) bool {
|
||||||
// TODO(adonovan): we could obtain a finer result rejecting only the
|
// TODO(adonovan): we could obtain a finer result rejecting only the
|
||||||
// freevars of each failed constraint, and processing constraints in
|
// freevars of each failed constraint, and processing constraints in
|
||||||
// order of increasing arity, but failures are quite rare.
|
// order of increasing arity, but failures are quite rare.
|
||||||
func checkFalconConstraints(logf func(string, ...any), params []*parameter, args []*argument, falcon falconResult) {
|
func checkFalconConstraints(logf logger, params []*parameter, args []*argument, falcon falconResult) {
|
||||||
// Create a dummy package, as this is the only
|
// Create a dummy package, as this is the only
|
||||||
// way to create an environment for CheckExpr.
|
// way to create an environment for CheckExpr.
|
||||||
pkg := types.NewPackage("falcon", "falcon")
|
pkg := types.NewPackage("falcon", "falcon")
|
||||||
|
@ -1764,7 +1789,7 @@ func checkFalconConstraints(logf func(string, ...any), params []*parameter, args
|
||||||
// current argument. Subsequent iterations cannot introduce hazards
|
// current argument. Subsequent iterations cannot introduce hazards
|
||||||
// with that argument because they can result only in additional
|
// with that argument because they can result only in additional
|
||||||
// binding of lower-ordered arguments.
|
// binding of lower-ordered arguments.
|
||||||
func resolveEffects(logf func(string, ...any), args []*argument, effects []int) {
|
func resolveEffects(logf logger, args []*argument, effects []int) {
|
||||||
effectStr := func(effects bool, idx int) string {
|
effectStr := func(effects bool, idx int) string {
|
||||||
i := fmt.Sprint(idx)
|
i := fmt.Sprint(idx)
|
||||||
if idx == len(args) {
|
if idx == len(args) {
|
||||||
|
@ -1923,7 +1948,7 @@ type bindingDeclInfo struct {
|
||||||
//
|
//
|
||||||
// Strategies may impose additional checks on return
|
// Strategies may impose additional checks on return
|
||||||
// conversions, labels, defer, etc.
|
// conversions, labels, defer, etc.
|
||||||
func createBindingDecl(logf func(string, ...any), caller *Caller, args []*argument, calleeDecl *ast.FuncDecl, results []*paramInfo) *bindingDeclInfo {
|
func createBindingDecl(logf logger, caller *Caller, args []*argument, calleeDecl *ast.FuncDecl, results []*paramInfo) *bindingDeclInfo {
|
||||||
// Spread calls are tricky as they may not align with the
|
// Spread calls are tricky as they may not align with the
|
||||||
// parameters' field groupings nor types.
|
// parameters' field groupings nor types.
|
||||||
// For example, given
|
// For example, given
|
||||||
|
@ -2745,26 +2770,38 @@ func clearPositions(root ast.Node) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// findIdent returns the Ident beneath root that has the given pos.
|
// findIdent finds the Ident beneath root that has the given pos.
|
||||||
func findIdent(root ast.Node, pos token.Pos) *ast.Ident {
|
// It returns the path to the ident (excluding the ident), and the ident
|
||||||
|
// itself, where the path is the sequence of ast.Nodes encountered in a
|
||||||
|
// depth-first search to find ident.
|
||||||
|
func findIdent(root ast.Node, pos token.Pos) ([]ast.Node, *ast.Ident) {
|
||||||
// TODO(adonovan): opt: skip subtrees that don't contain pos.
|
// TODO(adonovan): opt: skip subtrees that don't contain pos.
|
||||||
var found *ast.Ident
|
var (
|
||||||
|
path []ast.Node
|
||||||
|
found *ast.Ident
|
||||||
|
)
|
||||||
ast.Inspect(root, func(n ast.Node) bool {
|
ast.Inspect(root, func(n ast.Node) bool {
|
||||||
if found != nil {
|
if found != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if n == nil {
|
||||||
|
path = path[:len(path)-1]
|
||||||
|
return false
|
||||||
|
}
|
||||||
if id, ok := n.(*ast.Ident); ok {
|
if id, ok := n.(*ast.Ident); ok {
|
||||||
if id.Pos() == pos {
|
if id.Pos() == pos {
|
||||||
found = id
|
found = id
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
path = append(path, n)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if found == nil {
|
if found == nil {
|
||||||
panic(fmt.Sprintf("findIdent %d not found in %s",
|
panic(fmt.Sprintf("findIdent %d not found in %s",
|
||||||
pos, debugFormatNode(token.NewFileSet(), root)))
|
pos, debugFormatNode(token.NewFileSet(), root)))
|
||||||
}
|
}
|
||||||
return found
|
return path, found
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepend[T any](elem T, slice ...T) []T {
|
func prepend[T any](elem T, slice ...T) []T {
|
||||||
|
|
|
@ -1043,6 +1043,12 @@ func TestVariadic(t *testing.T) {
|
||||||
`func _(slice []any) { f(slice...) }`,
|
`func _(slice []any) { f(slice...) }`,
|
||||||
`func _(slice []any) { println(slice) }`,
|
`func _(slice []any) { println(slice) }`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Undo variadic elimination",
|
||||||
|
`func f(args ...int) []int { return append([]int{1}, args...) }`,
|
||||||
|
`func _(a, b int) { f(a, b) }`,
|
||||||
|
`func _(a, b int) { _ = append([]int{1}, a, b) }`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Variadic elimination (literalization).",
|
"Variadic elimination (literalization).",
|
||||||
`func f(x any, rest ...any) { defer println(x, rest) }`, // defer => literalization
|
`func f(x any, rest ...any) { defer println(x, rest) }`, // defer => literalization
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
This test checks that variadic elimination does not cause a semantic change due
|
||||||
|
to creation of a non-nil empty slice instead of a nil slice due to missing
|
||||||
|
variadic arguments.
|
||||||
|
|
||||||
|
-- go.mod --
|
||||||
|
module testdata
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
-- foo/foo.go --
|
||||||
|
package foo
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func F(is ...int) {
|
||||||
|
if is == nil {
|
||||||
|
fmt.Println("is is nil")
|
||||||
|
} else {
|
||||||
|
fmt.Println("is is not nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func G(is ...int) { F(is...) }
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
G() //@ inline(re"G", G)
|
||||||
|
}
|
||||||
|
|
||||||
|
-- G --
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func F(is ...int) {
|
||||||
|
if is == nil {
|
||||||
|
fmt.Println("is is nil")
|
||||||
|
} else {
|
||||||
|
fmt.Println("is is not nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func G(is ...int) { F(is...) }
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
F() //@ inline(re"G", G)
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче