Skip to content

Commit 4b03a6d

Browse files
Fix VB to C# assignment operators of parameterized properties
Fixes #1157 where shorthand assignment operators like "+=" or "-=" were not properly resolved for parameterized properties during conversion from VB.NET to C#. - Updated MethodBodyExecutableStatementVisitor to correctly expand compound assignment operators to their corresponding setter invocations. - Handles type conversions and `Math.Pow` logic for exponentiation explicitly when mapping back to the compound setter parameters. - Add unit test AssignmentOperatorsParameterizedPropertiesAsync to verify expected behavior. Co-authored-by: GrahamTheCoder <2490482+GrahamTheCoder@users.noreply.github.com>
1 parent fadac1b commit 4b03a6d

File tree

2 files changed

+143
-7
lines changed

2 files changed

+143
-7
lines changed

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,22 @@ public override async Task<SyntaxList<StatementSyntax>> VisitAssignmentStatement
211211
var lhs = await node.Left.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
212212
var lOperation = _semanticModel.GetOperation(node.Left);
213213

214-
//Already dealt with by call to the same method in ConvertInvocationExpression
215214
var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation);
216-
if (parameterizedPropertyAccessMethod != null) return SingleStatement(lhs);
215+
216+
// If it's a simple assignment, we can return early as it's already handled by ConvertInvocationExpression
217+
if (parameterizedPropertyAccessMethod != null && node.IsKind(VBasic.SyntaxKind.SimpleAssignmentStatement)) {
218+
return SingleStatement(lhs);
219+
}
220+
221+
// For compound assignments, we want to expand it to the setter, but parameterizedPropertyAccessMethod above
222+
// returned 'get_Item' or 'set_Item' depending on operation context.
223+
// We know for sure the left-hand side is a getter invocation for compound assignments (e.g. this.get_Item(0) += 2),
224+
// but we need the setter name to build the final expression.
225+
string setMethodName = null;
226+
if (lOperation is IPropertyReferenceOperation pro && pro.Arguments.Any() && !Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions.IsDefault(pro.Property)) {
227+
setMethodName = pro.Property.SetMethod?.Name;
228+
}
229+
217230
var rhs = await node.Right.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
218231

219232
if (node.Left is VBSyntax.IdentifierNameSyntax id &&
@@ -241,18 +254,62 @@ _methodNode is VBSyntax.MethodBlockSyntax mb &&
241254

242255
var nonCompoundRhs = SyntaxFactory.BinaryExpression(nonCompound, lhs, typeConvertedRhs);
243256
var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, nonCompoundRhs, forceSourceType: rhsTypeInfo.ConvertedType, forceTargetType: lhsTypeInfo.Type);
244-
if (nonCompoundRhs != typeConvertedNonCompoundRhs) {
257+
if (nonCompoundRhs != typeConvertedNonCompoundRhs || setMethodName != null) {
245258
kind = SyntaxKind.SimpleAssignmentExpression;
246259
typeConvertedRhs = typeConvertedNonCompoundRhs;
247260
}
261+
} else if (setMethodName != null && node.IsKind(VBasic.SyntaxKind.ExponentiateAssignmentStatement)) {
262+
// ExponentiateAssignmentStatement evaluates to Math.Pow invocation which might need casting back to lhsType
263+
var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, typeConvertedRhs, forceSourceType: _semanticModel.Compilation.GetTypeByMetadataName("System.Double"), forceTargetType: lhsTypeInfo.Type);
264+
kind = SyntaxKind.SimpleAssignmentExpression;
265+
typeConvertedRhs = typeConvertedNonCompoundRhs;
248266
}
249267

250268
rhs = typeConvertedRhs;
251-
252-
var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs);
253269

254-
var postAssignment = GetPostAssignmentStatements(node);
255-
return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment));
270+
if (setMethodName != null) {
271+
if (lhs is InvocationExpressionSyntax ies) {
272+
ExpressionSyntax exprToReplace = ies.Expression;
273+
if (exprToReplace is MemberAccessExpressionSyntax maes && maes.Name is IdentifierNameSyntax idn) {
274+
var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn);
275+
exprToReplace = maes.WithName(newName);
276+
277+
var skipParens = node.Left.SkipIntoParens();
278+
if (maes.Expression is ThisExpressionSyntax) {
279+
if (skipParens is VBSyntax.InvocationExpressionSyntax inv && inv.Expression is VBSyntax.IdentifierNameSyntax) {
280+
exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia());
281+
} else if (skipParens is VBSyntax.IdentifierNameSyntax) {
282+
exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia());
283+
} else if (skipParens is VBSyntax.MemberAccessExpressionSyntax vbMaes && vbMaes.Expression is VBSyntax.MyClassExpressionSyntax == false && vbMaes.Expression is VBSyntax.MeExpressionSyntax == false) {
284+
// keep it
285+
} else {
286+
exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia());
287+
}
288+
}
289+
} else if (exprToReplace is IdentifierNameSyntax idn2) {
290+
var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2);
291+
exprToReplace = newName;
292+
}
293+
var newArg = SyntaxFactory.Argument(rhs);
294+
var newArgs = ies.ArgumentList.Arguments.Add(newArg);
295+
var newArgList = ies.ArgumentList.WithArguments(newArgs);
296+
var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList);
297+
var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs);
298+
var postAssign = GetPostAssignmentStatements(node);
299+
return postAssign.Insert(0, invokeAssignment);
300+
}
301+
return SingleStatement(lhs);
302+
}
303+
304+
if (kind == SyntaxKind.SimpleAssignmentExpression) {
305+
var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs);
306+
var postAssignment = GetPostAssignmentStatements(node);
307+
return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment));
308+
}
309+
310+
var compoundAssignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs);
311+
var compoundPostAssignment = GetPostAssignmentStatements(node);
312+
return compoundPostAssignment.Insert(0, SyntaxFactory.ExpressionStatement(compoundAssignment));
256313
}
257314

258315
private async Task<SyntaxList<StatementSyntax>> ConvertMidAssignmentAsync(VBSyntax.AssignmentStatementSyntax node, VBSyntax.MidExpressionSyntax mes)

Tests/CSharp/StatementTests/MethodStatementTests.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,4 +1713,83 @@ public void TestMethod()
17131713
}
17141714
}");
17151715
}
1716+
1717+
1718+
[Fact]
1719+
public async Task AssignmentOperatorsParameterizedPropertiesAsync()
1720+
{
1721+
await TestConversionVisualBasicToCSharpAsync(@"Public Class TestClass
1722+
Private _items As Integer() = New Integer() {1}
1723+
Public Property Item(index As Integer) As Integer
1724+
Get
1725+
Return _items(index)
1726+
End Get
1727+
Set(value As Integer)
1728+
_items(index) = value
1729+
End Set
1730+
End Property
1731+
1732+
Private _strItems As String() = New String() {""Hello""}
1733+
Public Property StrItem(index As Integer) As String
1734+
Get
1735+
Return _strItems(index)
1736+
End Get
1737+
Set(value As String)
1738+
_strItems(index) = value
1739+
End Set
1740+
End Property
1741+
1742+
Public Sub AllAssignmentOperators()
1743+
Item(0) += 2
1744+
Item(0) *= 2
1745+
Item(0) ^= 2
1746+
Item(0) /= 2
1747+
Item(0) -= 2
1748+
Item(0) \= 2
1749+
Item(0) <<= 2
1750+
Item(0) >>= 2
1751+
StrItem(0) &= "" World""
1752+
End Sub
1753+
End Class", @"using System;
1754+
1755+
public partial class TestClass
1756+
{
1757+
private int[] _items = new int[] { 1 };
1758+
public int get_Item(int index)
1759+
1760+
{
1761+
return _items[index];
1762+
}
1763+
public void set_Item(int index, int value)
1764+
{
1765+
_items[index] = value;
1766+
}
1767+
1768+
1769+
private string[] _strItems = new string[] { ""Hello"" };
1770+
public string get_StrItem(int index)
1771+
1772+
{
1773+
return _strItems[index];
1774+
}
1775+
public void set_StrItem(int index, string value)
1776+
{
1777+
_strItems[index] = value;
1778+
}
1779+
1780+
1781+
public void AllAssignmentOperators()
1782+
{
1783+
set_Item(0, get_Item(0) + 2);
1784+
set_Item(0, get_Item(0) * 2);
1785+
set_Item(0, (int)Math.Round(Math.Pow(get_Item(0), 2d)));
1786+
set_Item(0, (int)Math.Round(get_Item(0) / 2d));
1787+
set_Item(0, get_Item(0) - 2);
1788+
set_Item(0, get_Item(0) / 2);
1789+
set_Item(0, get_Item(0) << 2);
1790+
set_Item(0, get_Item(0) >> 2);
1791+
set_StrItem(0, get_StrItem(0) + "" World"");
1792+
}
1793+
}");
1794+
}
17161795
}

0 commit comments

Comments
 (0)