Skip to content

Commit 8c3ba8c

Browse files
committed
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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 3b0b264 commit 8c3ba8c

File tree

5 files changed

+105
-18
lines changed

5 files changed

+105
-18
lines changed

gopls/internal/test/marker/testdata/codeaction/removeparam.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "refactor.rewrite.remove
180180
}
181181

182182
func _() {
183-
Ellipsis2(2, []int{3}...)
183+
Ellipsis2(2, 3)
184184
func(_, blank0 int, rest ...int) {
185185
Ellipsis2(blank0, rest...)
186186
}(h())

gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "refactor.rewrite.remove
191191
}
192192

193193
func _() {
194-
Ellipsis2(2, []int{3}...)
194+
Ellipsis2(2, 3)
195195
func(_, blank0 int, rest ...int) {
196196
Ellipsis2(blank0, rest...)
197197
}(h())

internal/refactor/inline/inline.go

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ type Caller struct {
4141
enclosingFunc *ast.FuncDecl // top-level function/method enclosing the call, if any
4242
}
4343

44+
type logger = func(string, ...any)
45+
4446
// Options specifies parameters affecting the inliner algorithm.
4547
// All fields are optional.
4648
type Options struct {
47-
Logf func(string, ...any) // log output function, records decision-making process
48-
IgnoreEffects bool // ignore potential side effects of arguments (unsound)
49+
Logf logger // log output function, records decision-making process
50+
IgnoreEffects bool // ignore potential side effects of arguments (unsound)
4951
}
5052

5153
// Result holds the result of code transformation.
@@ -737,19 +739,30 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
737739
return nil, err // "can't happen"
738740
}
739741

740-
// replaceCalleeID replaces an identifier in the callee.
741-
// The replacement tree must not belong to the caller; use cloneNode as needed.
742-
replaceCalleeID := func(offset int, repl ast.Expr) {
743-
id := findIdent(calleeDecl, calleeDecl.Pos()+token.Pos(offset))
742+
// replaceCalleeID replaces an identifier in the callee. See [replacer] for
743+
// more detailed semantics.
744+
replaceCalleeID := func(offset int, repl ast.Expr, unpackVariadic bool) {
745+
path, id := findIdent(calleeDecl, calleeDecl.Pos()+token.Pos(offset))
744746
logf("- replace id %q @ #%d to %q", id.Name, offset, debugFormatNode(calleeFset, repl))
747+
// Replace f([]T{a, b, c}...) with f(a, b, c).
748+
if lit, ok := repl.(*ast.CompositeLit); ok && unpackVariadic && len(path) > 0 {
749+
if call, ok := last(path).(*ast.CallExpr); ok &&
750+
call.Ellipsis.IsValid() &&
751+
id == last(call.Args) {
752+
753+
call.Args = append(call.Args[:len(call.Args)-1], lit.Elts...)
754+
call.Ellipsis = token.NoPos
755+
return
756+
}
757+
}
745758
replaceNode(calleeDecl, id, repl)
746759
}
747760

748761
// Generate replacements for each free identifier.
749762
// (The same tree may be spliced in multiple times, resulting in a DAG.)
750763
for _, ref := range callee.FreeRefs {
751764
if repl := objRenames[ref.Object]; repl != nil {
752-
replaceCalleeID(ref.Offset, repl)
765+
replaceCalleeID(ref.Offset, repl, false)
753766
}
754767
}
755768

@@ -825,6 +838,10 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
825838
// nop
826839
} else {
827840
// ordinary call: f(a1, ... aN) -> f([]T{a1, ..., aN})
841+
//
842+
// Substitution of []T{...} in the callee body may lead to
843+
// g([]T{a1, ..., aN}...), which we simplify to g(a1, ..., an)
844+
// later; see replaceCalleeID.
828845
n := len(params) - 1
829846
ordinary, extra := args[:n], args[n:]
830847
var elts []ast.Expr
@@ -849,6 +866,7 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
849866
effects: effects,
850867
duplicable: false,
851868
freevars: freevars,
869+
variadic: true,
852870
})
853871
}
854872
}
@@ -1297,6 +1315,7 @@ type argument struct {
12971315
duplicable bool // expr may be duplicated
12981316
freevars map[string]bool // free names of expr
12991317
substitutable bool // is candidate for substitution
1318+
variadic bool // is explicit []T{...} for eliminated variadic
13001319
}
13011320

13021321
// arguments returns the effective arguments of the call.
@@ -1452,6 +1471,12 @@ type parameter struct {
14521471
variadic bool // (final) parameter is unsimplified ...T
14531472
}
14541473

1474+
// A replacer replaces an identifier at the given offset in the callee.
1475+
// The replacement tree must not belong to the caller; use cloneNode as needed.
1476+
// If unpackVariadic is set, the replacement is a composite resulting from
1477+
// variadic elimination, and may be unpackeded into variadic calls.
1478+
type replacer = func(offset int, repl ast.Expr, unpackVariadic bool)
1479+
14551480
// substitute implements parameter elimination by substitution.
14561481
//
14571482
// It considers each parameter and its corresponding argument in turn
@@ -1471,7 +1496,7 @@ type parameter struct {
14711496
// parameter, and is provided with its relative offset and replacement
14721497
// expression (argument), and the corresponding elements of params and
14731498
// args are replaced by nil.
1474-
func substitute(logf func(string, ...any), caller *Caller, params []*parameter, args []*argument, effects []int, falcon falconResult, replaceCalleeID func(offset int, repl ast.Expr)) {
1499+
func substitute(logf logger, caller *Caller, params []*parameter, args []*argument, effects []int, falcon falconResult, replace replacer) {
14751500
// Inv:
14761501
// in calls to variadic, len(args) >= len(params)-1
14771502
// in spread calls to non-variadic, len(args) < len(params)
@@ -1621,7 +1646,7 @@ next:
16211646
logf("replacing parameter %q by argument %q",
16221647
param.info.Name, debugFormatNode(caller.Fset, arg.expr))
16231648
for _, ref := range param.info.Refs {
1624-
replaceCalleeID(ref, internalastutil.CloneNode(arg.expr).(ast.Expr))
1649+
replace(ref, internalastutil.CloneNode(arg.expr).(ast.Expr), arg.variadic)
16251650
}
16261651
params[i] = nil // substituted
16271652
args[i] = nil // substituted
@@ -1666,7 +1691,7 @@ func isUsedOutsideCall(caller *Caller, v *types.Var) bool {
16661691
// TODO(adonovan): we could obtain a finer result rejecting only the
16671692
// freevars of each failed constraint, and processing constraints in
16681693
// order of increasing arity, but failures are quite rare.
1669-
func checkFalconConstraints(logf func(string, ...any), params []*parameter, args []*argument, falcon falconResult) {
1694+
func checkFalconConstraints(logf logger, params []*parameter, args []*argument, falcon falconResult) {
16701695
// Create a dummy package, as this is the only
16711696
// way to create an environment for CheckExpr.
16721697
pkg := types.NewPackage("falcon", "falcon")
@@ -1764,7 +1789,7 @@ func checkFalconConstraints(logf func(string, ...any), params []*parameter, args
17641789
// current argument. Subsequent iterations cannot introduce hazards
17651790
// with that argument because they can result only in additional
17661791
// binding of lower-ordered arguments.
1767-
func resolveEffects(logf func(string, ...any), args []*argument, effects []int) {
1792+
func resolveEffects(logf logger, args []*argument, effects []int) {
17681793
effectStr := func(effects bool, idx int) string {
17691794
i := fmt.Sprint(idx)
17701795
if idx == len(args) {
@@ -1923,7 +1948,7 @@ type bindingDeclInfo struct {
19231948
//
19241949
// Strategies may impose additional checks on return
19251950
// conversions, labels, defer, etc.
1926-
func createBindingDecl(logf func(string, ...any), caller *Caller, args []*argument, calleeDecl *ast.FuncDecl, results []*paramInfo) *bindingDeclInfo {
1951+
func createBindingDecl(logf logger, caller *Caller, args []*argument, calleeDecl *ast.FuncDecl, results []*paramInfo) *bindingDeclInfo {
19271952
// Spread calls are tricky as they may not align with the
19281953
// parameters' field groupings nor types.
19291954
// For example, given
@@ -2745,26 +2770,38 @@ func clearPositions(root ast.Node) {
27452770
})
27462771
}
27472772

2748-
// findIdent returns the Ident beneath root that has the given pos.
2749-
func findIdent(root ast.Node, pos token.Pos) *ast.Ident {
2773+
// findIdent finds the Ident beneath root that has the given pos.
2774+
// It returns the path to the ident (excluding the ident), and the ident
2775+
// itself, where the path is the sequence of ast.Nodes encountered in a
2776+
// depth-first search to find ident.
2777+
func findIdent(root ast.Node, pos token.Pos) ([]ast.Node, *ast.Ident) {
27502778
// TODO(adonovan): opt: skip subtrees that don't contain pos.
2751-
var found *ast.Ident
2779+
var (
2780+
path []ast.Node
2781+
found *ast.Ident
2782+
)
27522783
ast.Inspect(root, func(n ast.Node) bool {
27532784
if found != nil {
27542785
return false
27552786
}
2787+
if n == nil {
2788+
path = path[:len(path)-1]
2789+
return false
2790+
}
27562791
if id, ok := n.(*ast.Ident); ok {
27572792
if id.Pos() == pos {
27582793
found = id
2794+
return true
27592795
}
27602796
}
2797+
path = append(path, n)
27612798
return true
27622799
})
27632800
if found == nil {
27642801
panic(fmt.Sprintf("findIdent %d not found in %s",
27652802
pos, debugFormatNode(token.NewFileSet(), root)))
27662803
}
2767-
return found
2804+
return path, found
27682805
}
27692806

27702807
func prepend[T any](elem T, slice ...T) []T {

internal/refactor/inline/inline_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,12 @@ func TestVariadic(t *testing.T) {
10431043
`func _(slice []any) { f(slice...) }`,
10441044
`func _(slice []any) { println(slice) }`,
10451045
},
1046+
{
1047+
"Undo variadic elimination",
1048+
`func f(args ...int) []int { return append([]int{1}, args...) }`,
1049+
`func _(a, b int) { f(a, b) }`,
1050+
`func _(a, b int) { _ = append([]int{1}, a, b) }`,
1051+
},
10461052
{
10471053
"Variadic elimination (literalization).",
10481054
`func f(x any, rest ...any) { defer println(x, rest) }`, // defer => literalization
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
This test checks that variadic elimination does not cause a semantic change due
2+
to creation of a non-nil empty slice instead of a nil slice due to missing
3+
variadic arguments.
4+
5+
-- go.mod --
6+
module testdata
7+
go 1.12
8+
9+
-- foo/foo.go --
10+
package foo
11+
import "fmt"
12+
13+
func F(is ...int) {
14+
if is == nil {
15+
fmt.Println("is is nil")
16+
} else {
17+
fmt.Println("is is not nil")
18+
}
19+
}
20+
21+
func G(is ...int) { F(is...) }
22+
23+
func main() {
24+
G() //@ inline(re"G", G)
25+
}
26+
27+
-- G --
28+
package foo
29+
30+
import "fmt"
31+
32+
func F(is ...int) {
33+
if is == nil {
34+
fmt.Println("is is nil")
35+
} else {
36+
fmt.Println("is is not nil")
37+
}
38+
}
39+
40+
func G(is ...int) { F(is...) }
41+
42+
func main() {
43+
F() //@ inline(re"G", G)
44+
}

0 commit comments

Comments
 (0)