Skip to content

Commit 169105a

Browse files
adonovangopherbot
authored andcommitted
internal/refactor/inline: insert conversions during substitution
Previously, we rejected arguments as substitution candidates if their type (or its types.Default) did not exactly match the parameter type. Now, we allow those substitutions to proceed but we wrap the argument in an explicit conversion so that the meaning of the program doesn't change. We had initially been reluctant to do this out of concern that it could cause a single conversion during argument passing to be moved into a loop, where its dynamic cost might be multiplied; for example, a string->any conversion allocates memory. But we decided that this modest dynamic cost is acceptable so long as the meaning does not change. Also, this change includes a fix for golang/go#63193, in which the type inferred for the argument expression in func(int16){}(1) is not "untyped int" but "int16". In other words, the type checker has incorporated knowledge of the parameter type. It is unsafe to assume that the expression 1 will have the same type in a different context, so we recompute the type of each argument expression in a neutral context (using CheckExpr). Fixes golang/go#63193 Change-Id: I0fd072cc7611d113af77193f6d3362268d9af158 Reviewed-on: https://go-review.googlesource.com/c/tools/+/530975 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent b3ada30 commit 169105a

File tree

2 files changed

+145
-30
lines changed

2 files changed

+145
-30
lines changed

internal/refactor/inline/inline.go

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -441,14 +441,29 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
441441
sig := calleeSymbol.Type().(*types.Signature)
442442
if sig.Recv() != nil {
443443
params = append(params, &parameter{
444-
obj: sig.Recv(),
445-
info: callee.Params[0],
444+
obj: sig.Recv(),
445+
fieldType: calleeDecl.Recv.List[0].Type,
446+
info: callee.Params[0],
446447
})
447448
}
449+
450+
// Flatten the list of syntactic types.
451+
var types []ast.Expr
452+
for _, field := range calleeDecl.Type.Params.List {
453+
if field.Names == nil {
454+
types = append(types, field.Type)
455+
} else {
456+
for range field.Names {
457+
types = append(types, field.Type)
458+
}
459+
}
460+
}
461+
448462
for i := 0; i < sig.Params().Len(); i++ {
449463
params = append(params, &parameter{
450-
obj: sig.Params().At(i),
451-
info: callee.Params[len(params)],
464+
obj: sig.Params().At(i),
465+
fieldType: types[i],
466+
info: callee.Params[len(params)],
452467
})
453468
}
454469

@@ -511,9 +526,9 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
511526

512527
// Log effective arguments.
513528
for i, arg := range args {
514-
logf("arg #%d: %s pure=%t effects=%t duplicable=%t free=%v",
529+
logf("arg #%d: %s pure=%t effects=%t duplicable=%t free=%v type=%v",
515530
i, debugFormatNode(caller.Fset, arg.expr),
516-
arg.pure, arg.effects, arg.duplicable, arg.freevars)
531+
arg.pure, arg.effects, arg.duplicable, arg.freevars, arg.typ)
517532
}
518533

519534
// Note: computation below should be expressed in terms of
@@ -992,13 +1007,44 @@ func arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var
9921007
freevars: freeVars(caller.Info, expr),
9931008
})
9941009
}
1010+
1011+
// Re-typecheck each constant argument expression in a neutral context.
1012+
//
1013+
// In a call such as func(int16){}(1), the type checker infers
1014+
// the type "int16", not "untyped int", for the argument 1,
1015+
// because it has incorporated information from the left-hand
1016+
// side of the assignment implicit in parameter passing, but
1017+
// of course in a different context, the expression 1 may have
1018+
// a different type.
1019+
//
1020+
// So, we must use CheckExpr to recompute the type of the
1021+
// argument in a neutral context to find its inherent type.
1022+
// (This is arguably a bug in go/types, but I'm pretty certain
1023+
// I requested it be this way long ago... -adonovan)
1024+
//
1025+
// This is only needed for constants. Other implicit
1026+
// assignment conversions, such as unnamed-to-named struct or
1027+
// chan to <-chan, do not result in the type-checker imposing
1028+
// the LHS type on the RHS value.
1029+
for _, arg := range args {
1030+
if caller.Info.Types[arg.expr].Value == nil {
1031+
continue // not a constant
1032+
}
1033+
info := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
1034+
if err := types.CheckExpr(caller.Fset, caller.Types, caller.Call.Pos(), arg.expr, info); err != nil {
1035+
return nil, err
1036+
}
1037+
arg.typ = info.TypeOf(arg.expr)
1038+
}
1039+
9951040
return args, nil
9961041
}
9971042

9981043
type parameter struct {
999-
obj *types.Var // parameter var from caller's signature
1000-
info *paramInfo // information from AnalyzeCallee
1001-
variadic bool // (final) parameter is unsimplified ...T
1044+
obj *types.Var // parameter var from caller's signature
1045+
fieldType ast.Expr // syntax of type, from calleeDecl.Type.{Recv,Params}
1046+
info *paramInfo // information from AnalyzeCallee
1047+
variadic bool // (final) parameter is unsimplified ...T
10021048
}
10031049

10041050
// substitute implements parameter elimination by substitution.
@@ -1079,20 +1125,6 @@ next:
10791125
}
10801126
}
10811127

1082-
// Check that eliminating the parameter wouldn't materially
1083-
// change the type.
1084-
//
1085-
// (We don't simply wrap the argument in an explicit conversion
1086-
// to the parameter type because that could increase allocation
1087-
// in the number of (e.g.) string -> any conversions.
1088-
// Even when Uses = 1, the sole ref might be in a loop or lambda that
1089-
// is multiply executed.)
1090-
if len(param.info.Refs) > 0 && !trivialConversion(args[i].typ, params[i].obj) {
1091-
logf("keeping param %q: argument passing converts %s to type %s",
1092-
param.info.Name, args[i].typ, params[i].obj.Type())
1093-
continue // implicit conversion is significant
1094-
}
1095-
10961128
// Check for shadowing.
10971129
//
10981130
// Consider inlining a call f(z, 1) to
@@ -1119,8 +1151,30 @@ next:
11191151
// The remaining candidates are safe to substitute.
11201152
for i, param := range params {
11211153
if arg := args[i]; arg.substitutable {
1154+
1155+
// Wrap the argument in an explicit conversion if
1156+
// substitution might materially change its type.
1157+
// (We already did the necessary shadowing check
1158+
// on the parameter type syntax.)
1159+
//
1160+
// This is only needed for substituted arguments. All
1161+
// other arguments are given explicit types in either
1162+
// a binding decl or when using the literalization
1163+
// strategy.
1164+
if len(param.info.Refs) > 0 && !trivialConversion(args[i].typ, params[i].obj) {
1165+
arg.expr = &ast.CallExpr{
1166+
Fun: params[i].fieldType, // formatter adds parens as needed
1167+
Args: []ast.Expr{arg.expr},
1168+
}
1169+
logf("param %q: adding explicit %s -> %s conversion around argument",
1170+
param.info.Name, args[i].typ, params[i].obj.Type())
1171+
}
1172+
11221173
// It is safe to substitute param and replace it with arg.
11231174
// The formatter introduces parens as needed for precedence.
1175+
//
1176+
// Because arg.expr belongs to the caller,
1177+
// we clone it before splicing it into the callee tree.
11241178
logf("replacing parameter %q by argument %q",
11251179
param.info.Name, debugFormatNode(caller.Fset, arg.expr))
11261180
for _, ref := range param.info.Refs {

internal/refactor/inline/inline_test.go

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ func TestTable(t *testing.T) {
410410
"Variadic elimination (literalization).",
411411
`func f(x any, rest ...any) { defer println(x, rest) }`, // defer => literalization
412412
`func _() { f(1, 2, 3) }`,
413-
`func _() { func(x any) { defer println(x, []any{2, 3}) }(1) }`,
413+
`func _() { func() { defer println(any(1), []any{2, 3}) }() }`,
414414
},
415415
{
416416
"Variadic elimination (reduction).",
@@ -448,13 +448,13 @@ func TestTable(t *testing.T) {
448448
}`,
449449
},
450450
{
451-
"Binding declaration (x, z eliminated).",
451+
"Binding declaration (x, y, z eliminated).",
452452
`func f(w, x, y any, z int) { println(w, y, z) }; func g(int) int`,
453453
`func _() { f(g(0), g(1), g(2), g(3)) }`,
454454
`func _() {
455455
{
456-
var w, _, y any = g(0), g(1), g(2)
457-
println(w, y, g(3))
456+
var w, _ any = g(0), g(1)
457+
println(w, any(g(2)), g(3))
458458
}
459459
}`,
460460
},
@@ -477,11 +477,16 @@ func TestTable(t *testing.T) {
477477
}`,
478478
},
479479
{
480-
"No binding decl due to shadowing of int",
480+
"Defer f() evaluates f() before unknown effects",
481481
`func f(int, y any, z int) { defer println(int, y, z) }; func g(int) int`,
482482
`func _() { f(g(1), g(2), g(3)) }`,
483-
`func _() { func(int, y any) { defer println(int, y, g(3)) }(g(1), g(2)) }
484-
`,
483+
`func _() { func() { defer println(any(g(1)), any(g(2)), g(3)) }() }`,
484+
},
485+
{
486+
"No binding decl due to shadowing of int",
487+
`func f(int, y any, z int) { defer g(0); println(int, y, z) }; func g(int) int`,
488+
`func _() { f(g(1), g(2), g(3)) }`,
489+
`func _() { func(int, y any, z int) { defer g(0); println(int, y, z) }(g(1), g(2), g(3)) }`,
485490
},
486491
// Embedded fields:
487492
{
@@ -754,6 +759,62 @@ func TestTable(t *testing.T) {
754759
}
755760
}`,
756761
},
762+
{
763+
"Substitution preserves argument type (#63193).",
764+
`func f(x int16) { y := x; _ = (*int16)(&y) }`,
765+
`func _() { f(1) }`,
766+
`func _() {
767+
{
768+
y := int16(1)
769+
_ = (*int16)(&y)
770+
}
771+
}`,
772+
},
773+
{
774+
"Same, with non-constant (unnamed to named struct) conversion.",
775+
`func f(x T) { y := x; _ = (*T)(&y) }; type T struct{}`,
776+
`func _() { f(struct{}{}) }`,
777+
`func _() {
778+
{
779+
y := T(struct{}{})
780+
_ = (*T)(&y)
781+
}
782+
}`,
783+
},
784+
{
785+
"Same, with non-constant (chan to <-chan) conversion.",
786+
`func f(x T) { y := x; _ = (*T)(&y) }; type T = <-chan int; var ch chan int`,
787+
`func _() { f(ch) }`,
788+
`func _() {
789+
{
790+
y := T(ch)
791+
_ = (*T)(&y)
792+
}
793+
}`,
794+
},
795+
{
796+
"Same, with untyped nil to typed nil conversion.",
797+
`func f(x *int) { y := x; _ = (**int)(&y) }`,
798+
`func _() { f(nil) }`,
799+
`func _() {
800+
{
801+
y := (*int)(nil)
802+
_ = (**int)(&y)
803+
}
804+
}`,
805+
},
806+
{
807+
"Conversion of untyped int to named type is made explicit.",
808+
`type T int; func (x T) f() { x.g() }; func (T) g() {}`,
809+
`func _() { T.f(1) }`,
810+
`func _() { T(1).g() }`,
811+
},
812+
{
813+
"Check for shadowing error on type used in the conversion.",
814+
`func f(x T) { _ = &x == (*T)(nil) }; type T int16`,
815+
`func _() { type T bool; f(1) }`,
816+
`error: T.*shadowed.*by.*type`,
817+
},
757818

758819
// TODO(adonovan): improve coverage of the cross
759820
// product of each strategy with the checklist of

0 commit comments

Comments
 (0)