Skip to content

Commit 4a26477

Browse files
vladvalkovadonovan
authored andcommitted
internal/refactor/inline: allow duplicable type conversions
The current implementation does not consider type conversions to be duplicable operations; this change resolves it by handling *ast.CallExpr. All type conversions except []byte(string) and []rune(string) are now considered to be duplicable. The change also removes redundant type conversions when typed constants are passed to inlined functions. Fixes golang/go#67589 Change-Id: I524dbff1ae09466f56459e0bf3b6425be38d157c GitHub-Last-Rev: 580a00a GitHub-Pull-Request: #494 Reviewed-on: https://go-review.googlesource.com/c/tools/+/588175 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent db513b0 commit 4a26477

File tree

3 files changed

+229
-50
lines changed

3 files changed

+229
-50
lines changed

internal/refactor/inline/inline.go

+48-10
Original file line numberDiff line numberDiff line change
@@ -1437,10 +1437,29 @@ next:
14371437
// other arguments are given explicit types in either
14381438
// a binding decl or when using the literalization
14391439
// strategy.
1440-
if len(param.info.Refs) > 0 && !trivialConversion(args[i].constant, args[i].typ, params[i].obj.Type()) {
1441-
arg.expr = convert(params[i].fieldType, arg.expr)
1440+
1441+
// If the types are identical, we can eliminate
1442+
// redundant type conversions such as this:
1443+
//
1444+
// Callee:
1445+
// func f(i int32) { print(i) }
1446+
// Caller:
1447+
// func g() { f(int32(1)) }
1448+
// Inlined as:
1449+
// func g() { print(int32(int32(1)))
1450+
//
1451+
// Recall that non-trivial does not imply non-identical
1452+
// for constant conversions; however, at this point state.arguments
1453+
// has already re-typechecked the constant and set arg.type to
1454+
// its (possibly "untyped") inherent type, so
1455+
// the conversion from untyped 1 to int32 is non-trivial even
1456+
// though both arg and param have identical types (int32).
1457+
if len(param.info.Refs) > 0 &&
1458+
!types.Identical(arg.typ, param.obj.Type()) &&
1459+
!trivialConversion(arg.constant, arg.typ, param.obj.Type()) {
1460+
arg.expr = convert(param.fieldType, arg.expr)
14421461
logf("param %q: adding explicit %s -> %s conversion around argument",
1443-
param.info.Name, args[i].typ, params[i].obj.Type())
1462+
param.info.Name, arg.typ, param.obj.Type())
14441463
}
14451464

14461465
// It is safe to substitute param and replace it with arg.
@@ -2206,16 +2225,35 @@ func duplicable(info *types.Info, e ast.Expr) bool {
22062225
return false
22072226

22082227
case *ast.CallExpr:
2209-
// Don't treat a conversion T(x) as duplicable even
2210-
// if x is duplicable because it could duplicate
2211-
// allocations.
2228+
// Treat type conversions as duplicable if they do not observably allocate.
2229+
// The only cases of observable allocations are
2230+
// the `[]byte(string)` and `[]rune(string)` conversions.
22122231
//
2213-
// TODO(adonovan): there are cases to tease apart here:
2214-
// duplicating string([]byte) conversions increases
2232+
// Duplicating string([]byte) conversions increases
22152233
// allocation but doesn't change behavior, but the
22162234
// reverse, []byte(string), allocates a distinct array,
2217-
// which is observable
2218-
return false
2235+
// which is observable.
2236+
2237+
if !info.Types[e.Fun].IsType() { // check whether e.Fun is a type conversion
2238+
return false
2239+
}
2240+
2241+
fun := info.TypeOf(e.Fun)
2242+
arg := info.TypeOf(e.Args[0])
2243+
2244+
switch fun := fun.Underlying().(type) {
2245+
case *types.Slice:
2246+
// Do not mark []byte(string) and []rune(string) as duplicable.
2247+
elem, ok := fun.Elem().Underlying().(*types.Basic)
2248+
if ok && (elem.Kind() == types.Rune || elem.Kind() == types.Byte) {
2249+
from, ok := arg.Underlying().(*types.Basic)
2250+
isString := ok && from.Info()&types.IsString != 0
2251+
return !isString
2252+
}
2253+
case *types.TypeParam:
2254+
return false // be conservative
2255+
}
2256+
return true
22192257

22202258
case *ast.SelectorExpr:
22212259
if seln, ok := info.Selections[e]; ok {

internal/refactor/inline/inline_test.go

+173-37
Original file line numberDiff line numberDiff line change
@@ -414,52 +414,171 @@ func TestBasics(t *testing.T) {
414414
}
415415

416416
func TestDuplicable(t *testing.T) {
417-
runTests(t, []testcase{
418-
{
419-
"Empty strings are duplicable.",
420-
`func f(s string) { print(s, s) }`,
421-
`func _() { f("") }`,
422-
`func _() { print("", "") }`,
423-
},
424-
{
425-
"Non-empty string literals are not duplicable.",
426-
`func f(s string) { print(s, s) }`,
427-
`func _() { f("hi") }`,
428-
`func _() {
417+
t.Run("basic", func(t *testing.T) {
418+
runTests(t, []testcase{
419+
{
420+
"Empty strings are duplicable.",
421+
`func f(s string) { print(s, s) }`,
422+
`func _() { f("") }`,
423+
`func _() { print("", "") }`,
424+
},
425+
{
426+
"Non-empty string literals are not duplicable.",
427+
`func f(s string) { print(s, s) }`,
428+
`func _() { f("hi") }`,
429+
`func _() {
429430
var s string = "hi"
430431
print(s, s)
431432
}`,
432-
},
433-
{
434-
"Empty array literals are duplicable.",
435-
`func f(a [2]int) { print(a, a) }`,
436-
`func _() { f([2]int{}) }`,
437-
`func _() { print([2]int{}, [2]int{}) }`,
438-
},
439-
{
440-
"Non-empty array literals are not duplicable.",
441-
`func f(a [2]int) { print(a, a) }`,
442-
`func _() { f([2]int{1, 2}) }`,
443-
`func _() {
433+
},
434+
{
435+
"Empty array literals are duplicable.",
436+
`func f(a [2]int) { print(a, a) }`,
437+
`func _() { f([2]int{}) }`,
438+
`func _() { print([2]int{}, [2]int{}) }`,
439+
},
440+
{
441+
"Non-empty array literals are not duplicable.",
442+
`func f(a [2]int) { print(a, a) }`,
443+
`func _() { f([2]int{1, 2}) }`,
444+
`func _() {
444445
var a [2]int = [2]int{1, 2}
445446
print(a, a)
446447
}`,
447-
},
448-
{
449-
"Empty struct literals are duplicable.",
450-
`func f(s S) { print(s, s) }; type S struct { x int }`,
451-
`func _() { f(S{}) }`,
452-
`func _() { print(S{}, S{}) }`,
453-
},
454-
{
455-
"Non-empty struct literals are not duplicable.",
456-
`func f(s S) { print(s, s) }; type S struct { x int }`,
457-
`func _() { f(S{x: 1}) }`,
458-
`func _() {
448+
},
449+
{
450+
"Empty struct literals are duplicable.",
451+
`func f(s S) { print(s, s) }; type S struct { x int }`,
452+
`func _() { f(S{}) }`,
453+
`func _() { print(S{}, S{}) }`,
454+
},
455+
{
456+
"Non-empty struct literals are not duplicable.",
457+
`func f(s S) { print(s, s) }; type S struct { x int }`,
458+
`func _() { f(S{x: 1}) }`,
459+
`func _() {
459460
var s S = S{x: 1}
460461
print(s, s)
461462
}`,
462-
},
463+
},
464+
})
465+
})
466+
467+
t.Run("conversions", func(t *testing.T) {
468+
runTests(t, []testcase{
469+
{
470+
"Conversions to integer are duplicable.",
471+
`func f(i int) { print(i, i) }`,
472+
`func _() { var i int8 = 1; f(int(i)) }`,
473+
`func _() { var i int8 = 1; print(int(i), int(i)) }`,
474+
},
475+
{
476+
"Implicit conversions from underlying types are duplicable.",
477+
`func f(i I) { print(i, i) }; type I int`,
478+
`func _() { f(1) }`,
479+
`func _() { print(I(1), I(1)) }`,
480+
},
481+
{
482+
"Conversions to array are duplicable.",
483+
`func f(a [2]int) { print(a, a) }; type A [2]int`,
484+
`func _() { var a A; f([2]int(a)) }`,
485+
`func _() { var a A; print([2]int(a), [2]int(a)) }`,
486+
},
487+
{
488+
"Conversions from array are duplicable.",
489+
`func f(a A) { print(a, a) }; type A [2]int`,
490+
`func _() { var a [2]int; f(A(a)) }`,
491+
`func _() { var a [2]int; print(A(a), A(a)) }`,
492+
},
493+
{
494+
"Conversions from byte slice to string are duplicable.",
495+
`func f(s string) { print(s, s) }`,
496+
`func _() { var b []byte; f(string(b)) }`,
497+
`func _() { var b []byte; print(string(b), string(b)) }`,
498+
},
499+
{
500+
"Conversions from string to byte slice are not duplicable.",
501+
`func f(b []byte) { print(b, b) }`,
502+
`func _() { var s string; f([]byte(s)) }`,
503+
`func _() {
504+
var s string
505+
var b []byte = []byte(s)
506+
print(b, b)
507+
}`,
508+
},
509+
{
510+
"Conversions from string to uint8 slice are not duplicable.",
511+
`func f(b []uint8) { print(b, b) }`,
512+
`func _() { var s string; f([]uint8(s)) }`,
513+
`func _() {
514+
var s string
515+
var b []uint8 = []uint8(s)
516+
print(b, b)
517+
}`,
518+
},
519+
{
520+
"Conversions from string to rune slice are not duplicable.",
521+
`func f(r []rune) { print(r, r) }`,
522+
`func _() { var s string; f([]rune(s)) }`,
523+
`func _() {
524+
var s string
525+
var r []rune = []rune(s)
526+
print(r, r)
527+
}`,
528+
},
529+
{
530+
"Conversions from string to named type with underlying byte slice are not duplicable.",
531+
`func f(b B) { print(b, b) }; type B []byte`,
532+
`func _() { var s string; f(B(s)) }`,
533+
`func _() {
534+
var s string
535+
var b B = B(s)
536+
print(b, b)
537+
}`,
538+
},
539+
{
540+
"Conversions from string to named type of string are duplicable.",
541+
`func f(s S) { print(s, s) }; type S string`,
542+
`func _() { var s string; f(S(s)) }`,
543+
`func _() { var s string; print(S(s), S(s)) }`,
544+
},
545+
{
546+
"Built-in function calls are not duplicable.",
547+
`func f(i int) { print(i, i) }`,
548+
`func _() { f(len("")) }`,
549+
`func _() {
550+
var i int = len("")
551+
print(i, i)
552+
}`,
553+
},
554+
{
555+
"Built-in function calls are not duplicable.",
556+
`func f(c complex128) { print(c, c) }`,
557+
`func _() { f(complex(1.0, 2.0)) }`,
558+
`func _() {
559+
var c complex128 = complex(1.0, 2.0)
560+
print(c, c)
561+
}`,
562+
},
563+
{
564+
"Non built-in function calls are not duplicable.",
565+
`func f(i int) { print(i, i) }
566+
//go:noinline
567+
func f1(i int) int { return i + 1 }`,
568+
`func _() { f(f1(1)) }`,
569+
`func _() {
570+
var i int = f1(1)
571+
print(i, i)
572+
}`,
573+
},
574+
{
575+
"Conversions between function types are duplicable.",
576+
`func f(f F) { print(f, f) }; type F func(); func f1() {}`,
577+
`func _() { f(F(f1)) }`,
578+
`func _() { print(F(f1), F(f1)) }`,
579+
},
580+
})
581+
463582
})
464583
}
465584

@@ -1358,6 +1477,23 @@ func TestSubstitutionPreservesParameterType(t *testing.T) {
13581477
})
13591478
}
13601479

1480+
func TestRedundantConversions(t *testing.T) {
1481+
runTests(t, []testcase{
1482+
{
1483+
"Type conversion must be added if the constant is untyped.",
1484+
`func f(i int32) { print(i) }`,
1485+
`func _() { f(1) }`,
1486+
`func _() { print(int32(1)) }`,
1487+
},
1488+
{
1489+
"Type conversion must not be added if the constant is typed.",
1490+
`func f(i int32) { print(i) }`,
1491+
`func _() { f(int32(1)) }`,
1492+
`func _() { print(int32(1)) }`,
1493+
},
1494+
})
1495+
}
1496+
13611497
func runTests(t *testing.T, tests []testcase) {
13621498
for _, test := range tests {
13631499
test := test

internal/refactor/inline/util.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,19 @@ func within(pos token.Pos, n ast.Node) bool {
6868
//
6969
// trivialConversion under-approximates trivial conversions, as unfortunately
7070
// go/types does not record the type of an expression *before* it is implicitly
71-
// converted, and therefore it cannot distinguish typed constant constant
71+
// converted, and therefore it cannot distinguish typed constant
7272
// expressions from untyped constant expressions. For example, in the
7373
// expression `c + 2`, where c is a uint32 constant, trivialConversion does not
74-
// detect that the default type of this express is actually uint32, not untyped
74+
// detect that the default type of this expression is actually uint32, not untyped
7575
// int.
7676
//
7777
// We could, of course, do better here by reverse engineering some of go/types'
78-
// constant handling. That may or may not be worthwhile..
78+
// constant handling. That may or may not be worthwhile.
79+
//
80+
// Example: in func f() int32 { return 0 },
81+
// the type recorded for 0 is int32, not untyped int;
82+
// although it is Identical to the result var,
83+
// the conversion is non-trivial.
7984
func trivialConversion(fromValue constant.Value, from, to types.Type) bool {
8085
if fromValue != nil {
8186
var defaultType types.Type

0 commit comments

Comments
 (0)