Skip to content

Commit ee20ddf

Browse files
committed
internal/refactor/inline: permit return conversions in tailcall
Previously, the tail-call strategies required that the callee's implicit return conversions must be trivial. That meant returning a nil error (for example) defeated these strategies, even though it is common in tail-call situations for the caller function to have identical result types. For example: func callee() error { return nil } // nontrivial conversion func caller() error { return callee() } // identical result types This change permits the tail-call strategies when the callee and caller's results tuples are identical. Fixes golang/go#63336 Change-Id: I57d62213023861a2cfebed25b01ec28921efe441 Reviewed-on: https://go-review.googlesource.com/c/tools/+/533075 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent db1d1e0 commit ee20ddf

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

internal/refactor/inline/inline.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,14 +760,15 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
760760
//
761761
// If:
762762
// - the body is just "return expr" with trivial implicit conversions,
763+
// or the caller's return type matches the callee's,
763764
// - all parameters and result vars can be eliminated
764765
// or replaced by a binding decl,
765766
// then the call expression can be replaced by the
766767
// callee's body expression, suitably substituted.
767768
if len(calleeDecl.Body.List) == 1 &&
768769
is[*ast.ReturnStmt](calleeDecl.Body.List[0]) &&
769770
len(calleeDecl.Body.List[0].(*ast.ReturnStmt).Results) > 0 && // not a bare return
770-
callee.TrivialReturns == callee.TotalReturns {
771+
safeReturn(caller, calleeSymbol, callee) {
771772
results := calleeDecl.Body.List[0].(*ast.ReturnStmt).Results
772773

773774
context := callContext(caller.path)
@@ -880,7 +881,8 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
880881
// so long as:
881882
// - all parameters can be eliminated or replaced by a binding decl,
882883
// - call is a tail-call;
883-
// - all returns in body have trivial result conversions;
884+
// - all returns in body have trivial result conversions,
885+
// or the caller's return type matches the callee's,
884886
// - there is no label conflict;
885887
// - no result variable is referenced by name,
886888
// or implicitly by a bare return.
@@ -896,7 +898,7 @@ func inline(logf func(string, ...any), caller *Caller, callee *gobCallee) (*resu
896898
// or implicit) return.
897899
if ret, ok := callContext(caller.path).(*ast.ReturnStmt); ok &&
898900
len(ret.Results) == 1 &&
899-
callee.TrivialReturns == callee.TotalReturns &&
901+
safeReturn(caller, calleeSymbol, callee) &&
900902
!callee.HasBareReturn &&
901903
(!needBindingDecl || bindingDeclStmt != nil) &&
902904
!hasLabelConflict(caller.path, callee.Labels) &&
@@ -2601,3 +2603,34 @@ func declares(stmts []ast.Stmt) map[string]bool {
26012603
}
26022604
return names
26032605
}
2606+
2607+
// safeReturn reports whether the callee's return statements may be safely
2608+
// used to return from the function enclosing the caller (which must exist).
2609+
func safeReturn(caller *Caller, calleeSymbol *types.Func, callee *gobCallee) bool {
2610+
// It is safe if all callee returns involve only trivial conversions.
2611+
if callee.TrivialReturns == callee.TotalReturns {
2612+
return true
2613+
}
2614+
2615+
var callerType types.Type
2616+
// Find type of innermost function enclosing call.
2617+
// (Beware: Caller.enclosingFunc is the outermost.)
2618+
loop:
2619+
for _, n := range caller.path {
2620+
switch f := n.(type) {
2621+
case *ast.FuncDecl:
2622+
callerType = caller.Info.ObjectOf(f.Name).Type()
2623+
break loop
2624+
case *ast.FuncLit:
2625+
callerType = caller.Info.TypeOf(f)
2626+
break loop
2627+
}
2628+
}
2629+
2630+
// Non-trivial return conversions in the callee are permitted
2631+
// if the same non-trivial conversion would occur after inlining,
2632+
// i.e. if the caller and callee results tuples are identical.
2633+
callerResults := callerType.(*types.Signature).Results()
2634+
calleeResults := calleeSymbol.Type().(*types.Signature).Results()
2635+
return types.Identical(callerResults, calleeResults)
2636+
}

internal/refactor/inline/inline_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,25 @@ func TestTailCallStrategy(t *testing.T) {
565565
`func _() { f() }`,
566566
`func _() { func() { defer f(); println() }() }`,
567567
},
568+
// Tests for issue #63336:
569+
{
570+
"Tail call with non-trivial return conversion (caller.sig = callee.sig).",
571+
`func f() error { if true { return nil } else { return e } }; var e struct{error}`,
572+
`func _() error { return f() }`,
573+
`func _() error {
574+
if true {
575+
return nil
576+
} else {
577+
return e
578+
}
579+
}`,
580+
},
581+
{
582+
"Tail call with non-trivial return conversion (caller.sig != callee.sig).",
583+
`func f() error { return E{} }; type E struct{error}`,
584+
`func _() any { return f() }`,
585+
`func _() any { return func() error { return E{} }() }`,
586+
},
568587
})
569588
}
570589

0 commit comments

Comments
 (0)