Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 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);
}
}
}
1 change: 1 addition & 0 deletions src/DelegateDecompiler.Tests/TestClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class TestClass : IEnumerable
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string MyStringProperty { get; set; } = "";

public void Add(DateTime start, DateTime end)
{
Expand Down
25 changes: 25 additions & 0 deletions src/DelegateDecompiler/ProcessorState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,32 @@ public Expression Final()
expressions[i] = Stack.Pop();
}

// 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 (expressions.Length == 2 &&
expressions[0] is BinaryExpression assignment &&
assignment.NodeType == ExpressionType.Assign &&
IsSameValueAsAssignmentRight(assignment.Right, expressions[1]))
{
return assignment;
}

return Expression.Block(expressions);
}

/// <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;
}
}
}