Browse Source

Surface the IL 'tail.' prefix as an inline comment in C# output

F# emits tail calls pervasively, but the 'tail.' prefix was dropped
entirely at the C# output stage, so the information never reached the
decompiled text. Render it inline as '/*tail.*/' before the call,
mirroring the existing 'constrained.' prefix comment in CallBuilder.

Fix #3817

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3816/head
Siegfried Pammer 2 weeks ago committed by Siegfried Pammer
parent
commit
99888eb78e
  1. 6
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  2. 11
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.cs
  3. 38
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.il
  4. 9
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs

6
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -135,6 +135,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -135,6 +135,12 @@ namespace ICSharpCode.Decompiler.Tests
await Run();
}
[Test]
public async Task TailCall()
{
await Run();
}
[Test]
public async Task CS1xSwitch_Debug()
{

11
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.cs

@ -0,0 +1,11 @@ @@ -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);
}
}

38
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/TailCall.il

@ -0,0 +1,38 @@ @@ -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
}
}

9
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -238,8 +238,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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)

Loading…
Cancel
Save