diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 11be019f1..ea8d0c7ff 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -135,6 +135,12 @@ namespace ICSharpCode.Decompiler.Tests await Run(); } + [Test] + public async Task TailCall() + { + await Run(); + } + [Test] public async Task CS1xSwitch_Debug() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.cs new file mode 100644 index 000000000..ed332bffc --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.cs @@ -0,0 +1,11 @@ +internal class TailCall +{ + public static int Callee(int x) + { + return x; + } + public static int Caller(int x) + { + return /*tail.*/Callee(x); + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.il new file mode 100644 index 000000000..a5ce6333b --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.il @@ -0,0 +1,38 @@ +#define CORE_ASSEMBLY "System.Runtime" + +.assembly extern CORE_ASSEMBLY +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 4:0:0:0 +} + +.assembly TailCall { } + +.class private auto ansi beforefieldinit TailCall + extends [CORE_ASSEMBLY]System.Object +{ + .method public hidebysig static int32 Callee (int32 x) cil managed + { + .maxstack 8 + ldarg.0 + ret + } + + .method public hidebysig static int32 Caller (int32 x) cil managed + { + .maxstack 8 + ldarg.0 + tail. + call int32 TailCall::Callee(int32) + ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + .maxstack 8 + ldarg.0 + call instance void [CORE_ASSEMBLY]System.Object::.ctor() + ret + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 822f4c5ea..75dca021b 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -238,8 +238,15 @@ namespace ICSharpCode.Decompiler.CSharp { return BuildStringConcat(inst.Method, operands).WithILInstruction(inst); } - return Build(inst.OpCode, inst.Method, inst.Arguments, constrainedTo: inst.ConstrainedTo) + var result = Build(inst.OpCode, inst.Method, inst.Arguments, constrainedTo: inst.ConstrainedTo) .WithILInstruction(inst); + if (inst.IsTail) + { + // Surface the IL 'tail.' prefix as an inline marker, e.g. '/*tail.*/Callee(x)'. + // F# emits tail calls pervasively, and the prefix is otherwise dropped entirely. + result.Expression.AddLeadingTrivia(new Comment("tail.", CommentType.MultiLine)); + } + return result; } private ExpressionWithResolveResult BuildStringConcat(IMethod method, List<(ILInstruction Instruction, KnownTypeCode TypeCode)> operands)