mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
628 lines
14 KiB
628 lines
14 KiB
// Copyright (c) 2026 Siegfried Pammer |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
|
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty |
|
{ |
|
internal class UserDefinedCompoundAssignment |
|
{ |
|
public class CompoundClass |
|
{ |
|
public int Value; |
|
|
|
public void operator +=(int rhs) |
|
{ |
|
Value += rhs; |
|
} |
|
|
|
public void operator checked +=(int rhs) |
|
{ |
|
checked |
|
{ |
|
Value += rhs; |
|
} |
|
} |
|
|
|
public void operator -=(int rhs) |
|
{ |
|
Value -= rhs; |
|
} |
|
|
|
public void operator checked -=(int rhs) |
|
{ |
|
checked |
|
{ |
|
Value -= rhs; |
|
} |
|
} |
|
|
|
public void operator *=(int rhs) |
|
{ |
|
Value *= rhs; |
|
} |
|
|
|
public void operator checked *=(int rhs) |
|
{ |
|
checked |
|
{ |
|
Value *= rhs; |
|
} |
|
} |
|
|
|
public void operator /=(int rhs) |
|
{ |
|
Value /= rhs; |
|
} |
|
|
|
public void operator checked /=(int rhs) |
|
{ |
|
Value /= rhs; |
|
} |
|
|
|
public void operator %=(int rhs) |
|
{ |
|
Value %= rhs; |
|
} |
|
|
|
public void operator &=(int rhs) |
|
{ |
|
Value &= rhs; |
|
} |
|
|
|
public void operator |=(int rhs) |
|
{ |
|
Value |= rhs; |
|
} |
|
|
|
public void operator ^=(int rhs) |
|
{ |
|
Value ^= rhs; |
|
} |
|
|
|
public void operator <<=(int rhs) |
|
{ |
|
Value <<= rhs; |
|
} |
|
|
|
public void operator >>=(int rhs) |
|
{ |
|
Value >>= rhs; |
|
} |
|
|
|
public void operator >>>=(int rhs) |
|
{ |
|
Value >>>= rhs; |
|
} |
|
|
|
public void operator ++() |
|
{ |
|
Value++; |
|
} |
|
|
|
public void operator checked ++() |
|
{ |
|
checked |
|
{ |
|
Value++; |
|
} |
|
} |
|
|
|
public void operator --() |
|
{ |
|
Value--; |
|
} |
|
|
|
public void operator checked --() |
|
{ |
|
checked |
|
{ |
|
Value--; |
|
} |
|
} |
|
|
|
public virtual void operator +=(long rhs) |
|
{ |
|
Value += (int)rhs; |
|
} |
|
|
|
// "this" is not an assignable variable in a class, so the copy is what makes the |
|
// operator form legal; it has to survive decompilation. |
|
public void AddViaThisCopy(int n) |
|
{ |
|
CompoundClass compoundClass = this; |
|
compoundClass += n; |
|
} |
|
|
|
public void IncrementViaThisCopy() |
|
{ |
|
CompoundClass compoundClass = this; |
|
compoundClass++; |
|
} |
|
} |
|
|
|
public struct CompoundStruct |
|
{ |
|
public int Value; |
|
|
|
public void operator +=(int rhs) |
|
{ |
|
Value += rhs; |
|
} |
|
|
|
public readonly void operator -=(int rhs) |
|
{ |
|
Console.WriteLine(Value - rhs); |
|
} |
|
|
|
public void operator ++() |
|
{ |
|
Value++; |
|
} |
|
|
|
public void AddViaThis() |
|
{ |
|
this += 10; |
|
} |
|
} |
|
|
|
public class DerivedCompoundClass : CompoundClass |
|
{ |
|
} |
|
|
|
public class OverridingCompoundClass : CompoundClass |
|
{ |
|
public override void operator +=(long rhs) |
|
{ |
|
Value += (int)(rhs * 2); |
|
} |
|
} |
|
|
|
public class ShadowingCompoundClass : CompoundClass |
|
{ |
|
public new void operator +=(int rhs) |
|
{ |
|
Value += rhs * 2; |
|
} |
|
} |
|
|
|
public class InParameterOperators |
|
{ |
|
public int Value; |
|
|
|
public static InParameterOperators operator +(InParameterOperators lhs, in InParameterOperators rhs) |
|
{ |
|
return new InParameterOperators { |
|
Value = lhs.Value + rhs.Value |
|
}; |
|
} |
|
|
|
public void operator +=(in InParameterOperators rhs) |
|
{ |
|
Value += rhs.Value; |
|
} |
|
} |
|
|
|
public class ShadowingInParameterOperators : InParameterOperators |
|
{ |
|
public new void operator +=(in InParameterOperators rhs) |
|
{ |
|
Value += rhs.Value * 2; |
|
} |
|
} |
|
|
|
public sealed class DisposableCompound : IDisposable |
|
{ |
|
public int Value; |
|
|
|
public void operator +=(int rhs) |
|
{ |
|
Value += rhs; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
} |
|
} |
|
|
|
public class ReadonlyFieldHolder |
|
{ |
|
public static readonly CompoundClass Shared; |
|
|
|
private readonly CompoundClass instance = new CompoundClass(); |
|
|
|
static ReadonlyFieldHolder() |
|
{ |
|
Shared = new CompoundClass(); |
|
// A readonly field is a variable inside the matching constructor, so the |
|
// operator form stays legal here. |
|
Shared += 1; |
|
} |
|
|
|
public ReadonlyFieldHolder() |
|
{ |
|
instance += 2; |
|
} |
|
} |
|
|
|
// A value-type element makes csc address the storage once with ldelema, which is a |
|
// different shape again from the reference-type case. |
|
public struct StaticOnlyStruct |
|
{ |
|
public int Value; |
|
|
|
public static StaticOnlyStruct operator +(StaticOnlyStruct lhs, int rhs) |
|
{ |
|
return new StaticOnlyStruct { |
|
Value = lhs.Value + rhs |
|
}; |
|
} |
|
} |
|
|
|
// No instance operator, so "x += n" reaches the static one and csc emits the compound |
|
// assignment shape rather than a load and a store. |
|
public class StaticOnlyOperators |
|
{ |
|
public int Value; |
|
|
|
public static StaticOnlyOperators operator +(StaticOnlyOperators lhs, int rhs) |
|
{ |
|
return new StaticOnlyOperators { |
|
Value = lhs.Value + rhs |
|
}; |
|
} |
|
|
|
public static StaticOnlyOperators operator ++(StaticOnlyOperators x) |
|
{ |
|
return new StaticOnlyOperators { |
|
Value = x.Value + 1 |
|
}; |
|
} |
|
} |
|
|
|
public class BothOperators |
|
{ |
|
public int Value; |
|
|
|
public static BothOperators operator +(BothOperators lhs, int rhs) |
|
{ |
|
return new BothOperators { |
|
Value = lhs.Value + rhs |
|
}; |
|
} |
|
|
|
public void operator +=(int rhs) |
|
{ |
|
Value += rhs; |
|
} |
|
|
|
public static BothOperators operator ++(BothOperators x) |
|
{ |
|
return new BothOperators { |
|
Value = x.Value + 1 |
|
}; |
|
} |
|
|
|
public void operator ++() |
|
{ |
|
Value++; |
|
} |
|
} |
|
|
|
public class MixedSignatureOperators |
|
{ |
|
public int Value; |
|
|
|
public static MixedSignatureOperators operator +(MixedSignatureOperators lhs, long rhs) |
|
{ |
|
return new MixedSignatureOperators { |
|
Value = lhs.Value + (int)rhs |
|
}; |
|
} |
|
|
|
public void operator +=(int rhs) |
|
{ |
|
Value += rhs; |
|
} |
|
} |
|
|
|
public interface ICompound<T> |
|
{ |
|
void operator +=(T rhs); |
|
void operator ++(); |
|
} |
|
|
|
public class ExplicitCompound : ICompound<int> |
|
{ |
|
public int Value; |
|
|
|
void ICompound<int>.operator +=(int rhs) |
|
{ |
|
Value += rhs; |
|
} |
|
|
|
void ICompound<int>.operator ++() |
|
{ |
|
Value++; |
|
} |
|
} |
|
|
|
private static CompoundClass staticField = new CompoundClass(); |
|
|
|
private CompoundClass instanceField = new CompoundClass(); |
|
|
|
private BothOperators bothField = new BothOperators(); |
|
|
|
private BothOperators[] bothArray = new BothOperators[4]; |
|
|
|
private static CompoundClass refReturnTarget = new CompoundClass(); |
|
|
|
private static CompoundStruct refReturnStructTarget; |
|
|
|
public BothOperators BothProperty { get; set; } |
|
|
|
public BothOperators this[int index] { |
|
get { |
|
return bothArray[index]; |
|
} |
|
set { |
|
bothArray[index] = value; |
|
} |
|
} |
|
|
|
public static void UseClass(CompoundClass c, int n) |
|
{ |
|
c += n; |
|
c += 1; |
|
c -= n; |
|
c *= n; |
|
c /= n; |
|
c %= n; |
|
c &= n; |
|
c |= n; |
|
c ^= n; |
|
c <<= n; |
|
c >>= n; |
|
c >>>= n; |
|
c += 2L; |
|
c++; |
|
c--; |
|
checked |
|
{ |
|
c += n; |
|
c -= n; |
|
c *= n; |
|
c /= n; |
|
c++; |
|
c--; |
|
} |
|
} |
|
|
|
public static void UseStruct(CompoundStruct s, int n) |
|
{ |
|
s += n; |
|
s -= n; |
|
s++; |
|
} |
|
|
|
public static void UseOtherTargets(CompoundClass[] arr, ref CompoundClass rc, ref CompoundStruct rs, UserDefinedCompoundAssignment inst, int n) |
|
{ |
|
staticField += n; |
|
inst.instanceField += n; |
|
arr[0] += n; |
|
rc += n; |
|
rs += n; |
|
rs++; |
|
} |
|
|
|
public static void UseGeneric<T>(T x, int n) where T : ICompound<int> |
|
{ |
|
x += n; |
|
x++; |
|
} |
|
|
|
public static void UseInheritedOperator(DerivedCompoundClass d, int n) |
|
{ |
|
d += n; |
|
d++; |
|
} |
|
|
|
public static void UseShadowedOperator(ShadowingCompoundClass s, int n) |
|
{ |
|
s += n; |
|
} |
|
|
|
public static void UseOverriddenOperator(OverridingCompoundClass o, long n) |
|
{ |
|
o += n; |
|
} |
|
|
|
public static BothOperators UsePostfixIncrementResultUsed(BothOperators b) |
|
{ |
|
return b++; |
|
} |
|
|
|
public static BothOperators UseStaticOperator(BothOperators b, int n) |
|
{ |
|
// The IL calls the static operator, so the decompiled code has to keep calling it: |
|
// "b += n" and "b++" would bind to the instance operators instead. |
|
b = b + n; |
|
b = b + 1; |
|
return b; |
|
} |
|
|
|
public static void UseCompoundAssignmentOnArrayElement(StaticOnlyOperators[] arr, int n) |
|
{ |
|
arr[0] += n; |
|
arr[0]++; |
|
} |
|
|
|
public static void UseCompoundAssignmentOnStructArrayElement(StaticOnlyStruct[] arr, int n) |
|
{ |
|
arr[0] += n; |
|
} |
|
|
|
public static void UseStaticOperatorOnArrayElement(BothOperators[] arr, int n) |
|
{ |
|
// The element type declares an instance operator too, so the static call has to stay |
|
// spelled out; array elements take a separate path through the decompiler to locals. |
|
arr[0] = arr[0] + n; |
|
arr[0] = arr[0] + 1; |
|
} |
|
|
|
public static void UseStaticOperatorOnField(UserDefinedCompoundAssignment inst, int n) |
|
{ |
|
inst.bothField = inst.bothField + n; |
|
} |
|
|
|
public static void UseStaticOperatorOnRefLocal(BothOperators[] arr, int n) |
|
{ |
|
// A ref local is a variable, so "r += n" would bind the instance operator here too. |
|
ref BothOperators reference = ref arr[0]; |
|
reference = reference + n; |
|
reference = reference + 1; |
|
} |
|
|
|
public static void UseBaseTypedLocalOfShadowingClass(ShadowingCompoundClass s, int n) |
|
{ |
|
// The local is what makes "c += n" bind the base operator rather than the shadowing |
|
// one, so it has to survive even where the compiler optimized it away. |
|
CompoundClass compoundClass = s; |
|
compoundClass += n; |
|
} |
|
|
|
public static void UseCheckedOperatorOfBaseTypedLocal(ShadowingCompoundClass s, int n) |
|
{ |
|
// The derived type hides only the regular flavor, but a checked context does not |
|
// exclude regular operators - it prefers a checked one within the type that wins the |
|
// lookup. On a derived-typed receiver the hiding operator would take this call, so |
|
// the base-typed local has to survive here too. |
|
CompoundClass compoundClass = s; |
|
checked |
|
{ |
|
compoundClass += n; |
|
} |
|
} |
|
|
|
public static void UseStaticOperatorOnPropertyAndIndexer(UserDefinedCompoundAssignment inst, int n) |
|
{ |
|
// A property and an indexer are not variables, so the instance operator is never a |
|
// candidate for them: "x += n" binds the static operator, which is the one the IL calls. |
|
inst.BothProperty += n; |
|
inst[0] += n; |
|
} |
|
|
|
public static void UseStaticOperatorWhenInstanceDoesNotApply(MixedSignatureOperators m, MixedSignatureOperators[] arr, long n) |
|
{ |
|
// The instance operator takes an int, so it is not a candidate for a long argument: |
|
// "x += n" binds the static operator here, which is the one the IL calls. |
|
m += n; |
|
arr[0] += n; |
|
} |
|
|
|
public static void UseInstanceOperator(BothOperators b, int n) |
|
{ |
|
b += n; |
|
b++; |
|
} |
|
|
|
public static ref CompoundClass GetRefTarget() |
|
{ |
|
return ref refReturnTarget; |
|
} |
|
|
|
public static ref CompoundStruct GetRefStructTarget() |
|
{ |
|
return ref refReturnStructTarget; |
|
} |
|
|
|
public static void UseRefReturnTarget(int n) |
|
{ |
|
// A ref-returning invocation is a variable, so the operator form binds here. |
|
GetRefTarget() += n; |
|
GetRefTarget()++; |
|
GetRefStructTarget() += n; |
|
GetRefStructTarget()++; |
|
} |
|
|
|
public unsafe static void UsePointerReceiver(CompoundStruct* p, int n) |
|
{ |
|
// A pointer indirection is a variable, so the operator form binds here. |
|
*p += n; |
|
(*p)++; |
|
} |
|
|
|
public static void UseForeachVariableCopy(List<CompoundClass> list, int n) |
|
{ |
|
// The iteration variable is read-only, so the operator is applied to a copy; the |
|
// copy has to survive even where the compiler optimized it away. |
|
foreach (CompoundClass item in list) |
|
{ |
|
CompoundClass compoundClass = item; |
|
compoundClass += n; |
|
} |
|
} |
|
|
|
public static void UseUsingVariableCopy(int n) |
|
{ |
|
// The using variable is read-only, so the operator is applied to a copy; the copy has |
|
// to survive even where the compiler optimized it away. |
|
using DisposableCompound disposableCompound = new DisposableCompound(); |
|
DisposableCompound disposableCompound2 = disposableCompound; |
|
disposableCompound2 += n; |
|
} |
|
|
|
public static void UseExplicitInterfaceOperator(ExplicitCompound e, int n) |
|
{ |
|
// The interface-typed variable is the only receiver that can bind an explicitly |
|
// implemented operator, so the copy has to survive decompilation. |
|
ICompound<int> compound = e; |
|
compound += n; |
|
ICompound<int> compound2 = e; |
|
compound2++; |
|
} |
|
|
|
public static void KeepStaticOperatorWithInParameter(InParameterOperators x, InParameterOperators y) |
|
{ |
|
// "x += y" binds the instance operator, so folding the static call the IL makes into |
|
// the operator form would change which operator runs. |
|
x = x + y; |
|
} |
|
|
|
public static void UseCopyForShadowedInParameterOperator(ShadowingInParameterOperators s, InParameterOperators y) |
|
{ |
|
// The receiver has to keep the base type: the derived type hides the operator with a |
|
// "new" one, which the operator form would bind instead. |
|
InParameterOperators inParameterOperators = s; |
|
inParameterOperators += y; |
|
} |
|
|
|
public static void UseForeachDeconstructionCopy(List<(CompoundClass, int)> list, int n) |
|
{ |
|
// The variables a foreach deconstruction designation introduces are read-only, so the |
|
// deconstruction stays in the body, where the operator has an assignable target. |
|
foreach (var item in list) |
|
{ |
|
var (compoundClass, value) = item; |
|
compoundClass += n; |
|
Console.WriteLine(value); |
|
} |
|
} |
|
} |
|
}
|
|
|