From dd2247a9a7e5b2510232f38e96bfea3f51a31187 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 01:04:21 +0000 Subject: [PATCH 01/18] Initial plan From d1e50545e2fe2da8e184310556835efed29dd929 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 01:25:29 +0000 Subject: [PATCH 02/18] Fix test method signatures and initial enum comparison fix attempt Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler.Tests/ByteEnumTests.cs | 70 +++++++++---------- src/DelegateDecompiler.Tests/EnumTests.cs | 2 +- src/DelegateDecompiler.Tests/LongEnumTests.cs | 70 +++++++++---------- src/DelegateDecompiler/Processor.cs | 55 ++++++++++++++- 4 files changed, 124 insertions(+), 73 deletions(-) diff --git a/src/DelegateDecompiler.Tests/ByteEnumTests.cs b/src/DelegateDecompiler.Tests/ByteEnumTests.cs index baf577e4..d5f835f5 100644 --- a/src/DelegateDecompiler.Tests/ByteEnumTests.cs +++ b/src/DelegateDecompiler.Tests/ByteEnumTests.cs @@ -28,7 +28,7 @@ public void TestEnumParameterEqualsEnumConstant() Expression> expected1 = x => x == TestEnum.Bar; Expression> expected2 = x => (int) x == (int) TestEnum.Bar; Func compiled = x => x == TestEnum.Bar; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -37,7 +37,7 @@ public void TestEnumConstantEqualsEnumParameter() Expression> expected1 = x => TestEnum.Bar == x; Expression> expected2 = x => (int) TestEnum.Bar == (int) x; Func compiled = x => TestEnum.Bar == x; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -46,7 +46,7 @@ public void TestEnumPropertyNotEqualsFooOrElseEnumPropertyEqualsBar() Expression> expected1 = x => (x != TestEnum.Bar) || (x == TestEnum.Foo); Expression> expected2 = x => ((int)x != (int)TestEnum.Bar) || ((int)x == (int)TestEnum.Foo); Func compiled = x => (x != TestEnum.Bar) || (x == TestEnum.Foo); - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -55,7 +55,7 @@ public void TestEnumPropertyEqualsFooOrElseEnumPropertyEqualsBar() Expression> expected1 = x => (x == TestEnum.Bar) || (x == TestEnum.Foo); Expression> expected2 = x => ((int)x == (int)TestEnum.Bar) || ((int)x == (int)TestEnum.Foo); Func compiled = x => (x == TestEnum.Bar) || (x == TestEnum.Foo); - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -63,7 +63,7 @@ public void TestEnumParametersEqual() { Expression> expected = (x, y) => x == y; Func compiled = (x, y) => x == y; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -72,7 +72,7 @@ public void TestEnumParameterNotEqualsEnumConstant() Expression> expected1 = x => x != TestEnum.Bar; Expression> expected2 = x => (int) x != (int) TestEnum.Bar; Func compiled = x => x != TestEnum.Bar; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -81,7 +81,7 @@ public void TestEnumConstantNotEqualsEnumParameter() Expression> expected1 = x => TestEnum.Bar != x; Expression> expected2 = x => (int) TestEnum.Bar != (int) x; Func compiled = x => TestEnum.Bar != x; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -89,7 +89,7 @@ public void TestEnumParametersNotEqual() { Expression> expected = (x, y) => x != y; Func compiled = (x, y) => x != y; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -97,7 +97,7 @@ public void TestEnumConstantHasFlagEnumParameter() { Expression> expected = x => TestFlagEnum.Bar.HasFlag(x); Func compiled = x => TestFlagEnum.Bar.HasFlag(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -105,7 +105,7 @@ public void TestEnumParameterHasFlagEnumConstant() { Expression> expected = x => x.HasFlag(TestFlagEnum.Bar); Func compiled = x => x.HasFlag(TestFlagEnum.Bar); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -113,7 +113,7 @@ public void TestEnumParameterHasFlagEnumParameter() { Expression> expected = x => x.HasFlag(x); Func compiled = x => x.HasFlag(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -121,7 +121,7 @@ public void TestEnumConstantAndEnumParameter() { Expression> expected = x => TestFlagEnum.Bar & x; Func compiled = x => TestFlagEnum.Bar & x; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -129,7 +129,7 @@ public void TestEnumParameterAndEnumConstant() { Expression> expected = x => x & TestFlagEnum.Bar ; Func compiled = x => x & TestFlagEnum.Bar ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -137,7 +137,7 @@ public void TestNotEnumParameter() { Expression> expected = x => ~ x; Func compiled = x => ~ x; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -145,7 +145,7 @@ public void TestEnumParameterAndEnumParameter() { Expression> expected = x => x & x; Func compiled = x => x & x ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -153,7 +153,7 @@ public void TestEnumConstantOrEnumParameter() { Expression> expected = x => TestFlagEnum.Bar | x; Func compiled = x => TestFlagEnum.Bar | x; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -161,7 +161,7 @@ public void TestEnumParameterOrEnumConstant() { Expression> expected = x => x | TestFlagEnum.Bar ; Func compiled = x => x | TestFlagEnum.Bar ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -169,7 +169,7 @@ public void TestEnumParameterOrEnumParameter() { Expression> expected = x => x | x; Func compiled = x => x | x ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -177,7 +177,7 @@ public void TestEnumParameterAsMethodParameter() { Expression> expected = x => TestEnumMethod(x); Func compiled = x => TestEnumMethod(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -185,7 +185,7 @@ public void TestEnumParameterAsMethodWithEnumParameter() { Expression> expected = x => EnumMethod(x); Func compiled = x => EnumMethod(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -193,7 +193,7 @@ public void TestEnumParameterAsMethodWithObjectParameter() { Expression> expected = x => ObjectMethod(x); Func compiled = x => ObjectMethod(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -201,7 +201,7 @@ public void TestEnumParameterAsMethodWithInt8Parameter() { Expression> expected = x => Int8Method((byte) x); Func compiled = x => Int8Method((byte) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -209,7 +209,7 @@ public void TestEnumParameterAsMethodWithInt16Parameter() { Expression> expected = x => Int16Method((short) x); Func compiled = x => Int16Method((short) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -217,7 +217,7 @@ public void TestEnumParameterAsMethodWithInt32Parameter() { Expression> expected = x => Int32Method((int) x); Func compiled = x => Int32Method((int) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -225,7 +225,7 @@ public void TestEnumParameterAsMethodWithInt64Parameter() { Expression> expected = x => Int64Method((long) x); Func compiled = x => Int64Method((long) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -233,7 +233,7 @@ public void TestEnumParameterAsGenericMethodParameter() { Expression> expected = x => GenericMethod(x); Func compiled = x => GenericMethod(x); - Test(expected, compiled); + Test(compiled, expected); } // The following tests check for the insertion of Expression.Convert in the expression tree for compatible types @@ -243,7 +243,7 @@ public void TestEnumCastSubtraction() { Expression> expected = x => (int)x - 10; Func compiled = x => (int)x - 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -251,7 +251,7 @@ public void TestEnumCastMod() { Expression> expected = x => (int)x % 10; Func compiled = x => (int)x % 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -259,7 +259,7 @@ public void TestEnumCastEquals() { Expression> expected = x => (int)x == 10; Func compiled = x => (int)x == 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -267,7 +267,7 @@ public void TestEnumCastGreaterThan() { Expression> expected = x => (int)x > 10; Func compiled = x => (int)x > 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -275,7 +275,7 @@ public void Issue61() { Expression> expected = x => Decimal.Round(x, 3, MidpointRounding.AwayFromZero); Func compiled = x => Decimal.Round(x, 3, MidpointRounding.AwayFromZero); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -283,7 +283,7 @@ public void Issue98A() { Expression> expected = (x, y) => x == y; Func compiled = (x, y) => x == y; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -291,7 +291,7 @@ public void Issue98B() { Expression> expected = x => x == TestEnum.Foo; Func compiled = x => x == TestEnum.Foo; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -300,7 +300,7 @@ public void Issue160() Expression> expected1 = x => (TestEnum?) x == TestEnum.Bar; Expression> expected2 = x => (x.HasValue ? (TestEnum?) (x ?? 0) : null) == TestEnum.Bar; Func compiled = x => (TestEnum?) x == TestEnum.Bar; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -308,7 +308,7 @@ public void Issue176Array() { Expression> expected = x => new [] {TestEnum.Foo, TestEnum.Bar}.Contains(x); Func compiled = x => new[] {TestEnum.Foo, TestEnum.Bar}.Contains(x); - Test(expected, compiled); + Test(compiled, expected); } private static bool TestEnumMethod(TestEnum p0) diff --git a/src/DelegateDecompiler.Tests/EnumTests.cs b/src/DelegateDecompiler.Tests/EnumTests.cs index e7881909..867ba083 100644 --- a/src/DelegateDecompiler.Tests/EnumTests.cs +++ b/src/DelegateDecompiler.Tests/EnumTests.cs @@ -226,7 +226,7 @@ public void TestEnumParameterAsMethodWithInt8Parameter() { Expression> expected = x => Int8Method((byte) x); Func compiled = x => Int8Method((byte) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] diff --git a/src/DelegateDecompiler.Tests/LongEnumTests.cs b/src/DelegateDecompiler.Tests/LongEnumTests.cs index 08e65476..875af875 100644 --- a/src/DelegateDecompiler.Tests/LongEnumTests.cs +++ b/src/DelegateDecompiler.Tests/LongEnumTests.cs @@ -28,7 +28,7 @@ public void TestEnumParameterEqualsEnumConstant() Expression> expected1 = x => x == TestEnum.Bar; Expression> expected2 = x => (int) x == (int) TestEnum.Bar; Func compiled = x => x == TestEnum.Bar; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -37,7 +37,7 @@ public void TestEnumConstantEqualsEnumParameter() Expression> expected1 = x => TestEnum.Bar == x; Expression> expected2 = x => (int) TestEnum.Bar == (int) x; Func compiled = x => TestEnum.Bar == x; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -46,7 +46,7 @@ public void TestEnumPropertyNotEqualsFooOrElseEnumPropertyEqualsBar() Expression> expected1 = x => (x != TestEnum.Bar) || (x == TestEnum.Foo); Expression> expected2 = x => ((int)x != (int)TestEnum.Bar) || ((int)x == (int)TestEnum.Foo); Func compiled = x => (x != TestEnum.Bar) || (x == TestEnum.Foo); - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -55,7 +55,7 @@ public void TestEnumPropertyEqualsFooOrElseEnumPropertyEqualsBar() Expression> expected1 = x => (x == TestEnum.Bar) || (x == TestEnum.Foo); Expression> expected2 = x => ((int)x == (int)TestEnum.Bar) || ((int)x == (int)TestEnum.Foo); Func compiled = x => (x == TestEnum.Bar) || (x == TestEnum.Foo); - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -63,7 +63,7 @@ public void TestEnumParametersEqual() { Expression> expected = (x, y) => x == y; Func compiled = (x, y) => x == y; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -72,7 +72,7 @@ public void TestEnumParameterNotEqualsEnumConstant() Expression> expected1 = x => x != TestEnum.Bar; Expression> expected2 = x => (int) x != (int) TestEnum.Bar; Func compiled = x => x != TestEnum.Bar; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -81,7 +81,7 @@ public void TestEnumConstantNotEqualsEnumParameter() Expression> expected1 = x => TestEnum.Bar != x; Expression> expected2 = x => (int) TestEnum.Bar != (int) x; Func compiled = x => TestEnum.Bar != x; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -89,7 +89,7 @@ public void TestEnumParametersNotEqual() { Expression> expected = (x, y) => x != y; Func compiled = (x, y) => x != y; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -97,7 +97,7 @@ public void TestEnumConstantHasFlagEnumParameter() { Expression> expected = x => TestFlagEnum.Bar.HasFlag(x); Func compiled = x => TestFlagEnum.Bar.HasFlag(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -105,7 +105,7 @@ public void TestEnumParameterHasFlagEnumConstant() { Expression> expected = x => x.HasFlag(TestFlagEnum.Bar); Func compiled = x => x.HasFlag(TestFlagEnum.Bar); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -113,7 +113,7 @@ public void TestEnumParameterHasFlagEnumParameter() { Expression> expected = x => x.HasFlag(x); Func compiled = x => x.HasFlag(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -121,7 +121,7 @@ public void TestEnumConstantAndEnumParameter() { Expression> expected = x => TestFlagEnum.Bar & x; Func compiled = x => TestFlagEnum.Bar & x; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -129,7 +129,7 @@ public void TestEnumParameterAndEnumConstant() { Expression> expected = x => x & TestFlagEnum.Bar ; Func compiled = x => x & TestFlagEnum.Bar ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -137,7 +137,7 @@ public void TestNotEnumParameter() { Expression> expected = x => ~ x; Func compiled = x => ~ x; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -145,7 +145,7 @@ public void TestEnumParameterAndEnumParameter() { Expression> expected = x => x & x; Func compiled = x => x & x ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -153,7 +153,7 @@ public void TestEnumConstantOrEnumParameter() { Expression> expected = x => TestFlagEnum.Bar | x; Func compiled = x => TestFlagEnum.Bar | x; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -161,7 +161,7 @@ public void TestEnumParameterOrEnumConstant() { Expression> expected = x => x | TestFlagEnum.Bar ; Func compiled = x => x | TestFlagEnum.Bar ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -169,7 +169,7 @@ public void TestEnumParameterOrEnumParameter() { Expression> expected = x => x | x; Func compiled = x => x | x ; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -177,7 +177,7 @@ public void TestEnumParameterAsMethodParameter() { Expression> expected = x => TestEnumMethod(x); Func compiled = x => TestEnumMethod(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -185,7 +185,7 @@ public void TestEnumParameterAsMethodWithEnumParameter() { Expression> expected = x => EnumMethod(x); Func compiled = x => EnumMethod(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -193,7 +193,7 @@ public void TestEnumParameterAsMethodWithObjectParameter() { Expression> expected = x => ObjectMethod(x); Func compiled = x => ObjectMethod(x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -201,7 +201,7 @@ public void TestEnumParameterAsMethodWithInt8Parameter() { Expression> expected = x => Int8Method((byte) x); Func compiled = x => Int8Method((byte) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -209,7 +209,7 @@ public void TestEnumParameterAsMethodWithInt16Parameter() { Expression> expected = x => Int16Method((short) x); Func compiled = x => Int16Method((short) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -217,7 +217,7 @@ public void TestEnumParameterAsMethodWithInt32Parameter() { Expression> expected = x => Int32Method((int) x); Func compiled = x => Int32Method((int) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -225,7 +225,7 @@ public void TestEnumParameterAsMethodWithInt64Parameter() { Expression> expected = x => Int64Method((long) x); Func compiled = x => Int64Method((long) x); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -233,7 +233,7 @@ public void TestEnumParameterAsGenericMethodParameter() { Expression> expected = x => GenericMethod(x); Func compiled = x => GenericMethod(x); - Test(expected, compiled); + Test(compiled, expected); } // The following tests check for the insertion of Expression.Convert in the expression tree for compatible types @@ -243,7 +243,7 @@ public void TestEnumCastSubtraction() { Expression> expected = x => (int)x - 10; Func compiled = x => (int)x - 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -251,7 +251,7 @@ public void TestEnumCastMod() { Expression> expected = x => (int)x % 10; Func compiled = x => (int)x % 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -259,7 +259,7 @@ public void TestEnumCastEquals() { Expression> expected = x => (int)x == 10; Func compiled = x => (int)x == 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -267,7 +267,7 @@ public void TestEnumCastGreaterThan() { Expression> expected = x => (int)x > 10; Func compiled = x => (int)x > 10; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -275,7 +275,7 @@ public void Issue61() { Expression> expected = x => Decimal.Round(x, 3, MidpointRounding.AwayFromZero); Func compiled = x => Decimal.Round(x, 3, MidpointRounding.AwayFromZero); - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -283,7 +283,7 @@ public void Issue98A() { Expression> expected = (x, y) => x == y; Func compiled = (x, y) => x == y; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -291,7 +291,7 @@ public void Issue98B() { Expression> expected = x => x == TestEnum.Foo; Func compiled = x => x == TestEnum.Foo; - Test(expected, compiled); + Test(compiled, expected); } [Test] @@ -300,7 +300,7 @@ public void Issue160() Expression> expected1 = x => (TestEnum?) x == TestEnum.Bar; Expression> expected2 = x => (x.HasValue ? (TestEnum?) (x ?? 0) : null) == TestEnum.Bar; Func compiled = x => (TestEnum?) x == TestEnum.Bar; - Test(expected1, expected2, compiled); + Test(compiled, expected1, expected2); } [Test] @@ -308,7 +308,7 @@ public void Issue176Array() { Expression> expected = x => new [] {TestEnum.Foo, TestEnum.Bar}.Contains(x); Func compiled = x => new[] {TestEnum.Foo, TestEnum.Bar}.Contains(x); - Test(expected, compiled); + Test(compiled, expected); } private static bool TestEnumMethod(TestEnum p0) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index c1c8b8b1..0767a4d7 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -228,12 +228,63 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ left = AdjustBooleanConstant(left, rightType); right = AdjustBooleanConstant(right, leftType); - left = ConvertEnumExpressionToUnderlyingType(left); - right = ConvertEnumExpressionToUnderlyingType(right); + + // For comparison operations, convert enums directly to int to avoid type mismatches + // This handles cases where an enum with a non-int underlying type (e.g., byte, long) is compared + // with an int constant loaded from IL + if (IsComparisonOperation(expressionType)) + { + left = ConvertEnumExpressionToInt(left); + right = ConvertEnumExpressionToInt(right); + } + else + { + left = ConvertEnumExpressionToUnderlyingType(left); + right = ConvertEnumExpressionToUnderlyingType(right); + } return Expression.MakeBinary(expressionType, left, right); } + static bool IsComparisonOperation(ExpressionType expressionType) + { + return expressionType == ExpressionType.Equal || + expressionType == ExpressionType.NotEqual || + expressionType == ExpressionType.LessThan || + expressionType == ExpressionType.LessThanOrEqual || + expressionType == ExpressionType.GreaterThan || + expressionType == ExpressionType.GreaterThanOrEqual; + } + + internal static Expression ConvertEnumExpressionToInt(Expression expression) + { + if (expression.Type.IsEnum) + return Expression.Convert(expression, typeof(int)); + + // If the expression is a Convert from an enum to its underlying type, + // replace it with a direct conversion to int to avoid double conversion + if (expression is UnaryExpression unary && + unary.NodeType == ExpressionType.Convert && + unary.Operand.Type.IsEnum) + { + return Expression.Convert(unary.Operand, typeof(int)); + } + + return expression; + } + + static bool IsIntegralType(Type type) + { + return type == typeof(byte) || + type == typeof(sbyte) || + type == typeof(short) || + type == typeof(ushort) || + type == typeof(int) || + type == typeof(uint) || + type == typeof(long) || + type == typeof(ulong); + } + internal static Expression ConvertEnumExpressionToUnderlyingType(Expression expression) { if (expression.Type.IsEnum) From 33ff9540c1cf45a521abd264ec0073e9d1b33c82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 01:39:51 +0000 Subject: [PATCH 03/18] Fix enum comparison for enums with non-int underlying types Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 0767a4d7..a333e33f 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -258,16 +258,25 @@ static bool IsComparisonOperation(ExpressionType expressionType) internal static Expression ConvertEnumExpressionToInt(Expression expression) { + // If the expression is already an int, return as-is + if (expression.Type == typeof(int)) + return expression; + if (expression.Type.IsEnum) return Expression.Convert(expression, typeof(int)); // If the expression is a Convert from an enum to its underlying type, // replace it with a direct conversion to int to avoid double conversion - if (expression is UnaryExpression unary && - unary.NodeType == ExpressionType.Convert && - unary.Operand.Type.IsEnum) + if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) { - return Expression.Convert(unary.Operand, typeof(int)); + // Check if we're converting an enum to its underlying type + var operand = unary.Operand; + + if (operand.Type.IsEnum && operand.Type.GetEnumUnderlyingType() == expression.Type) + { + // Replace Convert(enumValue, underlyingType) with Convert(enumValue, int) + return Expression.Convert(operand, typeof(int)); + } } return expression; From 4f04ada58c1a18c5c9910fc9babc97cc9389d04e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 01:41:11 +0000 Subject: [PATCH 04/18] Remove unused IsIntegralType method Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index a333e33f..d3ceb374 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -282,18 +282,6 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) return expression; } - static bool IsIntegralType(Type type) - { - return type == typeof(byte) || - type == typeof(sbyte) || - type == typeof(short) || - type == typeof(ushort) || - type == typeof(int) || - type == typeof(uint) || - type == typeof(long) || - type == typeof(ulong); - } - internal static Expression ConvertEnumExpressionToUnderlyingType(Expression expression) { if (expression.Type.IsEnum) From b387cf1b0b58377338f27f22aa0867bc6c3351ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 02:56:22 +0000 Subject: [PATCH 05/18] Add bitwise operation support for non-int enums and improve long enum handling Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 40 ++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index d3ceb374..27c45328 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -229,13 +229,29 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ left = AdjustBooleanConstant(left, rightType); right = AdjustBooleanConstant(right, leftType); - // For comparison operations, convert enums directly to int to avoid type mismatches - // This handles cases where an enum with a non-int underlying type (e.g., byte, long) is compared + // For comparison and bitwise operations, convert enums to handle type mismatches + // This handles cases where an enum with a non-int underlying type (e.g., byte, long) is used // with an int constant loaded from IL - if (IsComparisonOperation(expressionType)) + if (IsComparisonOperation(expressionType) || IsBitwiseOperation(expressionType)) { + // Determine if we're dealing with long enums + var leftIsLongEnum = left.Type.IsEnum && (left.Type.GetEnumUnderlyingType() == typeof(long) || left.Type.GetEnumUnderlyingType() == typeof(ulong)); + var rightIsLongEnum = right.Type.IsEnum && (right.Type.GetEnumUnderlyingType() == typeof(long) || right.Type.GetEnumUnderlyingType() == typeof(ulong)); + left = ConvertEnumExpressionToInt(left); right = ConvertEnumExpressionToInt(right); + + // For long enums: enum is converted to long, but constants stay as int (Expression.MakeBinary handles promotion) + // For other enums: both are converted to int + // So we only need to explicitly convert int constants to the target type for non-long enums + if (!leftIsLongEnum && !rightIsLongEnum) + { + // Ensure both are int for consistency + if (left.Type == typeof(byte) || left.Type == typeof(sbyte) || left.Type == typeof(short) || left.Type == typeof(ushort)) + left = Expression.Convert(left, typeof(int)); + if (right.Type == typeof(byte) || right.Type == typeof(sbyte) || right.Type == typeof(short) || right.Type == typeof(ushort)) + right = Expression.Convert(right, typeof(int)); + } } else { @@ -256,6 +272,13 @@ static bool IsComparisonOperation(ExpressionType expressionType) expressionType == ExpressionType.GreaterThanOrEqual; } + static bool IsBitwiseOperation(ExpressionType expressionType) + { + return expressionType == ExpressionType.And || + expressionType == ExpressionType.Or || + expressionType == ExpressionType.ExclusiveOr; + } + internal static Expression ConvertEnumExpressionToInt(Expression expression) { // If the expression is already an int, return as-is @@ -263,10 +286,16 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) return expression; if (expression.Type.IsEnum) + { + // For long/ulong enums, convert to their underlying type. For others, convert to int. + var underlyingType = expression.Type.GetEnumUnderlyingType(); + if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) + return Expression.Convert(expression, underlyingType); return Expression.Convert(expression, typeof(int)); + } // If the expression is a Convert from an enum to its underlying type, - // replace it with a direct conversion to int to avoid double conversion + // replace it with a direct conversion to int (or underlying type for long enums) to avoid double conversion if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) { // Check if we're converting an enum to its underlying type @@ -274,6 +303,9 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) if (operand.Type.IsEnum && operand.Type.GetEnumUnderlyingType() == expression.Type) { + // For long/ulong enums, convert to the underlying type. For others, convert to int. + if (expression.Type == typeof(long) || expression.Type == typeof(ulong)) + return Expression.Convert(operand, expression.Type); // Replace Convert(enumValue, underlyingType) with Convert(enumValue, int) return Expression.Convert(operand, typeof(int)); } From 7c617352f0e341fc78cedfd74890dafbd724d2e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:04:30 +0000 Subject: [PATCH 06/18] Add arithmetic operation support and long enum constant unwrapping Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 42 ++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 27c45328..94a3f421 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -229,22 +229,31 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ left = AdjustBooleanConstant(left, rightType); right = AdjustBooleanConstant(right, leftType); - // For comparison and bitwise operations, convert enums to handle type mismatches + // For comparison, bitwise, and arithmetic operations, convert enums to handle type mismatches // This handles cases where an enum with a non-int underlying type (e.g., byte, long) is used // with an int constant loaded from IL - if (IsComparisonOperation(expressionType) || IsBitwiseOperation(expressionType)) + if (IsComparisonOperation(expressionType) || IsBitwiseOperation(expressionType) || IsArithmeticOperation(expressionType)) { // Determine if we're dealing with long enums var leftIsLongEnum = left.Type.IsEnum && (left.Type.GetEnumUnderlyingType() == typeof(long) || left.Type.GetEnumUnderlyingType() == typeof(ulong)); var rightIsLongEnum = right.Type.IsEnum && (right.Type.GetEnumUnderlyingType() == typeof(long) || right.Type.GetEnumUnderlyingType() == typeof(ulong)); + // For long enums with constants that have been pre-converted to long, + // unwrap them back to their int value for cleaner expression trees + var hasLongEnum = leftIsLongEnum || rightIsLongEnum; + if (hasLongEnum) + { + // Unwrap Convert(intConstant, long) back to intConstant + left = UnwrapConstantConversion(left); + right = UnwrapConstantConversion(right); + } + left = ConvertEnumExpressionToInt(left); right = ConvertEnumExpressionToInt(right); - // For long enums: enum is converted to long, but constants stay as int (Expression.MakeBinary handles promotion) - // For other enums: both are converted to int - // So we only need to explicitly convert int constants to the target type for non-long enums - if (!leftIsLongEnum && !rightIsLongEnum) + // For long enums: enum is converted to long, constants can stay as long or int (Expression.MakeBinary handles it) + // For other enums: both should be converted to int for consistency + if (!hasLongEnum) { // Ensure both are int for consistency if (left.Type == typeof(byte) || left.Type == typeof(sbyte) || left.Type == typeof(short) || left.Type == typeof(ushort)) @@ -279,6 +288,27 @@ static bool IsBitwiseOperation(ExpressionType expressionType) expressionType == ExpressionType.ExclusiveOr; } + static bool IsArithmeticOperation(ExpressionType expressionType) + { + return expressionType == ExpressionType.Add || + expressionType == ExpressionType.Subtract || + expressionType == ExpressionType.Multiply || + expressionType == ExpressionType.Divide || + expressionType == ExpressionType.Modulo; + } + + static Expression UnwrapConstantConversion(Expression expression) + { + // If this is Convert(constant, targetType), unwrap it back to the constant + if (expression is UnaryExpression unary && + unary.NodeType == ExpressionType.Convert && + unary.Operand is ConstantExpression) + { + return unary.Operand; + } + return expression; + } + internal static Expression ConvertEnumExpressionToInt(Expression expression) { // If the expression is already an int, return as-is From c2adcf6c7ba2edd2638a898ea44a27582e4d8751 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:09:19 +0000 Subject: [PATCH 07/18] Optimize long constant conversion - 485/521 tests passing Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 94a3f421..29e5570f 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -238,24 +238,15 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ var leftIsLongEnum = left.Type.IsEnum && (left.Type.GetEnumUnderlyingType() == typeof(long) || left.Type.GetEnumUnderlyingType() == typeof(ulong)); var rightIsLongEnum = right.Type.IsEnum && (right.Type.GetEnumUnderlyingType() == typeof(long) || right.Type.GetEnumUnderlyingType() == typeof(ulong)); - // For long enums with constants that have been pre-converted to long, - // unwrap them back to their int value for cleaner expression trees var hasLongEnum = leftIsLongEnum || rightIsLongEnum; - if (hasLongEnum) - { - // Unwrap Convert(intConstant, long) back to intConstant - left = UnwrapConstantConversion(left); - right = UnwrapConstantConversion(right); - } left = ConvertEnumExpressionToInt(left); right = ConvertEnumExpressionToInt(right); - // For long enums: enum is converted to long, constants can stay as long or int (Expression.MakeBinary handles it) - // For other enums: both should be converted to int for consistency + // Ensure type compatibility after conversions if (!hasLongEnum) { - // Ensure both are int for consistency + // For byte/short enums, ensure both are int if (left.Type == typeof(byte) || left.Type == typeof(sbyte) || left.Type == typeof(short) || left.Type == typeof(ushort)) left = Expression.Convert(left, typeof(int)); if (right.Type == typeof(byte) || right.Type == typeof(sbyte) || right.Type == typeof(short) || right.Type == typeof(ushort)) @@ -339,6 +330,15 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) // Replace Convert(enumValue, underlyingType) with Convert(enumValue, int) return Expression.Convert(operand, typeof(int)); } + + // Optimize Convert(intConstant, long) to a direct long constant + if (operand is ConstantExpression constant && + constant.Type == typeof(int) && + (expression.Type == typeof(long) || expression.Type == typeof(ulong))) + { + var longValue = Convert.ToInt64(constant.Value); + return Expression.Constant(longValue, expression.Type); + } } return expression; From 3fdd774abee1a4c794b2bce1112beacf4acba31a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:34:39 +0000 Subject: [PATCH 08/18] Generalize enum conversion logic - remove operation-specific special cases Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 99 ++++++------------- .../Processors/BoxProcessor.cs | 15 +++ .../Processors/ConvertProcessor.cs | 19 +++- .../Processors/ConvertTypeProcessor.cs | 21 +++- .../Processors/UnaryExpressionProcessor.cs | 11 ++- 5 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 29e5570f..90a7c6d3 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -229,77 +229,18 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ left = AdjustBooleanConstant(left, rightType); right = AdjustBooleanConstant(right, leftType); - // For comparison, bitwise, and arithmetic operations, convert enums to handle type mismatches - // This handles cases where an enum with a non-int underlying type (e.g., byte, long) is used - // with an int constant loaded from IL - if (IsComparisonOperation(expressionType) || IsBitwiseOperation(expressionType) || IsArithmeticOperation(expressionType)) - { - // Determine if we're dealing with long enums - var leftIsLongEnum = left.Type.IsEnum && (left.Type.GetEnumUnderlyingType() == typeof(long) || left.Type.GetEnumUnderlyingType() == typeof(ulong)); - var rightIsLongEnum = right.Type.IsEnum && (right.Type.GetEnumUnderlyingType() == typeof(long) || right.Type.GetEnumUnderlyingType() == typeof(ulong)); - - var hasLongEnum = leftIsLongEnum || rightIsLongEnum; - - left = ConvertEnumExpressionToInt(left); - right = ConvertEnumExpressionToInt(right); - - // Ensure type compatibility after conversions - if (!hasLongEnum) - { - // For byte/short enums, ensure both are int - if (left.Type == typeof(byte) || left.Type == typeof(sbyte) || left.Type == typeof(short) || left.Type == typeof(ushort)) - left = Expression.Convert(left, typeof(int)); - if (right.Type == typeof(byte) || right.Type == typeof(sbyte) || right.Type == typeof(short) || right.Type == typeof(ushort)) - right = Expression.Convert(right, typeof(int)); - } - } - else - { - left = ConvertEnumExpressionToUnderlyingType(left); - right = ConvertEnumExpressionToUnderlyingType(right); - } + // Convert enums to their appropriate type for operations + // This uses ConvertEnumExpressionToInt which handles: + // - byte/short enums -> int + // - long/ulong enums -> long/ulong + // - Optimizes Convert(intConstant, long) -> longConstant + // - Handles Convert(Convert(enum, byte), int) -> Convert(enum, int) + left = ConvertEnumExpressionToInt(left); + right = ConvertEnumExpressionToInt(right); return Expression.MakeBinary(expressionType, left, right); } - static bool IsComparisonOperation(ExpressionType expressionType) - { - return expressionType == ExpressionType.Equal || - expressionType == ExpressionType.NotEqual || - expressionType == ExpressionType.LessThan || - expressionType == ExpressionType.LessThanOrEqual || - expressionType == ExpressionType.GreaterThan || - expressionType == ExpressionType.GreaterThanOrEqual; - } - - static bool IsBitwiseOperation(ExpressionType expressionType) - { - return expressionType == ExpressionType.And || - expressionType == ExpressionType.Or || - expressionType == ExpressionType.ExclusiveOr; - } - - static bool IsArithmeticOperation(ExpressionType expressionType) - { - return expressionType == ExpressionType.Add || - expressionType == ExpressionType.Subtract || - expressionType == ExpressionType.Multiply || - expressionType == ExpressionType.Divide || - expressionType == ExpressionType.Modulo; - } - - static Expression UnwrapConstantConversion(Expression expression) - { - // If this is Convert(constant, targetType), unwrap it back to the constant - if (expression is UnaryExpression unary && - unary.NodeType == ExpressionType.Convert && - unary.Operand is ConstantExpression) - { - return unary.Operand; - } - return expression; - } - internal static Expression ConvertEnumExpressionToInt(Expression expression) { // If the expression is already an int, return as-is @@ -331,6 +272,21 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) return Expression.Convert(operand, typeof(int)); } + // Optimize Convert(Convert(X, byte/short), Y) when going through unnecessary intermediate conversions + // This happens with operations like NOT that return int but IL converts to byte before enum + if (operand is UnaryExpression innerUnary && innerUnary.NodeType == ExpressionType.Convert) + { + var innerOperand = innerUnary.Operand; + // If we're converting from int through byte/short, skip the intermediate conversion + if (innerOperand.Type == typeof(int) && + (innerUnary.Type == typeof(byte) || innerUnary.Type == typeof(sbyte) || + innerUnary.Type == typeof(short) || innerUnary.Type == typeof(ushort))) + { + // Keep just the inner operand, let the outer conversion happen + return Expression.Convert(innerOperand, expression.Type); + } + } + // Optimize Convert(intConstant, long) to a direct long constant if (operand is ConstantExpression constant && constant.Type == typeof(int) && @@ -347,7 +303,14 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) internal static Expression ConvertEnumExpressionToUnderlyingType(Expression expression) { if (expression.Type.IsEnum) - return Expression.Convert(expression, expression.Type.GetEnumUnderlyingType()); + { + var underlyingType = expression.Type.GetEnumUnderlyingType(); + // C# promotes byte/sbyte/short/ushort enums to int for operations + // Only long/ulong stay as their underlying type + if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) + return Expression.Convert(expression, underlyingType); + return Expression.Convert(expression, typeof(int)); + } return expression; } diff --git a/src/DelegateDecompiler/Processors/BoxProcessor.cs b/src/DelegateDecompiler/Processors/BoxProcessor.cs index fcc1db19..5c41afb3 100644 --- a/src/DelegateDecompiler/Processors/BoxProcessor.cs +++ b/src/DelegateDecompiler/Processors/BoxProcessor.cs @@ -32,6 +32,21 @@ static Expression Box(Expression expression, Type type) if (expression.Type.IsEnum) return Expression.Convert(expression, type); + // Optimize Convert(Convert(int, byte/short), enum) -> Convert(int, enum) + // This happens when operations like NOT return int, IL converts to byte, then boxes to enum + if (type.IsEnum && expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + { + var operand = unary.Operand; + // Skip intermediate conversions from int to byte/short when boxing to enum + if (operand.Type == typeof(int) && + (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || + unary.Type == typeof(short) || unary.Type == typeof(ushort))) + { + // Box the int directly to the enum + return Expression.Convert(operand, type); + } + } + return expression; } } \ No newline at end of file diff --git a/src/DelegateDecompiler/Processors/ConvertProcessor.cs b/src/DelegateDecompiler/Processors/ConvertProcessor.cs index 204faa4d..0f5ba4ea 100644 --- a/src/DelegateDecompiler/Processors/ConvertProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertProcessor.cs @@ -23,6 +23,23 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { var val = state.Stack.Pop(); - state.Stack.Push(Expression.Convert(val, targetType)); + Expression expr = val; + + // Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X) + // This happens when operations like NOT return int, IL converts to byte, then to enum + if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + { + var operand = unary.Operand; + // Skip intermediate conversions from int to byte/short when going to another type + if (operand.Type == typeof(int) && + (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || + unary.Type == typeof(short) || unary.Type == typeof(ushort))) + { + // Use the int directly, skip the intermediate byte/short conversion + expr = operand; + } + } + + state.Stack.Push(Expression.Convert(expr, targetType)); } } \ No newline at end of file diff --git a/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs b/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs index b4df2d78..024acaf6 100644 --- a/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs @@ -15,8 +15,25 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { - var expression = state.Stack.Pop(); - state.Stack.Push(Expression.Convert(expression, (Type)instruction.Operand)); + var address = state.Stack.Pop(); + Expression expression = address; + var targetType = (Type)instruction.Operand; + + // Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X) + // This happens when operations like NOT return int, IL converts to byte, then casts to enum + if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + { + var operand = unary.Operand; + // Skip intermediate conversions from int to byte/short + if (operand.Type == typeof(int) && + (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || + unary.Type == typeof(short) || unary.Type == typeof(ushort))) + { + expression = operand; + } + } + + state.Stack.Push(Expression.Convert(expression, targetType)); } } } diff --git a/src/DelegateDecompiler/Processors/UnaryExpressionProcessor.cs b/src/DelegateDecompiler/Processors/UnaryExpressionProcessor.cs index 4d6d2744..63dda825 100644 --- a/src/DelegateDecompiler/Processors/UnaryExpressionProcessor.cs +++ b/src/DelegateDecompiler/Processors/UnaryExpressionProcessor.cs @@ -21,7 +21,16 @@ public void Process(ProcessorState state, Instruction instruction) static UnaryExpression MakeUnaryExpression(Expression operand, ExpressionType expressionType) { - operand = Processor.ConvertEnumExpressionToUnderlyingType(operand); + // For bitwise NOT on enums, convert to int (for byte/short) or underlying type (for long) + // to match the behavior of binary operations + if (expressionType == ExpressionType.Not && operand.Type.IsEnum) + { + operand = Processor.ConvertEnumExpressionToInt(operand); + } + else + { + operand = Processor.ConvertEnumExpressionToUnderlyingType(operand); + } return Expression.MakeUnary(expressionType, operand, operand.Type); } From ff04bc8312b3d66b2dbdbb608b7852b78e59d5b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 05:39:50 +0000 Subject: [PATCH 09/18] Fix unary NOT and method parameter conversions - 7 failures remaining Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 53 +++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 90a7c6d3..854e375f 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -34,7 +34,23 @@ public static Expression Process(ControlFlowGraph cfg, VariableInfo[] locals, IL ex = AdjustType(ex, returnType); if (!returnType.IsAssignableFrom(ex.Type) && returnType != typeof(void)) + { + // Optimize Convert(Convert(int, byte/short), enum) -> Convert(int, enum) + // This happens when operations like NOT return int, IL converts to byte, then to enum at return + if (returnType.IsEnum && ex is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + { + var operand = unary.Operand; + if (operand.Type == typeof(int) && + (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || + unary.Type == typeof(short) || unary.Type == typeof(ushort))) + { + // Skip the intermediate conversion, convert int directly to enum + return Expression.Convert(operand, returnType); + } + } + return Expression.Convert(ex, returnType); + } return ex; } @@ -375,9 +391,42 @@ internal static Expression AdjustType(Expression expression, Type type) } } - if (!type.IsAssignableFrom(expression.Type) && expression.Type.IsEnum && expression.Type.GetEnumUnderlyingType() == type) + if (!type.IsAssignableFrom(expression.Type)) { - return Expression.Convert(expression, type); + if (expression.Type.IsEnum) + { + var underlyingType = expression.Type.GetEnumUnderlyingType(); + // If enum's underlying type matches target, convert directly + if (underlyingType == type) + { + return Expression.Convert(expression, type); + } + // If target is int and enum has smaller underlying type (byte, short), convert to int + // This handles explicit casts like (int)byteEnum + if (type == typeof(int) && (underlyingType == typeof(byte) || underlyingType == typeof(sbyte) || + underlyingType == typeof(short) || underlyingType == typeof(ushort))) + { + return Expression.Convert(expression, type); + } + // If target is long and enum has long underlying type + if (type == typeof(long) && (underlyingType == typeof(long) || underlyingType == typeof(ulong))) + { + return Expression.Convert(expression, type); + } + // If target is short and enum has byte underlying type + if (type == typeof(short) && (underlyingType == typeof(byte) || underlyingType == typeof(sbyte))) + { + return Expression.Convert(expression, type); + } + } + + // Handle conversions between signed/unsigned long when expression is not enum + // This can happen after enum is converted to its underlying type + if ((type == typeof(long) && expression.Type == typeof(ulong)) || + (type == typeof(ulong) && expression.Type == typeof(long))) + { + return Expression.Convert(expression, type); + } } if (type.IsValueType != expression.Type.IsValueType) From 9117f8d524a37250b941977e691a7e9ae40b371b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 06:04:38 +0000 Subject: [PATCH 10/18] Fix array initialization and restore NOT optimization - 6 failures remaining Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 854e375f..6b1efbee 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -382,6 +382,12 @@ internal static Expression AdjustType(Expression expression, Type type) return Expression.Constant(Convert.ToUInt32(constant.Value)); } } + + // Handle long constants to enum conversion (for long-based enums) + if ((constant.Type == typeof(long) || constant.Type == typeof(ulong)) && type.IsEnum) + { + return Expression.Constant(Enum.ToObject(type, constant.Value)); + } } else { @@ -427,6 +433,37 @@ internal static Expression AdjustType(Expression expression, Type type) { return Expression.Convert(expression, type); } + + // Handle conversions from integral types to enum (non-constant case) + // This happens in array initialization where enum values are converted to underlying type + if (type.IsEnum && !expression.Type.IsEnum) + { + var underlyingType = type.GetEnumUnderlyingType(); + if (expression.Type == underlyingType) + { + // Optimize Convert(Convert(intConstant, long), enum) -> enum constant + if (expression is UnaryExpression unary && + unary.NodeType == ExpressionType.Convert && + unary.Operand is ConstantExpression innerConst) + { + return Expression.Constant(Enum.ToObject(type, innerConst.Value)); + } + + // Optimize Convert(int, byte/short) -> Convert(int, enum) for operations like NOT + // that return int but IL converts to underlying type before enum + if (expression is UnaryExpression unaryExpr && + unaryExpr.NodeType == ExpressionType.Convert && + unaryExpr.Operand.Type == typeof(int) && + (underlyingType == typeof(byte) || underlyingType == typeof(sbyte) || + underlyingType == typeof(short) || underlyingType == typeof(ushort))) + { + // Skip intermediate byte/short conversion, convert int directly to enum + return Expression.Convert(unaryExpr.Operand, type); + } + + return Expression.Convert(expression, type); + } + } } if (type.IsValueType != expression.Type.IsValueType) From 3ad4d49d361c08b7c7ef24082f59b1845893698b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 06:09:30 +0000 Subject: [PATCH 11/18] Add HasFlag instance type checking - 6 failures remaining Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- .../Processors/CallProcessor.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/DelegateDecompiler/Processors/CallProcessor.cs b/src/DelegateDecompiler/Processors/CallProcessor.cs index c092dd63..32402635 100644 --- a/src/DelegateDecompiler/Processors/CallProcessor.cs +++ b/src/DelegateDecompiler/Processors/CallProcessor.cs @@ -165,7 +165,43 @@ static Expression BuildMethodCallExpression(MethodInfo m, Address instance, Expr } if (instance.Expression != null) - return Expression.Call(instance, m, arguments); + { + // Ensure instance type matches the method's declaring type + // This handles cases where enum instances have been converted to their underlying type + var instanceExpr = instance.Expression; + if (m.DeclaringType != null && !m.DeclaringType.IsAssignableFrom(instanceExpr.Type)) + { + // If the declaring type is an enum and instance is the underlying type, convert back + if (m.DeclaringType.IsEnum && instanceExpr.Type == m.DeclaringType.GetEnumUnderlyingType()) + { + instanceExpr = Expression.Convert(instanceExpr, m.DeclaringType); + } + // Special case for Enum.HasFlag - declaring type is Enum but instance should be specific enum type + else if (m.DeclaringType == typeof(Enum) && instanceExpr.Type.IsValueType && !instanceExpr.Type.IsEnum) + { + // Try to infer the enum type from the instance expression + // Check if it's a direct MemberExpression + if (instanceExpr is MemberExpression memberExpr && + memberExpr.Member is FieldInfo field && + field.FieldType.IsEnum) + { + instanceExpr = Expression.Convert(instanceExpr, field.FieldType); + } + // Check if it's a Convert wrapping a MemberExpression + else if (instanceExpr is UnaryExpression unary && + unary.NodeType == ExpressionType.Convert && + unary.Operand is MemberExpression innerMember && + innerMember.Member is FieldInfo innerField && + innerField.FieldType.IsEnum) + { + // Use the enum type directly, unwrap the Convert + instanceExpr = innerMember; + } + } + } + + return Expression.Call(instanceExpr, m, arguments); + } return Expression.Call(null, m, arguments); } From 86ca5b96043d61c86909aaae7afa82f6740ac436 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 06:29:42 +0000 Subject: [PATCH 12/18] Fix int-based enum nullable conversions - 5 failures remaining Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 16 +++++ .../Processors/NewobjProcessor.cs | 64 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 6b1efbee..112edb0a 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -434,6 +434,22 @@ internal static Expression AdjustType(Expression expression, Type type) return Expression.Convert(expression, type); } + // Handle conversions to Nullable + // When converting int to Nullable, convert directly without intermediate TestEnum + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType.IsEnum && !expression.Type.IsEnum) + { + var enumUnderlyingType = underlyingType.GetEnumUnderlyingType(); + // If expression type matches the enum's underlying type, convert directly to Nullable + if (expression.Type == enumUnderlyingType) + { + return Expression.Convert(expression, type); + } + } + } + // Handle conversions from integral types to enum (non-constant case) // This happens in array initialization where enum values are converted to underlying type if (type.IsEnum && !expression.Type.IsEnum) diff --git a/src/DelegateDecompiler/Processors/NewobjProcessor.cs b/src/DelegateDecompiler/Processors/NewobjProcessor.cs index 68f75c8e..3a214690 100644 --- a/src/DelegateDecompiler/Processors/NewobjProcessor.cs +++ b/src/DelegateDecompiler/Processors/NewobjProcessor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; @@ -16,13 +17,72 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { var constructor = (ConstructorInfo)instruction.Operand; - var arguments = Processor.GetArguments(state, constructor); + if (constructor.DeclaringType.IsNullableType() && constructor.GetParameters().Length == 1) { - state.Stack.Push(Expression.Convert(arguments[0], constructor.DeclaringType)); + // For Nullable constructor, we want to simplify conversions + // Instead of Convert(Convert(int, Enum), Nullable), we want Convert(int, Nullable) + // But we need to get the argument first to apply optimizations + var parameters = constructor.GetParameters(); + var argument = state.Stack.Pop(); + var nullableType = constructor.DeclaringType; + var innerType = System.Nullable.GetUnderlyingType(nullableType); + + // If inner type is an enum, try to optimize the conversion + if (innerType.IsEnum) + { + var enumUnderlyingType = innerType.GetEnumUnderlyingType(); + // Use AdjustType but intercept if it tries to create intermediate conversions + var adjusted = Processor.AdjustType(argument, parameters[0].ParameterType); + + // Check if AdjustType created Convert(x, Enum) where x is int/byte/long + if (adjusted is UnaryExpression unary && + unary.NodeType == ExpressionType.Convert && + unary.Type == innerType) + { + var operand = unary.Operand; + + // Case 1: operand is the enum's underlying type - convert directly to nullable + if (operand.Type == enumUnderlyingType) + { + state.Stack.Push(Expression.Convert(operand, nullableType)); + return; + } + + // Case 2: operand is Convert(x, underlyingType) where x is int + // Skip the intermediate conversion + if (operand is UnaryExpression innerUnary && + innerUnary.NodeType == ExpressionType.Convert && + innerUnary.Type == enumUnderlyingType && + innerUnary.Operand.Type == typeof(int)) + { + state.Stack.Push(Expression.Convert(innerUnary.Operand, nullableType)); + return; + } + + // Case 3: operand is int and enum underlying is long - convert int directly + if (operand.Type == typeof(int) && + (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) + { + state.Stack.Push(Expression.Convert(operand, nullableType)); + return; + } + } + + // Use the adjusted argument + state.Stack.Push(Expression.Convert(adjusted, nullableType)); + } + else + { + // Non-enum nullable, use GetArguments normally + state.Stack.Push(argument); + var arguments = Processor.GetArguments(state, constructor); + state.Stack.Push(Expression.Convert(arguments[0], nullableType)); + } } else { + var arguments = Processor.GetArguments(state, constructor); state.Stack.Push(Expression.New(constructor, arguments)); } } From 7c673e53622695758ce35e2fbb5694df7af2f9a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 06:38:46 +0000 Subject: [PATCH 13/18] Fix nullable enum conversions for byte/short enums - 4 failures remaining Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- .../Processors/NewobjProcessor.cs | 104 ++++++++++++------ 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/src/DelegateDecompiler/Processors/NewobjProcessor.cs b/src/DelegateDecompiler/Processors/NewobjProcessor.cs index 3a214690..4c55c191 100644 --- a/src/DelegateDecompiler/Processors/NewobjProcessor.cs +++ b/src/DelegateDecompiler/Processors/NewobjProcessor.cs @@ -35,38 +35,12 @@ public void Process(ProcessorState state, Instruction instruction) // Use AdjustType but intercept if it tries to create intermediate conversions var adjusted = Processor.AdjustType(argument, parameters[0].ParameterType); - // Check if AdjustType created Convert(x, Enum) where x is int/byte/long - if (adjusted is UnaryExpression unary && - unary.NodeType == ExpressionType.Convert && - unary.Type == innerType) + // Try to unwrap unnecessary conversions for nullable enum creation + var simplified = TrySimplifyForNullableEnum(adjusted, innerType, enumUnderlyingType, nullableType); + if (simplified != null) { - var operand = unary.Operand; - - // Case 1: operand is the enum's underlying type - convert directly to nullable - if (operand.Type == enumUnderlyingType) - { - state.Stack.Push(Expression.Convert(operand, nullableType)); - return; - } - - // Case 2: operand is Convert(x, underlyingType) where x is int - // Skip the intermediate conversion - if (operand is UnaryExpression innerUnary && - innerUnary.NodeType == ExpressionType.Convert && - innerUnary.Type == enumUnderlyingType && - innerUnary.Operand.Type == typeof(int)) - { - state.Stack.Push(Expression.Convert(innerUnary.Operand, nullableType)); - return; - } - - // Case 3: operand is int and enum underlying is long - convert int directly - if (operand.Type == typeof(int) && - (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) - { - state.Stack.Push(Expression.Convert(operand, nullableType)); - return; - } + state.Stack.Push(simplified); + return; } // Use the adjusted argument @@ -86,4 +60,72 @@ public void Process(ProcessorState state, Instruction instruction) state.Stack.Push(Expression.New(constructor, arguments)); } } + + static Expression TrySimplifyForNullableEnum(Expression expr, Type enumType, Type enumUnderlyingType, Type nullableEnumType) + { + // Check if expr is Convert(x, Enum) + if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert && unary.Type == enumType) + { + var operand = unary.Operand; + + // Case 1: operand is the enum's underlying type + if (operand.Type == enumUnderlyingType) + { + // Check if we can simplify further - unwrap Convert(int, long) for long enums + if (operand is UnaryExpression underlyingUnary && + underlyingUnary.NodeType == ExpressionType.Convert && + underlyingUnary.Operand.Type == typeof(int) && + (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) + { + // Convert int directly to Nullable + return Expression.Convert(underlyingUnary.Operand, nullableEnumType); + } + + // Otherwise convert the underlying type value directly to nullable + return Expression.Convert(operand, nullableEnumType); + } + + // Case 2: operand is Convert(x, underlyingType) where x is int + // This happens for byte/short enums: Convert(Convert(int, byte), Enum) + // Skip to: Convert(int, Nullable) + if (operand is UnaryExpression innerUnary && + innerUnary.NodeType == ExpressionType.Convert && + innerUnary.Type == enumUnderlyingType && + innerUnary.Operand.Type == typeof(int)) + { + return Expression.Convert(innerUnary.Operand, nullableEnumType); + } + + // Case 3: operand is int and enum underlying is long/ulong - convert int directly + if (operand.Type == typeof(int) && + (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) + { + return Expression.Convert(operand, nullableEnumType); + } + + // Case 3b: operand is Convert(int, long) and enum underlying is long + // This happens when int is explicitly converted to long before enum + if (operand is UnaryExpression longUnary && + longUnary.NodeType == ExpressionType.Convert && + (longUnary.Type == typeof(long) || longUnary.Type == typeof(ulong)) && + longUnary.Operand.Type == typeof(int) && + (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) + { + // Convert int directly to Nullable, skip the long intermediate + return Expression.Convert(longUnary.Operand, nullableEnumType); + } + + // Case 4: operand is int and enum underlying is byte/short - convert int directly + // Expression.Convert(int, ByteEnum) is valid and handles the intermediate conversion + // But when creating Nullable, we want Convert(int, Nullable) directly + if (operand.Type == typeof(int) && + (enumUnderlyingType == typeof(byte) || enumUnderlyingType == typeof(sbyte) || + enumUnderlyingType == typeof(short) || enumUnderlyingType == typeof(ushort))) + { + return Expression.Convert(operand, nullableEnumType); + } + } + + return null; + } } \ No newline at end of file From feca7fce7f6d3bb2e576143db6c16eec29f012ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 08:10:21 +0000 Subject: [PATCH 14/18] Fix HasFlag and most enum conversion issues - 493/521 tests passing (2 remaining) Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 63 ++++++++- .../Processors/CallProcessor.cs | 130 ++++++++++++++++++ .../Processors/ConvertProcessor.cs | 27 +++- 3 files changed, 217 insertions(+), 3 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 112edb0a..31a4b3ae 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -402,6 +402,11 @@ internal static Expression AdjustType(Expression expression, Type type) if (expression.Type.IsEnum) { var underlyingType = expression.Type.GetEnumUnderlyingType(); + // If target is System.Enum (e.g., for HasFlag), convert enum directly + if (type == typeof(Enum)) + { + return Expression.Convert(expression, type); + } // If enum's underlying type matches target, convert directly if (underlyingType == type) { @@ -414,7 +419,8 @@ internal static Expression AdjustType(Expression expression, Type type) { return Expression.Convert(expression, type); } - // If target is long and enum has long underlying type + // If target is long and enum has long/ulong underlying type, convert directly + // This handles explicit casts like (long)longEnum if (type == typeof(long) && (underlyingType == typeof(long) || underlyingType == typeof(ulong))) { return Expression.Convert(expression, type); @@ -426,11 +432,53 @@ internal static Expression AdjustType(Expression expression, Type type) } } + // Handle enum that has already been converted to underlying type + // e.g., Convert(Convert(byteEnum, byte), long) -> Convert(byteEnum, long) + // This typically happens when byte enum needs to be passed as long parameter + if (expression is UnaryExpression unaryConv && + unaryConv.NodeType == ExpressionType.Convert && + unaryConv.Operand.Type.IsEnum) + { + var enumType = unaryConv.Operand.Type; + var enumUnderlyingType = enumType.GetEnumUnderlyingType(); + + // If we're converting from the enum's underlying type to another type, + // convert directly from the enum instead to avoid double conversion + if (unaryConv.Type == enumUnderlyingType) + { + // Check if target type is compatible with direct enum conversion + if (type == typeof(long) && (enumUnderlyingType == typeof(byte) || enumUnderlyingType == typeof(sbyte) || + enumUnderlyingType == typeof(short) || enumUnderlyingType == typeof(ushort) || + enumUnderlyingType == typeof(int) || enumUnderlyingType == typeof(uint))) + { + // Convert enum directly to long + return Expression.Convert(unaryConv.Operand, type); + } + if (type == typeof(int) && (enumUnderlyingType == typeof(byte) || enumUnderlyingType == typeof(sbyte) || + enumUnderlyingType == typeof(short) || enumUnderlyingType == typeof(ushort))) + { + // Convert enum directly to int + return Expression.Convert(unaryConv.Operand, type); + } + } + } + // Handle conversions between signed/unsigned long when expression is not enum // This can happen after enum is converted to its underlying type if ((type == typeof(long) && expression.Type == typeof(ulong)) || (type == typeof(ulong) && expression.Type == typeof(long))) { + // Check if this is Convert(Convert(enum, underlyingType), ulong) -> Convert(enum, long) + if (expression is UnaryExpression ulongConv && + ulongConv.NodeType == ExpressionType.Convert && + ulongConv.Operand is UnaryExpression innerConv && + innerConv.NodeType == ExpressionType.Convert && + innerConv.Operand.Type.IsEnum) + { + // Convert enum directly to target type + return Expression.Convert(innerConv.Operand, type); + } + return Expression.Convert(expression, type); } @@ -552,7 +600,18 @@ internal static Expression[] GetArguments(ProcessorState state, MethodBase m, ou var argument = state.Stack.Pop(); var parameter = parameterInfos[i]; var parameterType = parameter.ParameterType; - args[i] = AdjustType(argument, parameterType.IsByRef ? parameterType.GetElementType() : parameterType); + var targetType = parameterType.IsByRef ? parameterType.GetElementType() : parameterType; + + // Special case for Enum.HasFlag - don't convert to System.Enum here + // Let CallProcessor handle it after inferring the specific enum type + if (targetType == typeof(Enum) && m.Name == "HasFlag") + { + args[i] = argument; + } + else + { + args[i] = AdjustType(argument, targetType); + } addresses[i] = argument; } diff --git a/src/DelegateDecompiler/Processors/CallProcessor.cs b/src/DelegateDecompiler/Processors/CallProcessor.cs index 32402635..6a947ebd 100644 --- a/src/DelegateDecompiler/Processors/CallProcessor.cs +++ b/src/DelegateDecompiler/Processors/CallProcessor.cs @@ -50,6 +50,85 @@ static void Call(ProcessorState state, MethodInfo m) static Expression BuildMethodCallExpression(MethodInfo m, Address instance, Expression[] arguments, Address[] addresses) { + // Special handling for Enum.HasFlag before other processing + // HasFlag requires both instance and argument to be enum types, not underlying types + if (m.Name == "HasFlag" && m.DeclaringType == typeof(Enum)) + { + var instanceExpr = instance.Expression; + var argument = arguments.Length > 0 ? arguments[0] : null; + + // Try to determine the enum type from whichever one is already an enum + Type enumType = null; + if (instanceExpr != null && instanceExpr.Type.IsEnum) + { + enumType = instanceExpr.Type; + } + else if (argument != null && argument.Type.IsEnum) + { + enumType = argument.Type; + } + else + { + // Neither is typed as enum yet - try to infer from underlying expressions + enumType = TryInferEnumTypeFromExpression(instanceExpr) ?? TryInferEnumTypeFromExpression(argument); + } + + // If we found an enum type, ensure both instance and argument use it + if (enumType != null) + { + if (instanceExpr != null && !instanceExpr.Type.IsEnum) + { + // Check if it's a Convert(ConstantExpression, underlyingType) pattern + if (instanceExpr is UnaryExpression unaryInst && + unaryInst.NodeType == ExpressionType.Convert && + unaryInst.Operand is ConstantExpression innerConst) + { + // Convert constant directly to enum + instanceExpr = Expression.Constant(Enum.ToObject(enumType, innerConst.Value), enumType); + instance.Expression = instanceExpr; + } + else if (instanceExpr is ConstantExpression constInst) + { + instanceExpr = Expression.Constant(Enum.ToObject(enumType, constInst.Value), enumType); + instance.Expression = instanceExpr; + } + else if (instanceExpr.Type == enumType.GetEnumUnderlyingType()) + { + instanceExpr = Expression.Convert(instanceExpr, enumType); + instance.Expression = instanceExpr; + } + } + + if (argument != null && !argument.Type.IsEnum) + { + // Check if it's a Convert(ConstantExpression, underlyingType) pattern + if (argument is UnaryExpression unaryArg && + unaryArg.NodeType == ExpressionType.Convert && + unaryArg.Operand is ConstantExpression innerConstArg) + { + // Convert to enum, then to System.Enum for the method parameter + var enumConst = Expression.Constant(Enum.ToObject(enumType, innerConstArg.Value), enumType); + arguments[0] = Expression.Convert(enumConst, typeof(Enum)); + } + else if (argument is ConstantExpression constArg) + { + var enumConst = Expression.Constant(Enum.ToObject(enumType, constArg.Value), enumType); + arguments[0] = Expression.Convert(enumConst, typeof(Enum)); + } + else if (argument.Type == enumType.GetEnumUnderlyingType()) + { + // Convert to specific enum type first, then to System.Enum + arguments[0] = Expression.Convert(Expression.Convert(argument, enumType), typeof(Enum)); + } + } + else if (argument != null && argument.Type.IsEnum && argument.Type != typeof(Enum)) + { + // Argument is already a specific enum type, convert to System.Enum + arguments[0] = Expression.Convert(argument, typeof(Enum)); + } + } + } + if (m.Name == "Add" && instance.Expression != null && typeof(IEnumerable).IsAssignableFrom(instance.Type)) { switch (instance.Expression) @@ -197,6 +276,21 @@ innerMember.Member is FieldInfo innerField && // Use the enum type directly, unwrap the Convert instanceExpr = innerMember; } + // Check if it's a constant that should be an enum constant + else if (instanceExpr is ConstantExpression constExpr) + { + // Try to infer enum type from the method context or arguments + // For HasFlag, the argument should give us the enum type + if (m.Name == "HasFlag" && arguments.Length > 0) + { + var argType = arguments[0].Type; + if (argType.IsEnum) + { + // Convert constant to this enum type + instanceExpr = Expression.Constant(Enum.ToObject(argType, constExpr.Value), argType); + } + } + } } } @@ -206,6 +300,42 @@ innerMember.Member is FieldInfo innerField && return Expression.Call(null, m, arguments); } + static Type TryInferEnumTypeFromExpression(Expression expr) + { + if (expr == null) + return null; + + // If it's already an enum, return its type + if (expr.Type.IsEnum) + return expr.Type; + + // If it's a Convert from enum to underlying type, get the enum type + if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + { + if (unary.Operand.Type.IsEnum) + return unary.Operand.Type; + + // Recursively check operand + return TryInferEnumTypeFromExpression(unary.Operand); + } + + // If it's a member access (field or property), check if it's enum typed + if (expr is MemberExpression memberExpr) + { + var fieldOrPropType = memberExpr.Member.FieldOrPropertyType(); + if (fieldOrPropType.IsEnum) + return fieldOrPropType; + } + + // If it's a parameter, check if parameter type is enum + if (expr is ParameterExpression paramExpr && paramExpr.Type.IsEnum) + { + return paramExpr.Type; + } + + return null; + } + static bool TryParseOperator(MethodInfo m, out ExpressionType type) { switch (m.Name) diff --git a/src/DelegateDecompiler/Processors/ConvertProcessor.cs b/src/DelegateDecompiler/Processors/ConvertProcessor.cs index 0f5ba4ea..e564e9b7 100644 --- a/src/DelegateDecompiler/Processors/ConvertProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertProcessor.cs @@ -25,9 +25,34 @@ public void Process(ProcessorState state, Instruction instruction) var val = state.Stack.Pop(); Expression expr = val; + // Optimize Convert(Convert(X, intermediate), ulong/long) -> Convert(X, long) + // This happens when byte enum is cast to long: enum -> byte -> ulong -> long + // We want to skip all intermediate conversions and go straight from enum to long + if ((targetType == typeof(long) || targetType == typeof(ulong)) && + expr is UnaryExpression outerUnary && + outerUnary.NodeType == ExpressionType.Convert) + { + // Traverse down the conversion chain to find the original expression + Expression current = outerUnary.Operand; + while (current is UnaryExpression unaryChain && unaryChain.NodeType == ExpressionType.Convert) + { + // If we find an enum at any level, use it + if (unaryChain.Operand.Type.IsEnum) + { + expr = unaryChain.Operand; + break; + } + current = unaryChain.Operand; + } + // Also check if the final expression is an enum + if (current.Type.IsEnum) + { + expr = current; + } + } // Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X) // This happens when operations like NOT return int, IL converts to byte, then to enum - if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + else if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) { var operand = unary.Operand; // Skip intermediate conversions from int to byte/short when going to another type From f236a931f18a909753c491f2c679f1ab21ac09cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 08:13:23 +0000 Subject: [PATCH 15/18] Fix double conversion in enum to long cast - 520/521 tests passing! Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 23 +++++++++++++++++++ .../Processors/ConvertProcessor.cs | 17 ++++++-------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 31a4b3ae..04f7fae9 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -399,6 +399,29 @@ internal static Expression AdjustType(Expression expression, Type type) if (!type.IsAssignableFrom(expression.Type)) { + // Handle double conversions like Convert(Convert(enum, ulong), long) -> Convert(enum, long) + // This happens when byte enum is cast to long + if ((type == typeof(long) || type == typeof(int)) && + expression is UnaryExpression outerConv && + outerConv.NodeType == ExpressionType.Convert) + { + // Check if there's an enum somewhere in the conversion chain + var current = outerConv.Operand; + while (current is UnaryExpression innerConv && innerConv.NodeType == ExpressionType.Convert) + { + if (innerConv.Operand.Type.IsEnum) + { + // Found an enum - convert it directly to the target type + return Expression.Convert(innerConv.Operand, type); + } + current = innerConv.Operand; + } + if (current.Type.IsEnum) + { + return Expression.Convert(current, type); + } + } + if (expression.Type.IsEnum) { var underlyingType = expression.Type.GetEnumUnderlyingType(); diff --git a/src/DelegateDecompiler/Processors/ConvertProcessor.cs b/src/DelegateDecompiler/Processors/ConvertProcessor.cs index e564e9b7..a728e0ca 100644 --- a/src/DelegateDecompiler/Processors/ConvertProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertProcessor.cs @@ -25,18 +25,15 @@ public void Process(ProcessorState state, Instruction instruction) var val = state.Stack.Pop(); Expression expr = val; - // Optimize Convert(Convert(X, intermediate), ulong/long) -> Convert(X, long) - // This happens when byte enum is cast to long: enum -> byte -> ulong -> long - // We want to skip all intermediate conversions and go straight from enum to long - if ((targetType == typeof(long) || targetType == typeof(ulong)) && - expr is UnaryExpression outerUnary && - outerUnary.NodeType == ExpressionType.Convert) + // Optimize conversions to long/ulong - skip intermediate conversions from enums + // This handles: enum -> byte -> ulong -> long, simplifying to: enum -> long + if (targetType == typeof(long) || targetType == typeof(ulong)) { - // Traverse down the conversion chain to find the original expression - Expression current = outerUnary.Operand; + // Traverse down the conversion chain to find if there's an enum + Expression current = expr; while (current is UnaryExpression unaryChain && unaryChain.NodeType == ExpressionType.Convert) { - // If we find an enum at any level, use it + // If we find an enum, use it directly if (unaryChain.Operand.Type.IsEnum) { expr = unaryChain.Operand; @@ -44,7 +41,7 @@ expr is UnaryExpression outerUnary && } current = unaryChain.Operand; } - // Also check if the final expression is an enum + // Also check if current itself is an enum (in case no Convert wrapper) if (current.Type.IsEnum) { expr = current; From 34fc8622b5ebfd55d1238e61686fba00a7a7fe29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 08:16:08 +0000 Subject: [PATCH 16/18] Add enum check to coalesce optimizer - 520/521 tests passing (99.8%) Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/OptimizeExpressionVisitor.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs index 3bd0c3e8..e4ff7831 100644 --- a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs +++ b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs @@ -58,6 +58,15 @@ protected override Expression VisitConditional(ConditionalExpression node) return expression; } + // Don't create Coalesce for nullable enum conversions to avoid breaking expected expressions + // For nullable enums, keep the conditional expression as-is + var innerType = expression.Type.IsNullableType() ? Nullable.GetUnderlyingType(expression.Type) : expression.Type; + if (innerType != null && innerType.IsEnum) + { + // Keep as conditional for enum types + return node.Update(test, ifTrue, ifFalse); + } + return Expression.Coalesce(expression, ifFalse); } From 911b5842664fcbc0b90fa73a4d42107d999d6bec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 08:37:02 +0000 Subject: [PATCH 17/18] WIP: Refactor optimizations from Processors to OptimizeExpressionVisitor (105/108 tests passing) Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- .../OptimizeExpressionVisitor.cs | 106 ++++++++++++++++ src/DelegateDecompiler/Processor.cs | 44 +------ .../Processors/BoxProcessor.cs | 18 +-- .../Processors/ConvertProcessor.cs | 42 +------ .../Processors/ConvertTypeProcessor.cs | 18 +-- .../Processors/NewobjProcessor.cs | 113 +----------------- 6 files changed, 119 insertions(+), 222 deletions(-) diff --git a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs index e4ff7831..8d03ea40 100644 --- a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs +++ b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs @@ -381,6 +381,112 @@ node.Operand is BinaryExpression binary && return Visit(binary); } + // Optimize enum conversion chains BEFORE visiting operand + // This ensures we can optimize Convert(Convert(x, Enum), Nullable) patterns + if (node.NodeType == ExpressionType.Convert) + { + var targetType = node.Type; + + // Check for nullable enum optimization before visiting operand + if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var underlyingType = Nullable.GetUnderlyingType(targetType); + if (underlyingType != null && underlyingType.IsEnum && + node.Operand is UnaryExpression enumConv && + enumConv.NodeType == ExpressionType.Convert && + enumConv.Type == underlyingType) + { + var innerOperand = enumConv.Operand; + var enumUnderlyingType = underlyingType.GetEnumUnderlyingType(); + + // Unwrap multiple layers of conversions to find the source + Expression sourceOperand = innerOperand; + while (sourceOperand is UnaryExpression deepConv && deepConv.NodeType == ExpressionType.Convert) + { + sourceOperand = deepConv.Operand; + } + + // If we found something that returns the right type, convert it directly + if (sourceOperand.Type == typeof(int) || + sourceOperand.Type == enumUnderlyingType || + (sourceOperand.Type == typeof(int) && + (enumUnderlyingType == typeof(byte) || enumUnderlyingType == typeof(sbyte) || + enumUnderlyingType == typeof(short) || enumUnderlyingType == typeof(ushort) || + enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong)))) + { + // Visit the source operand and convert to nullable enum directly + return Expression.Convert(Visit(sourceOperand), targetType); + } + } + } + + // Now visit operand for other cases + var operand = Visit(node.Operand); + + // Optimize Convert(Convert(enum, byte/short/long), int/long) -> Convert(enum, int/long) + // This happens when enums are converted to underlying type then to int for operations + if (operand is UnaryExpression innerConv && innerConv.NodeType == ExpressionType.Convert) + { + var innerOperand = innerConv.Operand; + + // Case 1: Convert(Convert(enum, underlyingType), int/long) -> Convert(enum, int/long) + if (innerOperand.Type.IsEnum) + { + var underlyingType = innerOperand.Type.GetEnumUnderlyingType(); + if (innerConv.Type == underlyingType && + (targetType == typeof(int) || targetType == typeof(long) || targetType == typeof(ulong))) + { + return Expression.Convert(innerOperand, targetType); + } + } + + // Case 2: Convert(Convert(int, byte/short), X) -> Convert(int, X) + // This happens with operations like NOT that return int, IL converts to byte, then to target + if (innerOperand.Type == typeof(int) && + (innerConv.Type == typeof(byte) || innerConv.Type == typeof(sbyte) || + innerConv.Type == typeof(short) || innerConv.Type == typeof(ushort))) + { + return Expression.Convert(innerOperand, targetType); + } + + // Case 3: Convert(Convert(Convert(enum, byte), long), enum) -> Convert(enum, enum) (for long-based enums in arrays) + // Traverse deeper to find the original enum + if (targetType.IsEnum && innerOperand is UnaryExpression deeperConv && deeperConv.NodeType == ExpressionType.Convert) + { + if (deeperConv.Operand.Type.IsEnum) + { + // Found the original enum - convert directly + return Expression.Convert(deeperConv.Operand, targetType); + } + } + } + + // Optimize Convert(intConstant, long/ulong) -> longConstant + if (operand is ConstantExpression constant && + constant.Type == typeof(int) && + (targetType == typeof(long) || targetType == typeof(ulong))) + { + var longValue = Convert.ToInt64(constant.Value); + return Expression.Constant(longValue, targetType); + } + + // Optimize Convert(Convert(intConstant, long), enum) -> enum constant + if (targetType.IsEnum && + operand is UnaryExpression constConv && + constConv.NodeType == ExpressionType.Convert && + constConv.Operand is ConstantExpression innerConst) + { + return Expression.Constant(Enum.ToObject(targetType, innerConst.Value)); + } + + // The nullable enum optimization was moved before Visit(operand) above + + if (operand != node.Operand) + { + return Expression.Convert(operand, targetType); + } + } + return base.VisitUnary(node); } diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 04f7fae9..303898c3 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -35,20 +35,6 @@ public static Expression Process(ControlFlowGraph cfg, VariableInfo[] locals, IL if (!returnType.IsAssignableFrom(ex.Type) && returnType != typeof(void)) { - // Optimize Convert(Convert(int, byte/short), enum) -> Convert(int, enum) - // This happens when operations like NOT return int, IL converts to byte, then to enum at return - if (returnType.IsEnum && ex is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) - { - var operand = unary.Operand; - if (operand.Type == typeof(int) && - (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || - unary.Type == typeof(short) || unary.Type == typeof(ushort))) - { - // Skip the intermediate conversion, convert int directly to enum - return Expression.Convert(operand, returnType); - } - } - return Expression.Convert(ex, returnType); } @@ -259,9 +245,8 @@ internal static BinaryExpression MakeBinaryExpression(Address left, Address righ internal static Expression ConvertEnumExpressionToInt(Expression expression) { - // If the expression is already an int, return as-is - if (expression.Type == typeof(int)) - return expression; + // Required: Convert enums to int/long to prevent type mismatches in Expression.MakeBinary + // Optimizations are handled in OptimizeExpressionVisitor if (expression.Type.IsEnum) { @@ -276,7 +261,6 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) // replace it with a direct conversion to int (or underlying type for long enums) to avoid double conversion if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) { - // Check if we're converting an enum to its underlying type var operand = unary.Operand; if (operand.Type.IsEnum && operand.Type.GetEnumUnderlyingType() == expression.Type) @@ -287,30 +271,6 @@ internal static Expression ConvertEnumExpressionToInt(Expression expression) // Replace Convert(enumValue, underlyingType) with Convert(enumValue, int) return Expression.Convert(operand, typeof(int)); } - - // Optimize Convert(Convert(X, byte/short), Y) when going through unnecessary intermediate conversions - // This happens with operations like NOT that return int but IL converts to byte before enum - if (operand is UnaryExpression innerUnary && innerUnary.NodeType == ExpressionType.Convert) - { - var innerOperand = innerUnary.Operand; - // If we're converting from int through byte/short, skip the intermediate conversion - if (innerOperand.Type == typeof(int) && - (innerUnary.Type == typeof(byte) || innerUnary.Type == typeof(sbyte) || - innerUnary.Type == typeof(short) || innerUnary.Type == typeof(ushort))) - { - // Keep just the inner operand, let the outer conversion happen - return Expression.Convert(innerOperand, expression.Type); - } - } - - // Optimize Convert(intConstant, long) to a direct long constant - if (operand is ConstantExpression constant && - constant.Type == typeof(int) && - (expression.Type == typeof(long) || expression.Type == typeof(ulong))) - { - var longValue = Convert.ToInt64(constant.Value); - return Expression.Constant(longValue, expression.Type); - } } return expression; diff --git a/src/DelegateDecompiler/Processors/BoxProcessor.cs b/src/DelegateDecompiler/Processors/BoxProcessor.cs index 5c41afb3..ed312883 100644 --- a/src/DelegateDecompiler/Processors/BoxProcessor.cs +++ b/src/DelegateDecompiler/Processors/BoxProcessor.cs @@ -23,30 +23,18 @@ static Expression Box(Expression expression, Type type) if (expression.Type == type) return expression; + // Required for correctness: convert constant to enum if (expression is ConstantExpression constantExpression) { if (type.IsEnum) return Expression.Constant(Enum.ToObject(type, constantExpression.Value)); } + // Required for correctness: convert enum to boxed type if (expression.Type.IsEnum) return Expression.Convert(expression, type); - // Optimize Convert(Convert(int, byte/short), enum) -> Convert(int, enum) - // This happens when operations like NOT return int, IL converts to byte, then boxes to enum - if (type.IsEnum && expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) - { - var operand = unary.Operand; - // Skip intermediate conversions from int to byte/short when boxing to enum - if (operand.Type == typeof(int) && - (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || - unary.Type == typeof(short) || unary.Type == typeof(ushort))) - { - // Box the int directly to the enum - return Expression.Convert(operand, type); - } - } - + // No optimizations here - they're handled in OptimizeExpressionVisitor return expression; } } \ No newline at end of file diff --git a/src/DelegateDecompiler/Processors/ConvertProcessor.cs b/src/DelegateDecompiler/Processors/ConvertProcessor.cs index a728e0ca..37dc5f8d 100644 --- a/src/DelegateDecompiler/Processors/ConvertProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertProcessor.cs @@ -23,45 +23,7 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { var val = state.Stack.Pop(); - Expression expr = val; - - // Optimize conversions to long/ulong - skip intermediate conversions from enums - // This handles: enum -> byte -> ulong -> long, simplifying to: enum -> long - if (targetType == typeof(long) || targetType == typeof(ulong)) - { - // Traverse down the conversion chain to find if there's an enum - Expression current = expr; - while (current is UnaryExpression unaryChain && unaryChain.NodeType == ExpressionType.Convert) - { - // If we find an enum, use it directly - if (unaryChain.Operand.Type.IsEnum) - { - expr = unaryChain.Operand; - break; - } - current = unaryChain.Operand; - } - // Also check if current itself is an enum (in case no Convert wrapper) - if (current.Type.IsEnum) - { - expr = current; - } - } - // Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X) - // This happens when operations like NOT return int, IL converts to byte, then to enum - else if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) - { - var operand = unary.Operand; - // Skip intermediate conversions from int to byte/short when going to another type - if (operand.Type == typeof(int) && - (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || - unary.Type == typeof(short) || unary.Type == typeof(ushort))) - { - // Use the int directly, skip the intermediate byte/short conversion - expr = operand; - } - } - - state.Stack.Push(Expression.Convert(expr, targetType)); + // No optimizations here - they're handled in OptimizeExpressionVisitor + state.Stack.Push(Expression.Convert(val, targetType)); } } \ No newline at end of file diff --git a/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs b/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs index 024acaf6..f586b106 100644 --- a/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs @@ -16,24 +16,10 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { var address = state.Stack.Pop(); - Expression expression = address; var targetType = (Type)instruction.Operand; - // Optimize Convert(Convert(int, byte/short), X) -> Convert(int, X) - // This happens when operations like NOT return int, IL converts to byte, then casts to enum - if (expression is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) - { - var operand = unary.Operand; - // Skip intermediate conversions from int to byte/short - if (operand.Type == typeof(int) && - (unary.Type == typeof(byte) || unary.Type == typeof(sbyte) || - unary.Type == typeof(short) || unary.Type == typeof(ushort))) - { - expression = operand; - } - } - - state.Stack.Push(Expression.Convert(expression, targetType)); + // No optimizations here - they're handled in OptimizeExpressionVisitor + state.Stack.Push(Expression.Convert(address, targetType)); } } } diff --git a/src/DelegateDecompiler/Processors/NewobjProcessor.cs b/src/DelegateDecompiler/Processors/NewobjProcessor.cs index 4c55c191..9e27caa6 100644 --- a/src/DelegateDecompiler/Processors/NewobjProcessor.cs +++ b/src/DelegateDecompiler/Processors/NewobjProcessor.cs @@ -17,115 +17,10 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { var constructor = (ConstructorInfo)instruction.Operand; + var arguments = Processor.GetArguments(state, constructor); - if (constructor.DeclaringType.IsNullableType() && constructor.GetParameters().Length == 1) - { - // For Nullable constructor, we want to simplify conversions - // Instead of Convert(Convert(int, Enum), Nullable), we want Convert(int, Nullable) - // But we need to get the argument first to apply optimizations - var parameters = constructor.GetParameters(); - var argument = state.Stack.Pop(); - var nullableType = constructor.DeclaringType; - var innerType = System.Nullable.GetUnderlyingType(nullableType); - - // If inner type is an enum, try to optimize the conversion - if (innerType.IsEnum) - { - var enumUnderlyingType = innerType.GetEnumUnderlyingType(); - // Use AdjustType but intercept if it tries to create intermediate conversions - var adjusted = Processor.AdjustType(argument, parameters[0].ParameterType); - - // Try to unwrap unnecessary conversions for nullable enum creation - var simplified = TrySimplifyForNullableEnum(adjusted, innerType, enumUnderlyingType, nullableType); - if (simplified != null) - { - state.Stack.Push(simplified); - return; - } - - // Use the adjusted argument - state.Stack.Push(Expression.Convert(adjusted, nullableType)); - } - else - { - // Non-enum nullable, use GetArguments normally - state.Stack.Push(argument); - var arguments = Processor.GetArguments(state, constructor); - state.Stack.Push(Expression.Convert(arguments[0], nullableType)); - } - } - else - { - var arguments = Processor.GetArguments(state, constructor); - state.Stack.Push(Expression.New(constructor, arguments)); - } - } - - static Expression TrySimplifyForNullableEnum(Expression expr, Type enumType, Type enumUnderlyingType, Type nullableEnumType) - { - // Check if expr is Convert(x, Enum) - if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert && unary.Type == enumType) - { - var operand = unary.Operand; - - // Case 1: operand is the enum's underlying type - if (operand.Type == enumUnderlyingType) - { - // Check if we can simplify further - unwrap Convert(int, long) for long enums - if (operand is UnaryExpression underlyingUnary && - underlyingUnary.NodeType == ExpressionType.Convert && - underlyingUnary.Operand.Type == typeof(int) && - (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) - { - // Convert int directly to Nullable - return Expression.Convert(underlyingUnary.Operand, nullableEnumType); - } - - // Otherwise convert the underlying type value directly to nullable - return Expression.Convert(operand, nullableEnumType); - } - - // Case 2: operand is Convert(x, underlyingType) where x is int - // This happens for byte/short enums: Convert(Convert(int, byte), Enum) - // Skip to: Convert(int, Nullable) - if (operand is UnaryExpression innerUnary && - innerUnary.NodeType == ExpressionType.Convert && - innerUnary.Type == enumUnderlyingType && - innerUnary.Operand.Type == typeof(int)) - { - return Expression.Convert(innerUnary.Operand, nullableEnumType); - } - - // Case 3: operand is int and enum underlying is long/ulong - convert int directly - if (operand.Type == typeof(int) && - (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) - { - return Expression.Convert(operand, nullableEnumType); - } - - // Case 3b: operand is Convert(int, long) and enum underlying is long - // This happens when int is explicitly converted to long before enum - if (operand is UnaryExpression longUnary && - longUnary.NodeType == ExpressionType.Convert && - (longUnary.Type == typeof(long) || longUnary.Type == typeof(ulong)) && - longUnary.Operand.Type == typeof(int) && - (enumUnderlyingType == typeof(long) || enumUnderlyingType == typeof(ulong))) - { - // Convert int directly to Nullable, skip the long intermediate - return Expression.Convert(longUnary.Operand, nullableEnumType); - } - - // Case 4: operand is int and enum underlying is byte/short - convert int directly - // Expression.Convert(int, ByteEnum) is valid and handles the intermediate conversion - // But when creating Nullable, we want Convert(int, Nullable) directly - if (operand.Type == typeof(int) && - (enumUnderlyingType == typeof(byte) || enumUnderlyingType == typeof(sbyte) || - enumUnderlyingType == typeof(short) || enumUnderlyingType == typeof(ushort))) - { - return Expression.Convert(operand, nullableEnumType); - } - } - - return null; + // No special handling needed here - OptimizeExpressionVisitor converts + // new Nullable(value) to Convert(value, Nullable) + state.Stack.Push(Expression.New(constructor, arguments)); } } \ No newline at end of file From 5105bad0b5522730b61e7a32f8d717495d94bf3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 12:07:53 +0000 Subject: [PATCH 18/18] Simplify CallProcessor and move HasFlag logic to minimal required code - 69/71 tests passing Co-authored-by: hazzik <144791+hazzik@users.noreply.github.com> --- src/DelegateDecompiler/Processor.cs | 25 ++- .../Processors/BoxProcessor.cs | 2 - .../Processors/CallProcessor.cs | 211 ++++++------------ .../Processors/ConvertProcessor.cs | 1 - .../Processors/ConvertTypeProcessor.cs | 1 - 5 files changed, 94 insertions(+), 146 deletions(-) diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 303898c3..b5114a0a 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -298,6 +298,25 @@ internal static Expression AdjustType(Expression expression, Type type) return expression; } + if (type == typeof(Enum)) + { + if (expression.Type.IsEnum) + { + return Expression.Convert(expression, typeof(Enum)); + } + + // Unwrap Convert expressions to find enum + var unwrapped = expression; + while (unwrapped is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) + { + if (unary.Operand.Type.IsEnum) + { + return Expression.Convert(unary.Operand, typeof(Enum)); + } + unwrapped = unary.Operand; + } + } + if (expression is ConstantExpression constant) { if (constant.Value == null) @@ -585,9 +604,9 @@ internal static Expression[] GetArguments(ProcessorState state, MethodBase m, ou var parameterType = parameter.ParameterType; var targetType = parameterType.IsByRef ? parameterType.GetElementType() : parameterType; - // Special case for Enum.HasFlag - don't convert to System.Enum here - // Let CallProcessor handle it after inferring the specific enum type - if (targetType == typeof(Enum) && m.Name == "HasFlag") + // For HasFlag, skip type adjustment for System.Enum parameter + // CallProcessor will handle it with context from the instance + if (targetType == typeof(Enum) && m.Name == "HasFlag" && m.DeclaringType == typeof(Enum)) { args[i] = argument; } diff --git a/src/DelegateDecompiler/Processors/BoxProcessor.cs b/src/DelegateDecompiler/Processors/BoxProcessor.cs index ed312883..253870aa 100644 --- a/src/DelegateDecompiler/Processors/BoxProcessor.cs +++ b/src/DelegateDecompiler/Processors/BoxProcessor.cs @@ -30,11 +30,9 @@ static Expression Box(Expression expression, Type type) return Expression.Constant(Enum.ToObject(type, constantExpression.Value)); } - // Required for correctness: convert enum to boxed type if (expression.Type.IsEnum) return Expression.Convert(expression, type); - // No optimizations here - they're handled in OptimizeExpressionVisitor return expression; } } \ No newline at end of file diff --git a/src/DelegateDecompiler/Processors/CallProcessor.cs b/src/DelegateDecompiler/Processors/CallProcessor.cs index 6a947ebd..46c85dcf 100644 --- a/src/DelegateDecompiler/Processors/CallProcessor.cs +++ b/src/DelegateDecompiler/Processors/CallProcessor.cs @@ -50,82 +50,100 @@ static void Call(ProcessorState state, MethodInfo m) static Expression BuildMethodCallExpression(MethodInfo m, Address instance, Expression[] arguments, Address[] addresses) { - // Special handling for Enum.HasFlag before other processing - // HasFlag requires both instance and argument to be enum types, not underlying types if (m.Name == "HasFlag" && m.DeclaringType == typeof(Enum)) { var instanceExpr = instance.Expression; var argument = arguments.Length > 0 ? arguments[0] : null; - // Try to determine the enum type from whichever one is already an enum + // Unwrap Convert expressions on instance to get to the actual enum + // Stop if we find an enum or if we've unwrapped to a constant (which needs to be converted to enum) + while (instanceExpr is UnaryExpression unaryInst && + unaryInst.NodeType == ExpressionType.Convert && + !(unaryInst.Operand is ConstantExpression)) + { + if (unaryInst.Operand.Type.IsEnum) + { + instanceExpr = unaryInst.Operand; + break; + } + instanceExpr = unaryInst.Operand; + } + + // If instance is a constant wrapped in a convert, we need to figure out what enum type it should be + // Check the argument to infer the enum type Type enumType = null; - if (instanceExpr != null && instanceExpr.Type.IsEnum) + if (instanceExpr.Type.IsEnum) { enumType = instanceExpr.Type; } - else if (argument != null && argument.Type.IsEnum) + else if (argument != null) { - enumType = argument.Type; + // Try to infer from argument + var argCheck = argument; + while (argCheck is UnaryExpression unaryCheck && unaryCheck.NodeType == ExpressionType.Convert) + { + if (unaryCheck.Operand.Type.IsEnum) + { + enumType = unaryCheck.Operand.Type; + break; + } + argCheck = unaryCheck.Operand; + } + if (enumType == null && argCheck.Type.IsEnum) + { + enumType = argCheck.Type; + } } - else + + // Convert instance to enum type if needed + if (enumType != null && !instanceExpr.Type.IsEnum) { - // Neither is typed as enum yet - try to infer from underlying expressions - enumType = TryInferEnumTypeFromExpression(instanceExpr) ?? TryInferEnumTypeFromExpression(argument); + if (instanceExpr is UnaryExpression unaryInst && + unaryInst.NodeType == ExpressionType.Convert && + unaryInst.Operand is ConstantExpression constInst) + { + instanceExpr = Expression.Constant(Enum.ToObject(enumType, constInst.Value), enumType); + } + else if (instanceExpr is ConstantExpression constExpr) + { + instanceExpr = Expression.Constant(Enum.ToObject(enumType, constExpr.Value), enumType); + } } + instance.Expression = instanceExpr; - // If we found an enum type, ensure both instance and argument use it - if (enumType != null) + // Convert argument properly + if (argument != null) { - if (instanceExpr != null && !instanceExpr.Type.IsEnum) + // If argument is already an enum, convert to System.Enum + if (argument.Type.IsEnum) { - // Check if it's a Convert(ConstantExpression, underlyingType) pattern - if (instanceExpr is UnaryExpression unaryInst && - unaryInst.NodeType == ExpressionType.Convert && - unaryInst.Operand is ConstantExpression innerConst) - { - // Convert constant directly to enum - instanceExpr = Expression.Constant(Enum.ToObject(enumType, innerConst.Value), enumType); - instance.Expression = instanceExpr; - } - else if (instanceExpr is ConstantExpression constInst) - { - instanceExpr = Expression.Constant(Enum.ToObject(enumType, constInst.Value), enumType); - instance.Expression = instanceExpr; - } - else if (instanceExpr.Type == enumType.GetEnumUnderlyingType()) - { - instanceExpr = Expression.Convert(instanceExpr, enumType); - instance.Expression = instanceExpr; - } + arguments[0] = Expression.Convert(argument, typeof(Enum)); } - - if (argument != null && !argument.Type.IsEnum) + // Otherwise, try to unwrap and convert + else if (enumType != null) { - // Check if it's a Convert(ConstantExpression, underlyingType) pattern - if (argument is UnaryExpression unaryArg && - unaryArg.NodeType == ExpressionType.Convert && - unaryArg.Operand is ConstantExpression innerConstArg) + var unwrappedArg = argument; + while (unwrappedArg is UnaryExpression unaryArg && + unaryArg.NodeType == ExpressionType.Convert) { - // Convert to enum, then to System.Enum for the method parameter - var enumConst = Expression.Constant(Enum.ToObject(enumType, innerConstArg.Value), enumType); - arguments[0] = Expression.Convert(enumConst, typeof(Enum)); - } - else if (argument is ConstantExpression constArg) - { - var enumConst = Expression.Constant(Enum.ToObject(enumType, constArg.Value), enumType); - arguments[0] = Expression.Convert(enumConst, typeof(Enum)); + if (unaryArg.Operand.Type.IsEnum) + { + arguments[0] = Expression.Convert(unaryArg.Operand, typeof(Enum)); + unwrappedArg = null; + break; + } + unwrappedArg = unaryArg.Operand; } - else if (argument.Type == enumType.GetEnumUnderlyingType()) + + if (unwrappedArg != null && !unwrappedArg.Type.IsEnum) { - // Convert to specific enum type first, then to System.Enum - arguments[0] = Expression.Convert(Expression.Convert(argument, enumType), typeof(Enum)); + if (unwrappedArg is ConstantExpression constArg) + { + var enumConst = Expression.Constant(Enum.ToObject(enumType, constArg.Value), enumType); + arguments[0] = Expression.Convert(enumConst, typeof(Enum)); + } } } - else if (argument != null && argument.Type.IsEnum && argument.Type != typeof(Enum)) - { - // Argument is already a specific enum type, convert to System.Enum - arguments[0] = Expression.Convert(argument, typeof(Enum)); - } } } @@ -245,96 +263,11 @@ static Expression BuildMethodCallExpression(MethodInfo m, Address instance, Expr if (instance.Expression != null) { - // Ensure instance type matches the method's declaring type - // This handles cases where enum instances have been converted to their underlying type - var instanceExpr = instance.Expression; - if (m.DeclaringType != null && !m.DeclaringType.IsAssignableFrom(instanceExpr.Type)) - { - // If the declaring type is an enum and instance is the underlying type, convert back - if (m.DeclaringType.IsEnum && instanceExpr.Type == m.DeclaringType.GetEnumUnderlyingType()) - { - instanceExpr = Expression.Convert(instanceExpr, m.DeclaringType); - } - // Special case for Enum.HasFlag - declaring type is Enum but instance should be specific enum type - else if (m.DeclaringType == typeof(Enum) && instanceExpr.Type.IsValueType && !instanceExpr.Type.IsEnum) - { - // Try to infer the enum type from the instance expression - // Check if it's a direct MemberExpression - if (instanceExpr is MemberExpression memberExpr && - memberExpr.Member is FieldInfo field && - field.FieldType.IsEnum) - { - instanceExpr = Expression.Convert(instanceExpr, field.FieldType); - } - // Check if it's a Convert wrapping a MemberExpression - else if (instanceExpr is UnaryExpression unary && - unary.NodeType == ExpressionType.Convert && - unary.Operand is MemberExpression innerMember && - innerMember.Member is FieldInfo innerField && - innerField.FieldType.IsEnum) - { - // Use the enum type directly, unwrap the Convert - instanceExpr = innerMember; - } - // Check if it's a constant that should be an enum constant - else if (instanceExpr is ConstantExpression constExpr) - { - // Try to infer enum type from the method context or arguments - // For HasFlag, the argument should give us the enum type - if (m.Name == "HasFlag" && arguments.Length > 0) - { - var argType = arguments[0].Type; - if (argType.IsEnum) - { - // Convert constant to this enum type - instanceExpr = Expression.Constant(Enum.ToObject(argType, constExpr.Value), argType); - } - } - } - } - } - - return Expression.Call(instanceExpr, m, arguments); + return Expression.Call(instance.Expression, m, arguments); } return Expression.Call(null, m, arguments); } - - static Type TryInferEnumTypeFromExpression(Expression expr) - { - if (expr == null) - return null; - - // If it's already an enum, return its type - if (expr.Type.IsEnum) - return expr.Type; - - // If it's a Convert from enum to underlying type, get the enum type - if (expr is UnaryExpression unary && unary.NodeType == ExpressionType.Convert) - { - if (unary.Operand.Type.IsEnum) - return unary.Operand.Type; - - // Recursively check operand - return TryInferEnumTypeFromExpression(unary.Operand); - } - - // If it's a member access (field or property), check if it's enum typed - if (expr is MemberExpression memberExpr) - { - var fieldOrPropType = memberExpr.Member.FieldOrPropertyType(); - if (fieldOrPropType.IsEnum) - return fieldOrPropType; - } - - // If it's a parameter, check if parameter type is enum - if (expr is ParameterExpression paramExpr && paramExpr.Type.IsEnum) - { - return paramExpr.Type; - } - - return null; - } static bool TryParseOperator(MethodInfo m, out ExpressionType type) { diff --git a/src/DelegateDecompiler/Processors/ConvertProcessor.cs b/src/DelegateDecompiler/Processors/ConvertProcessor.cs index 37dc5f8d..204faa4d 100644 --- a/src/DelegateDecompiler/Processors/ConvertProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertProcessor.cs @@ -23,7 +23,6 @@ public static void Register(Dictionary processors) public void Process(ProcessorState state, Instruction instruction) { var val = state.Stack.Pop(); - // No optimizations here - they're handled in OptimizeExpressionVisitor state.Stack.Push(Expression.Convert(val, targetType)); } } \ No newline at end of file diff --git a/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs b/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs index f586b106..6f09d121 100644 --- a/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs +++ b/src/DelegateDecompiler/Processors/ConvertTypeProcessor.cs @@ -18,7 +18,6 @@ public void Process(ProcessorState state, Instruction instruction) var address = state.Stack.Pop(); var targetType = (Type)instruction.Operand; - // No optimizations here - they're handled in OptimizeExpressionVisitor state.Stack.Push(Expression.Convert(address, targetType)); } }