Skip to content

Commit 2c2b64e

Browse files
Merge pull request #1229 from GrahamTheCoder/fix-1157-vb-parameterized-property-assignment-15193145234634278293
Fix VB to C# assignment operators of parameterized properties
2 parents 2df8e04 + d038168 commit 2c2b64e

File tree

2 files changed

+118
-5
lines changed

2 files changed

+118
-5
lines changed

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 43 additions & 5 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,16 +254,41 @@ _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

270+
if (setMethodName != null) {
271+
if (lhs is InvocationExpressionSyntax ies) {
272+
ExpressionSyntax exprToReplace = ies.Expression;
273+
if (exprToReplace is MemberAccessExpressionSyntax maes) {
274+
var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(maes.Name);
275+
var stripThis = maes.Expression is ThisExpressionSyntax
276+
&& node.Left.SkipIntoParens() is not VBSyntax.MemberAccessExpressionSyntax {
277+
Expression: not (VBSyntax.MeExpressionSyntax or VBSyntax.MyClassExpressionSyntax)
278+
};
279+
exprToReplace = stripThis ? newName.WithTriviaFrom(maes) : maes.WithName(newName);
280+
} else if (exprToReplace is IdentifierNameSyntax) {
281+
exprToReplace = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(exprToReplace);
282+
}
283+
var newArgList = ies.ArgumentList.AddArguments(SyntaxFactory.Argument(rhs));
284+
var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList);
285+
var postAssign = GetPostAssignmentStatements(node);
286+
return postAssign.Insert(0, SyntaxFactory.ExpressionStatement(newLhs));
287+
}
288+
return SingleStatement(lhs);
289+
}
290+
291+
var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs);
254292
var postAssignment = GetPostAssignmentStatements(node);
255293
return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment));
256294
}

Tests/CSharp/StatementTests/MethodStatementTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,4 +1713,79 @@ 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+
return _items[index];
1761+
}
1762+
public void set_Item(int index, int value)
1763+
{
1764+
_items[index] = value;
1765+
}
1766+
1767+
private string[] _strItems = new string[] { ""Hello"" };
1768+
public string get_StrItem(int index)
1769+
{
1770+
return _strItems[index];
1771+
}
1772+
public void set_StrItem(int index, string value)
1773+
{
1774+
_strItems[index] = value;
1775+
}
1776+
1777+
public void AllAssignmentOperators()
1778+
{
1779+
set_Item(0, get_Item(0) + 2);
1780+
set_Item(0, get_Item(0) * 2);
1781+
set_Item(0, (int)Math.Round(Math.Pow(get_Item(0), 2d)));
1782+
set_Item(0, (int)Math.Round(get_Item(0) / 2d));
1783+
set_Item(0, get_Item(0) - 2);
1784+
set_Item(0, get_Item(0) / 2);
1785+
set_Item(0, get_Item(0) << 2);
1786+
set_Item(0, get_Item(0) >> 2);
1787+
set_StrItem(0, get_StrItem(0) + "" World"");
1788+
}
1789+
}");
1790+
}
17161791
}

0 commit comments

Comments
 (0)