Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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
53 changes: 53 additions & 0 deletions src/DelegateDecompiler.Tests/AssignmentExpressionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Linq.Expressions;
using NUnit.Framework;

namespace DelegateDecompiler.Tests
{
[TestFixture]
public class AssignmentExpressionTests : DecompilerTestsBase
{
class MyClass
{
public string MyProperty { get; set; } = "";
}

[Test]
public void ShouldDecompilePropertyAssignmentAsAssignmentExpression()
{
// Create a function that assigns to a property
string TestAssignment(MyClass v) => v.MyProperty = "test value";
Func<MyClass, string> compiled = TestAssignment;

// Decompile it
LambdaExpression decompiled = DecompileExtensions.Decompile(compiled);

// The body should be a single assignment expression, not a block
Assert.That(decompiled.Body.NodeType, Is.EqualTo(ExpressionType.Assign));
Assert.That(decompiled.Body.Type, Is.EqualTo(typeof(string)));

// Verify it's a proper assignment expression
var assignment = (BinaryExpression)decompiled.Body;
Assert.That(assignment.Left.NodeType, Is.EqualTo(ExpressionType.MemberAccess));
Assert.That(assignment.Right.NodeType, Is.EqualTo(ExpressionType.Constant));

var constant = (ConstantExpression)assignment.Right;
Assert.That(constant.Value, Is.EqualTo("test value"));
}

[Test]
public void ShouldDecompileMultiplePropertyAssignmentAsAssignmentExpression()
{
// Test with another property to make sure the fix is general
DateTime TestPropertyAssignment(TestClass v) => v.StartDate = new DateTime(2023, 1, 1);
Func<TestClass, DateTime> compiled = TestPropertyAssignment;

// Decompile it
LambdaExpression decompiled = DecompileExtensions.Decompile(compiled);

// The body should be a single assignment expression, not a block
Assert.That(decompiled.Body.NodeType, Is.EqualTo(ExpressionType.Assign));
Assert.That(decompiled.Body.Type, Is.EqualTo(typeof(DateTime)));
}
}
}
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;
}
}
}