diff --git a/src/DelegateDecompiler.Tests/AssignmentTests.cs b/src/DelegateDecompiler.Tests/AssignmentTests.cs index f20f2563..00617e34 100644 --- a/src/DelegateDecompiler.Tests/AssignmentTests.cs +++ b/src/DelegateDecompiler.Tests/AssignmentTests.cs @@ -1,16 +1,19 @@ -using System.Linq.Expressions; +using System; +using System.Linq.Expressions; using NUnit.Framework; namespace DelegateDecompiler.Tests { [TestFixture] - public class AssignmentTests + public class AssignmentTests : DecompilerTestsBase { public class TestClass { private static int staticField; private int myField; private int MyProperty { get; set; } + public string MyStringProperty { get; set; } = ""; + public DateTime StartDate { get; set; } public void SetStaticField(int value) { @@ -75,5 +78,34 @@ public void TestSetMultiple() Assert.That(block.Expressions[1].ToString(), Is.EqualTo("(this.myField = value)")); Assert.That(block.Expressions[2].ToString(), Is.EqualTo("(this.MyProperty = value)")); } + + [Test] + public void ShouldDecompilePropertyAssignmentExpression() + { + // Manually construct the expected assignment expression + var parameter = Expression.Parameter(typeof(TestClass), "v"); + var property = Expression.Property(parameter, nameof(TestClass.MyStringProperty)); + var value = Expression.Constant("test value", typeof(string)); + var assignment = Expression.Assign(property, value); + var expected = Expression.Lambda>(assignment, parameter); + + Func compiled = v => v.MyStringProperty = "test value"; + Test(expected, compiled); + } + + [Test] + public void ShouldDecompileComplexAssignmentExpression() + { + // Manually construct the expected assignment expression + var parameter = Expression.Parameter(typeof(TestClass), "v"); + var property = Expression.Property(parameter, nameof(TestClass.StartDate)); + var newDateTime = Expression.New(typeof(DateTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) }), + Expression.Constant(2023), Expression.Constant(1), Expression.Constant(1)); + var assignment = Expression.Assign(property, newDateTime); + var expected = Expression.Lambda>(assignment, parameter); + + Func compiled = v => v.StartDate = new DateTime(2023, 1, 1); + Test(expected, compiled); + } } } diff --git a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs index 4c040989..8ea1d780 100644 --- a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs +++ b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs @@ -537,6 +537,38 @@ protected override Expression VisitParameter(ParameterExpression node) } } + protected override Expression VisitBlock(BlockExpression node) + { + var block = (BlockExpression)base.VisitBlock(node); + + // Handle C# compiler pattern: assignment expressions that duplicate the assigned value + // The C# compiler generates IL with 'dup' instruction that leaves both the assignment + // and the assigned value on the stack. Since assignment expressions in .NET already + // return the assigned value, we can return just the assignment expression. + if (block.Expressions.Count == 2 && + block.Expressions[0] is BinaryExpression assignment && + assignment.NodeType == ExpressionType.Assign && + IsSameValueAsAssignmentRight(assignment.Right, block.Expressions[1])) + { + return assignment; + } + + return block; + } + + /// + /// Determines if the second expression represents the same value as the first expression. + /// This uses reference equality which works for the C# compiler's 'dup' pattern where + /// the exact same expression object is placed on the stack twice. + /// + private static bool IsSameValueAsAssignmentRight(Expression assignmentRight, Expression stackValue) + { + // Use reference equality as used elsewhere in the codebase (OptimizeExpressionVisitor, Address.cs) + // This works because the C# compiler's 'dup' instruction results in the same expression + // object being referenced in both positions + return assignmentRight == stackValue; + } + public static Expression Optimize(Expression expression) { return new GetValueOrDefaultToCoalesceConverter().Visit(new OptimizeExpressionVisitor().Visit(expression));