Browse Source

Merge pull request #3017 from icsharpcode/operator-checked

pull/3030/head
Siegfried Pammer 3 years ago committed by GitHub
parent
commit
19e69e6105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 73
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs
  2. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 11
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 18
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  5. 17
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  6. 60
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs
  7. 7
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  8. 79
      ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
  9. 21
      ICSharpCode.Decompiler/DecompilerSettings.cs
  10. 13
      ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs
  11. 2
      ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs
  12. 4
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs
  13. 6
      ICSharpCode.Decompiler/Output/IAmbience.cs
  14. 4
      ILSpy/Languages/CSharpLanguage.cs
  15. 9
      ILSpy/Properties/Resources.Designer.cs
  16. 3
      ILSpy/Properties/Resources.resx

73
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs

@ -42,6 +42,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return null; return null;
} }
#if CS110
public static AllOperators operator checked +(AllOperators a, AllOperators b)
{
return null;
}
public static AllOperators operator checked -(AllOperators a, AllOperators b)
{
return null;
}
public static AllOperators operator checked *(AllOperators a, AllOperators b)
{
return null;
}
public static AllOperators operator checked /(AllOperators a, AllOperators b)
{
return null;
}
#endif
public static AllOperators operator %(AllOperators a, AllOperators b) public static AllOperators operator %(AllOperators a, AllOperators b)
{ {
return null; return null;
@ -109,6 +131,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return null; return null;
} }
#if CS110
public static AllOperators operator checked -(AllOperators a)
{
return null;
}
public static AllOperators operator checked ++(AllOperators a)
{
return null;
}
public static AllOperators operator checked --(AllOperators a)
{
return null;
}
#endif
public static bool operator true(AllOperators a) public static bool operator true(AllOperators a)
{ {
return false; return false;
@ -158,6 +197,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return 0; return 0;
} }
#if CS110
public static explicit operator checked int(AllOperators a)
{
return 0;
}
#endif
} }
public class UseAllOperators public class UseAllOperators
@ -171,6 +217,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
c = a - b; c = a - b;
c = a * b; c = a * b;
c = a / b; c = a / b;
#if CS110
checked
{
c = a + b;
c = a - b;
c = a * b;
c = a / b;
}
// force end of checked block:
++a;
#endif
c = a % b; c = a % b;
c = a & b; c = a & b;
c = a | b; c = a | b;
@ -186,6 +244,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
c = +a; c = +a;
c = ++a; c = ++a;
c = --a; c = --a;
#if CS110
checked
{
c = -a;
c = ++a;
c = --a;
}
// force end of checked block:
++a;
#endif
if (a) if (a)
{ {
Console.WriteLine("a"); Console.WriteLine("a");
@ -219,6 +287,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("a >= b"); Console.WriteLine("a >= b");
} }
int num = (int)a; int num = (int)a;
#if CS110
num = checked((int)a);
// force end of checked block:
num = (int)a;
#endif
a = num; a = num;
} }
} }

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -531,6 +531,7 @@ namespace ICSharpCode.Decompiler.CSharp
typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses; typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses;
typeSystemAstBuilder.SupportRecordStructs = settings.RecordStructs; typeSystemAstBuilder.SupportRecordStructs = settings.RecordStructs;
typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift; typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift;
typeSystemAstBuilder.SupportOperatorChecked = settings.CheckedOperators;
typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal; typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal;
return typeSystemAstBuilder; return typeSystemAstBuilder;
} }

11
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1810,6 +1810,15 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
target = Translate(inst.Target, loadType); target = Translate(inst.Target, loadType);
} }
var opType = OperatorDeclaration.GetOperatorType(inst.Method.Name);
if (opType != null && OperatorDeclaration.IsChecked(opType.Value))
{
target.Expression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation);
}
else if (ReplaceMethodCallsWithOperators.HasCheckedEquivalent(inst.Method))
{
target.Expression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation);
}
if (UserDefinedCompoundAssign.IsStringConcat(inst.Method)) if (UserDefinedCompoundAssign.IsStringConcat(inst.Method))
{ {
Debug.Assert(inst.Method.Parameters.Count == 2); Debug.Assert(inst.Method.Parameters.Count == 2);
@ -1876,8 +1885,10 @@ namespace ICSharpCode.Decompiler.CSharp
switch (name) switch (name)
{ {
case "op_Increment": case "op_Increment":
case "op_CheckedIncrement":
return isPostfix ? UnaryOperatorType.PostIncrement : UnaryOperatorType.Increment; return isPostfix ? UnaryOperatorType.PostIncrement : UnaryOperatorType.Increment;
case "op_Decrement": case "op_Decrement":
case "op_CheckedDecrement":
return isPostfix ? UnaryOperatorType.PostDecrement : UnaryOperatorType.Decrement; return isPostfix ? UnaryOperatorType.PostDecrement : UnaryOperatorType.Decrement;
default: default:
return null; return null;

18
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -238,6 +238,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
astBuilder.SupportRecordClasses = (ConversionFlags & ConversionFlags.SupportRecordClasses) != 0; astBuilder.SupportRecordClasses = (ConversionFlags & ConversionFlags.SupportRecordClasses) != 0;
astBuilder.SupportRecordStructs = (ConversionFlags & ConversionFlags.SupportRecordStructs) != 0; astBuilder.SupportRecordStructs = (ConversionFlags & ConversionFlags.SupportRecordStructs) != 0;
astBuilder.SupportUnsignedRightShift = (ConversionFlags & ConversionFlags.SupportUnsignedRightShift) != 0; astBuilder.SupportUnsignedRightShift = (ConversionFlags & ConversionFlags.SupportUnsignedRightShift) != 0;
astBuilder.SupportOperatorChecked = (ConversionFlags & ConversionFlags.SupportOperatorChecked) != 0;
return astBuilder; return astBuilder;
} }
@ -296,20 +297,35 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
ConvertType(member.ReturnType, writer, formattingPolicy); ConvertType(member.ReturnType, writer, formattingPolicy);
break; break;
case "op_Explicit": case "op_Explicit":
case "op_CheckedExplicit":
writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit"); writer.WriteKeyword(OperatorDeclaration.ExplicitRole, "explicit");
writer.Space(); writer.Space();
writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
writer.Space(); writer.Space();
if (member.Name == "op_CheckedExplicit")
{
writer.WriteToken(OperatorDeclaration.CheckedKeywordRole, "checked");
writer.Space();
}
ConvertType(member.ReturnType, writer, formattingPolicy); ConvertType(member.ReturnType, writer, formattingPolicy);
break; break;
default: default:
writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator"); writer.WriteKeyword(OperatorDeclaration.OperatorKeywordRole, "operator");
writer.Space(); writer.Space();
var operatorType = OperatorDeclaration.GetOperatorType(member.Name); var operatorType = OperatorDeclaration.GetOperatorType(member.Name);
if (operatorType.HasValue) if (operatorType.HasValue && !((ConversionFlags & ConversionFlags.SupportOperatorChecked) == 0 && OperatorDeclaration.IsChecked(operatorType.Value)))
{
if (OperatorDeclaration.IsChecked(operatorType.Value))
{
writer.WriteToken(OperatorDeclaration.CheckedKeywordRole, "checked");
writer.Space();
}
writer.WriteToken(OperatorDeclaration.GetRole(operatorType.Value), OperatorDeclaration.GetToken(operatorType.Value)); writer.WriteToken(OperatorDeclaration.GetRole(operatorType.Value), OperatorDeclaration.GetToken(operatorType.Value));
}
else else
{
writer.WriteIdentifier(node.NameToken); writer.WriteIdentifier(node.NameToken);
}
break; break;
} }
break; break;

17
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -2528,7 +2528,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
StartNode(operatorDeclaration); StartNode(operatorDeclaration);
WriteAttributes(operatorDeclaration.Attributes); WriteAttributes(operatorDeclaration.Attributes);
WriteModifiers(operatorDeclaration.ModifierTokens); WriteModifiers(operatorDeclaration.ModifierTokens);
if (operatorDeclaration.OperatorType == OperatorType.Explicit) if (operatorDeclaration.OperatorType == OperatorType.Explicit || operatorDeclaration.OperatorType == OperatorType.CheckedExplicit)
{ {
WriteKeyword(OperatorDeclaration.ExplicitRole); WriteKeyword(OperatorDeclaration.ExplicitRole);
} }
@ -2542,7 +2542,13 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} }
WriteKeyword(OperatorDeclaration.OperatorKeywordRole); WriteKeyword(OperatorDeclaration.OperatorKeywordRole);
Space(); Space();
if (OperatorDeclaration.IsChecked(operatorDeclaration.OperatorType))
{
WriteKeyword(OperatorDeclaration.CheckedKeywordRole);
Space();
}
if (operatorDeclaration.OperatorType == OperatorType.Explicit if (operatorDeclaration.OperatorType == OperatorType.Explicit
|| operatorDeclaration.OperatorType == OperatorType.CheckedExplicit
|| operatorDeclaration.OperatorType == OperatorType.Implicit) || operatorDeclaration.OperatorType == OperatorType.Implicit)
{ {
operatorDeclaration.ReturnType.AcceptVisitor(this); operatorDeclaration.ReturnType.AcceptVisitor(this);
@ -3082,7 +3088,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
break; break;
case SymbolKind.Operator: case SymbolKind.Operator:
var opType = documentationReference.OperatorType; var opType = documentationReference.OperatorType;
if (opType == OperatorType.Explicit) if (opType == OperatorType.Explicit || opType == OperatorType.CheckedExplicit)
{ {
WriteKeyword(OperatorDeclaration.ExplicitRole); WriteKeyword(OperatorDeclaration.ExplicitRole);
} }
@ -3092,7 +3098,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} }
WriteKeyword(OperatorDeclaration.OperatorKeywordRole); WriteKeyword(OperatorDeclaration.OperatorKeywordRole);
Space(); Space();
if (opType == OperatorType.Explicit || opType == OperatorType.Implicit) if (OperatorDeclaration.IsChecked(opType))
{
WriteKeyword(OperatorDeclaration.CheckedKeywordRole);
Space();
}
if (opType == OperatorType.Explicit || opType == OperatorType.Implicit || opType == OperatorType.CheckedExplicit)
{ {
documentationReference.ConversionOperatorReturnType.AcceptVisitor(this); documentationReference.ConversionOperatorReturnType.AcceptVisitor(this);
} }

60
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs

@ -37,20 +37,25 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
LogicalNot, LogicalNot,
OnesComplement, OnesComplement,
Increment, Increment,
CheckedIncrement,
Decrement, Decrement,
CheckedDecrement,
True, True,
False, False,
// Unary and Binary operators
Addition,
Subtraction,
UnaryPlus, UnaryPlus,
UnaryNegation, UnaryNegation,
CheckedUnaryNegation,
// Binary operators // Binary operators
Addition,
CheckedAddition,
Subtraction,
CheckedSubtraction,
Multiply, Multiply,
CheckedMultiply,
Division, Division,
CheckedDivision,
Modulus, Modulus,
BitwiseAnd, BitwiseAnd,
BitwiseOr, BitwiseOr,
@ -67,12 +72,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
// Implicit and Explicit // Implicit and Explicit
Implicit, Implicit,
Explicit Explicit,
CheckedExplicit
} }
public class OperatorDeclaration : EntityDeclaration public class OperatorDeclaration : EntityDeclaration
{ {
public static readonly TokenRole OperatorKeywordRole = new TokenRole("operator"); public static readonly TokenRole OperatorKeywordRole = new TokenRole("operator");
public static readonly TokenRole CheckedKeywordRole = new TokenRole("checked");
// Unary operators // Unary operators
public static readonly TokenRole LogicalNotRole = new TokenRole("!"); public static readonly TokenRole LogicalNotRole = new TokenRole("!");
@ -110,19 +117,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
static OperatorDeclaration() static OperatorDeclaration()
{ {
names = new string[(int)OperatorType.Explicit + 1][]; names = new string[(int)OperatorType.CheckedExplicit + 1][];
names[(int)OperatorType.LogicalNot] = new string[] { "!", "op_LogicalNot" }; names[(int)OperatorType.LogicalNot] = new string[] { "!", "op_LogicalNot" };
names[(int)OperatorType.OnesComplement] = new string[] { "~", "op_OnesComplement" }; names[(int)OperatorType.OnesComplement] = new string[] { "~", "op_OnesComplement" };
names[(int)OperatorType.Increment] = new string[] { "++", "op_Increment" }; names[(int)OperatorType.Increment] = new string[] { "++", "op_Increment" };
names[(int)OperatorType.CheckedIncrement] = new string[] { "++", "op_CheckedIncrement" };
names[(int)OperatorType.Decrement] = new string[] { "--", "op_Decrement" }; names[(int)OperatorType.Decrement] = new string[] { "--", "op_Decrement" };
names[(int)OperatorType.CheckedDecrement] = new string[] { "--", "op_CheckedDecrement" };
names[(int)OperatorType.True] = new string[] { "true", "op_True" }; names[(int)OperatorType.True] = new string[] { "true", "op_True" };
names[(int)OperatorType.False] = new string[] { "false", "op_False" }; names[(int)OperatorType.False] = new string[] { "false", "op_False" };
names[(int)OperatorType.Addition] = new string[] { "+", "op_Addition" };
names[(int)OperatorType.Subtraction] = new string[] { "-", "op_Subtraction" };
names[(int)OperatorType.UnaryPlus] = new string[] { "+", "op_UnaryPlus" }; names[(int)OperatorType.UnaryPlus] = new string[] { "+", "op_UnaryPlus" };
names[(int)OperatorType.UnaryNegation] = new string[] { "-", "op_UnaryNegation" }; names[(int)OperatorType.UnaryNegation] = new string[] { "-", "op_UnaryNegation" };
names[(int)OperatorType.CheckedUnaryNegation] = new string[] { "-", "op_CheckedUnaryNegation" };
names[(int)OperatorType.Addition] = new string[] { "+", "op_Addition" };
names[(int)OperatorType.CheckedAddition] = new string[] { "+", "op_CheckedAddition" };
names[(int)OperatorType.Subtraction] = new string[] { "-", "op_Subtraction" };
names[(int)OperatorType.CheckedSubtraction] = new string[] { "-", "op_CheckedSubtraction" };
names[(int)OperatorType.Multiply] = new string[] { "*", "op_Multiply" }; names[(int)OperatorType.Multiply] = new string[] { "*", "op_Multiply" };
names[(int)OperatorType.CheckedMultiply] = new string[] { "*", "op_CheckedMultiply" };
names[(int)OperatorType.Division] = new string[] { "/", "op_Division" }; names[(int)OperatorType.Division] = new string[] { "/", "op_Division" };
names[(int)OperatorType.CheckedDivision] = new string[] { "/", "op_CheckedDivision" };
names[(int)OperatorType.Modulus] = new string[] { "%", "op_Modulus" }; names[(int)OperatorType.Modulus] = new string[] { "%", "op_Modulus" };
names[(int)OperatorType.BitwiseAnd] = new string[] { "&", "op_BitwiseAnd" }; names[(int)OperatorType.BitwiseAnd] = new string[] { "&", "op_BitwiseAnd" };
names[(int)OperatorType.BitwiseOr] = new string[] { "|", "op_BitwiseOr" }; names[(int)OperatorType.BitwiseOr] = new string[] { "|", "op_BitwiseOr" };
@ -138,6 +152,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
names[(int)OperatorType.LessThanOrEqual] = new string[] { "<=", "op_LessThanOrEqual" }; names[(int)OperatorType.LessThanOrEqual] = new string[] { "<=", "op_LessThanOrEqual" };
names[(int)OperatorType.Implicit] = new string[] { "implicit", "op_Implicit" }; names[(int)OperatorType.Implicit] = new string[] { "implicit", "op_Implicit" };
names[(int)OperatorType.Explicit] = new string[] { "explicit", "op_Explicit" }; names[(int)OperatorType.Explicit] = new string[] { "explicit", "op_Explicit" };
names[(int)OperatorType.CheckedExplicit] = new string[] { "explicit", "op_CheckedExplicit" };
} }
public override SymbolKind SymbolKind { public override SymbolKind SymbolKind {
@ -202,8 +217,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case OperatorType.OnesComplement: case OperatorType.OnesComplement:
return OnesComplementRole; return OnesComplementRole;
case OperatorType.Increment: case OperatorType.Increment:
case OperatorType.CheckedIncrement:
return IncrementRole; return IncrementRole;
case OperatorType.Decrement: case OperatorType.Decrement:
case OperatorType.CheckedDecrement:
return DecrementRole; return DecrementRole;
case OperatorType.True: case OperatorType.True:
return TrueRole; return TrueRole;
@ -211,15 +228,20 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return FalseRole; return FalseRole;
case OperatorType.Addition: case OperatorType.Addition:
case OperatorType.CheckedAddition:
case OperatorType.UnaryPlus: case OperatorType.UnaryPlus:
return AdditionRole; return AdditionRole;
case OperatorType.Subtraction: case OperatorType.Subtraction:
case OperatorType.CheckedSubtraction:
case OperatorType.UnaryNegation: case OperatorType.UnaryNegation:
case OperatorType.CheckedUnaryNegation:
return SubtractionRole; return SubtractionRole;
case OperatorType.Multiply: case OperatorType.Multiply:
case OperatorType.CheckedMultiply:
return MultiplyRole; return MultiplyRole;
case OperatorType.Division: case OperatorType.Division:
case OperatorType.CheckedDivision:
return DivisionRole; return DivisionRole;
case OperatorType.Modulus: case OperatorType.Modulus:
return ModulusRole; return ModulusRole;
@ -251,6 +273,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case OperatorType.Implicit: case OperatorType.Implicit:
return ImplicitRole; return ImplicitRole;
case OperatorType.Explicit: case OperatorType.Explicit:
case OperatorType.CheckedExplicit:
return ExplicitRole; return ExplicitRole;
default: default:
@ -269,7 +292,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
/// <summary> /// <summary>
/// Gets the token for the operator type ("+", "implicit", etc.) /// Gets whether the operator type is a C# 11 "operator checked".
/// </summary>
public static bool IsChecked(OperatorType type)
{
return type switch {
OperatorType.CheckedAddition => true,
OperatorType.CheckedSubtraction => true,
OperatorType.CheckedMultiply => true,
OperatorType.CheckedDivision => true,
OperatorType.CheckedUnaryNegation => true,
OperatorType.CheckedIncrement => true,
OperatorType.CheckedDecrement => true,
OperatorType.CheckedExplicit => true,
_ => false,
};
}
/// <summary>
/// Gets the token for the operator type ("+", "implicit", etc.).
/// Does not include the "checked" modifier.
/// </summary> /// </summary>
public static string GetToken(OperatorType type) public static string GetToken(OperatorType type)
{ {

7
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -230,6 +230,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary> /// </summary>
public bool SupportUnsignedRightShift { get; set; } public bool SupportUnsignedRightShift { get; set; }
/// <summary>
/// Controls whether C# 11 "operator checked" is supported.
/// </summary>
public bool SupportOperatorChecked { get; set; }
/// <summary> /// <summary>
/// Controls whether all fully qualified type names should be prefixed with "global::". /// Controls whether all fully qualified type names should be prefixed with "global::".
/// </summary> /// </summary>
@ -2224,6 +2229,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ConvertMethod(op); return ConvertMethod(op);
if (opType == OperatorType.UnsignedRightShift && !SupportUnsignedRightShift) if (opType == OperatorType.UnsignedRightShift && !SupportUnsignedRightShift)
return ConvertMethod(op); return ConvertMethod(op);
if (!SupportOperatorChecked && OperatorDeclaration.IsChecked(opType.Value))
return ConvertMethod(op);
OperatorDeclaration decl = new OperatorDeclaration(); OperatorDeclaration decl = new OperatorDeclaration();
decl.Modifiers = GetMemberModifiers(op); decl.Modifiers = GetMemberModifiers(op);

79
ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs

@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -142,10 +143,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
break; break;
} }
BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name, context.Settings); bool isChecked;
BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name, out isChecked, context.Settings);
if (bop != null && arguments.Length == 2) if (bop != null && arguments.Length == 2)
{ {
invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression
if (isChecked)
{
invocationExpression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation);
}
else if (HasCheckedEquivalent(method))
{
invocationExpression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation);
}
invocationExpression.ReplaceWith( invocationExpression.ReplaceWith(
new BinaryOperatorExpression( new BinaryOperatorExpression(
arguments[0].UnwrapInDirectionExpression(), arguments[0].UnwrapInDirectionExpression(),
@ -155,9 +165,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
); );
return; return;
} }
UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(method.Name); UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(method.Name, out isChecked, context.Settings);
if (uop != null && arguments.Length == 1) if (uop != null && arguments.Length == 1)
{ {
if (isChecked)
{
invocationExpression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation);
}
else if (HasCheckedEquivalent(method))
{
invocationExpression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation);
}
if (uop == UnaryOperatorType.Increment || uop == UnaryOperatorType.Decrement) if (uop == UnaryOperatorType.Increment || uop == UnaryOperatorType.Decrement)
{ {
// `op_Increment(a)` is not equivalent to `++a`, // `op_Increment(a)` is not equivalent to `++a`,
@ -174,17 +192,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
).CopyAnnotationsFrom(invocationExpression) ).CopyAnnotationsFrom(invocationExpression)
); );
} }
return;
} }
arguments[0].Remove(); // detach argument else
invocationExpression.ReplaceWith( {
new UnaryOperatorExpression(uop.Value, arguments[0].UnwrapInDirectionExpression()).CopyAnnotationsFrom(invocationExpression) arguments[0].Remove(); // detach argument
); invocationExpression.ReplaceWith(
new UnaryOperatorExpression(uop.Value, arguments[0].UnwrapInDirectionExpression()).CopyAnnotationsFrom(invocationExpression)
);
}
return; return;
} }
if (method.Name == "op_Explicit" && arguments.Length == 1) if (method.Name is "op_Explicit" or "op_CheckedExplicit" && arguments.Length == 1)
{ {
arguments[0].Remove(); // detach argument arguments[0].Remove(); // detach argument
if (method.Name == "op_CheckedExplicit")
{
invocationExpression.AddAnnotation(AddCheckedBlocks.CheckedAnnotation);
}
else if (HasCheckedEquivalent(method))
{
invocationExpression.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation);
}
invocationExpression.ReplaceWith( invocationExpression.ReplaceWith(
new CastExpression(context.TypeSystemAstBuilder.ConvertType(method.ReturnType), arguments[0].UnwrapInDirectionExpression()) new CastExpression(context.TypeSystemAstBuilder.ConvertType(method.ReturnType), arguments[0].UnwrapInDirectionExpression())
.CopyAnnotationsFrom(invocationExpression) .CopyAnnotationsFrom(invocationExpression)
@ -200,6 +228,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return; return;
} }
internal static bool HasCheckedEquivalent(IMethod method)
{
string name = method.Name;
if (name.StartsWith("op_", StringComparison.Ordinal))
name = "op_Checked" + name.Substring(3);
return method.DeclaringType.GetMethods(m => m.IsOperator && m.Name == name).Any();
}
bool IsInstantiableTypeParameter(IType type) bool IsInstantiableTypeParameter(IType type)
{ {
return type is ITypeParameter tp && tp.HasDefaultConstructorConstraint; return type is ITypeParameter tp && tp.HasDefaultConstructorConstraint;
@ -350,8 +386,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
} }
static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name, DecompilerSettings settings) static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name, out bool isChecked, DecompilerSettings settings)
{ {
isChecked = false;
switch (name) switch (name)
{ {
case "op_Addition": case "op_Addition":
@ -362,6 +399,18 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return BinaryOperatorType.Multiply; return BinaryOperatorType.Multiply;
case "op_Division": case "op_Division":
return BinaryOperatorType.Divide; return BinaryOperatorType.Divide;
case "op_CheckedAddition" when settings.CheckedOperators:
isChecked = true;
return BinaryOperatorType.Add;
case "op_CheckedSubtraction" when settings.CheckedOperators:
isChecked = true;
return BinaryOperatorType.Subtract;
case "op_CheckedMultiply" when settings.CheckedOperators:
isChecked = true;
return BinaryOperatorType.Multiply;
case "op_CheckedDivision" when settings.CheckedOperators:
isChecked = true;
return BinaryOperatorType.Divide;
case "op_Modulus": case "op_Modulus":
return BinaryOperatorType.Modulus; return BinaryOperatorType.Modulus;
case "op_BitwiseAnd": case "op_BitwiseAnd":
@ -393,8 +442,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
} }
static UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name) static UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name, out bool isChecked, DecompilerSettings settings)
{ {
isChecked = false;
switch (name) switch (name)
{ {
case "op_LogicalNot": case "op_LogicalNot":
@ -403,12 +453,21 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return UnaryOperatorType.BitNot; return UnaryOperatorType.BitNot;
case "op_UnaryNegation": case "op_UnaryNegation":
return UnaryOperatorType.Minus; return UnaryOperatorType.Minus;
case "op_CheckedUnaryNegation" when settings.CheckedOperators:
isChecked = true;
return UnaryOperatorType.Minus;
case "op_UnaryPlus": case "op_UnaryPlus":
return UnaryOperatorType.Plus; return UnaryOperatorType.Plus;
case "op_Increment": case "op_Increment":
return UnaryOperatorType.Increment; return UnaryOperatorType.Increment;
case "op_Decrement": case "op_Decrement":
return UnaryOperatorType.Decrement; return UnaryOperatorType.Decrement;
case "op_CheckedIncrement" when settings.CheckedOperators:
isChecked = true;
return UnaryOperatorType.Increment;
case "op_CheckedDecrement" when settings.CheckedOperators:
isChecked = true;
return UnaryOperatorType.Decrement;
default: default:
return null; return null;
} }

21
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -155,12 +155,13 @@ namespace ICSharpCode.Decompiler
numericIntPtr = false; numericIntPtr = false;
utf8StringLiterals = false; utf8StringLiterals = false;
unsignedRightShift = false; unsignedRightShift = false;
checkedOperators = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift) if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
return CSharp.LanguageVersion.CSharp11_0; return CSharp.LanguageVersion.CSharp11_0;
if (fileScopedNamespaces || recordStructs) if (fileScopedNamespaces || recordStructs)
return CSharp.LanguageVersion.CSharp10_0; return CSharp.LanguageVersion.CSharp10_0;
@ -1218,6 +1219,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool checkedOperators = true;
/// <summary>
/// Gets/Sets whether to use C# 11.0 user-defined checked operators.
/// </summary>
[Category("C# 11.0 / VS 2022.4")]
[Description("DecompilerSettings.CheckedOperators")]
public bool CheckedOperators {
get { return checkedOperators; }
set {
if (checkedOperators != value)
{
checkedOperators = value;
OnPropertyChanged();
}
}
}
bool showXmlDocumentation = true; bool showXmlDocumentation = true;
/// <summary> /// <summary>

13
ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs

@ -315,7 +315,18 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Method = method; this.Method = method;
Debug.Assert(Method.IsOperator || IsStringConcat(method)); Debug.Assert(Method.IsOperator || IsStringConcat(method));
Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || (Method.Name == "op_Increment" || Method.Name == "op_Decrement")); Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || IsIncrementOrDecrement(method));
}
public static bool IsIncrementOrDecrement(IMethod method, DecompilerSettings? settings = null)
{
if (!(method.IsOperator && method.IsStatic))
return false;
if (method.Name is "op_Increment" or "op_Decrement")
return true;
if (method.Name is "op_CheckedIncrement" or "op_CheckedDecrement")
return settings?.CheckedOperators ?? true;
return false;
} }
public static bool IsStringConcat(IMethod method) public static bool IsStringConcat(IMethod method)

2
ICSharpCode.Decompiler/IL/Transforms/FixRemainingIncrements.cs

@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var callsToFix = new List<Call>(); var callsToFix = new List<Call>();
foreach (var call in function.Descendants.OfType<Call>()) foreach (var call in function.Descendants.OfType<Call>())
{ {
if (!(call.Method.IsOperator && (call.Method.Name == "op_Increment" || call.Method.Name == "op_Decrement"))) if (!UserDefinedCompoundAssign.IsIncrementOrDecrement(call.Method, context.Settings))
continue; continue;
if (call.Arguments.Count != 1) if (call.Arguments.Count != 1)
continue; continue;

4
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -392,7 +392,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
else if (operatorCall.Arguments.Count == 1) else if (operatorCall.Arguments.Count == 1)
{ {
if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement")) if (!UserDefinedCompoundAssign.IsIncrementOrDecrement(operatorCall.Method, context.Settings))
return false; return false;
// use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls // use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls
rhs = new LdcI4(1); rhs = new LdcI4(1);
@ -988,7 +988,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (!operatorCall.Arguments[0].MatchLdLoc(tmpVar)) if (!operatorCall.Arguments[0].MatchLdLoc(tmpVar))
return false; return false;
if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement")) if (!UserDefinedCompoundAssign.IsIncrementOrDecrement(operatorCall.Method, context.Settings))
return false; return false;
if (operatorCall.IsLifted) if (operatorCall.IsLifted)
return false; // TODO: add tests and think about whether nullables need special considerations return false; // TODO: add tests and think about whether nullables need special considerations

6
ICSharpCode.Decompiler/Output/IAmbience.cs

@ -113,6 +113,10 @@ namespace ICSharpCode.Decompiler.Output
/// Support <c>&gt;&gt;&gt;</c> as unsigned right shift operator. /// Support <c>&gt;&gt;&gt;</c> as unsigned right shift operator.
/// </summary> /// </summary>
SupportUnsignedRightShift = 0x80000, SupportUnsignedRightShift = 0x80000,
/// <summary>
/// Support C# 11 <c>operator checked</c>.
/// </summary>
SupportOperatorChecked = 0x100000,
StandardConversionFlags = ShowParameterNames | StandardConversionFlags = ShowParameterNames |
ShowAccessibility | ShowAccessibility |
@ -127,7 +131,7 @@ namespace ICSharpCode.Decompiler.Output
ShowDefinitionKeyword | ShowDefinitionKeyword |
ShowBody, ShowBody,
All = 0x7ffff, All = 0x1fffff,
} }
/// <summary> /// <summary>

4
ILSpy/Languages/CSharpLanguage.cs

@ -753,6 +753,10 @@ namespace ICSharpCode.ILSpy
{ {
flags |= ConversionFlags.SupportUnsignedRightShift; flags |= ConversionFlags.SupportUnsignedRightShift;
} }
if (settings.CheckedOperators)
{
flags |= ConversionFlags.SupportOperatorChecked;
}
if (settings.InitAccessors) if (settings.InitAccessors)
{ {
flags |= ConversionFlags.SupportInitAccessors; flags |= ConversionFlags.SupportInitAccessors;

9
ILSpy/Properties/Resources.Designer.cs generated

@ -765,6 +765,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to User-defined checked operators.
/// </summary>
public static string DecompilerSettings_CheckedOperators {
get {
return ResourceManager.GetString("DecompilerSettings.CheckedOperators", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Decompile anonymous methods/lambdas. /// Looks up a localized string similar to Decompile anonymous methods/lambdas.
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -279,6 +279,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.AsyncEnumerator" xml:space="preserve"> <data name="DecompilerSettings.AsyncEnumerator" xml:space="preserve">
<value>Decompile async IAsyncEnumerator methods</value> <value>Decompile async IAsyncEnumerator methods</value>
</data> </data>
<data name="DecompilerSettings.CheckedOperators" xml:space="preserve">
<value>User-defined checked operators</value>
</data>
<data name="DecompilerSettings.DecompileAnonymousMethodsLambdas" xml:space="preserve"> <data name="DecompilerSettings.DecompileAnonymousMethodsLambdas" xml:space="preserve">
<value>Decompile anonymous methods/lambdas</value> <value>Decompile anonymous methods/lambdas</value>
</data> </data>

Loading…
Cancel
Save