diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index c0f5e3763..c341835e0 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -312,6 +312,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task Operators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task Generics([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index e9c391ae3..f0e437f49 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -201,6 +201,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { throw new NotImplementedException(); } +#if CS110 + public static CustomStruct operator >>>(CustomStruct lhs, int rhs) + { + throw new NotImplementedException(); + } +#endif public static CustomStruct operator &(CustomStruct lhs, CustomStruct rhs) { throw new NotImplementedException(); @@ -1685,6 +1691,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #endif } +#if CS110 + public static void ShortUnsignedRightShiftTest(short p, CustomClass c, CustomStruct2 s) + { + //X(p >>>= 5); + shortField >>>= 5; + ShortProp >>>= 5; + c.ShortField >>>= 5; + c.ShortProp >>>= 5; + s.ShortField >>>= 5; + s.ShortProp >>>= 5; + customClassField.ShortField >>>= 5; + customClassField.ShortProp >>>= 5; + otherCustomStructField.ShortField >>>= 5; + otherCustomStructField.ShortProp >>>= 5; + CustomClassProp.ShortField >>>= 5; + CustomClassProp.ShortProp >>>= 5; + GetClass().ShortField >>>= 5; + GetClass().ShortProp >>>= 5; + GetRefStruct().ShortField >>>= 5; + GetRefStruct().ShortProp >>>= 5; + GetRefShort() >>>= 5; + } +#endif + public static void ShortBitAndTest(short p, CustomClass c, CustomStruct2 s) { //short l = 0; @@ -2047,6 +2077,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #endif } +#if CS110 + public static void UshortUnsignedRightShiftTest(ushort p, CustomClass c, CustomStruct2 s) + { + //ushort l = 0; + //p >>>= 5; + //l >>>= 5; + ushortField >>>= 5; + UshortProp >>>= 5; + c.UshortField >>>= 5; + c.UshortProp >>>= 5; + s.UshortField >>>= 5; + s.UshortProp >>>= 5; + customClassField.UshortField >>>= 5; + customClassField.UshortProp >>>= 5; + otherCustomStructField.UshortField >>>= 5; + otherCustomStructField.UshortProp >>>= 5; + CustomClassProp.UshortField >>>= 5; + CustomClassProp.UshortProp >>>= 5; + GetClass().UshortField >>>= 5; + GetClass().UshortProp >>>= 5; + GetRefStruct().UshortField >>>= 5; + GetRefStruct().UshortProp >>>= 5; + GetRefUshort() >>>= 5; + } +#endif + public static void UshortBitAndTest(ushort p, CustomClass c, CustomStruct2 s) { //ushort l = 0; @@ -2409,6 +2465,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #endif } +#if CS110 + public static void IntUnsignedRightShiftTest(int p, CustomClass c, CustomStruct2 s) + { + X(p >>>= 5); + intField >>>= 5; + IntProp >>>= 5; + c.IntField >>>= 5; + c.IntProp >>>= 5; + s.IntField >>>= 5; + s.IntProp >>>= 5; + customClassField.IntField >>>= 5; + customClassField.IntProp >>>= 5; + otherCustomStructField.IntField >>>= 5; + otherCustomStructField.IntProp >>>= 5; + CustomClassProp.IntField >>>= 5; + CustomClassProp.IntProp >>>= 5; + GetClass().IntField >>>= 5; + GetClass().IntProp >>>= 5; + GetRefStruct().IntField >>>= 5; + GetRefStruct().IntProp >>>= 5; + GetRefInt() >>>= 5; + } +#endif + public static void IntBitAndTest(int p, CustomClass c, CustomStruct2 s) { //int l = 0; @@ -4219,6 +4299,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #endif } +#if CS110 + public static void CustomStructUnsignedRightShiftTest(CustomStruct p, CustomClass c, CustomStruct2 s) + { + //CustomStruct l = default(CustomStruct); + //p >>>= 5; + //l >>>= 5; + customStructField >>>= 5; + CustomStructProp >>>= 5; + c.CustomStructField >>>= 5; + c.CustomStructProp >>>= 5; + s.CustomStructField >>>= 5; + s.CustomStructProp >>>= 5; + customClassField.CustomStructField >>>= 5; + customClassField.CustomStructProp >>>= 5; + otherCustomStructField.CustomStructField >>>= 5; + otherCustomStructField.CustomStructProp >>>= 5; + CustomClassProp.CustomStructField >>>= 5; + CustomClassProp.CustomStructProp >>>= 5; + GetClass().CustomStructField >>>= 5; + GetClass().CustomStructProp >>>= 5; + GetRefStruct().CustomStructField >>>= 5; + GetRefStruct().CustomStructProp >>>= 5; + GetRefCustomStruct() >>>= 5; + } +#endif + public static void CustomStructBitAndTest(CustomStruct p, CustomClass c, CustomStruct2 s) { //CustomStruct l = default(CustomStruct); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs new file mode 100644 index 000000000..0e73a7952 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Operators.cs @@ -0,0 +1,225 @@ +// Copyright (c) Daniel Grunwald +// +// 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; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class AllOperators + { + public static AllOperators operator +(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator -(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator *(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator /(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator %(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator &(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator |(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator ^(AllOperators a, AllOperators b) + { + return null; + } + + public static AllOperators operator <<(AllOperators a, int b) + { + return null; + } + + public static AllOperators operator >>(AllOperators a, int b) + { + return null; + } + +#if CS110 + public static AllOperators operator >>>(AllOperators a, int b) + { + return null; + } +#endif + + public static AllOperators operator ~(AllOperators a) + { + return null; + } + + public static AllOperators operator !(AllOperators a) + { + return null; + } + + public static AllOperators operator -(AllOperators a) + { + return null; + } + + public static AllOperators operator +(AllOperators a) + { + return null; + } + + public static AllOperators operator ++(AllOperators a) + { + return null; + } + + public static AllOperators operator --(AllOperators a) + { + return null; + } + + public static bool operator true(AllOperators a) + { + return false; + } + + public static bool operator false(AllOperators a) + { + return false; + } + + public static bool operator ==(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator !=(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator <(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator >(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator <=(AllOperators a, AllOperators b) + { + return false; + } + + public static bool operator >=(AllOperators a, AllOperators b) + { + return false; + } + + public static implicit operator AllOperators(int a) + { + return null; + } + + public static explicit operator int(AllOperators a) + { + return 0; + } + } + + public class UseAllOperators + { + private AllOperators a = new AllOperators(); + private AllOperators b = new AllOperators(); + private AllOperators c; + public void Test() + { + c = a + b; + c = a - b; + c = a * b; + c = a / b; + c = a % b; + c = a & b; + c = a | b; + c = a ^ b; + c = a << 5; + c = a >> 5; +#if CS110 + c = a >>> 5; +#endif + c = ~a; + c = !a; + c = -a; + c = +a; + c = ++a; + c = --a; + if (a) + { + Console.WriteLine("a"); + } + if (!a) + { + Console.WriteLine("!a"); + } + if (a == b) + { + Console.WriteLine("a == b"); + } + if (a != b) + { + Console.WriteLine("a != b"); + } + if (a < b) + { + Console.WriteLine("a < b"); + } + if (a > b) + { + Console.WriteLine("a > b"); + } + if (a <= b) + { + Console.WriteLine("a <= b"); + } + if (a >= b) + { + Console.WriteLine("a >= b"); + } + int num = (int)a; + a = num; + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs index 95f8e6269..7cd3368e6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/TypeAnalysisTests.cs @@ -106,9 +106,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public uint RShiftByteWithZeroExtension(byte num) { + // zero extend -> cast to unsigned -> unsigned shift return (uint)num >> 8; } + public int RShiftByteWithZeroExtensionReturnAsSigned(byte num) + { +#if CS110 + // zero extend -> unsigned shift + return num >>> 8; +#else + // zero extend -> cast to unsigned -> unsigned shift -> cast to signed + return (int)((uint)num >> 8); +#endif + } + public int RShiftByteAsSByte(byte num) { return (sbyte)num >> 8; @@ -121,9 +133,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public uint RShiftSByteWithZeroExtension(sbyte num) { - return (uint)num >> 8; + return (uint)((byte)num >> 4); } + public uint RShiftSByteWithSignExtension(sbyte num) + { + // sign extend -> cast to unsigned -> unsigned shift + return (uint)num >> 4; + } + + public int RShiftSByteWithSignExtensionReturnAsSigned(sbyte num) + { +#if CS110 + // sign extend -> unsigned shift + return num >>> 4; +#else + // sign extend -> cast to unsigned -> unsigned shift -> cast to signed + return (int)((uint)num >> 4); +#endif + } public int RShiftSByteAsByte(sbyte num) { return (byte)num >> 8; diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 86ae6bf22..08d815d91 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -530,6 +530,7 @@ namespace ICSharpCode.Decompiler.CSharp typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors; typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses; typeSystemAstBuilder.SupportRecordStructs = settings.RecordStructs; + typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift; typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal; return typeSystemAstBuilder; } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0972105f1..6c0e30200 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1208,9 +1208,9 @@ namespace ICSharpCode.Decompiler.CSharp case BinaryNumericOperator.BitXor: return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr, context); case BinaryNumericOperator.ShiftLeft: - return HandleShift(inst, BinaryOperatorType.ShiftLeft); + return HandleShift(inst, BinaryOperatorType.ShiftLeft, context); case BinaryNumericOperator.ShiftRight: - return HandleShift(inst, BinaryOperatorType.ShiftRight); + return HandleShift(inst, BinaryOperatorType.ShiftRight, context); default: throw new ArgumentOutOfRangeException(); } @@ -1729,13 +1729,14 @@ namespace ICSharpCode.Decompiler.CSharp case BinaryOperatorType.ExclusiveOr: case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: + case BinaryOperatorType.UnsignedShiftRight: return false; default: return true; } } - TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorType op) + TranslatedExpression HandleShift(BinaryNumericInstruction inst, BinaryOperatorType op, TranslationContext context) { var left = Translate(inst.Left); var right = Translate(inst.Right); @@ -1744,10 +1745,27 @@ namespace ICSharpCode.Decompiler.CSharp Sign sign = inst.Sign; var leftUType = NullableType.GetUnderlyingType(left.Type); - if (leftUType.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.UnderlyingResultType == StackType.I4) + bool couldUseUnsignedRightShift = ( + sign == Sign.Unsigned && op == BinaryOperatorType.ShiftRight && settings.UnsignedRightShift + && (leftUType.IsCSharpPrimitiveIntegerType() || leftUType.IsCSharpNativeIntegerType()) + // If we need to cast to unsigned anyway, don't use >>> operator. + && context.TypeHint.GetSign() != Sign.Unsigned + ); + if (leftUType.IsCSharpSmallIntegerType() && inst.UnderlyingResultType == StackType.I4 && + (sign != Sign.Unsigned || couldUseUnsignedRightShift)) { // With small integer types, C# will promote to int and perform signed shifts. // We thus don't need any casts in this case. + // The >>> operator also promotes to signed int, but then performs an unsigned shift. + if (sign == Sign.Unsigned) + { + op = BinaryOperatorType.UnsignedShiftRight; + } + } + else if (couldUseUnsignedRightShift && leftUType.GetSize() == inst.UnderlyingResultType.GetSize() && leftUType.GetSign() == Sign.Signed) + { + // Use C# 11 unsigned right shift operator. We don't need any casts in this case. + op = BinaryOperatorType.UnsignedShiftRight; } else { @@ -1804,7 +1822,7 @@ namespace ICSharpCode.Decompiler.CSharp else if (inst.Method.Parameters.Count == 2) { var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this); - AssignmentOperatorType? op = GetAssignmentOperatorTypeFromMetadataName(inst.Method.Name); + AssignmentOperatorType? op = GetAssignmentOperatorTypeFromMetadataName(inst.Method.Name, settings); Debug.Assert(op != null); return new AssignmentExpression(target, op.Value, value) @@ -1822,7 +1840,7 @@ namespace ICSharpCode.Decompiler.CSharp } } - internal static AssignmentOperatorType? GetAssignmentOperatorTypeFromMetadataName(string name) + internal static AssignmentOperatorType? GetAssignmentOperatorTypeFromMetadataName(string name, DecompilerSettings settings) { switch (name) { @@ -1846,6 +1864,8 @@ namespace ICSharpCode.Decompiler.CSharp return AssignmentOperatorType.ShiftLeft; case "op_RightShift": return AssignmentOperatorType.ShiftRight; + case "op_UnsignedRightShift" when settings.UnsignedRightShift: + return AssignmentOperatorType.UnsignedShiftRight; default: return null; } @@ -1887,7 +1907,22 @@ namespace ICSharpCode.Decompiler.CSharp case BinaryNumericOperator.ShiftLeft: return HandleCompoundShift(inst, AssignmentOperatorType.ShiftLeft); case BinaryNumericOperator.ShiftRight: - return HandleCompoundShift(inst, AssignmentOperatorType.ShiftRight); + if (inst.Sign == Sign.Unsigned && inst.Type.GetSign() == Sign.Signed) + { + Debug.Assert(settings.UnsignedRightShift); + return HandleCompoundShift(inst, AssignmentOperatorType.UnsignedShiftRight); + } + else if (inst.Sign == Sign.Unsigned && inst.Type.IsCSharpSmallIntegerType() && settings.UnsignedRightShift) + { + // For small unsigned integer types promoted to signed int, the sign bit will be zero, + // so there is no difference between signed and unsigned shift. + // However the IL still indicates which C# operator was used, so preserve that if the setting allows us to. + return HandleCompoundShift(inst, AssignmentOperatorType.UnsignedShiftRight); + } + else + { + return HandleCompoundShift(inst, AssignmentOperatorType.ShiftRight); + } default: throw new ArgumentOutOfRangeException(); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index 2e04d0575..207613994 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -237,6 +237,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor astBuilder.SupportInitAccessors = (ConversionFlags & ConversionFlags.SupportInitAccessors) != 0; astBuilder.SupportRecordClasses = (ConversionFlags & ConversionFlags.SupportRecordClasses) != 0; astBuilder.SupportRecordStructs = (ConversionFlags & ConversionFlags.SupportRecordStructs) != 0; + astBuilder.SupportUnsignedRightShift = (ConversionFlags & ConversionFlags.SupportUnsignedRightShift) != 0; return astBuilder; } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index ce48d69ca..9a4788f70 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -826,6 +826,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor break; case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: + case BinaryOperatorType.UnsignedShiftRight: spacePolicy = policy.SpaceAroundShiftOperator; break; case BinaryOperatorType.NullCoalescing: diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs index 74e99d8c0..e7f372f96 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/GenericGrammarAmbiguityVisitor.cs @@ -92,6 +92,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor case BinaryOperatorType.ShiftRight when genericNestingLevel >= 2: genericNestingLevel -= 2; break; + case BinaryOperatorType.UnsignedShiftRight when genericNestingLevel >= 3: + genericNestingLevel -= 3; + break; default: return true; // stop visiting, no ambiguity found } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs index e279cf021..2fb3a4546 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs @@ -122,6 +122,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor return PrecedenceLevel.Additive; case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftRight: + case BinaryOperatorType.UnsignedShiftRight: return PrecedenceLevel.Shift; case BinaryOperatorType.GreaterThan: case BinaryOperatorType.GreaterThanOrEqual: diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs index b3f453157..fff06f134 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs @@ -674,6 +674,27 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } } + + OperatorMethod[]? unsignedShiftRightOperators; + + public OperatorMethod[] UnsignedShiftRightOperators { + get { + OperatorMethod[]? ops = LazyInit.VolatileRead(ref unsignedShiftRightOperators); + if (ops != null) + { + return ops; + } + else + { + return LazyInit.GetOrSet(ref unsignedShiftRightOperators, Lift( + new LambdaBinaryOperatorMethod(this, (a, b) => (int)((uint)a >> b)), + new LambdaBinaryOperatorMethod(this, (a, b) => a >> b), + new LambdaBinaryOperatorMethod(this, (a, b) => (long)((ulong)a >> b)), + new LambdaBinaryOperatorMethod(this, (a, b) => a >> b) + )); + } + } + } #endregion #region Equality operators diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 1e6d5df8b..f235e0a98 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -685,7 +685,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver { isNullable = true; } - if (op == BinaryOperatorType.ShiftLeft || op == BinaryOperatorType.ShiftRight) + if (op == BinaryOperatorType.ShiftLeft || op == BinaryOperatorType.ShiftRight || op == BinaryOperatorType.UnsignedShiftRight) { // special case: the shift operators allow "var x = null << null", producing int?. if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null) @@ -805,6 +805,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver case BinaryOperatorType.ShiftRight: methodGroup = operators.ShiftRightOperators; break; + case BinaryOperatorType.UnsignedShiftRight: + methodGroup = operators.UnsignedShiftRightOperators; + break; case BinaryOperatorType.Equality: case BinaryOperatorType.InEquality: case BinaryOperatorType.LessThan: @@ -1256,6 +1259,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return "op_LeftShift"; case BinaryOperatorType.ShiftRight: return "op_RightShift"; + case BinaryOperatorType.UnsignedShiftRight: + return "op_UnsignedRightShift"; case BinaryOperatorType.Equality: return "op_Equality"; case BinaryOperatorType.InEquality: diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs index acd1038a8..378b33bfa 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/AssignmentExpression.cs @@ -47,6 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public readonly static TokenRole ModulusRole = new TokenRole("%="); public readonly static TokenRole ShiftLeftRole = new TokenRole("<<="); public readonly static TokenRole ShiftRightRole = new TokenRole(">>="); + public readonly static TokenRole UnsignedShiftRightRole = new TokenRole(">>>="); public readonly static TokenRole BitwiseAndRole = new TokenRole("&="); public readonly static TokenRole BitwiseOrRole = new TokenRole("|="); public readonly static TokenRole ExclusiveOrRole = new TokenRole("^="); @@ -129,6 +130,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ShiftLeftRole; case AssignmentOperatorType.ShiftRight: return ShiftRightRole; + case AssignmentOperatorType.UnsignedShiftRight: + return UnsignedShiftRightRole; case AssignmentOperatorType.BitwiseAnd: return BitwiseAndRole; case AssignmentOperatorType.BitwiseOr: @@ -164,6 +167,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return BinaryOperatorType.ShiftLeft; case AssignmentOperatorType.ShiftRight: return BinaryOperatorType.ShiftRight; + case AssignmentOperatorType.UnsignedShiftRight: + return BinaryOperatorType.UnsignedShiftRight; case AssignmentOperatorType.BitwiseAnd: return BinaryOperatorType.BitwiseAnd; case AssignmentOperatorType.BitwiseOr: @@ -195,6 +200,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ExpressionType.LeftShiftAssign; case AssignmentOperatorType.ShiftRight: return ExpressionType.RightShiftAssign; + case AssignmentOperatorType.UnsignedShiftRight: + return ExpressionType.Extension; case AssignmentOperatorType.BitwiseAnd: return ExpressionType.AndAssign; case AssignmentOperatorType.BitwiseOr: @@ -259,6 +266,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ShiftLeft, /// left >>= right ShiftRight, + /// left >>>= right + UnsignedShiftRight, /// left &= right BitwiseAnd, diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs index f64752e06..1386e1029 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs @@ -52,6 +52,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public readonly static TokenRole ModulusRole = new TokenRole("%"); public readonly static TokenRole ShiftLeftRole = new TokenRole("<<"); public readonly static TokenRole ShiftRightRole = new TokenRole(">>"); + public readonly static TokenRole UnsignedShiftRightRole = new TokenRole(">>>"); public readonly static TokenRole NullCoalescingRole = new TokenRole("??"); public readonly static TokenRole RangeRole = new TokenRole(".."); public readonly static TokenRole IsKeywordRole = IsExpression.IsKeywordRole; @@ -151,6 +152,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ShiftLeftRole; case BinaryOperatorType.ShiftRight: return ShiftRightRole; + case BinaryOperatorType.UnsignedShiftRight: + return UnsignedShiftRightRole; case BinaryOperatorType.NullCoalescing: return NullCoalescingRole; case BinaryOperatorType.Range: @@ -205,6 +208,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax case BinaryOperatorType.NullCoalescing: return ExpressionType.Coalesce; case BinaryOperatorType.Range: + case BinaryOperatorType.UnsignedShiftRight: return ExpressionType.Extension; default: throw new NotSupportedException("Invalid value for BinaryOperatorType"); @@ -262,6 +266,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ShiftLeft, /// left >> right ShiftRight, + /// left >>> right + UnsignedShiftRight, /// left ?? right NullCoalescing, diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs index c0f8236b8..4d8677ca7 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/OperatorDeclaration.cs @@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ExclusiveOr, LeftShift, RightShift, + UnsignedRightShift, Equality, Inequality, GreaterThan, @@ -94,6 +95,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public static readonly TokenRole ExclusiveOrRole = new TokenRole("^"); public static readonly TokenRole LeftShiftRole = new TokenRole("<<"); public static readonly TokenRole RightShiftRole = new TokenRole(">>"); + public static readonly TokenRole UnsignedRightShiftRole = new TokenRole(">>>"); public static readonly TokenRole EqualityRole = new TokenRole("=="); public static readonly TokenRole InequalityRole = new TokenRole("!="); public static readonly TokenRole GreaterThanRole = new TokenRole(">"); @@ -127,6 +129,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax names[(int)OperatorType.ExclusiveOr] = new string[] { "^", "op_ExclusiveOr" }; names[(int)OperatorType.LeftShift] = new string[] { "<<", "op_LeftShift" }; names[(int)OperatorType.RightShift] = new string[] { ">>", "op_RightShift" }; + names[(int)OperatorType.UnsignedRightShift] = new string[] { ">>>", "op_UnsignedRightShift" }; names[(int)OperatorType.Equality] = new string[] { "==", "op_Equality" }; names[(int)OperatorType.Inequality] = new string[] { "!=", "op_Inequality" }; names[(int)OperatorType.GreaterThan] = new string[] { ">", "op_GreaterThan" }; @@ -230,6 +233,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return LeftShiftRole; case OperatorType.RightShift: return RightShiftRole; + case OperatorType.UnsignedRightShift: + return UnsignedRightShiftRole; case OperatorType.Equality: return EqualityRole; case OperatorType.Inequality: diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 3751c4454..17b896710 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -225,6 +225,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// public bool SupportRecordStructs { get; set; } + /// + /// Controls whether C# 11 "operator >>>" is supported. + /// + public bool SupportUnsignedRightShift { get; set; } + /// /// Controls whether all fully qualified type names should be prefixed with "global::". /// @@ -2217,6 +2222,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax OperatorType? opType = OperatorDeclaration.GetOperatorType(op.Name); if (opType == null) return ConvertMethod(op); + if (opType == OperatorType.UnsignedRightShift && !SupportUnsignedRightShift) + return ConvertMethod(op); OperatorDeclaration decl = new OperatorDeclaration(); decl.Modifiers = GetMemberModifiers(op); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs index d2ae9b2d5..7fb22fadb 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs @@ -98,6 +98,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return AssignmentOperatorType.ShiftLeft; case BinaryOperatorType.ShiftRight: return AssignmentOperatorType.ShiftRight; + case BinaryOperatorType.UnsignedShiftRight: + return AssignmentOperatorType.UnsignedShiftRight; case BinaryOperatorType.BitwiseAnd: return AssignmentOperatorType.BitwiseAnd; case BinaryOperatorType.BitwiseOr: diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index f2f7a355f..738fcd46a 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms break; } - BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name); + BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name, context.Settings); if (bop != null && arguments.Length == 2) { invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression @@ -350,7 +350,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name) + static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name, DecompilerSettings settings) { switch (name) { @@ -374,6 +374,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return BinaryOperatorType.ShiftLeft; case "op_RightShift": return BinaryOperatorType.ShiftRight; + case "op_UnsignedRightShift" when settings.UnsignedRightShift: + return BinaryOperatorType.UnsignedShiftRight; case "op_Equality": return BinaryOperatorType.Equality; case "op_Inequality": diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 2a8e2a4ae..dac595efa 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -154,12 +154,13 @@ namespace ICSharpCode.Decompiler requiredMembers = false; numericIntPtr = false; utf8StringLiterals = false; + unsignedRightShift = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals) + if (parameterNullCheck || scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift) return CSharp.LanguageVersion.CSharp11_0; if (fileScopedNamespaces || recordStructs) return CSharp.LanguageVersion.CSharp10_0; @@ -1199,6 +1200,24 @@ namespace ICSharpCode.Decompiler } } + bool unsignedRightShift = true; + + /// + /// Gets/Sets whether to use C# 11.0 unsigned right shift operator. + /// + [Category("C# 11.0 / VS 2022.4")] + [Description("DecompilerSettings.UnsignedRightShift")] + public bool UnsignedRightShift { + get { return unsignedRightShift; } + set { + if (unsignedRightShift != value) + { + unsignedRightShift = value; + OnPropertyChanged(); + } + } + } + bool showXmlDocumentation = true; /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs index c35f986e0..a25e39a96 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs @@ -215,8 +215,9 @@ namespace ICSharpCode.Decompiler.IL return false; // operator not supported on pointer types } } - else if (type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) + else if ((type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) && type.Kind is not TypeKind.NInt or TypeKind.NUInt) { + // If the LHS is C# 9 IntPtr (but not nint or C# 11 IntPtr): // "target.intptr *= 2;" is compiler error, but // "target.intptr *= (nint)2;" works if (settings != null && !settings.NativeIntegers) @@ -234,16 +235,17 @@ namespace ICSharpCode.Decompiler.IL } if (binary.Sign != Sign.None) { + bool signMismatchAllowed = (binary.Sign == Sign.Unsigned && binary.Operator == BinaryNumericOperator.ShiftRight && (settings == null || settings.UnsignedRightShift)); if (type.IsCSharpSmallIntegerType()) { // C# will use numeric promotion to int, binary op must be signed - if (binary.Sign != Sign.Signed) + if (binary.Sign != Sign.Signed && !signMismatchAllowed) return false; } else { - // C# will use sign from type - if (type.GetSign() != binary.Sign) + // C# will use sign from type; except for right shift with C# 11 >>> operator. + if (type.GetSign() != binary.Sign && !signMismatchAllowed) return false; } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs index 7407179b4..849ac8840 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs @@ -386,7 +386,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILInstruction rhs; if (operatorCall.Arguments.Count == 2) { - if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name) == null) + if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name, context.Settings) == null) return false; rhs = operatorCall.Arguments[1]; } diff --git a/ICSharpCode.Decompiler/Output/IAmbience.cs b/ICSharpCode.Decompiler/Output/IAmbience.cs index d496204b7..74bbcf072 100644 --- a/ICSharpCode.Decompiler/Output/IAmbience.cs +++ b/ICSharpCode.Decompiler/Output/IAmbience.cs @@ -109,6 +109,10 @@ namespace ICSharpCode.Decompiler.Output /// Support record structs. /// SupportRecordStructs = 0x40000, + /// + /// Support >>> as unsigned right shift operator. + /// + SupportUnsignedRightShift = 0x80000, StandardConversionFlags = ShowParameterNames | ShowAccessibility | diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index a5cd1e1a0..4486366a9 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -749,6 +749,10 @@ namespace ICSharpCode.ILSpy { flags |= ConversionFlags.SupportRecordStructs; } + if (settings.UnsignedRightShift) + { + flags |= ConversionFlags.SupportUnsignedRightShift; + } if (settings.InitAccessors) { flags |= ConversionFlags.SupportInitAccessors; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 6ff0a2fda..ccc37662f 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1280,6 +1280,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Unsigned right shift (>>>). + /// + public static string DecompilerSettings_UnsignedRightShift { + get { + return ResourceManager.GetString("DecompilerSettings.UnsignedRightShift", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use discards. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 04fc39c00..af3e6bfdc 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -450,6 +450,9 @@ Are you sure you want to continue? Switch expressions + + Unsigned right shift (>>>) + Use discards