mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
TypeSystemAstBuilder writes the C# 14 "operator +=" declaration form, behind a support flag like the other version-gated operator syntax; a non-public operator has no legal operator declaration (CS9308) and falls back to a plain method, except an explicit interface implementation, which is private in metadata but still written in operator form. An instance operator hides by signature like an ordinary method, so it can carry "new"; the [CompilerFeatureRequired] marker the compiler emits is removed like the other feature markers. Tooltips get the same rendering via a ConversionFlags bit; widening ConversionFlags.All also turns on the existing checked-operator and unsigned-right-shift flags, which the tooltip ambience now sets explicitly. The test fixtures land here, where the pipeline is complete end to end: pretty and IL round-trips, correctness runs against Roslyn's C# 14 binding (including the operator-inheritance matrix and a ref-local target), and the ugly configuration pins the output with the setting off. Assisted-by: Claude:claude-opus-5:Claude Code
244 lines
5.5 KiB
C#
244 lines
5.5 KiB
C#
using System.Runtime.CompilerServices;
|
|
|
|
public class BadArityTarget
|
|
{
|
|
public int Value;
|
|
|
|
// C# 14 compound assignment operators take exactly one parameter and the instance
|
|
// increment operators none; any other arity stays a plain method.
|
|
[SpecialName]
|
|
public void op_AdditionAssignment(int a, int b)
|
|
{
|
|
}
|
|
|
|
[SpecialName]
|
|
public void op_SubtractionAssignment()
|
|
{
|
|
}
|
|
|
|
[SpecialName]
|
|
public void op_IncrementAssignment(int a)
|
|
{
|
|
}
|
|
}
|
|
public class BadParameterShapes
|
|
{
|
|
// C# allows only value, "in" and "ref readonly" parameters on a compound assignment
|
|
// operator; ref and params stay plain methods.
|
|
[SpecialName]
|
|
public void op_SubtractionAssignment(ref int rhs)
|
|
{
|
|
}
|
|
|
|
[SpecialName]
|
|
public void op_MultiplicationAssignment(params int[] rhs)
|
|
{
|
|
}
|
|
}
|
|
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]
|
|
public CompoundTarget op_SubtractionAssignment(int rhs)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
[SpecialName]
|
|
public static CompoundTarget op_MultiplicationAssignment(CompoundTarget lhs, int rhs)
|
|
{
|
|
return lhs;
|
|
}
|
|
}
|
|
public class CovariantBase
|
|
{
|
|
public int Value;
|
|
|
|
public static CovariantDerived operator ++(CovariantBase x)
|
|
{
|
|
return (CovariantDerived)x;
|
|
}
|
|
}
|
|
public class CovariantDerived : CovariantBase
|
|
{
|
|
public void operator ++()
|
|
{
|
|
}
|
|
}
|
|
public class DerivedCompoundTarget : CompoundTarget
|
|
{
|
|
public void CallBaseOperator(int n)
|
|
{
|
|
// "this" cannot be the target of "x op= y", so the receiver keeps a copy of it;
|
|
// the copy binds the same inherited operator the IL calls.
|
|
DerivedCompoundTarget derivedCompoundTarget = this;
|
|
derivedCompoundTarget += n;
|
|
}
|
|
}
|
|
public class EdgeCases
|
|
{
|
|
public static int Sink;
|
|
|
|
public static ShadowTarget Shared;
|
|
|
|
// A statement-level "x++" considers the instance operator first; the only form that always
|
|
// binds the static operator is a postfix increment whose result is used, so the result goes
|
|
// to a discard.
|
|
public static void StatementLevelPostIncrement()
|
|
{
|
|
_ = Shared++;
|
|
}
|
|
|
|
// A pre-increment whose result is used prefers the instance operator too, so the increment
|
|
// becomes a statement of its own before the uses of the new value.
|
|
public static void ValueUsedPreIncrement()
|
|
{
|
|
ShadowTarget shared = Shared;
|
|
_ = shared++;
|
|
UseValue(Shared = shared);
|
|
}
|
|
|
|
public static void UseValue(ShadowTarget t)
|
|
{
|
|
}
|
|
|
|
// The same holds for the shape that stores the old value inside the operator call.
|
|
public static void InlineStorePostIncrement()
|
|
{
|
|
_ = Shared++;
|
|
}
|
|
|
|
// An array element is a variable, so a statement-level "++arr[0]" would bind the instance
|
|
// operator too.
|
|
public static void ArrayElementIncrement(ShadowTarget[] arr)
|
|
{
|
|
ShadowTarget shadowTarget = arr[0];
|
|
_ = shadowTarget++;
|
|
arr[0] = shadowTarget;
|
|
}
|
|
|
|
public static CompoundTarget GetTarget()
|
|
{
|
|
return new CompoundTarget();
|
|
}
|
|
|
|
public static void NonVariableReceiver(int n)
|
|
{
|
|
CompoundTarget target = GetTarget();
|
|
target += n;
|
|
}
|
|
|
|
public static void UnconstrainedGenericReceiver<T>(T x, int n) where T : IOther
|
|
{
|
|
ICompound compound = (ICompound)(object)x;
|
|
compound += n;
|
|
}
|
|
|
|
// Not "x += n": C# requires a user-defined operator to be public, so an operator that
|
|
// is not cannot be bound by the operator form and the call has to stay a call.
|
|
public static void InaccessibleOperator(InaccessibleOperatorTarget x, int n)
|
|
{
|
|
x.op_AdditionAssignment(n);
|
|
}
|
|
|
|
public static void MismatchedReceiverType(object o, int n)
|
|
{
|
|
((CompoundTarget)o).op_AdditionAssignment(n);
|
|
}
|
|
|
|
// A prefix increment prefers the instance "operator ++()" even where its result is used, so
|
|
// the increment is a statement with a discarded postfix result; the copy keeps the
|
|
// parameter unchanged, as in the IL.
|
|
public static ShadowTarget ShadowedIncrement(ShadowTarget x)
|
|
{
|
|
ShadowTarget result = x;
|
|
_ = result++;
|
|
return result;
|
|
}
|
|
|
|
public static void CallNonCSharpOperators(CompoundTarget t, int n)
|
|
{
|
|
t.op_SubtractionAssignment(n);
|
|
CompoundTarget.op_MultiplicationAssignment(t, n);
|
|
}
|
|
|
|
// The static operator's covariant return lets the result be stored in a derived-typed
|
|
// local, and on that type "++x" would bind the instance operator; a postfix increment with
|
|
// its result discarded binds the static one.
|
|
public static CovariantDerived CovariantIncrement(CovariantDerived d)
|
|
{
|
|
CovariantDerived covariantDerived = d;
|
|
_ = covariantDerived++;
|
|
Sink = covariantDerived.Value;
|
|
return covariantDerived;
|
|
}
|
|
|
|
// Not "x += n": the "in" overload is what the IL calls, but "x += n" binds the by-value
|
|
// overload, so the call has to stay a call.
|
|
public static void CallInOverload(InOverloads x, int n)
|
|
{
|
|
int rhs = n;
|
|
x.op_AdditionAssignment(in rhs);
|
|
}
|
|
}
|
|
public interface ICompound
|
|
{
|
|
void operator +=(int rhs);
|
|
}
|
|
public class InaccessibleOperatorTarget
|
|
{
|
|
public int Value;
|
|
|
|
// Not "operator +=": a non-public operator is not declarable as one in C#.
|
|
private void op_AdditionAssignment(int rhs)
|
|
{
|
|
Value += rhs;
|
|
}
|
|
}
|
|
public class InOverloads
|
|
{
|
|
public int Value;
|
|
|
|
public void operator +=(int rhs)
|
|
{
|
|
}
|
|
|
|
public void operator +=(in int rhs)
|
|
{
|
|
}
|
|
}
|
|
public interface IOther
|
|
{
|
|
void M();
|
|
}
|
|
public class ShadowTarget
|
|
{
|
|
public int Value;
|
|
|
|
public static ShadowTarget operator ++(ShadowTarget x)
|
|
{
|
|
return x;
|
|
}
|
|
|
|
public void operator ++()
|
|
{
|
|
Value++;
|
|
}
|
|
}
|
|
public static class StaticClassOps
|
|
{
|
|
// a static class cannot contain operators
|
|
[SpecialName]
|
|
public void op_AdditionAssignment(int rhs)
|
|
{
|
|
}
|
|
}
|