Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@
<None Include="TestCases\ILPretty\CS1xSwitch_Release.cs" />
<Compile Remove="TestCases\ILPretty\CallIndirect.cs" />
<None Include="TestCases\ILPretty\CallIndirect.cs" />
<Compile Remove="TestCases\ILPretty\CompoundAssignmentOperatorEdgeCases.cs" />
<None Include="TestCases\ILPretty\CompoundAssignmentOperatorEdgeCases.cs" />
<None Include="TestCases\ILPretty\CompoundAssignmentOperatorEdgeCases.il" />
<Compile Remove="TestCases\ILPretty\EmptyBodies.cs" />
<None Include="TestCases\ILPretty\EmptyBodies.cs" />
<Compile Remove="TestCases\ILPretty\TruncatedAccessorBody.cs" />
Expand Down
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ public async Task CallIndirect()
await Run();
}

[Test]
public async Task CompoundAssignmentOperatorEdgeCases()
{
await Run();
}

[Test]
public async Task FSharpLoops_Debug()
{
Expand Down
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,12 @@ public async Task RefStructInterfaces([ValueSource(nameof(roslyn4OrNewerOptions)
await RunForLibrary(cscOptions: cscOptions);
}

[Test]
public async Task UserDefinedCompoundAssignment([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}

[Test]
public async Task ExpandParamsArgumentsDisabled([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
锘縰sing System.Runtime.CompilerServices;

public class CompoundTarget
{
public int Value;

public void operator +=(int rhs)
{
Value += rhs;
}

// Not "operator -=": C# 14 only recognizes void-returning instance methods as compound
// assignment operators, so a value-returning one stays a plain method.
[SpecialName]
Comment thread
siegfriedpammer marked this conversation as resolved.
public CompoundTarget op_SubtractionAssignment(int rhs)
{
return this;
}

[SpecialName]
public static CompoundTarget op_MultiplicationAssignment(CompoundTarget lhs, int rhs)
{
return lhs;
}
}
public class DerivedCompoundTarget : CompoundTarget
{
public void CallBaseOperator(int n)
{
base.op_AdditionAssignment(n);
}
}
public class EdgeCases
{
public static CompoundTarget GetTarget()
{
return new CompoundTarget();
}

public static void NonVariableReceiver(int n)
{
GetTarget().op_AdditionAssignment(n);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better if we had avoided inlining.
There's already a change in inlining that should do so -> investigate why that doesn't work.

Reviewed with Stampeded!

}

public static void UnconstrainedGenericReceiver<T>(T x, int n) where T : IOther
{
((ICompound)(object)x).op_AdditionAssignment(n);
}

public static void MismatchedReceiverType(object o, int n)
{
((CompoundTarget)o).op_AdditionAssignment(n);
}

public static void CallNonCSharpOperators(CompoundTarget t, int n)
{
t.op_SubtractionAssignment(n);
CompoundTarget.op_MultiplicationAssignment(t, n);
}
}
public interface ICompound
{
void operator +=(int rhs);
}
public interface IOther
{
void M();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Metadata version: v4.0.30319
.assembly extern System.Runtime
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 11:0:0:0
}
.assembly CompoundAssignmentOperatorEdgeCases
{
.ver 1:0:0:0
}
.module CompoundAssignmentOperatorEdgeCases.dll
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY

.class public auto ansi beforefieldinit CompoundTarget
extends [System.Runtime]System.Object
{
.field public int32 Value

// A C# 14 user-defined compound assignment operator.
.method public hidebysig specialname instance void
op_AdditionAssignment(int32 rhs) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.0
IL_0002: ldfld int32 CompoundTarget::Value
IL_0007: ldarg.1
IL_0008: add
IL_0009: stfld int32 CompoundTarget::Value
IL_000e: ret
}

// C++/CLI emits value-returning instance operators; C# has no syntax for those.
.method public hidebysig specialname instance class CompoundTarget
op_SubtractionAssignment(int32 rhs) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ret
}

// F# mangles "static member (*=)" to a static, value-returning op_MultiplicationAssignment.
.method public hidebysig specialname static class CompoundTarget
op_MultiplicationAssignment(class CompoundTarget lhs,
int32 rhs) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ret
}

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
}
}

.class interface public abstract auto ansi beforefieldinit ICompound
{
.method public hidebysig newslot specialname abstract virtual
instance void op_AdditionAssignment(int32 rhs) cil managed
{
}
}

.class interface public abstract auto ansi beforefieldinit IOther
{
.method public hidebysig newslot abstract virtual
instance void M() cil managed
{
}
}

.class public auto ansi beforefieldinit DerivedCompoundTarget
extends CompoundTarget
{
.method public hidebysig instance void CallBaseOperator(int32 n) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: call instance void CompoundTarget::op_AdditionAssignment(int32)
IL_0007: ret
}

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void CompoundTarget::.ctor()
IL_0006: ret
}
}

.class public auto ansi beforefieldinit EdgeCases
extends [System.Runtime]System.Object
{
.method public hidebysig static class CompoundTarget
GetTarget() cil managed
{
.maxstack 8
IL_0000: newobj instance void CompoundTarget::.ctor()
IL_0005: ret
}

// The receiver is not a variable, so it cannot become the target of "x += n".
.method public hidebysig static void NonVariableReceiver(int32 n) cil managed
{
.maxstack 8
IL_0000: call class CompoundTarget EdgeCases::GetTarget()
IL_0005: ldarg.0
IL_0006: callvirt instance void CompoundTarget::op_AdditionAssignment(int32)
IL_000b: ret
}

// T is not constrained to ICompound, so the cast selects the operator and has to stay.
.method public hidebysig static void UnconstrainedGenericReceiver<(IOther) T>(!!T x,
int32 n) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: castclass ICompound
IL_000b: ldarg.1
IL_000c: callvirt instance void ICompound::op_AdditionAssignment(int32)
IL_0011: ret
}

// The receiver's static type does not declare the operator, so overload resolution has no
// candidate to pick.
.method public hidebysig static void MismatchedReceiverType(object o,
int32 n) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: callvirt instance void CompoundTarget::op_AdditionAssignment(int32)
IL_0007: ret
}

.method public hidebysig static void CallNonCSharpOperators(class CompoundTarget t,
int32 n) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: callvirt instance class CompoundTarget CompoundTarget::op_SubtractionAssignment(int32)
IL_0007: pop
IL_0008: ldarg.0
IL_0009: ldarg.1
IL_000a: call class CompoundTarget CompoundTarget::op_MultiplicationAssignment(class CompoundTarget,
int32)
IL_000f: pop
IL_0010: ret
}

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
}
}
Loading
Loading