Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions src/DelegateDecompiler.Tests/AssignmentTests.cs
Original file line number Diff line number Diff line change
@@ -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)
{
Expand Down Expand Up @@ -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<Func<TestClass, string>>(assignment, parameter);

Func<TestClass, string> 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<Func<TestClass, DateTime>>(assignment, parameter);

Func<TestClass, DateTime> compiled = v => v.StartDate = new DateTime(2023, 1, 1);
Test(expected, compiled);
}
}
}
32 changes: 32 additions & 0 deletions src/DelegateDecompiler/OptimizeExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
/// 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.
/// </summary>
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));
Expand Down