diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 8f2a4856..bc48a7dc 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -211,9 +211,22 @@ public override async Task> VisitAssignmentStatement var lhs = await node.Left.AcceptAsync(_expressionVisitor); var lOperation = _semanticModel.GetOperation(node.Left); - //Already dealt with by call to the same method in ConvertInvocationExpression var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation); - if (parameterizedPropertyAccessMethod != null) return SingleStatement(lhs); + + // If it's a simple assignment, we can return early as it's already handled by ConvertInvocationExpression + if (parameterizedPropertyAccessMethod != null && node.IsKind(VBasic.SyntaxKind.SimpleAssignmentStatement)) { + return SingleStatement(lhs); + } + + // For compound assignments, we want to expand it to the setter, but parameterizedPropertyAccessMethod above + // returned 'get_Item' or 'set_Item' depending on operation context. + // We know for sure the left-hand side is a getter invocation for compound assignments (e.g. this.get_Item(0) += 2), + // but we need the setter name to build the final expression. + string setMethodName = null; + if (lOperation is IPropertyReferenceOperation pro && pro.Arguments.Any() && !Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions.IsDefault(pro.Property)) { + setMethodName = pro.Property.SetMethod?.Name; + } + var rhs = await node.Right.AcceptAsync(_expressionVisitor); if (node.Left is VBSyntax.IdentifierNameSyntax id && @@ -241,16 +254,41 @@ _methodNode is VBSyntax.MethodBlockSyntax mb && var nonCompoundRhs = SyntaxFactory.BinaryExpression(nonCompound, lhs, typeConvertedRhs); var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, nonCompoundRhs, forceSourceType: rhsTypeInfo.ConvertedType, forceTargetType: lhsTypeInfo.Type); - if (nonCompoundRhs != typeConvertedNonCompoundRhs) { + if (nonCompoundRhs != typeConvertedNonCompoundRhs || setMethodName != null) { kind = SyntaxKind.SimpleAssignmentExpression; typeConvertedRhs = typeConvertedNonCompoundRhs; } + } else if (setMethodName != null && node.IsKind(VBasic.SyntaxKind.ExponentiateAssignmentStatement)) { + // ExponentiateAssignmentStatement evaluates to Math.Pow invocation which might need casting back to lhsType + var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, typeConvertedRhs, forceSourceType: _semanticModel.Compilation.GetTypeByMetadataName("System.Double"), forceTargetType: lhsTypeInfo.Type); + kind = SyntaxKind.SimpleAssignmentExpression; + typeConvertedRhs = typeConvertedNonCompoundRhs; } rhs = typeConvertedRhs; - - var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); + if (setMethodName != null) { + if (lhs is InvocationExpressionSyntax ies) { + ExpressionSyntax exprToReplace = ies.Expression; + if (exprToReplace is MemberAccessExpressionSyntax maes) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(maes.Name); + var stripThis = maes.Expression is ThisExpressionSyntax + && node.Left.SkipIntoParens() is not VBSyntax.MemberAccessExpressionSyntax { + Expression: not (VBSyntax.MeExpressionSyntax or VBSyntax.MyClassExpressionSyntax) + }; + exprToReplace = stripThis ? newName.WithTriviaFrom(maes) : maes.WithName(newName); + } else if (exprToReplace is IdentifierNameSyntax) { + exprToReplace = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(exprToReplace); + } + var newArgList = ies.ArgumentList.AddArguments(SyntaxFactory.Argument(rhs)); + var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); + var postAssign = GetPostAssignmentStatements(node); + return postAssign.Insert(0, SyntaxFactory.ExpressionStatement(newLhs)); + } + return SingleStatement(lhs); + } + + var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); var postAssignment = GetPostAssignmentStatements(node); return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); } diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index 42c13f4d..bc20f218 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1713,4 +1713,79 @@ public void TestMethod() } }"); } + + + [Fact] + public async Task AssignmentOperatorsParameterizedPropertiesAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class TestClass + Private _items As Integer() = New Integer() {1} + Public Property Item(index As Integer) As Integer + Get + Return _items(index) + End Get + Set(value As Integer) + _items(index) = value + End Set + End Property + + Private _strItems As String() = New String() {""Hello""} + Public Property StrItem(index As Integer) As String + Get + Return _strItems(index) + End Get + Set(value As String) + _strItems(index) = value + End Set + End Property + + Public Sub AllAssignmentOperators() + Item(0) += 2 + Item(0) *= 2 + Item(0) ^= 2 + Item(0) /= 2 + Item(0) -= 2 + Item(0) \= 2 + Item(0) <<= 2 + Item(0) >>= 2 + StrItem(0) &= "" World"" + End Sub +End Class", @"using System; + +public partial class TestClass +{ + private int[] _items = new int[] { 1 }; + public int get_Item(int index) + { + return _items[index]; + } + public void set_Item(int index, int value) + { + _items[index] = value; + } + + private string[] _strItems = new string[] { ""Hello"" }; + public string get_StrItem(int index) + { + return _strItems[index]; + } + public void set_StrItem(int index, string value) + { + _strItems[index] = value; + } + + public void AllAssignmentOperators() + { + set_Item(0, get_Item(0) + 2); + set_Item(0, get_Item(0) * 2); + set_Item(0, (int)Math.Round(Math.Pow(get_Item(0), 2d))); + set_Item(0, (int)Math.Round(get_Item(0) / 2d)); + set_Item(0, get_Item(0) - 2); + set_Item(0, get_Item(0) / 2); + set_Item(0, get_Item(0) << 2); + set_Item(0, get_Item(0) >> 2); + set_StrItem(0, get_StrItem(0) + "" World""); + } +}"); + } }