From 4b03a6dff293511ff76ab10314ceac6d09079ece Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:09:14 +0000 Subject: [PATCH 1/5] 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> --- .../MethodBodyExecutableStatementVisitor.cs | 71 +++++++++++++++-- .../StatementTests/MethodStatementTests.cs | 79 +++++++++++++++++++ 2 files changed, 143 insertions(+), 7 deletions(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 8f2a4856..27355dc7 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,18 +254,62 @@ _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); - var postAssignment = GetPostAssignmentStatements(node); - return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); + if (setMethodName != null) { + if (lhs is InvocationExpressionSyntax ies) { + ExpressionSyntax exprToReplace = ies.Expression; + if (exprToReplace is MemberAccessExpressionSyntax maes && maes.Name is IdentifierNameSyntax idn) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn); + exprToReplace = maes.WithName(newName); + + var skipParens = node.Left.SkipIntoParens(); + if (maes.Expression is ThisExpressionSyntax) { + if (skipParens is VBSyntax.InvocationExpressionSyntax inv && inv.Expression is VBSyntax.IdentifierNameSyntax) { + exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); + } else if (skipParens is VBSyntax.IdentifierNameSyntax) { + exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); + } else if (skipParens is VBSyntax.MemberAccessExpressionSyntax vbMaes && vbMaes.Expression is VBSyntax.MyClassExpressionSyntax == false && vbMaes.Expression is VBSyntax.MeExpressionSyntax == false) { + // keep it + } else { + exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); + } + } + } else if (exprToReplace is IdentifierNameSyntax idn2) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2); + exprToReplace = newName; + } + var newArg = SyntaxFactory.Argument(rhs); + var newArgs = ies.ArgumentList.Arguments.Add(newArg); + var newArgList = ies.ArgumentList.WithArguments(newArgs); + var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); + var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs); + var postAssign = GetPostAssignmentStatements(node); + return postAssign.Insert(0, invokeAssignment); + } + return SingleStatement(lhs); + } + + if (kind == SyntaxKind.SimpleAssignmentExpression) { + var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); + var postAssignment = GetPostAssignmentStatements(node); + return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); + } + + var compoundAssignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); + var compoundPostAssignment = GetPostAssignmentStatements(node); + return compoundPostAssignment.Insert(0, SyntaxFactory.ExpressionStatement(compoundAssignment)); } private async Task> ConvertMidAssignmentAsync(VBSyntax.AssignmentStatementSyntax node, VBSyntax.MidExpressionSyntax mes) diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index 42c13f4d..e5de0265 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1713,4 +1713,83 @@ 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""); + } +}"); + } } From 0d0fd07d3a1aaa7b986e10ac909a3826207e6ef7 Mon Sep 17 00:00:00 2001 From: Graham Date: Tue, 10 Mar 2026 23:52:41 +0000 Subject: [PATCH 2/5] Dedupe --- .../MethodBodyExecutableStatementVisitor.cs | 2438 ++++++++--------- 1 file changed, 1216 insertions(+), 1222 deletions(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 27355dc7..487be8a7 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -1,1223 +1,1217 @@ -using System.Collections; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using static Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions; -using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -using Microsoft.CodeAnalysis.Text; -using ICSharpCode.CodeConverter.Util.FromRoslyn; -using ICSharpCode.CodeConverter.VB; -using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind; - -namespace ICSharpCode.CodeConverter.CSharp; - -/// -/// Executable statements - which includes executable blocks such as if statements -/// Maintains state relevant to the called method-like object. A fresh one must be used for each method, and the same one must be reused for statements in the same method -/// -internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVisitor>> -{ - private readonly VBasic.VisualBasicSyntaxNode _methodNode; - private readonly SemanticModel _semanticModel; - private readonly CommentConvertingVisitorWrapper _expressionVisitor; - private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; - private readonly Stack _withBlockLhs; - private readonly HashSet _extraUsingDirectives; - private readonly HandledEventsAnalysis _handledEventsAnalysis; - private readonly HashSet _generatedNames = new(); - private readonly HashSet _localsToInlineInLoop; - private readonly PerScopeState _perScopeState; - - public bool IsIterator { get; set; } - public IdentifierNameSyntax ReturnVariable { get; set; } - public bool HasReturnVariable => ReturnVariable != null; - public VBasic.VisualBasicSyntaxVisitor>> CommentConvertingVisitor { get; } - - private CommonConversions CommonConversions { get; } - - public static async Task CreateAsync(VisualBasicSyntaxNode node, SemanticModel semanticModel, - CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor, CommonConversions commonConversions, VisualBasicEqualityComparison visualBasicEqualityComparison, - Stack withBlockLhs, HashSet extraUsingDirectives, - ITypeContext typeContext, bool isIterator, IdentifierNameSyntax csReturnVariable) - { - var solution = commonConversions.Document.Project.Solution; - var declarationsToInlineInLoop = await solution.GetDescendantsToInlineInLoopAsync(semanticModel, node); - return new MethodBodyExecutableStatementVisitor(node, semanticModel, triviaConvertingExpressionVisitor, commonConversions, visualBasicEqualityComparison, withBlockLhs, extraUsingDirectives, typeContext, declarationsToInlineInLoop) { - IsIterator = isIterator, - ReturnVariable = csReturnVariable, - }; - } - - private MethodBodyExecutableStatementVisitor(VisualBasicSyntaxNode methodNode, SemanticModel semanticModel, - CommentConvertingVisitorWrapper expressionVisitor, CommonConversions commonConversions, - VisualBasicEqualityComparison visualBasicEqualityComparison, - Stack withBlockLhs, HashSet extraUsingDirectives, - ITypeContext typeContext, HashSet localsToInlineInLoop) - { - _methodNode = methodNode; - _semanticModel = semanticModel; - _expressionVisitor = expressionVisitor; - _visualBasicEqualityComparison = visualBasicEqualityComparison; - CommonConversions = commonConversions; - _withBlockLhs = withBlockLhs; - _extraUsingDirectives = extraUsingDirectives; - _handledEventsAnalysis = typeContext.HandledEventsAnalysis; - _perScopeState = typeContext.PerScopeState; - var byRefParameterVisitor = new PerScopeStateVisitorDecorator(this, _perScopeState, semanticModel, _generatedNames); - CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor); - _localsToInlineInLoop = localsToInlineInLoop; - } - - public override async Task> DefaultVisit(SyntaxNode node) - { - throw new NotImplementedException($"Conversion for {VBasic.VisualBasicExtensions.Kind(node)} not implemented, please report this issue") - .WithNodeInformation(node); - } - - public override async Task> VisitStopOrEndStatement(VBSyntax.StopOrEndStatementSyntax node) - { - return SingleStatement(SyntaxFactory.ParseStatement(ConvertStopOrEndToCSharpStatementText(node))); - } - - private string ConvertStopOrEndToCSharpStatementText(VBSyntax.StopOrEndStatementSyntax node) - { - switch (VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword)) { - case VBasic.SyntaxKind.StopKeyword: - _extraUsingDirectives.Add("System.Diagnostics"); - return "Debugger.Break();"; - case VBasic.SyntaxKind.EndKeyword: - _extraUsingDirectives.Add("System"); - return "Environment.Exit(0);"; - default: - throw new NotImplementedException(VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword) + " not implemented!"); - } - } - - public override async Task> VisitLocalDeclarationStatement(VBSyntax.LocalDeclarationStatementSyntax node) - { - var modifiers = CommonConversions.ConvertModifiers(node.Declarators[0].Names[0], node.Modifiers, TokenContext.Local); - var isConst = modifiers.Any(a => a.IsKind(SyntaxKind.ConstKeyword)); - var isVBStatic = node.Modifiers.Any(a => a.IsKind(VBasic.SyntaxKind.StaticKeyword)); - - var declarations = new List(); - - foreach (var declarator in node.Declarators) { - var (variables, methods) = await SplitVariableDeclarationsAsync(declarator, preferExplicitType: isConst || isVBStatic); - var localDeclarationStatementSyntaxs = variables.Select(declAndType => SyntaxFactory.LocalDeclarationStatement(modifiers, declAndType.Decl)); - if (isVBStatic) { - foreach (var decl in localDeclarationStatementSyntaxs) { - var variable = decl.Declaration.Variables.Single(); - var initializeValue = variable.Initializer?.Value; - string methodName; - SyntaxTokenList methodModifiers; - - MethodKind parentAccessorKind = MethodKind.Ordinary; - if (_methodNode is VBSyntax.MethodBlockSyntax methodBlock) { - var methodStatement = methodBlock.BlockStatement as VBSyntax.MethodStatementSyntax; - methodModifiers = methodStatement.Modifiers; - methodName = methodStatement.Identifier.Text; - } else if (_methodNode is VBSyntax.ConstructorBlockSyntax constructorBlock) { - methodModifiers = constructorBlock.BlockStatement.Modifiers; - methodName = null; - } else if (_methodNode is VBSyntax.AccessorBlockSyntax accessorBlock) { - var propertyBlock = accessorBlock.Parent as VBSyntax.PropertyBlockSyntax; - methodName = propertyBlock.PropertyStatement.Identifier.Text; - parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? MethodKind.PropertyGet : MethodKind.PropertySet; - methodModifiers = propertyBlock.PropertyStatement.Modifiers; - } else { - throw new NotImplementedException(_methodNode.GetType() + " not implemented!"); - } - - var isVbShared = methodModifiers.Any(a => a.IsKind(VBasic.SyntaxKind.SharedKeyword)); - _perScopeState.HoistToTopLevel(new HoistedFieldFromVbStaticVariable(methodName, variable.Identifier.Text, parentAccessorKind, initializeValue, decl.Declaration.Type, isVbShared)); - } - } else { - var shouldPullVariablesBeforeLoop = _perScopeState.IsInsideLoop() && declarator.Initializer is null && declarator.AsClause is not VBSyntax.AsNewClauseSyntax; - if (shouldPullVariablesBeforeLoop) { - localDeclarationStatementSyntaxs = HoistVariablesBeforeLoopWhenNeeded(variables) - .Select(variableDecl => SyntaxFactory.LocalDeclarationStatement(modifiers, variableDecl)); - } - - declarations.AddRange(localDeclarationStatementSyntaxs); - } - var localFunctions = methods.Cast(); - declarations.AddRange(localFunctions); - } - - return SyntaxFactory.List(declarations); - } - - private IEnumerable HoistVariablesBeforeLoopWhenNeeded(IEnumerable variablesDeclarations) - { - foreach (var variablesDecl in variablesDeclarations) { - var variablesToRemove = new List(); - foreach (var (csVariable, vbVariable) in variablesDecl.Variables) { - var symbol = _semanticModel.GetDeclaredSymbol(vbVariable); - var assignedBeforeRead = _semanticModel.IsDefinitelyAssignedBeforeRead(symbol, vbVariable); - if (!assignedBeforeRead) { - _perScopeState.Hoist(new HoistedDefaultInitializedLoopVariable( - csVariable.Identifier.Text, - // e.g. "b As Boolean" has no initializer but can turn into "var b = default(bool)" - csVariable.Initializer?.Value, - variablesDecl.Decl.Type, - _perScopeState.IsInsideNestedLoop())); - variablesToRemove.Add(csVariable); - } - } - - var variablesToDeclareLocally = variablesDecl.Variables.Select(t => t.CsVar).Except(variablesToRemove).ToArray(); - if(variablesToDeclareLocally.Any()) { - yield return variablesDecl.Decl.WithVariables(SyntaxFactory.SeparatedList(variablesToDeclareLocally)); - } - } - } - - public override async Task> VisitAddRemoveHandlerStatement(VBSyntax.AddRemoveHandlerStatementSyntax node) - { - var syntaxKind = ConvertAddRemoveHandlerToCSharpSyntaxKind(node); - return SingleStatement(SyntaxFactory.AssignmentExpression(syntaxKind, - await node.EventExpression.AcceptAsync(_expressionVisitor), - await node.DelegateExpression.AcceptAsync(_expressionVisitor))); - } - - private static SyntaxKind ConvertAddRemoveHandlerToCSharpSyntaxKind(VBSyntax.AddRemoveHandlerStatementSyntax node) - { - switch (node.Kind()) { - case VBasic.SyntaxKind.AddHandlerStatement: - return SyntaxKind.AddAssignmentExpression; - case VBasic.SyntaxKind.RemoveHandlerStatement: - return SyntaxKind.SubtractAssignmentExpression; - default: - throw new NotImplementedException(node.Kind() + " not implemented!"); - } - } - - public override async Task> VisitExpressionStatement(VBSyntax.ExpressionStatementSyntax node) - { - if (node.Expression is VBSyntax.InvocationExpressionSyntax invoke && invoke.Expression is VBSyntax.MemberAccessExpressionSyntax access && access.Expression is VBSyntax.MyBaseExpressionSyntax && access.Name.Identifier.ValueText.Equals("Finalize", StringComparison.OrdinalIgnoreCase)) { - return new SyntaxList(); - } - - return SingleStatement(await node.Expression.AcceptAsync(_expressionVisitor)); - } - - public override async Task> VisitAssignmentStatement(VBSyntax.AssignmentStatementSyntax node) - { - if (node.IsKind(VBasic.SyntaxKind.MidAssignmentStatement) && node.Left is VBSyntax.MidExpressionSyntax mes) { - return await ConvertMidAssignmentAsync(node, mes); - } - - var lhs = await node.Left.AcceptAsync(_expressionVisitor); - var lOperation = _semanticModel.GetOperation(node.Left); - - var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation); - - // 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 && - _methodNode is VBSyntax.MethodBlockSyntax mb && - HasReturnVariable && - id.Identifier.ValueText.Equals(mb.SubOrFunctionStatement.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)) { - lhs = ReturnVariable; - } - - if (node.IsKind(VBasic.SyntaxKind.ExponentiateAssignmentStatement)) { - rhs = SyntaxFactory.InvocationExpression( - ValidSyntaxFactory.MemberAccess(nameof(Math), nameof(Math.Pow)), - ExpressionSyntaxExtensions.CreateArgList(lhs, rhs)); - } - var kind = node.Kind().ConvertToken(); - - - var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left); - var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right); - - var typeConvertedRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); - - // Split out compound operator if type conversion needed on result - if (TypeConversionAnalyzer.GetNonCompoundOrNull(kind) is {} nonCompound) { - - var nonCompoundRhs = SyntaxFactory.BinaryExpression(nonCompound, lhs, typeConvertedRhs); - var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, nonCompoundRhs, forceSourceType: rhsTypeInfo.ConvertedType, forceTargetType: lhsTypeInfo.Type); - 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; - - if (setMethodName != null) { - if (lhs is InvocationExpressionSyntax ies) { - ExpressionSyntax exprToReplace = ies.Expression; - if (exprToReplace is MemberAccessExpressionSyntax maes && maes.Name is IdentifierNameSyntax idn) { - var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn); - exprToReplace = maes.WithName(newName); - - var skipParens = node.Left.SkipIntoParens(); - if (maes.Expression is ThisExpressionSyntax) { - if (skipParens is VBSyntax.InvocationExpressionSyntax inv && inv.Expression is VBSyntax.IdentifierNameSyntax) { - exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); - } else if (skipParens is VBSyntax.IdentifierNameSyntax) { - exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); - } else if (skipParens is VBSyntax.MemberAccessExpressionSyntax vbMaes && vbMaes.Expression is VBSyntax.MyClassExpressionSyntax == false && vbMaes.Expression is VBSyntax.MeExpressionSyntax == false) { - // keep it - } else { - exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); - } - } - } else if (exprToReplace is IdentifierNameSyntax idn2) { - var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2); - exprToReplace = newName; - } - var newArg = SyntaxFactory.Argument(rhs); - var newArgs = ies.ArgumentList.Arguments.Add(newArg); - var newArgList = ies.ArgumentList.WithArguments(newArgs); - var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); - var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs); - var postAssign = GetPostAssignmentStatements(node); - return postAssign.Insert(0, invokeAssignment); - } - return SingleStatement(lhs); - } - - if (kind == SyntaxKind.SimpleAssignmentExpression) { - var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); - var postAssignment = GetPostAssignmentStatements(node); - return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); - } - - var compoundAssignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); - var compoundPostAssignment = GetPostAssignmentStatements(node); - return compoundPostAssignment.Insert(0, SyntaxFactory.ExpressionStatement(compoundAssignment)); - } - - private async Task> ConvertMidAssignmentAsync(VBSyntax.AssignmentStatementSyntax node, VBSyntax.MidExpressionSyntax mes) - { - _extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices"); - var midFunction = ValidSyntaxFactory.MemberAccess("StringType", "MidStmtStr"); - var midArgList = await mes.ArgumentList.AcceptAsync(_expressionVisitor); - var (reusable, statements, _) = await GetExpressionWithoutSideEffectsAsync(node.Right, "midTmp"); - if (midArgList.Arguments.Count == 2) { - var length = ValidSyntaxFactory.MemberAccess(reusable, "Length"); - midArgList = midArgList.AddArguments(SyntaxFactory.Argument(length)); - } - midArgList = midArgList.AddArguments(SyntaxFactory.Argument(reusable)); - var invokeMid = SyntaxFactory.InvocationExpression(midFunction, midArgList); - return statements.Add(SyntaxFactory.ExpressionStatement(invokeMid)); - } - - /// - /// ensures we convert the property access to a field access - /// - private SyntaxList GetPostAssignmentStatements(VBSyntax.AssignmentStatementSyntax node) - { - var potentialPropertySymbol = _semanticModel.GetSymbolInfo(node.Left).ExtractBestMatch(); - return GetPostAssignmentStatements(node, potentialPropertySymbol); - } - - /// - /// Make winforms designer work: https://github.com/icsharpcode/CodeConverter/issues/321 - /// - public SyntaxList GetPostAssignmentStatements(Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax node, ISymbol potentialPropertySymbol) - { - if (CommonConversions.WinformsConversions.MayNeedToInlinePropertyAccess(node, potentialPropertySymbol)) { - return _handledEventsAnalysis.GetPostAssignmentStatements(potentialPropertySymbol); - } - - return SyntaxFactory.List(); - } - - public override async Task> VisitEraseStatement(VBSyntax.EraseStatementSyntax node) - { - var eraseStatements = await node.Expressions.SelectAsync(async arrayExpression => { - var lhs = await arrayExpression.AcceptAsync(_expressionVisitor); - var rhs = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); - var assignmentExpressionSyntax = - SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, lhs, - rhs); - return SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); - }); - return SyntaxFactory.List(eraseStatements); - } - - public override async Task> VisitReDimStatement(VBSyntax.ReDimStatementSyntax node) - { - return SyntaxFactory.List(await node.Clauses.SelectManyAsync(async arrayExpression => (IEnumerable) await ConvertRedimClauseAsync(arrayExpression))); - } - - /// - /// RedimClauseSyntax isn't an executable statement, therefore this isn't a "Visit" method. - /// Since it returns multiple statements it's easiest for it to be here in the current architecture. - /// - private async Task> ConvertRedimClauseAsync(VBSyntax.RedimClauseSyntax node) - { - bool preserve = node.Parent is VBSyntax.ReDimStatementSyntax rdss && rdss.PreserveKeyword.IsKind(VBasic.SyntaxKind.PreserveKeyword); - - var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); - var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); - if (preserve && convertedBounds.Count == 1) { - bool isProperty = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true; - var arrayToResize = isProperty ? CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression) : default; - var resizeArg = isProperty ? (ExpressionSyntax)arrayToResize.Reference : csTargetArrayExpression; - - var argumentList = new[] { resizeArg, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); - var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList); - - if (isProperty) { - var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, arrayToResize.Reference); - return SyntaxFactory.List(new StatementSyntax[] { arrayToResize.Declaration, SyntaxFactory.ExpressionStatement(arrayResize), SyntaxFactory.ExpressionStatement(assignment) }); - } - - return SingleStatement(arrayResize); - } - var newArrayAssignment = CreateNewArrayAssignment(node.Expression, csTargetArrayExpression, convertedBounds); - if (!preserve) return SingleStatement(newArrayAssignment); - - var lastIdentifierText = node.Expression.DescendantNodesAndSelf().OfType().Last().Identifier.Text; - string variableNameBase = "old" + lastIdentifierText.ToPascalCase(); - var expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csTargetArrayExpression); - var (stmt, oldTargetExpression) = CreateLocalVariableWithUniqueName(node.Expression, variableNameBase, expr); - - var arrayCopyIfNotNull = CreateConditionalArrayCopy(node, oldTargetExpression, csTargetArrayExpression, convertedBounds); - - return SyntaxFactory.List(new[] { stmt, newArrayAssignment, arrayCopyIfNotNull}); - } - - /// - /// Cut down version of Microsoft.VisualBasic.CompilerServices.Utils.CopyArray - /// - private IfStatementSyntax CreateConditionalArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, - IdentifierNameSyntax sourceArrayExpression, - ExpressionSyntax targetArrayExpression, - List convertedBounds) - { - var sourceLength = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, sourceArrayExpression, ValidSyntaxFactory.IdentifierName("Length")); - var arrayCopyStatement = convertedBounds.Count == 1 - ? CreateArrayCopyWithMinOfLengths(sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds.Single()) - : CreateArrayCopy(originalVbNode, sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds); - - var oldTargetNotEqualToNull = CommonConversions.NotNothingComparison(sourceArrayExpression, true); - return SyntaxFactory.IfStatement(oldTargetNotEqualToNull, arrayCopyStatement); - } - - /// - /// Array copy for multiple array dimensions represented by - /// - /// - /// Exception cases will sometimes silently succeed in the converted code, - /// but existing VB code relying on the exception thrown from a multidimensional redim preserve on - /// different rank arrays is hopefully rare enough that it's worth saving a few lines of code - /// - private StatementSyntax CreateArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, - IdentifierNameSyntax sourceArrayExpression, - MemberAccessExpressionSyntax sourceLength, - ExpressionSyntax targetArrayExpression, ICollection convertedBounds) - { - var lastSourceLengthArgs = ExpressionSyntaxExtensions.CreateArgList(CommonConversions.Literal(convertedBounds.Count - 1)); - var sourceLastRankLength = SyntaxFactory.InvocationExpression( - SyntaxFactory.ParseExpression($"{sourceArrayExpression.Identifier}.GetLength"), lastSourceLengthArgs); - var targetLastRankLength = - SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression($"{targetArrayExpression}.GetLength"), - lastSourceLengthArgs); - var length = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(sourceLastRankLength, targetLastRankLength)); - - var loopVariableName = GetUniqueVariableNameInScope(originalVbNode, "i"); - var loopVariableIdentifier = ValidSyntaxFactory.IdentifierName(loopVariableName); - var sourceStartForThisIteration = - SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, sourceLastRankLength); - var targetStartForThisIteration = - SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, targetLastRankLength); - - var arrayCopy = CreateArrayCopyWithStartingPoints(sourceArrayExpression, sourceStartForThisIteration, targetArrayExpression, - targetStartForThisIteration, length); - - var sourceArrayCount = SyntaxFactory.BinaryExpression(SyntaxKind.SubtractExpression, - SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, sourceLength, sourceLastRankLength), CommonConversions.Literal(1)); - - return CreateForZeroToValueLoop(loopVariableIdentifier, arrayCopy, sourceArrayCount); - } - - private static ForStatementSyntax CreateForZeroToValueLoop(SimpleNameSyntax loopVariableIdentifier, StatementSyntax loopStatement, ExpressionSyntax inclusiveLoopUpperBound) - { - var loopVariableAssignment = CommonConversions.CreateVariableDeclarationAndAssignment(loopVariableIdentifier.Identifier.Text, CommonConversions.Literal(0)); - var lessThanSourceBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, - loopVariableIdentifier, inclusiveLoopUpperBound); - var incrementors = SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.PrefixUnaryExpression(SyntaxKind.PreIncrementExpression, loopVariableIdentifier)); - var forStatementSyntax = SyntaxFactory.ForStatement(loopVariableAssignment, - SyntaxFactory.SeparatedList(), - lessThanSourceBounds, incrementors, loopStatement); - return forStatementSyntax; - } - - private static ExpressionStatementSyntax CreateArrayCopyWithMinOfLengths( - IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceLength, - ExpressionSyntax targetExpression, ExpressionSyntax targetLength) - { - var minLength = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(targetLength, sourceLength)); - var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, targetExpression, minLength); - var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); - return SyntaxFactory.ExpressionStatement(arrayCopy); - } - - private static ExpressionStatementSyntax CreateArrayCopyWithStartingPoints( - IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceStart, - ExpressionSyntax targetExpression, ExpressionSyntax targetStart, ExpressionSyntax length) - { - var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, sourceStart, targetExpression, targetStart, length); - var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); - return SyntaxFactory.ExpressionStatement(arrayCopy); - } - - private ExpressionStatementSyntax CreateNewArrayAssignment(VBSyntax.ExpressionSyntax vbArrayExpression, - ExpressionSyntax csArrayExpression, List convertedBounds) - { - var convertedType = (IArrayTypeSymbol) _semanticModel.GetTypeInfo(vbArrayExpression).ConvertedType; - var arrayRankSpecifierSyntax = SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(convertedBounds)); - var rankSpecifiers = SyntaxFactory.SingletonList(arrayRankSpecifierSyntax); - while (convertedType.ElementType is IArrayTypeSymbol ats) { - convertedType = ats; - rankSpecifiers = rankSpecifiers.Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.OmittedArraySizeExpression()))); - }; - var typeSyntax = CommonConversions.GetTypeSyntax(convertedType.ElementType); - var arrayCreation = - SyntaxFactory.ArrayCreationExpression(SyntaxFactory.ArrayType(typeSyntax, rankSpecifiers)); - var assignmentExpressionSyntax = - SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csArrayExpression, arrayCreation); - var newArrayAssignment = SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); - return newArrayAssignment; - } - - public override async Task> VisitThrowStatement(VBSyntax.ThrowStatementSyntax node) - { - return SingleStatement(SyntaxFactory.ThrowStatement(await node.Expression.AcceptAsync(_expressionVisitor))); - } - - public override async Task> VisitReturnStatement(VBSyntax.ReturnStatementSyntax node) - { - if (IsIterator) - return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); - - var csExpression = await node.Expression.AcceptAsync(_expressionVisitor); - csExpression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csExpression); - return SingleStatement(SyntaxFactory.ReturnStatement(csExpression)); - } - - public override async Task> VisitContinueStatement(VBSyntax.ContinueStatementSyntax node) - { - var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); - return SyntaxFactory.List(_perScopeState.ConvertContinue(vbBlockKeywordKind)); - } - - public override async Task> VisitYieldStatement(VBSyntax.YieldStatementSyntax node) - { - return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, await node.Expression.AcceptAsync(_expressionVisitor))); - } - - public override async Task> VisitExitStatement(VBSyntax.ExitStatementSyntax node) - { - var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); - switch (vbBlockKeywordKind) { - case VBasic.SyntaxKind.SubKeyword: - case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) != true: - return SingleStatement(SyntaxFactory.ReturnStatement()); - case VBasic.SyntaxKind.FunctionKeyword: - case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) == true: - VBasic.VisualBasicSyntaxNode typeContainer = node.GetAncestor() - ?? (VBasic.VisualBasicSyntaxNode)node.GetAncestor() - ?? node.GetAncestor(); - var enclosingMethodInfo = typeContainer switch { - VBSyntax.LambdaExpressionSyntax e => _semanticModel.GetSymbolInfo(e).Symbol, - VBSyntax.MethodBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.SubOrFunctionStatement), - VBSyntax.AccessorBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.AccessorStatement), - _ => null - } as IMethodSymbol; - - if (IsIterator) return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); - - if (!enclosingMethodInfo.ReturnsVoidOrAsyncTask()) { - ExpressionSyntax expr = HasReturnVariable ? ReturnVariable : SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression); - return SingleStatement(SyntaxFactory.ReturnStatement(expr)); - } - - return SingleStatement(SyntaxFactory.ReturnStatement()); - default: - return SyntaxFactory.List(_perScopeState.ConvertExit(vbBlockKeywordKind)); - } - } - - public override async Task> VisitRaiseEventStatement(VBSyntax.RaiseEventStatementSyntax node) - { - var argumentListSyntax = await node.ArgumentList.AcceptAsync(_expressionVisitor) ?? SyntaxFactory.ArgumentList(); - - var symbolInfo = _semanticModel.GetSymbolInfo(node.Name).ExtractBestMatch(); - if (symbolInfo?.RaiseMethod != null) { - return SingleStatement(SyntaxFactory.InvocationExpression( - ValidSyntaxFactory.IdentifierName($"On{symbolInfo.Name}"), - argumentListSyntax)); - } - - var memberBindingExpressionSyntax = SyntaxFactory.MemberBindingExpression(ValidSyntaxFactory.IdentifierName("Invoke")); - var conditionalAccessExpressionSyntax = SyntaxFactory.ConditionalAccessExpression( - await node.Name.AcceptAsync(_expressionVisitor), - SyntaxFactory.InvocationExpression(memberBindingExpressionSyntax, argumentListSyntax) - ); - return SingleStatement( - conditionalAccessExpressionSyntax - ); - } - - public override async Task> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node) - { - var condition = await node.Condition.AcceptAsync(_expressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - ElseClauseSyntax elseClause = null; - - if (node.ElseClause != null) { - var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(node.ElseClause.Statements)); - elseClause = SyntaxFactory.ElseClause(elseBlock.UnpackNonNestedBlock()); - } - return SingleStatement(SyntaxFactory.IfStatement(condition, block.UnpackNonNestedBlock(), elseClause)); - } - - public override async Task> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node) - { - var condition = await node.IfStatement.Condition.AcceptAsync(_expressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - - var elseClause = await ConvertElseClauseAsync(node.ElseBlock); - elseClause = elseClause.WithVbSourceMappingFrom(node.ElseBlock); //Special case where explicit mapping is needed since block becomes clause so cannot be easily visited - - var elseIfBlocks = await node.ElseIfBlocks.SelectAsync(async elseIf => await ConvertElseIfAsync(elseIf)); - foreach (var elseIf in Enumerable.Reverse(elseIfBlocks)) { - var ifStmt = SyntaxFactory.IfStatement(elseIf.ElseIfCondition, elseIf.ElseBlock, elseClause); - elseClause = SyntaxFactory.ElseClause(ifStmt); - } - - return SingleStatement(SyntaxFactory.IfStatement(condition, block, elseClause)); - } - - private async Task<(ExpressionSyntax ElseIfCondition, BlockSyntax ElseBlock)> ConvertElseIfAsync(VBSyntax.ElseIfBlockSyntax elseIf) - { - var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements)); - var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); - elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: CommonConversions.KnownTypes.Boolean); - return (elseIfCondition, elseBlock); - } - - private async Task ConvertElseClauseAsync(VBSyntax.ElseBlockSyntax elseBlock) - { - if (elseBlock == null) return null; - - var csStatements = await ConvertStatementsAsync(elseBlock.Statements); - if (csStatements.TryUnpackSingleStatement(out var stmt) && stmt.IsKind(SyntaxKind.IfStatement)) { - // so that you get a neat "else if" at the end - return SyntaxFactory.ElseClause(stmt); - } - - return SyntaxFactory.ElseClause(SyntaxFactory.Block(csStatements)); - } - - private async Task ConvertStatementsAsync(SyntaxList statementSyntaxs) - { - return await statementSyntaxs.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); - } - - /// - /// See https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/for-next-statement#BKMK_Counter - /// - public override async Task> VisitForBlock(VBSyntax.ForBlockSyntax node) - { - var stmt = node.ForStatement; - VariableDeclarationSyntax declaration = null; - ExpressionSyntax id; - var controlVarSymbol = _semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol ?? (_semanticModel.GetOperation(stmt.ControlVariable) as IVariableDeclaratorOperation)?.Symbol; - - // If missing semantic info, the compiler just guesses object, let's try to improve on that guess: - var controlVarType = controlVarSymbol?.GetSymbolType().Yield().Concat( - new SyntaxNode[] {stmt.ControlVariable, stmt.FromValue, stmt.ToValue, stmt.StepClause?.StepValue} - .Select(exp => _semanticModel.GetTypeInfo(exp).Type) - ).FirstOrDefault(t => t != null && t.SpecialType != SpecialType.System_Object); - var startValue = await stmt.FromValue.AcceptAsync(_expressionVisitor); - startValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.FromValue, startValue?.SkipIntoParens(), forceTargetType: controlVarType); - - var initializers = new List(); - var controlVarTypeSyntax = CommonConversions.GetTypeSyntax(controlVarType); - if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax v) { - declaration = (await SplitVariableDeclarationsAsync(v)).Variables.Single().Decl; - declaration = declaration.WithVariables(SyntaxFactory.SingletonSeparatedList(declaration.Variables[0].WithInitializer(SyntaxFactory.EqualsValueClause(startValue)))); - id = ValidSyntaxFactory.IdentifierName(declaration.Variables[0].Identifier); - } else { - id = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); - - if (controlVarSymbol != null && controlVarSymbol.DeclaringSyntaxReferences.Any(r => r.Span.OverlapsWith(stmt.ControlVariable.Span))) { - declaration = CommonConversions.CreateVariableDeclarationAndAssignment(controlVarSymbol.Name, startValue, controlVarTypeSyntax); - } else { - startValue = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, id, startValue); - initializers.Add(startValue); - } - } - - - var preLoopStatements = new List(); - var csToValue = await stmt.ToValue.AcceptAsync(_expressionVisitor); - csToValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.ToValue, csToValue?.SkipIntoParens(), forceTargetType: controlVarType); - - // In Visual Basic, the To expression is only evaluated once, but in C# will be evaluated every loop. - // If it could evaluate differently or has side effects, it must be extracted as a variable - if (!_semanticModel.GetConstantValue(stmt.ToValue).HasValue) { - var loopToVariableName = GetUniqueVariableNameInScope(node, "loopTo"); - var toVariableId = ValidSyntaxFactory.IdentifierName(loopToVariableName); - - var loopToAssignment = CommonConversions.CreateVariableDeclarator(loopToVariableName, csToValue); - if (initializers.Any()) { - var loopEndDeclaration = SyntaxFactory.LocalDeclarationStatement( - CommonConversions.CreateVariableDeclarationAndAssignment(loopToVariableName, csToValue)); - // Does not do anything about porting newline trivia upwards to maintain spacing above the loop - preLoopStatements.Add(loopEndDeclaration); - } else { - declaration = declaration == null - ? SyntaxFactory.VariableDeclaration(controlVarTypeSyntax, - SyntaxFactory.SingletonSeparatedList(loopToAssignment)) - : declaration.AddVariables(loopToAssignment).WithType(controlVarTypeSyntax); - } - - csToValue = toVariableId; - } - - var (csCondition, csStep) = await ConvertConditionAndStepClauseAsync(stmt, id, csToValue, controlVarType); - - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - var forStatementSyntax = SyntaxFactory.ForStatement( - declaration, - SyntaxFactory.SeparatedList(initializers), - csCondition, - SyntaxFactory.SingletonSeparatedList(csStep), - block.UnpackNonNestedBlock()); - return SyntaxFactory.List(preLoopStatements.Concat(new[] { forStatementSyntax })); - } - - private async Task<(IReadOnlyCollection Variables, IReadOnlyCollection Methods)> SplitVariableDeclarationsAsync(VBSyntax.VariableDeclaratorSyntax v, bool preferExplicitType = false) - { - return await CommonConversions.SplitVariableDeclarationsAsync(v, _localsToInlineInLoop, preferExplicitType); - } - - private async Task<(ExpressionSyntax, ExpressionSyntax)> ConvertConditionAndStepClauseAsync(VBSyntax.ForStatementSyntax stmt, ExpressionSyntax id, ExpressionSyntax csToValue, ITypeSymbol controlVarType) - { - var vbStepValue = stmt.StepClause?.StepValue; - var csStepValue = await (stmt.StepClause?.StepValue).AcceptAsync(_expressionVisitor); - // For an enum, you need to add on an integer for example: - var forceStepType = controlVarType is INamedTypeSymbol nt && nt.IsEnumType() ? nt.EnumUnderlyingType : controlVarType; - csStepValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbStepValue, csStepValue?.SkipIntoParens(), forceTargetType: forceStepType); - - var nonNegativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, id, csToValue); - var negativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, id, csToValue); - if (csStepValue == null) { - return (nonNegativeCondition, SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, id)); - } - - ExpressionSyntax csCondition; - ExpressionSyntax csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.AddAssignmentExpression, id, csStepValue); - var vbStepConstValue = _semanticModel.GetConstantValue(vbStepValue); - var constValue = !vbStepConstValue.HasValue ? null : (dynamic)vbStepConstValue.Value; - if (constValue == null) { - var ifStepNonNegative = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, csStepValue, CommonConversions.Literal(0)); - csCondition = SyntaxFactory.ConditionalExpression(ifStepNonNegative, nonNegativeCondition, negativeCondition); - } else if (constValue < 0) { - csCondition = negativeCondition; - if (csStepValue is PrefixUnaryExpressionSyntax pues && pues.OperatorToken.IsKind(SyntaxKind.MinusToken)) { - csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.SubtractAssignmentExpression, id, pues.Operand); - } - } else { - csCondition = nonNegativeCondition; - } - - return (csCondition, csStep); - } - - public override async Task> VisitForEachBlock(VBSyntax.ForEachBlockSyntax node) - { - var stmt = node.ForEachStatement; - - TypeSyntax type; - SyntaxToken id; - List statements = new List(); - if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax vds) { - var declaration = (await SplitVariableDeclarationsAsync(vds)).Variables.Single().Decl; - type = declaration.Type; - id = declaration.Variables.Single().Identifier; - } else if (_semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol is { } varSymbol) { - var variableType = varSymbol.GetSymbolType(); - var explicitCastWouldHaveNoEffect = variableType?.SpecialType == SpecialType.System_Object || _semanticModel.GetTypeInfo(stmt.Expression).ConvertedType.IsEnumerableOfExactType(variableType); - type = CommonConversions.GetTypeSyntax(varSymbol.GetSymbolType(), explicitCastWouldHaveNoEffect); - var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); - if (_localsToInlineInLoop.Contains(varSymbol, SymbolEqualityComparer.IncludeNullability) && v is IdentifierNameSyntax vId) { - id = vId.Identifier; - } else { - id = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "current" + varSymbol.Name.ToPascalCase())); - statements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, v, ValidSyntaxFactory.IdentifierName(id)))); - } - } else { - var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); - id = v.Identifier; - type = ValidSyntaxFactory.VarType; - } - - var block = SyntaxFactory.Block(statements.Concat(await ConvertStatementsAsync(node.Statements))); - var csExpression = await stmt.Expression.AcceptAsync(_expressionVisitor); - return SingleStatement(SyntaxFactory.ForEachStatement( - type, - id, - CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.Expression, csExpression), - block.UnpackNonNestedBlock() - )); - } - - public override async Task> VisitLabelStatement(VBSyntax.LabelStatementSyntax node) - { - return SingleStatement(SyntaxFactory.LabeledStatement(CommonConversions.CsEscapedIdentifier(node.LabelToken.Text), SyntaxFactory.EmptyStatement())); - } - - public override async Task> VisitGoToStatement(VBSyntax.GoToStatementSyntax node) - { - return SingleStatement(SyntaxFactory.GotoStatement(SyntaxKind.GotoStatement, - ValidSyntaxFactory.IdentifierName((node.Label.LabelToken.Text)))); - } - - public override async Task> VisitSelectBlock(VBSyntax.SelectBlockSyntax node) - { - var vbExpr = node.SelectStatement.Expression; - var vbEquality = CommonConversions.VisualBasicEqualityComparison; - - var csSwitchExpr = await vbExpr.AcceptAsync(_expressionVisitor); - csSwitchExpr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csSwitchExpr); - var switchExprTypeInfo = _semanticModel.GetTypeInfo(vbExpr); - var isObjectComparison = switchExprTypeInfo.ConvertedType.SpecialType == SpecialType.System_Object; - var isStringComparison = !isObjectComparison && switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String || switchExprTypeInfo.ConvertedType?.IsArrayOf(SpecialType.System_Char) == true; - var caseInsensitiveStringComparison = vbEquality.OptionCompareTextCaseInsensitive && - isStringComparison; - if (isStringComparison) { - csSwitchExpr = vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo); - } - - var usedConstantValues = new HashSet(); - var sections = new List(); - foreach (var block in node.CaseBlocks) { - var labels = new List(); - foreach (var c in block.CaseStatement.Cases) { - if (c is VBSyntax.SimpleCaseClauseSyntax s) { - var originalExpressionSyntax = await s.Value.AcceptAsync(_expressionVisitor); - var caseTypeInfo = _semanticModel.GetTypeInfo(s.Value); - var typeConversionKind = CommonConversions.TypeConversionAnalyzer.AnalyzeConversion(s.Value); - var correctTypeExpressionSyntax = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(s.Value, originalExpressionSyntax, typeConversionKind, true, true); - var constantValue = _semanticModel.GetConstantValue(s.Value); - var notAlreadyUsed = !constantValue.HasValue || usedConstantValues.Add(constantValue.Value); - - // Pass both halves in case we can optimize away the check based on the switch expr - var wrapForStringComparison = isStringComparison && (caseInsensitiveStringComparison || - vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo, true, s.Value, originalExpressionSyntax, caseTypeInfo, false).rhs != originalExpressionSyntax); - - // CSharp requires an explicit cast from the base type (e.g. int) in most cases switching on an enum - var isBooleanCase = caseTypeInfo.Type?.SpecialType == SpecialType.System_Boolean; - bool enumRelated = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) || IsEnumOrNullableEnum(caseTypeInfo.Type); - bool convertingEnum = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) ^ IsEnumOrNullableEnum(caseTypeInfo.Type); - var csExpressionToUse = !isObjectComparison && !isBooleanCase && (convertingEnum || !enumRelated && correctTypeExpressionSyntax.IsConst) - ? correctTypeExpressionSyntax.Expr - : originalExpressionSyntax; - - var caseSwitchLabelSyntax = !isObjectComparison && !wrapForStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed - ? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(csExpressionToUse) - : WrapInCasePatternSwitchLabelSyntax(node, s.Value, csExpressionToUse, isBooleanCase); - labels.Add(caseSwitchLabelSyntax); - } else if (c is VBSyntax.ElseCaseClauseSyntax) { - labels.Add(SyntaxFactory.DefaultSwitchLabel()); - } else if (c is VBSyntax.RelationalCaseClauseSyntax relational) { - - var operatorKind = VBasic.VisualBasicExtensions.Kind(relational); - var csRelationalValue = await relational.Value.AcceptAsync(_expressionVisitor); - CasePatternSwitchLabelSyntax caseSwitchLabelSyntax; - if (isObjectComparison) { - caseSwitchLabelSyntax = WrapInCasePatternSwitchLabelSyntax(node, relational.Value, csRelationalValue, false, operatorKind); - } - else { - var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); - ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName); - csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue); - var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(), csLeft, csRelationalValue); - caseSwitchLabelSyntax = VarWhen(varName, binaryExp); - } - labels.Add(caseSwitchLabelSyntax); - } else if (c is VBSyntax.RangeCaseClauseSyntax range) { - var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); - ExpressionSyntax csCaseVar = ValidSyntaxFactory.IdentifierName(varName); - var lowerBound = await range.LowerBound.AcceptAsync(_expressionVisitor); - ExpressionSyntax lowerBoundCheck; - if (isObjectComparison) { - var caseTypeInfo = _semanticModel.GetTypeInfo(range.LowerBound); - lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual); - } else { - lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound); - lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar); - } - var upperBound = await range.UpperBound.AcceptAsync(_expressionVisitor); - ExpressionSyntax upperBoundCheck; - if (isObjectComparison) { - var caseTypeInfo = _semanticModel.GetTypeInfo(range.UpperBound); - upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual); - } else { - upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound); - upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound); - } - var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck); - labels.Add(VarWhen(varName, withinBounds)); - } else { - throw new NotSupportedException(c.Kind().ToString()); - } - } - - var csBlockStatements = (await ConvertStatementsAsync(block.Statements)).ToList(); - if (!DefinitelyExits(csBlockStatements.LastOrDefault())) { - csBlockStatements.Add(SyntaxFactory.BreakStatement()); - } - var list = SingleStatement(SyntaxFactory.Block(csBlockStatements)); - sections.Add(SyntaxFactory.SwitchSection(SyntaxFactory.List(labels), list)); - } - - var switchStatementSyntax = ValidSyntaxFactory.SwitchStatement(csSwitchExpr, sections); - return SingleStatement(switchStatementSyntax); - } - - private static bool DefinitelyExits(StatementSyntax statement) - { - if (statement == null) { - return false; - } - - StatementSyntax GetLastStatement(StatementSyntax node) => node is BlockSyntax block ? block.Statements.LastOrDefault() : node; - bool IsExitStatement(StatementSyntax node) - { - node = GetLastStatement(node); - return node != null && node.IsKind(SyntaxKind.ReturnStatement, SyntaxKind.BreakStatement, SyntaxKind.ThrowStatement, SyntaxKind.ContinueStatement); - } - - if (IsExitStatement(statement)) { - return true; - } - - if (statement is not IfStatementSyntax ifStatement) { - return false; - } - - while(ifStatement.Else != null) { - if (!IsExitStatement(ifStatement.Statement)) { - return false; - } - - if (ifStatement.Else.Statement is IfStatementSyntax x) { - ifStatement = x; - } else { - return IsExitStatement(ifStatement.Else.Statement); - } - } - - return false; - } - - private static bool IsEnumOrNullableEnum(ITypeSymbol convertedType) => - convertedType?.IsEnumType() == true || convertedType?.GetNullableUnderlyingType()?.IsEnumType() == true; - - private static CasePatternSwitchLabelSyntax VarWhen(SyntaxToken varName, ExpressionSyntax binaryExp) - { - var patternMatch = ValidSyntaxFactory.VarPattern(varName); - return SyntaxFactory.CasePatternSwitchLabel(patternMatch, - SyntaxFactory.WhenClause(binaryExp), SyntaxFactory.Token(SyntaxKind.ColonToken)); - } - - private async Task<(ExpressionSyntax Reusable, SyntaxList Statements, ExpressionSyntax SingleUse)> GetExpressionWithoutSideEffectsAsync(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase) - { - var expr = await vbExpr.AcceptAsync(_expressionVisitor); - expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, expr); - SyntaxList stmts = SyntaxFactory.List(); - ExpressionSyntax exprWithoutSideEffects; - ExpressionSyntax reusableExprWithoutSideEffects; - if (IsReusableReadOnlyLocalKind(_semanticModel.GetSymbolInfo(vbExpr).Symbol) || await CanEvaluateMultipleTimesAsync(vbExpr)) { - exprWithoutSideEffects = expr; - reusableExprWithoutSideEffects = expr.WithoutSourceMapping(); - } else { - TypeSyntax forceType = null; - if (_semanticModel.GetOperation(vbExpr.SkipIntoParens()).IsAssignableExpression()) { - forceType = SyntaxFactory.RefType(ValidSyntaxFactory.VarType); - expr = SyntaxFactory.RefExpression(expr); - } - - var (stmt, id) = CreateLocalVariableWithUniqueName(vbExpr, variableNameBase, expr, forceType); - stmts = stmts.Add(stmt); - reusableExprWithoutSideEffects = exprWithoutSideEffects = id; - } - - return (reusableExprWithoutSideEffects, stmts, exprWithoutSideEffects); - } - - private static bool IsReusableReadOnlyLocalKind(ISymbol symbol) => symbol is ILocalSymbol ls && (VBasic.VisualBasicExtensions.IsForEach(ls) || ls.IsUsing); - - private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) - { - var contextNode = vbExpr.GetAncestor() ?? (VBasic.VisualBasicSyntaxNode) vbExpr.Parent; - return CreateLocalVariableWithUniqueName(contextNode, variableNameBase, expr, forceType); - } - - private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VisualBasicSyntaxNode contextNode, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) - { - var varName = GetUniqueVariableNameInScope(contextNode, variableNameBase); - var stmt = CommonConversions.CreateLocalVariableDeclarationAndAssignment(varName, expr, forceType); - return (stmt, ValidSyntaxFactory.IdentifierName(varName)); - } - - private async Task CanEvaluateMultipleTimesAsync(VBSyntax.ExpressionSyntax vbExpr) - { - return _semanticModel.GetConstantValue(vbExpr).HasValue || vbExpr.IsKind(VBasic.SyntaxKind.MeExpression) || vbExpr.SkipIntoParens() is VBSyntax.NameSyntax ns && await IsNeverMutatedAsync(ns); - } - - private async Task IsNeverMutatedAsync(VBSyntax.NameSyntax ns) - { - var allowedLocation = Location.Create(ns.SyntaxTree, TextSpan.FromBounds(ns.GetAncestor().SpanStart, ns.Span.End)); - var symbol = _semanticModel.GetSymbolInfo(ns).Symbol; - //Perf optimization: Looking across the whole solution is expensive, so assume non-local symbols are written somewhere - return symbol.MatchesKind(SymbolKind.Parameter, SymbolKind.Local) && await CommonConversions.Document.Project.Solution.IsNeverWrittenAsync(symbol, allowedLocation); - } - - private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool treatAsBoolean = false, VBasic.SyntaxKind caseClauseKind = VBasic.SyntaxKind.CaseEqualsClause) - { - var typeInfo = _semanticModel.GetTypeInfo(node.SelectStatement.Expression); - - DeclarationPatternSyntax patternMatch; - if (typeInfo.ConvertedType.SpecialType == SpecialType.System_Boolean || treatAsBoolean) { - patternMatch = SyntaxFactory.DeclarationPattern( - SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), - SyntaxFactory.DiscardDesignation()); - } else { - var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); - patternMatch = ValidSyntaxFactory.VarPattern(varName); - ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName), csRight = expression; - var caseTypeInfo = _semanticModel.GetTypeInfo(vbCase); - expression = ComparisonAdjustedForStringComparison(node, vbCase, typeInfo, csLeft, csRight, caseTypeInfo, GetComparisonKind(caseClauseKind)); - } - - var colonToken = SyntaxFactory.Token(SyntaxKind.ColonToken); - return SyntaxFactory.CasePatternSwitchLabel(patternMatch, SyntaxFactory.WhenClause(expression), colonToken); - } - - private ComparisonKind GetComparisonKind(VBasic.SyntaxKind caseClauseKind) => caseClauseKind switch { - VBasic.SyntaxKind.CaseLessThanClause => ComparisonKind.LessThan, - VBasic.SyntaxKind.CaseLessThanOrEqualClause => ComparisonKind.LessThanOrEqual, - VBasic.SyntaxKind.CaseEqualsClause => ComparisonKind.Equals, - VBasic.SyntaxKind.CaseNotEqualsClause => ComparisonKind.NotEquals, - VBasic.SyntaxKind.CaseGreaterThanOrEqualClause => ComparisonKind.GreaterThanOrEqual, - VBasic.SyntaxKind.CaseGreaterThanClause => ComparisonKind.GreaterThan, - _ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null) - }; - - private ExpressionSyntax ComparisonAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo, ComparisonKind comparisonKind) - { - var vbEquality = CommonConversions.VisualBasicEqualityComparison; - switch (_visualBasicEqualityComparison.GetObjectEqualityType(lhsTypeInfo, rhsTypeInfo)) { - case VisualBasicEqualityComparison.RequiredType.Object: - return vbEquality.GetFullExpressionForVbObjectComparison(csLeft, csRight, comparisonKind); - case VisualBasicEqualityComparison.RequiredType.StringOnly: - // We know lhs isn't null, because we always coalesce it in the switch expression - (csLeft, csRight) = vbEquality - .AdjustForVbStringComparison(node.SelectStatement.Expression, csLeft, lhsTypeInfo, true, vbCase, csRight, rhsTypeInfo, false); - break; - } - return SyntaxFactory.BinaryExpression(GetSyntaxKind(comparisonKind), csLeft, csRight); - } - - private CS.SyntaxKind GetSyntaxKind(ComparisonKind comparisonKind) => comparisonKind switch { - ComparisonKind.LessThan => CS.SyntaxKind.LessThanExpression, - ComparisonKind.LessThanOrEqual => CS.SyntaxKind.LessThanOrEqualExpression, - ComparisonKind.Equals => CS.SyntaxKind.EqualsExpression, - ComparisonKind.NotEquals => CS.SyntaxKind.NotEqualsExpression, - ComparisonKind.GreaterThanOrEqual => CS.SyntaxKind.GreaterThanOrEqualExpression, - ComparisonKind.GreaterThan => CS.SyntaxKind.GreaterThanExpression, - _ => throw new ArgumentOutOfRangeException(nameof(comparisonKind), comparisonKind, null) - }; - - public override async Task> VisitWithBlock(VBSyntax.WithBlockSyntax node) - { - var (lhsExpression, prefixDeclarations, _) = await GetExpressionWithoutSideEffectsAsync(node.WithStatement.Expression, "withBlock"); - - _withBlockLhs.Push(lhsExpression); - try { - var statements = await ConvertStatementsAsync(node.Statements); - - var statementSyntaxs = SyntaxFactory.List(prefixDeclarations.Concat(statements)); - return prefixDeclarations.Any() - ? SingleStatement(SyntaxFactory.Block(statementSyntaxs)) - : statementSyntaxs; - } finally { - _withBlockLhs.Pop(); - } - } - - private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase) - { - return NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase); - } - - public override async Task> VisitTryBlock(VBSyntax.TryBlockSyntax node) - { - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - return SingleStatement( - SyntaxFactory.TryStatement( - block, - SyntaxFactory.List(await node.CatchBlocks.SelectAsync(async c => await ConvertCatchBlockAsync(c))), - await ConvertFinallyBlockAsync(node.FinallyBlock) - ) - ); - - async Task ConvertCatchBlockAsync(VBasic.Syntax.CatchBlockSyntax node) - { - var stmt = node.CatchStatement; - CatchDeclarationSyntax catcher = null; - if (stmt.AsClause != null) { - catcher = SyntaxFactory.CatchDeclaration( - ConvertTypeSyntax(stmt.AsClause.Type), - CommonConversions.ConvertIdentifier(stmt.IdentifierName.Identifier) - ); - } - - var filter = await ConvertCatchFilterClauseAsync(stmt.WhenClause); - var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); - return SyntaxFactory.CatchClause( - catcher, - filter, - SyntaxFactory.Block(stmts) - ); - } - - async Task ConvertCatchFilterClauseAsync(VBasic.Syntax.CatchFilterClauseSyntax node) - { - if (node == null) return null; - return SyntaxFactory.CatchFilterClause(await node.Filter.AcceptAsync(_expressionVisitor)); - } - - async Task ConvertFinallyBlockAsync(VBasic.Syntax.FinallyBlockSyntax node) - { - if (node == null) return null; - var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); - return SyntaxFactory.FinallyClause(SyntaxFactory.Block(stmts)); - } - } - - private TypeSyntax ConvertTypeSyntax(VBSyntax.TypeSyntax vbType) - { - if (_semanticModel.GetSymbolInfo(vbType).Symbol is ITypeSymbol typeSymbol) - return CommonConversions.GetTypeSyntax(typeSymbol); - return SyntaxFactory.ParseTypeName(vbType.ToString()); - } - - public override async Task> VisitSyncLockBlock(VBSyntax.SyncLockBlockSyntax node) - { - return SingleStatement(SyntaxFactory.LockStatement( - await node.SyncLockStatement.Expression.AcceptAsync(_expressionVisitor), - SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() - )); - } - - public override async Task> VisitUsingBlock(VBSyntax.UsingBlockSyntax node) - { - var statementSyntax = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - if (node.UsingStatement.Expression == null) { - StatementSyntax stmt = statementSyntax; - foreach (var v in node.UsingStatement.Variables.Reverse()) - foreach (var declaration in (await SplitVariableDeclarationsAsync(v)).Variables.Reverse()) - stmt = SyntaxFactory.UsingStatement(declaration.Decl, null, stmt); - return SingleStatement(stmt); - } - - var expr = await node.UsingStatement.Expression.AcceptAsync(_expressionVisitor); - var unpackPossiblyNestedBlock = statementSyntax.UnpackPossiblyNestedBlock(); // Allow reduced indentation for multiple usings in a row - return SingleStatement(SyntaxFactory.UsingStatement(null, expr, unpackPossiblyNestedBlock)); - } - - public override async Task> VisitWhileBlock(VBSyntax.WhileBlockSyntax node) - { - return SingleStatement(SyntaxFactory.WhileStatement( - await node.WhileStatement.Condition.AcceptAsync(_expressionVisitor), - SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() - )); - } - - public override async Task> VisitDoLoopBlock(VBSyntax.DoLoopBlockSyntax node) - { - var statements = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock(); - - if (node.DoStatement.WhileOrUntilClause != null) { - var stmt = node.DoStatement.WhileOrUntilClause; - if (stmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.WhileKeyword)) - return SingleStatement(SyntaxFactory.WhileStatement( - await stmt.Condition.AcceptAsync(_expressionVisitor), - statements - )); - return SingleStatement(SyntaxFactory.WhileStatement( - (await stmt.Condition.AcceptAsync(_expressionVisitor)).InvertCondition(), - statements - )); - } - - var whileOrUntilStmt = node.LoopStatement.WhileOrUntilClause; - ExpressionSyntax conditionExpression; - bool isUntilStmt; - if (whileOrUntilStmt != null) { - conditionExpression = await whileOrUntilStmt.Condition.AcceptAsync(_expressionVisitor); - isUntilStmt = whileOrUntilStmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.UntilKeyword); - } else { - conditionExpression = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression); - isUntilStmt = false; - } - - if (isUntilStmt) { - conditionExpression = conditionExpression.InvertCondition(); - } - - return SingleStatement(SyntaxFactory.DoStatement(statements, conditionExpression)); - } - - public override async Task> VisitCallStatement(VBSyntax.CallStatementSyntax node) - { - return SingleStatement(await node.Invocation.AcceptAsync(_expressionVisitor)); - } - - private static SyntaxList SingleStatement(StatementSyntax statement) - { - return SyntaxFactory.SingletonList(statement); - } - - private static SyntaxList SingleStatement(ExpressionSyntax expression) - { - return SyntaxFactory.SingletonList(SyntaxFactory.ExpressionStatement(expression)); - } +using System.Collections; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; +using static Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions; +using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using Microsoft.CodeAnalysis.Text; +using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ICSharpCode.CodeConverter.VB; +using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind; + +namespace ICSharpCode.CodeConverter.CSharp; + +/// +/// Executable statements - which includes executable blocks such as if statements +/// Maintains state relevant to the called method-like object. A fresh one must be used for each method, and the same one must be reused for statements in the same method +/// +internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVisitor>> +{ + private readonly VBasic.VisualBasicSyntaxNode _methodNode; + private readonly SemanticModel _semanticModel; + private readonly CommentConvertingVisitorWrapper _expressionVisitor; + private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; + private readonly Stack _withBlockLhs; + private readonly HashSet _extraUsingDirectives; + private readonly HandledEventsAnalysis _handledEventsAnalysis; + private readonly HashSet _generatedNames = new(); + private readonly HashSet _localsToInlineInLoop; + private readonly PerScopeState _perScopeState; + + public bool IsIterator { get; set; } + public IdentifierNameSyntax ReturnVariable { get; set; } + public bool HasReturnVariable => ReturnVariable != null; + public VBasic.VisualBasicSyntaxVisitor>> CommentConvertingVisitor { get; } + + private CommonConversions CommonConversions { get; } + + public static async Task CreateAsync(VisualBasicSyntaxNode node, SemanticModel semanticModel, + CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor, CommonConversions commonConversions, VisualBasicEqualityComparison visualBasicEqualityComparison, + Stack withBlockLhs, HashSet extraUsingDirectives, + ITypeContext typeContext, bool isIterator, IdentifierNameSyntax csReturnVariable) + { + var solution = commonConversions.Document.Project.Solution; + var declarationsToInlineInLoop = await solution.GetDescendantsToInlineInLoopAsync(semanticModel, node); + return new MethodBodyExecutableStatementVisitor(node, semanticModel, triviaConvertingExpressionVisitor, commonConversions, visualBasicEqualityComparison, withBlockLhs, extraUsingDirectives, typeContext, declarationsToInlineInLoop) { + IsIterator = isIterator, + ReturnVariable = csReturnVariable, + }; + } + + private MethodBodyExecutableStatementVisitor(VisualBasicSyntaxNode methodNode, SemanticModel semanticModel, + CommentConvertingVisitorWrapper expressionVisitor, CommonConversions commonConversions, + VisualBasicEqualityComparison visualBasicEqualityComparison, + Stack withBlockLhs, HashSet extraUsingDirectives, + ITypeContext typeContext, HashSet localsToInlineInLoop) + { + _methodNode = methodNode; + _semanticModel = semanticModel; + _expressionVisitor = expressionVisitor; + _visualBasicEqualityComparison = visualBasicEqualityComparison; + CommonConversions = commonConversions; + _withBlockLhs = withBlockLhs; + _extraUsingDirectives = extraUsingDirectives; + _handledEventsAnalysis = typeContext.HandledEventsAnalysis; + _perScopeState = typeContext.PerScopeState; + var byRefParameterVisitor = new PerScopeStateVisitorDecorator(this, _perScopeState, semanticModel, _generatedNames); + CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor); + _localsToInlineInLoop = localsToInlineInLoop; + } + + public override async Task> DefaultVisit(SyntaxNode node) + { + throw new NotImplementedException($"Conversion for {VBasic.VisualBasicExtensions.Kind(node)} not implemented, please report this issue") + .WithNodeInformation(node); + } + + public override async Task> VisitStopOrEndStatement(VBSyntax.StopOrEndStatementSyntax node) + { + return SingleStatement(SyntaxFactory.ParseStatement(ConvertStopOrEndToCSharpStatementText(node))); + } + + private string ConvertStopOrEndToCSharpStatementText(VBSyntax.StopOrEndStatementSyntax node) + { + switch (VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword)) { + case VBasic.SyntaxKind.StopKeyword: + _extraUsingDirectives.Add("System.Diagnostics"); + return "Debugger.Break();"; + case VBasic.SyntaxKind.EndKeyword: + _extraUsingDirectives.Add("System"); + return "Environment.Exit(0);"; + default: + throw new NotImplementedException(VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword) + " not implemented!"); + } + } + + public override async Task> VisitLocalDeclarationStatement(VBSyntax.LocalDeclarationStatementSyntax node) + { + var modifiers = CommonConversions.ConvertModifiers(node.Declarators[0].Names[0], node.Modifiers, TokenContext.Local); + var isConst = modifiers.Any(a => a.IsKind(SyntaxKind.ConstKeyword)); + var isVBStatic = node.Modifiers.Any(a => a.IsKind(VBasic.SyntaxKind.StaticKeyword)); + + var declarations = new List(); + + foreach (var declarator in node.Declarators) { + var (variables, methods) = await SplitVariableDeclarationsAsync(declarator, preferExplicitType: isConst || isVBStatic); + var localDeclarationStatementSyntaxs = variables.Select(declAndType => SyntaxFactory.LocalDeclarationStatement(modifiers, declAndType.Decl)); + if (isVBStatic) { + foreach (var decl in localDeclarationStatementSyntaxs) { + var variable = decl.Declaration.Variables.Single(); + var initializeValue = variable.Initializer?.Value; + string methodName; + SyntaxTokenList methodModifiers; + + MethodKind parentAccessorKind = MethodKind.Ordinary; + if (_methodNode is VBSyntax.MethodBlockSyntax methodBlock) { + var methodStatement = methodBlock.BlockStatement as VBSyntax.MethodStatementSyntax; + methodModifiers = methodStatement.Modifiers; + methodName = methodStatement.Identifier.Text; + } else if (_methodNode is VBSyntax.ConstructorBlockSyntax constructorBlock) { + methodModifiers = constructorBlock.BlockStatement.Modifiers; + methodName = null; + } else if (_methodNode is VBSyntax.AccessorBlockSyntax accessorBlock) { + var propertyBlock = accessorBlock.Parent as VBSyntax.PropertyBlockSyntax; + methodName = propertyBlock.PropertyStatement.Identifier.Text; + parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? MethodKind.PropertyGet : MethodKind.PropertySet; + methodModifiers = propertyBlock.PropertyStatement.Modifiers; + } else { + throw new NotImplementedException(_methodNode.GetType() + " not implemented!"); + } + + var isVbShared = methodModifiers.Any(a => a.IsKind(VBasic.SyntaxKind.SharedKeyword)); + _perScopeState.HoistToTopLevel(new HoistedFieldFromVbStaticVariable(methodName, variable.Identifier.Text, parentAccessorKind, initializeValue, decl.Declaration.Type, isVbShared)); + } + } else { + var shouldPullVariablesBeforeLoop = _perScopeState.IsInsideLoop() && declarator.Initializer is null && declarator.AsClause is not VBSyntax.AsNewClauseSyntax; + if (shouldPullVariablesBeforeLoop) { + localDeclarationStatementSyntaxs = HoistVariablesBeforeLoopWhenNeeded(variables) + .Select(variableDecl => SyntaxFactory.LocalDeclarationStatement(modifiers, variableDecl)); + } + + declarations.AddRange(localDeclarationStatementSyntaxs); + } + var localFunctions = methods.Cast(); + declarations.AddRange(localFunctions); + } + + return SyntaxFactory.List(declarations); + } + + private IEnumerable HoistVariablesBeforeLoopWhenNeeded(IEnumerable variablesDeclarations) + { + foreach (var variablesDecl in variablesDeclarations) { + var variablesToRemove = new List(); + foreach (var (csVariable, vbVariable) in variablesDecl.Variables) { + var symbol = _semanticModel.GetDeclaredSymbol(vbVariable); + var assignedBeforeRead = _semanticModel.IsDefinitelyAssignedBeforeRead(symbol, vbVariable); + if (!assignedBeforeRead) { + _perScopeState.Hoist(new HoistedDefaultInitializedLoopVariable( + csVariable.Identifier.Text, + // e.g. "b As Boolean" has no initializer but can turn into "var b = default(bool)" + csVariable.Initializer?.Value, + variablesDecl.Decl.Type, + _perScopeState.IsInsideNestedLoop())); + variablesToRemove.Add(csVariable); + } + } + + var variablesToDeclareLocally = variablesDecl.Variables.Select(t => t.CsVar).Except(variablesToRemove).ToArray(); + if(variablesToDeclareLocally.Any()) { + yield return variablesDecl.Decl.WithVariables(SyntaxFactory.SeparatedList(variablesToDeclareLocally)); + } + } + } + + public override async Task> VisitAddRemoveHandlerStatement(VBSyntax.AddRemoveHandlerStatementSyntax node) + { + var syntaxKind = ConvertAddRemoveHandlerToCSharpSyntaxKind(node); + return SingleStatement(SyntaxFactory.AssignmentExpression(syntaxKind, + await node.EventExpression.AcceptAsync(_expressionVisitor), + await node.DelegateExpression.AcceptAsync(_expressionVisitor))); + } + + private static SyntaxKind ConvertAddRemoveHandlerToCSharpSyntaxKind(VBSyntax.AddRemoveHandlerStatementSyntax node) + { + switch (node.Kind()) { + case VBasic.SyntaxKind.AddHandlerStatement: + return SyntaxKind.AddAssignmentExpression; + case VBasic.SyntaxKind.RemoveHandlerStatement: + return SyntaxKind.SubtractAssignmentExpression; + default: + throw new NotImplementedException(node.Kind() + " not implemented!"); + } + } + + public override async Task> VisitExpressionStatement(VBSyntax.ExpressionStatementSyntax node) + { + if (node.Expression is VBSyntax.InvocationExpressionSyntax invoke && invoke.Expression is VBSyntax.MemberAccessExpressionSyntax access && access.Expression is VBSyntax.MyBaseExpressionSyntax && access.Name.Identifier.ValueText.Equals("Finalize", StringComparison.OrdinalIgnoreCase)) { + return new SyntaxList(); + } + + return SingleStatement(await node.Expression.AcceptAsync(_expressionVisitor)); + } + + public override async Task> VisitAssignmentStatement(VBSyntax.AssignmentStatementSyntax node) + { + if (node.IsKind(VBasic.SyntaxKind.MidAssignmentStatement) && node.Left is VBSyntax.MidExpressionSyntax mes) { + return await ConvertMidAssignmentAsync(node, mes); + } + + var lhs = await node.Left.AcceptAsync(_expressionVisitor); + var lOperation = _semanticModel.GetOperation(node.Left); + + var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation); + + // 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 && + _methodNode is VBSyntax.MethodBlockSyntax mb && + HasReturnVariable && + id.Identifier.ValueText.Equals(mb.SubOrFunctionStatement.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)) { + lhs = ReturnVariable; + } + + if (node.IsKind(VBasic.SyntaxKind.ExponentiateAssignmentStatement)) { + rhs = SyntaxFactory.InvocationExpression( + ValidSyntaxFactory.MemberAccess(nameof(Math), nameof(Math.Pow)), + ExpressionSyntaxExtensions.CreateArgList(lhs, rhs)); + } + var kind = node.Kind().ConvertToken(); + + + var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left); + var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right); + + var typeConvertedRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); + + // Split out compound operator if type conversion needed on result + if (TypeConversionAnalyzer.GetNonCompoundOrNull(kind) is {} nonCompound) { + + var nonCompoundRhs = SyntaxFactory.BinaryExpression(nonCompound, lhs, typeConvertedRhs); + var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, nonCompoundRhs, forceSourceType: rhsTypeInfo.ConvertedType, forceTargetType: lhsTypeInfo.Type); + 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; + + if (setMethodName != null) { + if (lhs is InvocationExpressionSyntax ies) { + ExpressionSyntax exprToReplace = ies.Expression; + if (exprToReplace is MemberAccessExpressionSyntax maes && maes.Name is IdentifierNameSyntax idn) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn); + exprToReplace = maes.WithName(newName); + + var skipParens = node.Left.SkipIntoParens(); + if (maes.Expression is ThisExpressionSyntax) { + if (skipParens is VBSyntax.InvocationExpressionSyntax inv && inv.Expression is VBSyntax.IdentifierNameSyntax) { + exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); + } else if (skipParens is VBSyntax.IdentifierNameSyntax) { + exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); + } else if (skipParens is VBSyntax.MemberAccessExpressionSyntax vbMaes && vbMaes.Expression is VBSyntax.MyClassExpressionSyntax == false && vbMaes.Expression is VBSyntax.MeExpressionSyntax == false) { + // keep it + } else { + exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); + } + } + } else if (exprToReplace is IdentifierNameSyntax idn2) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2); + exprToReplace = newName; + } + var newArg = SyntaxFactory.Argument(rhs); + var newArgs = ies.ArgumentList.Arguments.Add(newArg); + var newArgList = ies.ArgumentList.WithArguments(newArgs); + var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); + var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs); + var postAssign = GetPostAssignmentStatements(node); + return postAssign.Insert(0, invokeAssignment); + } + return SingleStatement(lhs); + } + + var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); + var postAssignment = GetPostAssignmentStatements(node); + return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); + } + + private async Task> ConvertMidAssignmentAsync(VBSyntax.AssignmentStatementSyntax node, VBSyntax.MidExpressionSyntax mes) + { + _extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices"); + var midFunction = ValidSyntaxFactory.MemberAccess("StringType", "MidStmtStr"); + var midArgList = await mes.ArgumentList.AcceptAsync(_expressionVisitor); + var (reusable, statements, _) = await GetExpressionWithoutSideEffectsAsync(node.Right, "midTmp"); + if (midArgList.Arguments.Count == 2) { + var length = ValidSyntaxFactory.MemberAccess(reusable, "Length"); + midArgList = midArgList.AddArguments(SyntaxFactory.Argument(length)); + } + midArgList = midArgList.AddArguments(SyntaxFactory.Argument(reusable)); + var invokeMid = SyntaxFactory.InvocationExpression(midFunction, midArgList); + return statements.Add(SyntaxFactory.ExpressionStatement(invokeMid)); + } + + /// + /// ensures we convert the property access to a field access + /// + private SyntaxList GetPostAssignmentStatements(VBSyntax.AssignmentStatementSyntax node) + { + var potentialPropertySymbol = _semanticModel.GetSymbolInfo(node.Left).ExtractBestMatch(); + return GetPostAssignmentStatements(node, potentialPropertySymbol); + } + + /// + /// Make winforms designer work: https://github.com/icsharpcode/CodeConverter/issues/321 + /// + public SyntaxList GetPostAssignmentStatements(Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax node, ISymbol potentialPropertySymbol) + { + if (CommonConversions.WinformsConversions.MayNeedToInlinePropertyAccess(node, potentialPropertySymbol)) { + return _handledEventsAnalysis.GetPostAssignmentStatements(potentialPropertySymbol); + } + + return SyntaxFactory.List(); + } + + public override async Task> VisitEraseStatement(VBSyntax.EraseStatementSyntax node) + { + var eraseStatements = await node.Expressions.SelectAsync(async arrayExpression => { + var lhs = await arrayExpression.AcceptAsync(_expressionVisitor); + var rhs = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); + var assignmentExpressionSyntax = + SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, lhs, + rhs); + return SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); + }); + return SyntaxFactory.List(eraseStatements); + } + + public override async Task> VisitReDimStatement(VBSyntax.ReDimStatementSyntax node) + { + return SyntaxFactory.List(await node.Clauses.SelectManyAsync(async arrayExpression => (IEnumerable) await ConvertRedimClauseAsync(arrayExpression))); + } + + /// + /// RedimClauseSyntax isn't an executable statement, therefore this isn't a "Visit" method. + /// Since it returns multiple statements it's easiest for it to be here in the current architecture. + /// + private async Task> ConvertRedimClauseAsync(VBSyntax.RedimClauseSyntax node) + { + bool preserve = node.Parent is VBSyntax.ReDimStatementSyntax rdss && rdss.PreserveKeyword.IsKind(VBasic.SyntaxKind.PreserveKeyword); + + var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); + var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); + if (preserve && convertedBounds.Count == 1) { + bool isProperty = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true; + var arrayToResize = isProperty ? CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression) : default; + var resizeArg = isProperty ? (ExpressionSyntax)arrayToResize.Reference : csTargetArrayExpression; + + var argumentList = new[] { resizeArg, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); + var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList); + + if (isProperty) { + var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, arrayToResize.Reference); + return SyntaxFactory.List(new StatementSyntax[] { arrayToResize.Declaration, SyntaxFactory.ExpressionStatement(arrayResize), SyntaxFactory.ExpressionStatement(assignment) }); + } + + return SingleStatement(arrayResize); + } + var newArrayAssignment = CreateNewArrayAssignment(node.Expression, csTargetArrayExpression, convertedBounds); + if (!preserve) return SingleStatement(newArrayAssignment); + + var lastIdentifierText = node.Expression.DescendantNodesAndSelf().OfType().Last().Identifier.Text; + string variableNameBase = "old" + lastIdentifierText.ToPascalCase(); + var expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csTargetArrayExpression); + var (stmt, oldTargetExpression) = CreateLocalVariableWithUniqueName(node.Expression, variableNameBase, expr); + + var arrayCopyIfNotNull = CreateConditionalArrayCopy(node, oldTargetExpression, csTargetArrayExpression, convertedBounds); + + return SyntaxFactory.List(new[] { stmt, newArrayAssignment, arrayCopyIfNotNull}); + } + + /// + /// Cut down version of Microsoft.VisualBasic.CompilerServices.Utils.CopyArray + /// + private IfStatementSyntax CreateConditionalArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, + IdentifierNameSyntax sourceArrayExpression, + ExpressionSyntax targetArrayExpression, + List convertedBounds) + { + var sourceLength = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, sourceArrayExpression, ValidSyntaxFactory.IdentifierName("Length")); + var arrayCopyStatement = convertedBounds.Count == 1 + ? CreateArrayCopyWithMinOfLengths(sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds.Single()) + : CreateArrayCopy(originalVbNode, sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds); + + var oldTargetNotEqualToNull = CommonConversions.NotNothingComparison(sourceArrayExpression, true); + return SyntaxFactory.IfStatement(oldTargetNotEqualToNull, arrayCopyStatement); + } + + /// + /// Array copy for multiple array dimensions represented by + /// + /// + /// Exception cases will sometimes silently succeed in the converted code, + /// but existing VB code relying on the exception thrown from a multidimensional redim preserve on + /// different rank arrays is hopefully rare enough that it's worth saving a few lines of code + /// + private StatementSyntax CreateArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, + IdentifierNameSyntax sourceArrayExpression, + MemberAccessExpressionSyntax sourceLength, + ExpressionSyntax targetArrayExpression, ICollection convertedBounds) + { + var lastSourceLengthArgs = ExpressionSyntaxExtensions.CreateArgList(CommonConversions.Literal(convertedBounds.Count - 1)); + var sourceLastRankLength = SyntaxFactory.InvocationExpression( + SyntaxFactory.ParseExpression($"{sourceArrayExpression.Identifier}.GetLength"), lastSourceLengthArgs); + var targetLastRankLength = + SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression($"{targetArrayExpression}.GetLength"), + lastSourceLengthArgs); + var length = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(sourceLastRankLength, targetLastRankLength)); + + var loopVariableName = GetUniqueVariableNameInScope(originalVbNode, "i"); + var loopVariableIdentifier = ValidSyntaxFactory.IdentifierName(loopVariableName); + var sourceStartForThisIteration = + SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, sourceLastRankLength); + var targetStartForThisIteration = + SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, targetLastRankLength); + + var arrayCopy = CreateArrayCopyWithStartingPoints(sourceArrayExpression, sourceStartForThisIteration, targetArrayExpression, + targetStartForThisIteration, length); + + var sourceArrayCount = SyntaxFactory.BinaryExpression(SyntaxKind.SubtractExpression, + SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, sourceLength, sourceLastRankLength), CommonConversions.Literal(1)); + + return CreateForZeroToValueLoop(loopVariableIdentifier, arrayCopy, sourceArrayCount); + } + + private static ForStatementSyntax CreateForZeroToValueLoop(SimpleNameSyntax loopVariableIdentifier, StatementSyntax loopStatement, ExpressionSyntax inclusiveLoopUpperBound) + { + var loopVariableAssignment = CommonConversions.CreateVariableDeclarationAndAssignment(loopVariableIdentifier.Identifier.Text, CommonConversions.Literal(0)); + var lessThanSourceBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, + loopVariableIdentifier, inclusiveLoopUpperBound); + var incrementors = SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.PrefixUnaryExpression(SyntaxKind.PreIncrementExpression, loopVariableIdentifier)); + var forStatementSyntax = SyntaxFactory.ForStatement(loopVariableAssignment, + SyntaxFactory.SeparatedList(), + lessThanSourceBounds, incrementors, loopStatement); + return forStatementSyntax; + } + + private static ExpressionStatementSyntax CreateArrayCopyWithMinOfLengths( + IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceLength, + ExpressionSyntax targetExpression, ExpressionSyntax targetLength) + { + var minLength = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(targetLength, sourceLength)); + var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, targetExpression, minLength); + var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); + return SyntaxFactory.ExpressionStatement(arrayCopy); + } + + private static ExpressionStatementSyntax CreateArrayCopyWithStartingPoints( + IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceStart, + ExpressionSyntax targetExpression, ExpressionSyntax targetStart, ExpressionSyntax length) + { + var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, sourceStart, targetExpression, targetStart, length); + var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); + return SyntaxFactory.ExpressionStatement(arrayCopy); + } + + private ExpressionStatementSyntax CreateNewArrayAssignment(VBSyntax.ExpressionSyntax vbArrayExpression, + ExpressionSyntax csArrayExpression, List convertedBounds) + { + var convertedType = (IArrayTypeSymbol) _semanticModel.GetTypeInfo(vbArrayExpression).ConvertedType; + var arrayRankSpecifierSyntax = SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(convertedBounds)); + var rankSpecifiers = SyntaxFactory.SingletonList(arrayRankSpecifierSyntax); + while (convertedType.ElementType is IArrayTypeSymbol ats) { + convertedType = ats; + rankSpecifiers = rankSpecifiers.Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.OmittedArraySizeExpression()))); + }; + var typeSyntax = CommonConversions.GetTypeSyntax(convertedType.ElementType); + var arrayCreation = + SyntaxFactory.ArrayCreationExpression(SyntaxFactory.ArrayType(typeSyntax, rankSpecifiers)); + var assignmentExpressionSyntax = + SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csArrayExpression, arrayCreation); + var newArrayAssignment = SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); + return newArrayAssignment; + } + + public override async Task> VisitThrowStatement(VBSyntax.ThrowStatementSyntax node) + { + return SingleStatement(SyntaxFactory.ThrowStatement(await node.Expression.AcceptAsync(_expressionVisitor))); + } + + public override async Task> VisitReturnStatement(VBSyntax.ReturnStatementSyntax node) + { + if (IsIterator) + return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); + + var csExpression = await node.Expression.AcceptAsync(_expressionVisitor); + csExpression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csExpression); + return SingleStatement(SyntaxFactory.ReturnStatement(csExpression)); + } + + public override async Task> VisitContinueStatement(VBSyntax.ContinueStatementSyntax node) + { + var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); + return SyntaxFactory.List(_perScopeState.ConvertContinue(vbBlockKeywordKind)); + } + + public override async Task> VisitYieldStatement(VBSyntax.YieldStatementSyntax node) + { + return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, await node.Expression.AcceptAsync(_expressionVisitor))); + } + + public override async Task> VisitExitStatement(VBSyntax.ExitStatementSyntax node) + { + var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); + switch (vbBlockKeywordKind) { + case VBasic.SyntaxKind.SubKeyword: + case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) != true: + return SingleStatement(SyntaxFactory.ReturnStatement()); + case VBasic.SyntaxKind.FunctionKeyword: + case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) == true: + VBasic.VisualBasicSyntaxNode typeContainer = node.GetAncestor() + ?? (VBasic.VisualBasicSyntaxNode)node.GetAncestor() + ?? node.GetAncestor(); + var enclosingMethodInfo = typeContainer switch { + VBSyntax.LambdaExpressionSyntax e => _semanticModel.GetSymbolInfo(e).Symbol, + VBSyntax.MethodBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.SubOrFunctionStatement), + VBSyntax.AccessorBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.AccessorStatement), + _ => null + } as IMethodSymbol; + + if (IsIterator) return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); + + if (!enclosingMethodInfo.ReturnsVoidOrAsyncTask()) { + ExpressionSyntax expr = HasReturnVariable ? ReturnVariable : SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression); + return SingleStatement(SyntaxFactory.ReturnStatement(expr)); + } + + return SingleStatement(SyntaxFactory.ReturnStatement()); + default: + return SyntaxFactory.List(_perScopeState.ConvertExit(vbBlockKeywordKind)); + } + } + + public override async Task> VisitRaiseEventStatement(VBSyntax.RaiseEventStatementSyntax node) + { + var argumentListSyntax = await node.ArgumentList.AcceptAsync(_expressionVisitor) ?? SyntaxFactory.ArgumentList(); + + var symbolInfo = _semanticModel.GetSymbolInfo(node.Name).ExtractBestMatch(); + if (symbolInfo?.RaiseMethod != null) { + return SingleStatement(SyntaxFactory.InvocationExpression( + ValidSyntaxFactory.IdentifierName($"On{symbolInfo.Name}"), + argumentListSyntax)); + } + + var memberBindingExpressionSyntax = SyntaxFactory.MemberBindingExpression(ValidSyntaxFactory.IdentifierName("Invoke")); + var conditionalAccessExpressionSyntax = SyntaxFactory.ConditionalAccessExpression( + await node.Name.AcceptAsync(_expressionVisitor), + SyntaxFactory.InvocationExpression(memberBindingExpressionSyntax, argumentListSyntax) + ); + return SingleStatement( + conditionalAccessExpressionSyntax + ); + } + + public override async Task> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node) + { + var condition = await node.Condition.AcceptAsync(_expressionVisitor); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + ElseClauseSyntax elseClause = null; + + if (node.ElseClause != null) { + var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(node.ElseClause.Statements)); + elseClause = SyntaxFactory.ElseClause(elseBlock.UnpackNonNestedBlock()); + } + return SingleStatement(SyntaxFactory.IfStatement(condition, block.UnpackNonNestedBlock(), elseClause)); + } + + public override async Task> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node) + { + var condition = await node.IfStatement.Condition.AcceptAsync(_expressionVisitor); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + + var elseClause = await ConvertElseClauseAsync(node.ElseBlock); + elseClause = elseClause.WithVbSourceMappingFrom(node.ElseBlock); //Special case where explicit mapping is needed since block becomes clause so cannot be easily visited + + var elseIfBlocks = await node.ElseIfBlocks.SelectAsync(async elseIf => await ConvertElseIfAsync(elseIf)); + foreach (var elseIf in Enumerable.Reverse(elseIfBlocks)) { + var ifStmt = SyntaxFactory.IfStatement(elseIf.ElseIfCondition, elseIf.ElseBlock, elseClause); + elseClause = SyntaxFactory.ElseClause(ifStmt); + } + + return SingleStatement(SyntaxFactory.IfStatement(condition, block, elseClause)); + } + + private async Task<(ExpressionSyntax ElseIfCondition, BlockSyntax ElseBlock)> ConvertElseIfAsync(VBSyntax.ElseIfBlockSyntax elseIf) + { + var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements)); + var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); + elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: CommonConversions.KnownTypes.Boolean); + return (elseIfCondition, elseBlock); + } + + private async Task ConvertElseClauseAsync(VBSyntax.ElseBlockSyntax elseBlock) + { + if (elseBlock == null) return null; + + var csStatements = await ConvertStatementsAsync(elseBlock.Statements); + if (csStatements.TryUnpackSingleStatement(out var stmt) && stmt.IsKind(SyntaxKind.IfStatement)) { + // so that you get a neat "else if" at the end + return SyntaxFactory.ElseClause(stmt); + } + + return SyntaxFactory.ElseClause(SyntaxFactory.Block(csStatements)); + } + + private async Task ConvertStatementsAsync(SyntaxList statementSyntaxs) + { + return await statementSyntaxs.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); + } + + /// + /// See https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/for-next-statement#BKMK_Counter + /// + public override async Task> VisitForBlock(VBSyntax.ForBlockSyntax node) + { + var stmt = node.ForStatement; + VariableDeclarationSyntax declaration = null; + ExpressionSyntax id; + var controlVarSymbol = _semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol ?? (_semanticModel.GetOperation(stmt.ControlVariable) as IVariableDeclaratorOperation)?.Symbol; + + // If missing semantic info, the compiler just guesses object, let's try to improve on that guess: + var controlVarType = controlVarSymbol?.GetSymbolType().Yield().Concat( + new SyntaxNode[] {stmt.ControlVariable, stmt.FromValue, stmt.ToValue, stmt.StepClause?.StepValue} + .Select(exp => _semanticModel.GetTypeInfo(exp).Type) + ).FirstOrDefault(t => t != null && t.SpecialType != SpecialType.System_Object); + var startValue = await stmt.FromValue.AcceptAsync(_expressionVisitor); + startValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.FromValue, startValue?.SkipIntoParens(), forceTargetType: controlVarType); + + var initializers = new List(); + var controlVarTypeSyntax = CommonConversions.GetTypeSyntax(controlVarType); + if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax v) { + declaration = (await SplitVariableDeclarationsAsync(v)).Variables.Single().Decl; + declaration = declaration.WithVariables(SyntaxFactory.SingletonSeparatedList(declaration.Variables[0].WithInitializer(SyntaxFactory.EqualsValueClause(startValue)))); + id = ValidSyntaxFactory.IdentifierName(declaration.Variables[0].Identifier); + } else { + id = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); + + if (controlVarSymbol != null && controlVarSymbol.DeclaringSyntaxReferences.Any(r => r.Span.OverlapsWith(stmt.ControlVariable.Span))) { + declaration = CommonConversions.CreateVariableDeclarationAndAssignment(controlVarSymbol.Name, startValue, controlVarTypeSyntax); + } else { + startValue = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, id, startValue); + initializers.Add(startValue); + } + } + + + var preLoopStatements = new List(); + var csToValue = await stmt.ToValue.AcceptAsync(_expressionVisitor); + csToValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.ToValue, csToValue?.SkipIntoParens(), forceTargetType: controlVarType); + + // In Visual Basic, the To expression is only evaluated once, but in C# will be evaluated every loop. + // If it could evaluate differently or has side effects, it must be extracted as a variable + if (!_semanticModel.GetConstantValue(stmt.ToValue).HasValue) { + var loopToVariableName = GetUniqueVariableNameInScope(node, "loopTo"); + var toVariableId = ValidSyntaxFactory.IdentifierName(loopToVariableName); + + var loopToAssignment = CommonConversions.CreateVariableDeclarator(loopToVariableName, csToValue); + if (initializers.Any()) { + var loopEndDeclaration = SyntaxFactory.LocalDeclarationStatement( + CommonConversions.CreateVariableDeclarationAndAssignment(loopToVariableName, csToValue)); + // Does not do anything about porting newline trivia upwards to maintain spacing above the loop + preLoopStatements.Add(loopEndDeclaration); + } else { + declaration = declaration == null + ? SyntaxFactory.VariableDeclaration(controlVarTypeSyntax, + SyntaxFactory.SingletonSeparatedList(loopToAssignment)) + : declaration.AddVariables(loopToAssignment).WithType(controlVarTypeSyntax); + } + + csToValue = toVariableId; + } + + var (csCondition, csStep) = await ConvertConditionAndStepClauseAsync(stmt, id, csToValue, controlVarType); + + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + var forStatementSyntax = SyntaxFactory.ForStatement( + declaration, + SyntaxFactory.SeparatedList(initializers), + csCondition, + SyntaxFactory.SingletonSeparatedList(csStep), + block.UnpackNonNestedBlock()); + return SyntaxFactory.List(preLoopStatements.Concat(new[] { forStatementSyntax })); + } + + private async Task<(IReadOnlyCollection Variables, IReadOnlyCollection Methods)> SplitVariableDeclarationsAsync(VBSyntax.VariableDeclaratorSyntax v, bool preferExplicitType = false) + { + return await CommonConversions.SplitVariableDeclarationsAsync(v, _localsToInlineInLoop, preferExplicitType); + } + + private async Task<(ExpressionSyntax, ExpressionSyntax)> ConvertConditionAndStepClauseAsync(VBSyntax.ForStatementSyntax stmt, ExpressionSyntax id, ExpressionSyntax csToValue, ITypeSymbol controlVarType) + { + var vbStepValue = stmt.StepClause?.StepValue; + var csStepValue = await (stmt.StepClause?.StepValue).AcceptAsync(_expressionVisitor); + // For an enum, you need to add on an integer for example: + var forceStepType = controlVarType is INamedTypeSymbol nt && nt.IsEnumType() ? nt.EnumUnderlyingType : controlVarType; + csStepValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbStepValue, csStepValue?.SkipIntoParens(), forceTargetType: forceStepType); + + var nonNegativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, id, csToValue); + var negativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, id, csToValue); + if (csStepValue == null) { + return (nonNegativeCondition, SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, id)); + } + + ExpressionSyntax csCondition; + ExpressionSyntax csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.AddAssignmentExpression, id, csStepValue); + var vbStepConstValue = _semanticModel.GetConstantValue(vbStepValue); + var constValue = !vbStepConstValue.HasValue ? null : (dynamic)vbStepConstValue.Value; + if (constValue == null) { + var ifStepNonNegative = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, csStepValue, CommonConversions.Literal(0)); + csCondition = SyntaxFactory.ConditionalExpression(ifStepNonNegative, nonNegativeCondition, negativeCondition); + } else if (constValue < 0) { + csCondition = negativeCondition; + if (csStepValue is PrefixUnaryExpressionSyntax pues && pues.OperatorToken.IsKind(SyntaxKind.MinusToken)) { + csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.SubtractAssignmentExpression, id, pues.Operand); + } + } else { + csCondition = nonNegativeCondition; + } + + return (csCondition, csStep); + } + + public override async Task> VisitForEachBlock(VBSyntax.ForEachBlockSyntax node) + { + var stmt = node.ForEachStatement; + + TypeSyntax type; + SyntaxToken id; + List statements = new List(); + if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax vds) { + var declaration = (await SplitVariableDeclarationsAsync(vds)).Variables.Single().Decl; + type = declaration.Type; + id = declaration.Variables.Single().Identifier; + } else if (_semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol is { } varSymbol) { + var variableType = varSymbol.GetSymbolType(); + var explicitCastWouldHaveNoEffect = variableType?.SpecialType == SpecialType.System_Object || _semanticModel.GetTypeInfo(stmt.Expression).ConvertedType.IsEnumerableOfExactType(variableType); + type = CommonConversions.GetTypeSyntax(varSymbol.GetSymbolType(), explicitCastWouldHaveNoEffect); + var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); + if (_localsToInlineInLoop.Contains(varSymbol, SymbolEqualityComparer.IncludeNullability) && v is IdentifierNameSyntax vId) { + id = vId.Identifier; + } else { + id = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "current" + varSymbol.Name.ToPascalCase())); + statements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, v, ValidSyntaxFactory.IdentifierName(id)))); + } + } else { + var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); + id = v.Identifier; + type = ValidSyntaxFactory.VarType; + } + + var block = SyntaxFactory.Block(statements.Concat(await ConvertStatementsAsync(node.Statements))); + var csExpression = await stmt.Expression.AcceptAsync(_expressionVisitor); + return SingleStatement(SyntaxFactory.ForEachStatement( + type, + id, + CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.Expression, csExpression), + block.UnpackNonNestedBlock() + )); + } + + public override async Task> VisitLabelStatement(VBSyntax.LabelStatementSyntax node) + { + return SingleStatement(SyntaxFactory.LabeledStatement(CommonConversions.CsEscapedIdentifier(node.LabelToken.Text), SyntaxFactory.EmptyStatement())); + } + + public override async Task> VisitGoToStatement(VBSyntax.GoToStatementSyntax node) + { + return SingleStatement(SyntaxFactory.GotoStatement(SyntaxKind.GotoStatement, + ValidSyntaxFactory.IdentifierName((node.Label.LabelToken.Text)))); + } + + public override async Task> VisitSelectBlock(VBSyntax.SelectBlockSyntax node) + { + var vbExpr = node.SelectStatement.Expression; + var vbEquality = CommonConversions.VisualBasicEqualityComparison; + + var csSwitchExpr = await vbExpr.AcceptAsync(_expressionVisitor); + csSwitchExpr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csSwitchExpr); + var switchExprTypeInfo = _semanticModel.GetTypeInfo(vbExpr); + var isObjectComparison = switchExprTypeInfo.ConvertedType.SpecialType == SpecialType.System_Object; + var isStringComparison = !isObjectComparison && switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String || switchExprTypeInfo.ConvertedType?.IsArrayOf(SpecialType.System_Char) == true; + var caseInsensitiveStringComparison = vbEquality.OptionCompareTextCaseInsensitive && + isStringComparison; + if (isStringComparison) { + csSwitchExpr = vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo); + } + + var usedConstantValues = new HashSet(); + var sections = new List(); + foreach (var block in node.CaseBlocks) { + var labels = new List(); + foreach (var c in block.CaseStatement.Cases) { + if (c is VBSyntax.SimpleCaseClauseSyntax s) { + var originalExpressionSyntax = await s.Value.AcceptAsync(_expressionVisitor); + var caseTypeInfo = _semanticModel.GetTypeInfo(s.Value); + var typeConversionKind = CommonConversions.TypeConversionAnalyzer.AnalyzeConversion(s.Value); + var correctTypeExpressionSyntax = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(s.Value, originalExpressionSyntax, typeConversionKind, true, true); + var constantValue = _semanticModel.GetConstantValue(s.Value); + var notAlreadyUsed = !constantValue.HasValue || usedConstantValues.Add(constantValue.Value); + + // Pass both halves in case we can optimize away the check based on the switch expr + var wrapForStringComparison = isStringComparison && (caseInsensitiveStringComparison || + vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo, true, s.Value, originalExpressionSyntax, caseTypeInfo, false).rhs != originalExpressionSyntax); + + // CSharp requires an explicit cast from the base type (e.g. int) in most cases switching on an enum + var isBooleanCase = caseTypeInfo.Type?.SpecialType == SpecialType.System_Boolean; + bool enumRelated = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) || IsEnumOrNullableEnum(caseTypeInfo.Type); + bool convertingEnum = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) ^ IsEnumOrNullableEnum(caseTypeInfo.Type); + var csExpressionToUse = !isObjectComparison && !isBooleanCase && (convertingEnum || !enumRelated && correctTypeExpressionSyntax.IsConst) + ? correctTypeExpressionSyntax.Expr + : originalExpressionSyntax; + + var caseSwitchLabelSyntax = !isObjectComparison && !wrapForStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed + ? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(csExpressionToUse) + : WrapInCasePatternSwitchLabelSyntax(node, s.Value, csExpressionToUse, isBooleanCase); + labels.Add(caseSwitchLabelSyntax); + } else if (c is VBSyntax.ElseCaseClauseSyntax) { + labels.Add(SyntaxFactory.DefaultSwitchLabel()); + } else if (c is VBSyntax.RelationalCaseClauseSyntax relational) { + + var operatorKind = VBasic.VisualBasicExtensions.Kind(relational); + var csRelationalValue = await relational.Value.AcceptAsync(_expressionVisitor); + CasePatternSwitchLabelSyntax caseSwitchLabelSyntax; + if (isObjectComparison) { + caseSwitchLabelSyntax = WrapInCasePatternSwitchLabelSyntax(node, relational.Value, csRelationalValue, false, operatorKind); + } + else { + var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); + ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName); + csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue); + var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(), csLeft, csRelationalValue); + caseSwitchLabelSyntax = VarWhen(varName, binaryExp); + } + labels.Add(caseSwitchLabelSyntax); + } else if (c is VBSyntax.RangeCaseClauseSyntax range) { + var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); + ExpressionSyntax csCaseVar = ValidSyntaxFactory.IdentifierName(varName); + var lowerBound = await range.LowerBound.AcceptAsync(_expressionVisitor); + ExpressionSyntax lowerBoundCheck; + if (isObjectComparison) { + var caseTypeInfo = _semanticModel.GetTypeInfo(range.LowerBound); + lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual); + } else { + lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound); + lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar); + } + var upperBound = await range.UpperBound.AcceptAsync(_expressionVisitor); + ExpressionSyntax upperBoundCheck; + if (isObjectComparison) { + var caseTypeInfo = _semanticModel.GetTypeInfo(range.UpperBound); + upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual); + } else { + upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound); + upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound); + } + var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck); + labels.Add(VarWhen(varName, withinBounds)); + } else { + throw new NotSupportedException(c.Kind().ToString()); + } + } + + var csBlockStatements = (await ConvertStatementsAsync(block.Statements)).ToList(); + if (!DefinitelyExits(csBlockStatements.LastOrDefault())) { + csBlockStatements.Add(SyntaxFactory.BreakStatement()); + } + var list = SingleStatement(SyntaxFactory.Block(csBlockStatements)); + sections.Add(SyntaxFactory.SwitchSection(SyntaxFactory.List(labels), list)); + } + + var switchStatementSyntax = ValidSyntaxFactory.SwitchStatement(csSwitchExpr, sections); + return SingleStatement(switchStatementSyntax); + } + + private static bool DefinitelyExits(StatementSyntax statement) + { + if (statement == null) { + return false; + } + + StatementSyntax GetLastStatement(StatementSyntax node) => node is BlockSyntax block ? block.Statements.LastOrDefault() : node; + bool IsExitStatement(StatementSyntax node) + { + node = GetLastStatement(node); + return node != null && node.IsKind(SyntaxKind.ReturnStatement, SyntaxKind.BreakStatement, SyntaxKind.ThrowStatement, SyntaxKind.ContinueStatement); + } + + if (IsExitStatement(statement)) { + return true; + } + + if (statement is not IfStatementSyntax ifStatement) { + return false; + } + + while(ifStatement.Else != null) { + if (!IsExitStatement(ifStatement.Statement)) { + return false; + } + + if (ifStatement.Else.Statement is IfStatementSyntax x) { + ifStatement = x; + } else { + return IsExitStatement(ifStatement.Else.Statement); + } + } + + return false; + } + + private static bool IsEnumOrNullableEnum(ITypeSymbol convertedType) => + convertedType?.IsEnumType() == true || convertedType?.GetNullableUnderlyingType()?.IsEnumType() == true; + + private static CasePatternSwitchLabelSyntax VarWhen(SyntaxToken varName, ExpressionSyntax binaryExp) + { + var patternMatch = ValidSyntaxFactory.VarPattern(varName); + return SyntaxFactory.CasePatternSwitchLabel(patternMatch, + SyntaxFactory.WhenClause(binaryExp), SyntaxFactory.Token(SyntaxKind.ColonToken)); + } + + private async Task<(ExpressionSyntax Reusable, SyntaxList Statements, ExpressionSyntax SingleUse)> GetExpressionWithoutSideEffectsAsync(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase) + { + var expr = await vbExpr.AcceptAsync(_expressionVisitor); + expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, expr); + SyntaxList stmts = SyntaxFactory.List(); + ExpressionSyntax exprWithoutSideEffects; + ExpressionSyntax reusableExprWithoutSideEffects; + if (IsReusableReadOnlyLocalKind(_semanticModel.GetSymbolInfo(vbExpr).Symbol) || await CanEvaluateMultipleTimesAsync(vbExpr)) { + exprWithoutSideEffects = expr; + reusableExprWithoutSideEffects = expr.WithoutSourceMapping(); + } else { + TypeSyntax forceType = null; + if (_semanticModel.GetOperation(vbExpr.SkipIntoParens()).IsAssignableExpression()) { + forceType = SyntaxFactory.RefType(ValidSyntaxFactory.VarType); + expr = SyntaxFactory.RefExpression(expr); + } + + var (stmt, id) = CreateLocalVariableWithUniqueName(vbExpr, variableNameBase, expr, forceType); + stmts = stmts.Add(stmt); + reusableExprWithoutSideEffects = exprWithoutSideEffects = id; + } + + return (reusableExprWithoutSideEffects, stmts, exprWithoutSideEffects); + } + + private static bool IsReusableReadOnlyLocalKind(ISymbol symbol) => symbol is ILocalSymbol ls && (VBasic.VisualBasicExtensions.IsForEach(ls) || ls.IsUsing); + + private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) + { + var contextNode = vbExpr.GetAncestor() ?? (VBasic.VisualBasicSyntaxNode) vbExpr.Parent; + return CreateLocalVariableWithUniqueName(contextNode, variableNameBase, expr, forceType); + } + + private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VisualBasicSyntaxNode contextNode, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) + { + var varName = GetUniqueVariableNameInScope(contextNode, variableNameBase); + var stmt = CommonConversions.CreateLocalVariableDeclarationAndAssignment(varName, expr, forceType); + return (stmt, ValidSyntaxFactory.IdentifierName(varName)); + } + + private async Task CanEvaluateMultipleTimesAsync(VBSyntax.ExpressionSyntax vbExpr) + { + return _semanticModel.GetConstantValue(vbExpr).HasValue || vbExpr.IsKind(VBasic.SyntaxKind.MeExpression) || vbExpr.SkipIntoParens() is VBSyntax.NameSyntax ns && await IsNeverMutatedAsync(ns); + } + + private async Task IsNeverMutatedAsync(VBSyntax.NameSyntax ns) + { + var allowedLocation = Location.Create(ns.SyntaxTree, TextSpan.FromBounds(ns.GetAncestor().SpanStart, ns.Span.End)); + var symbol = _semanticModel.GetSymbolInfo(ns).Symbol; + //Perf optimization: Looking across the whole solution is expensive, so assume non-local symbols are written somewhere + return symbol.MatchesKind(SymbolKind.Parameter, SymbolKind.Local) && await CommonConversions.Document.Project.Solution.IsNeverWrittenAsync(symbol, allowedLocation); + } + + private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool treatAsBoolean = false, VBasic.SyntaxKind caseClauseKind = VBasic.SyntaxKind.CaseEqualsClause) + { + var typeInfo = _semanticModel.GetTypeInfo(node.SelectStatement.Expression); + + DeclarationPatternSyntax patternMatch; + if (typeInfo.ConvertedType.SpecialType == SpecialType.System_Boolean || treatAsBoolean) { + patternMatch = SyntaxFactory.DeclarationPattern( + SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), + SyntaxFactory.DiscardDesignation()); + } else { + var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); + patternMatch = ValidSyntaxFactory.VarPattern(varName); + ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName), csRight = expression; + var caseTypeInfo = _semanticModel.GetTypeInfo(vbCase); + expression = ComparisonAdjustedForStringComparison(node, vbCase, typeInfo, csLeft, csRight, caseTypeInfo, GetComparisonKind(caseClauseKind)); + } + + var colonToken = SyntaxFactory.Token(SyntaxKind.ColonToken); + return SyntaxFactory.CasePatternSwitchLabel(patternMatch, SyntaxFactory.WhenClause(expression), colonToken); + } + + private ComparisonKind GetComparisonKind(VBasic.SyntaxKind caseClauseKind) => caseClauseKind switch { + VBasic.SyntaxKind.CaseLessThanClause => ComparisonKind.LessThan, + VBasic.SyntaxKind.CaseLessThanOrEqualClause => ComparisonKind.LessThanOrEqual, + VBasic.SyntaxKind.CaseEqualsClause => ComparisonKind.Equals, + VBasic.SyntaxKind.CaseNotEqualsClause => ComparisonKind.NotEquals, + VBasic.SyntaxKind.CaseGreaterThanOrEqualClause => ComparisonKind.GreaterThanOrEqual, + VBasic.SyntaxKind.CaseGreaterThanClause => ComparisonKind.GreaterThan, + _ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null) + }; + + private ExpressionSyntax ComparisonAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo, ComparisonKind comparisonKind) + { + var vbEquality = CommonConversions.VisualBasicEqualityComparison; + switch (_visualBasicEqualityComparison.GetObjectEqualityType(lhsTypeInfo, rhsTypeInfo)) { + case VisualBasicEqualityComparison.RequiredType.Object: + return vbEquality.GetFullExpressionForVbObjectComparison(csLeft, csRight, comparisonKind); + case VisualBasicEqualityComparison.RequiredType.StringOnly: + // We know lhs isn't null, because we always coalesce it in the switch expression + (csLeft, csRight) = vbEquality + .AdjustForVbStringComparison(node.SelectStatement.Expression, csLeft, lhsTypeInfo, true, vbCase, csRight, rhsTypeInfo, false); + break; + } + return SyntaxFactory.BinaryExpression(GetSyntaxKind(comparisonKind), csLeft, csRight); + } + + private CS.SyntaxKind GetSyntaxKind(ComparisonKind comparisonKind) => comparisonKind switch { + ComparisonKind.LessThan => CS.SyntaxKind.LessThanExpression, + ComparisonKind.LessThanOrEqual => CS.SyntaxKind.LessThanOrEqualExpression, + ComparisonKind.Equals => CS.SyntaxKind.EqualsExpression, + ComparisonKind.NotEquals => CS.SyntaxKind.NotEqualsExpression, + ComparisonKind.GreaterThanOrEqual => CS.SyntaxKind.GreaterThanOrEqualExpression, + ComparisonKind.GreaterThan => CS.SyntaxKind.GreaterThanExpression, + _ => throw new ArgumentOutOfRangeException(nameof(comparisonKind), comparisonKind, null) + }; + + public override async Task> VisitWithBlock(VBSyntax.WithBlockSyntax node) + { + var (lhsExpression, prefixDeclarations, _) = await GetExpressionWithoutSideEffectsAsync(node.WithStatement.Expression, "withBlock"); + + _withBlockLhs.Push(lhsExpression); + try { + var statements = await ConvertStatementsAsync(node.Statements); + + var statementSyntaxs = SyntaxFactory.List(prefixDeclarations.Concat(statements)); + return prefixDeclarations.Any() + ? SingleStatement(SyntaxFactory.Block(statementSyntaxs)) + : statementSyntaxs; + } finally { + _withBlockLhs.Pop(); + } + } + + private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase) + { + return NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase); + } + + public override async Task> VisitTryBlock(VBSyntax.TryBlockSyntax node) + { + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + return SingleStatement( + SyntaxFactory.TryStatement( + block, + SyntaxFactory.List(await node.CatchBlocks.SelectAsync(async c => await ConvertCatchBlockAsync(c))), + await ConvertFinallyBlockAsync(node.FinallyBlock) + ) + ); + + async Task ConvertCatchBlockAsync(VBasic.Syntax.CatchBlockSyntax node) + { + var stmt = node.CatchStatement; + CatchDeclarationSyntax catcher = null; + if (stmt.AsClause != null) { + catcher = SyntaxFactory.CatchDeclaration( + ConvertTypeSyntax(stmt.AsClause.Type), + CommonConversions.ConvertIdentifier(stmt.IdentifierName.Identifier) + ); + } + + var filter = await ConvertCatchFilterClauseAsync(stmt.WhenClause); + var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); + return SyntaxFactory.CatchClause( + catcher, + filter, + SyntaxFactory.Block(stmts) + ); + } + + async Task ConvertCatchFilterClauseAsync(VBasic.Syntax.CatchFilterClauseSyntax node) + { + if (node == null) return null; + return SyntaxFactory.CatchFilterClause(await node.Filter.AcceptAsync(_expressionVisitor)); + } + + async Task ConvertFinallyBlockAsync(VBasic.Syntax.FinallyBlockSyntax node) + { + if (node == null) return null; + var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); + return SyntaxFactory.FinallyClause(SyntaxFactory.Block(stmts)); + } + } + + private TypeSyntax ConvertTypeSyntax(VBSyntax.TypeSyntax vbType) + { + if (_semanticModel.GetSymbolInfo(vbType).Symbol is ITypeSymbol typeSymbol) + return CommonConversions.GetTypeSyntax(typeSymbol); + return SyntaxFactory.ParseTypeName(vbType.ToString()); + } + + public override async Task> VisitSyncLockBlock(VBSyntax.SyncLockBlockSyntax node) + { + return SingleStatement(SyntaxFactory.LockStatement( + await node.SyncLockStatement.Expression.AcceptAsync(_expressionVisitor), + SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() + )); + } + + public override async Task> VisitUsingBlock(VBSyntax.UsingBlockSyntax node) + { + var statementSyntax = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + if (node.UsingStatement.Expression == null) { + StatementSyntax stmt = statementSyntax; + foreach (var v in node.UsingStatement.Variables.Reverse()) + foreach (var declaration in (await SplitVariableDeclarationsAsync(v)).Variables.Reverse()) + stmt = SyntaxFactory.UsingStatement(declaration.Decl, null, stmt); + return SingleStatement(stmt); + } + + var expr = await node.UsingStatement.Expression.AcceptAsync(_expressionVisitor); + var unpackPossiblyNestedBlock = statementSyntax.UnpackPossiblyNestedBlock(); // Allow reduced indentation for multiple usings in a row + return SingleStatement(SyntaxFactory.UsingStatement(null, expr, unpackPossiblyNestedBlock)); + } + + public override async Task> VisitWhileBlock(VBSyntax.WhileBlockSyntax node) + { + return SingleStatement(SyntaxFactory.WhileStatement( + await node.WhileStatement.Condition.AcceptAsync(_expressionVisitor), + SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() + )); + } + + public override async Task> VisitDoLoopBlock(VBSyntax.DoLoopBlockSyntax node) + { + var statements = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock(); + + if (node.DoStatement.WhileOrUntilClause != null) { + var stmt = node.DoStatement.WhileOrUntilClause; + if (stmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.WhileKeyword)) + return SingleStatement(SyntaxFactory.WhileStatement( + await stmt.Condition.AcceptAsync(_expressionVisitor), + statements + )); + return SingleStatement(SyntaxFactory.WhileStatement( + (await stmt.Condition.AcceptAsync(_expressionVisitor)).InvertCondition(), + statements + )); + } + + var whileOrUntilStmt = node.LoopStatement.WhileOrUntilClause; + ExpressionSyntax conditionExpression; + bool isUntilStmt; + if (whileOrUntilStmt != null) { + conditionExpression = await whileOrUntilStmt.Condition.AcceptAsync(_expressionVisitor); + isUntilStmt = whileOrUntilStmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.UntilKeyword); + } else { + conditionExpression = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression); + isUntilStmt = false; + } + + if (isUntilStmt) { + conditionExpression = conditionExpression.InvertCondition(); + } + + return SingleStatement(SyntaxFactory.DoStatement(statements, conditionExpression)); + } + + public override async Task> VisitCallStatement(VBSyntax.CallStatementSyntax node) + { + return SingleStatement(await node.Invocation.AcceptAsync(_expressionVisitor)); + } + + private static SyntaxList SingleStatement(StatementSyntax statement) + { + return SyntaxFactory.SingletonList(statement); + } + + private static SyntaxList SingleStatement(ExpressionSyntax expression) + { + return SyntaxFactory.SingletonList(SyntaxFactory.ExpressionStatement(expression)); + } } \ No newline at end of file From 7c293c6f0f144d27727c359d014ae807a584ae92 Mon Sep 17 00:00:00 2001 From: Graham Date: Wed, 11 Mar 2026 00:27:42 +0000 Subject: [PATCH 3/5] Simplify --- .../MethodBodyExecutableStatementVisitor.cs | 2430 ++++++++--------- 1 file changed, 1214 insertions(+), 1216 deletions(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 487be8a7..7271b682 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -1,1217 +1,1215 @@ -using System.Collections; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using static Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions; -using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -using Microsoft.CodeAnalysis.Text; -using ICSharpCode.CodeConverter.Util.FromRoslyn; -using ICSharpCode.CodeConverter.VB; -using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind; - -namespace ICSharpCode.CodeConverter.CSharp; - -/// -/// Executable statements - which includes executable blocks such as if statements -/// Maintains state relevant to the called method-like object. A fresh one must be used for each method, and the same one must be reused for statements in the same method -/// -internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVisitor>> -{ - private readonly VBasic.VisualBasicSyntaxNode _methodNode; - private readonly SemanticModel _semanticModel; - private readonly CommentConvertingVisitorWrapper _expressionVisitor; - private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; - private readonly Stack _withBlockLhs; - private readonly HashSet _extraUsingDirectives; - private readonly HandledEventsAnalysis _handledEventsAnalysis; - private readonly HashSet _generatedNames = new(); - private readonly HashSet _localsToInlineInLoop; - private readonly PerScopeState _perScopeState; - - public bool IsIterator { get; set; } - public IdentifierNameSyntax ReturnVariable { get; set; } - public bool HasReturnVariable => ReturnVariable != null; - public VBasic.VisualBasicSyntaxVisitor>> CommentConvertingVisitor { get; } - - private CommonConversions CommonConversions { get; } - - public static async Task CreateAsync(VisualBasicSyntaxNode node, SemanticModel semanticModel, - CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor, CommonConversions commonConversions, VisualBasicEqualityComparison visualBasicEqualityComparison, - Stack withBlockLhs, HashSet extraUsingDirectives, - ITypeContext typeContext, bool isIterator, IdentifierNameSyntax csReturnVariable) - { - var solution = commonConversions.Document.Project.Solution; - var declarationsToInlineInLoop = await solution.GetDescendantsToInlineInLoopAsync(semanticModel, node); - return new MethodBodyExecutableStatementVisitor(node, semanticModel, triviaConvertingExpressionVisitor, commonConversions, visualBasicEqualityComparison, withBlockLhs, extraUsingDirectives, typeContext, declarationsToInlineInLoop) { - IsIterator = isIterator, - ReturnVariable = csReturnVariable, - }; - } - - private MethodBodyExecutableStatementVisitor(VisualBasicSyntaxNode methodNode, SemanticModel semanticModel, - CommentConvertingVisitorWrapper expressionVisitor, CommonConversions commonConversions, - VisualBasicEqualityComparison visualBasicEqualityComparison, - Stack withBlockLhs, HashSet extraUsingDirectives, - ITypeContext typeContext, HashSet localsToInlineInLoop) - { - _methodNode = methodNode; - _semanticModel = semanticModel; - _expressionVisitor = expressionVisitor; - _visualBasicEqualityComparison = visualBasicEqualityComparison; - CommonConversions = commonConversions; - _withBlockLhs = withBlockLhs; - _extraUsingDirectives = extraUsingDirectives; - _handledEventsAnalysis = typeContext.HandledEventsAnalysis; - _perScopeState = typeContext.PerScopeState; - var byRefParameterVisitor = new PerScopeStateVisitorDecorator(this, _perScopeState, semanticModel, _generatedNames); - CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor); - _localsToInlineInLoop = localsToInlineInLoop; - } - - public override async Task> DefaultVisit(SyntaxNode node) - { - throw new NotImplementedException($"Conversion for {VBasic.VisualBasicExtensions.Kind(node)} not implemented, please report this issue") - .WithNodeInformation(node); - } - - public override async Task> VisitStopOrEndStatement(VBSyntax.StopOrEndStatementSyntax node) - { - return SingleStatement(SyntaxFactory.ParseStatement(ConvertStopOrEndToCSharpStatementText(node))); - } - - private string ConvertStopOrEndToCSharpStatementText(VBSyntax.StopOrEndStatementSyntax node) - { - switch (VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword)) { - case VBasic.SyntaxKind.StopKeyword: - _extraUsingDirectives.Add("System.Diagnostics"); - return "Debugger.Break();"; - case VBasic.SyntaxKind.EndKeyword: - _extraUsingDirectives.Add("System"); - return "Environment.Exit(0);"; - default: - throw new NotImplementedException(VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword) + " not implemented!"); - } - } - - public override async Task> VisitLocalDeclarationStatement(VBSyntax.LocalDeclarationStatementSyntax node) - { - var modifiers = CommonConversions.ConvertModifiers(node.Declarators[0].Names[0], node.Modifiers, TokenContext.Local); - var isConst = modifiers.Any(a => a.IsKind(SyntaxKind.ConstKeyword)); - var isVBStatic = node.Modifiers.Any(a => a.IsKind(VBasic.SyntaxKind.StaticKeyword)); - - var declarations = new List(); - - foreach (var declarator in node.Declarators) { - var (variables, methods) = await SplitVariableDeclarationsAsync(declarator, preferExplicitType: isConst || isVBStatic); - var localDeclarationStatementSyntaxs = variables.Select(declAndType => SyntaxFactory.LocalDeclarationStatement(modifiers, declAndType.Decl)); - if (isVBStatic) { - foreach (var decl in localDeclarationStatementSyntaxs) { - var variable = decl.Declaration.Variables.Single(); - var initializeValue = variable.Initializer?.Value; - string methodName; - SyntaxTokenList methodModifiers; - - MethodKind parentAccessorKind = MethodKind.Ordinary; - if (_methodNode is VBSyntax.MethodBlockSyntax methodBlock) { - var methodStatement = methodBlock.BlockStatement as VBSyntax.MethodStatementSyntax; - methodModifiers = methodStatement.Modifiers; - methodName = methodStatement.Identifier.Text; - } else if (_methodNode is VBSyntax.ConstructorBlockSyntax constructorBlock) { - methodModifiers = constructorBlock.BlockStatement.Modifiers; - methodName = null; - } else if (_methodNode is VBSyntax.AccessorBlockSyntax accessorBlock) { - var propertyBlock = accessorBlock.Parent as VBSyntax.PropertyBlockSyntax; - methodName = propertyBlock.PropertyStatement.Identifier.Text; - parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? MethodKind.PropertyGet : MethodKind.PropertySet; - methodModifiers = propertyBlock.PropertyStatement.Modifiers; - } else { - throw new NotImplementedException(_methodNode.GetType() + " not implemented!"); - } - - var isVbShared = methodModifiers.Any(a => a.IsKind(VBasic.SyntaxKind.SharedKeyword)); - _perScopeState.HoistToTopLevel(new HoistedFieldFromVbStaticVariable(methodName, variable.Identifier.Text, parentAccessorKind, initializeValue, decl.Declaration.Type, isVbShared)); - } - } else { - var shouldPullVariablesBeforeLoop = _perScopeState.IsInsideLoop() && declarator.Initializer is null && declarator.AsClause is not VBSyntax.AsNewClauseSyntax; - if (shouldPullVariablesBeforeLoop) { - localDeclarationStatementSyntaxs = HoistVariablesBeforeLoopWhenNeeded(variables) - .Select(variableDecl => SyntaxFactory.LocalDeclarationStatement(modifiers, variableDecl)); - } - - declarations.AddRange(localDeclarationStatementSyntaxs); - } - var localFunctions = methods.Cast(); - declarations.AddRange(localFunctions); - } - - return SyntaxFactory.List(declarations); - } - - private IEnumerable HoistVariablesBeforeLoopWhenNeeded(IEnumerable variablesDeclarations) - { - foreach (var variablesDecl in variablesDeclarations) { - var variablesToRemove = new List(); - foreach (var (csVariable, vbVariable) in variablesDecl.Variables) { - var symbol = _semanticModel.GetDeclaredSymbol(vbVariable); - var assignedBeforeRead = _semanticModel.IsDefinitelyAssignedBeforeRead(symbol, vbVariable); - if (!assignedBeforeRead) { - _perScopeState.Hoist(new HoistedDefaultInitializedLoopVariable( - csVariable.Identifier.Text, - // e.g. "b As Boolean" has no initializer but can turn into "var b = default(bool)" - csVariable.Initializer?.Value, - variablesDecl.Decl.Type, - _perScopeState.IsInsideNestedLoop())); - variablesToRemove.Add(csVariable); - } - } - - var variablesToDeclareLocally = variablesDecl.Variables.Select(t => t.CsVar).Except(variablesToRemove).ToArray(); - if(variablesToDeclareLocally.Any()) { - yield return variablesDecl.Decl.WithVariables(SyntaxFactory.SeparatedList(variablesToDeclareLocally)); - } - } - } - - public override async Task> VisitAddRemoveHandlerStatement(VBSyntax.AddRemoveHandlerStatementSyntax node) - { - var syntaxKind = ConvertAddRemoveHandlerToCSharpSyntaxKind(node); - return SingleStatement(SyntaxFactory.AssignmentExpression(syntaxKind, - await node.EventExpression.AcceptAsync(_expressionVisitor), - await node.DelegateExpression.AcceptAsync(_expressionVisitor))); - } - - private static SyntaxKind ConvertAddRemoveHandlerToCSharpSyntaxKind(VBSyntax.AddRemoveHandlerStatementSyntax node) - { - switch (node.Kind()) { - case VBasic.SyntaxKind.AddHandlerStatement: - return SyntaxKind.AddAssignmentExpression; - case VBasic.SyntaxKind.RemoveHandlerStatement: - return SyntaxKind.SubtractAssignmentExpression; - default: - throw new NotImplementedException(node.Kind() + " not implemented!"); - } - } - - public override async Task> VisitExpressionStatement(VBSyntax.ExpressionStatementSyntax node) - { - if (node.Expression is VBSyntax.InvocationExpressionSyntax invoke && invoke.Expression is VBSyntax.MemberAccessExpressionSyntax access && access.Expression is VBSyntax.MyBaseExpressionSyntax && access.Name.Identifier.ValueText.Equals("Finalize", StringComparison.OrdinalIgnoreCase)) { - return new SyntaxList(); - } - - return SingleStatement(await node.Expression.AcceptAsync(_expressionVisitor)); - } - - public override async Task> VisitAssignmentStatement(VBSyntax.AssignmentStatementSyntax node) - { - if (node.IsKind(VBasic.SyntaxKind.MidAssignmentStatement) && node.Left is VBSyntax.MidExpressionSyntax mes) { - return await ConvertMidAssignmentAsync(node, mes); - } - - var lhs = await node.Left.AcceptAsync(_expressionVisitor); - var lOperation = _semanticModel.GetOperation(node.Left); - - var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation); - - // 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 && - _methodNode is VBSyntax.MethodBlockSyntax mb && - HasReturnVariable && - id.Identifier.ValueText.Equals(mb.SubOrFunctionStatement.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)) { - lhs = ReturnVariable; - } - - if (node.IsKind(VBasic.SyntaxKind.ExponentiateAssignmentStatement)) { - rhs = SyntaxFactory.InvocationExpression( - ValidSyntaxFactory.MemberAccess(nameof(Math), nameof(Math.Pow)), - ExpressionSyntaxExtensions.CreateArgList(lhs, rhs)); - } - var kind = node.Kind().ConvertToken(); - - - var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left); - var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right); - - var typeConvertedRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); - - // Split out compound operator if type conversion needed on result - if (TypeConversionAnalyzer.GetNonCompoundOrNull(kind) is {} nonCompound) { - - var nonCompoundRhs = SyntaxFactory.BinaryExpression(nonCompound, lhs, typeConvertedRhs); - var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, nonCompoundRhs, forceSourceType: rhsTypeInfo.ConvertedType, forceTargetType: lhsTypeInfo.Type); - 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; - - if (setMethodName != null) { - if (lhs is InvocationExpressionSyntax ies) { - ExpressionSyntax exprToReplace = ies.Expression; - if (exprToReplace is MemberAccessExpressionSyntax maes && maes.Name is IdentifierNameSyntax idn) { - var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn); - exprToReplace = maes.WithName(newName); - - var skipParens = node.Left.SkipIntoParens(); - if (maes.Expression is ThisExpressionSyntax) { - if (skipParens is VBSyntax.InvocationExpressionSyntax inv && inv.Expression is VBSyntax.IdentifierNameSyntax) { - exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); - } else if (skipParens is VBSyntax.IdentifierNameSyntax) { - exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); - } else if (skipParens is VBSyntax.MemberAccessExpressionSyntax vbMaes && vbMaes.Expression is VBSyntax.MyClassExpressionSyntax == false && vbMaes.Expression is VBSyntax.MeExpressionSyntax == false) { - // keep it - } else { - exprToReplace = newName.WithLeadingTrivia(maes.GetLeadingTrivia()).WithTrailingTrivia(maes.GetTrailingTrivia()); - } - } - } else if (exprToReplace is IdentifierNameSyntax idn2) { - var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2); - exprToReplace = newName; - } - var newArg = SyntaxFactory.Argument(rhs); - var newArgs = ies.ArgumentList.Arguments.Add(newArg); - var newArgList = ies.ArgumentList.WithArguments(newArgs); - var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); - var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs); - var postAssign = GetPostAssignmentStatements(node); - return postAssign.Insert(0, invokeAssignment); - } - return SingleStatement(lhs); - } - - var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); - var postAssignment = GetPostAssignmentStatements(node); - return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); - } - - private async Task> ConvertMidAssignmentAsync(VBSyntax.AssignmentStatementSyntax node, VBSyntax.MidExpressionSyntax mes) - { - _extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices"); - var midFunction = ValidSyntaxFactory.MemberAccess("StringType", "MidStmtStr"); - var midArgList = await mes.ArgumentList.AcceptAsync(_expressionVisitor); - var (reusable, statements, _) = await GetExpressionWithoutSideEffectsAsync(node.Right, "midTmp"); - if (midArgList.Arguments.Count == 2) { - var length = ValidSyntaxFactory.MemberAccess(reusable, "Length"); - midArgList = midArgList.AddArguments(SyntaxFactory.Argument(length)); - } - midArgList = midArgList.AddArguments(SyntaxFactory.Argument(reusable)); - var invokeMid = SyntaxFactory.InvocationExpression(midFunction, midArgList); - return statements.Add(SyntaxFactory.ExpressionStatement(invokeMid)); - } - - /// - /// ensures we convert the property access to a field access - /// - private SyntaxList GetPostAssignmentStatements(VBSyntax.AssignmentStatementSyntax node) - { - var potentialPropertySymbol = _semanticModel.GetSymbolInfo(node.Left).ExtractBestMatch(); - return GetPostAssignmentStatements(node, potentialPropertySymbol); - } - - /// - /// Make winforms designer work: https://github.com/icsharpcode/CodeConverter/issues/321 - /// - public SyntaxList GetPostAssignmentStatements(Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax node, ISymbol potentialPropertySymbol) - { - if (CommonConversions.WinformsConversions.MayNeedToInlinePropertyAccess(node, potentialPropertySymbol)) { - return _handledEventsAnalysis.GetPostAssignmentStatements(potentialPropertySymbol); - } - - return SyntaxFactory.List(); - } - - public override async Task> VisitEraseStatement(VBSyntax.EraseStatementSyntax node) - { - var eraseStatements = await node.Expressions.SelectAsync(async arrayExpression => { - var lhs = await arrayExpression.AcceptAsync(_expressionVisitor); - var rhs = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); - var assignmentExpressionSyntax = - SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, lhs, - rhs); - return SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); - }); - return SyntaxFactory.List(eraseStatements); - } - - public override async Task> VisitReDimStatement(VBSyntax.ReDimStatementSyntax node) - { - return SyntaxFactory.List(await node.Clauses.SelectManyAsync(async arrayExpression => (IEnumerable) await ConvertRedimClauseAsync(arrayExpression))); - } - - /// - /// RedimClauseSyntax isn't an executable statement, therefore this isn't a "Visit" method. - /// Since it returns multiple statements it's easiest for it to be here in the current architecture. - /// - private async Task> ConvertRedimClauseAsync(VBSyntax.RedimClauseSyntax node) - { - bool preserve = node.Parent is VBSyntax.ReDimStatementSyntax rdss && rdss.PreserveKeyword.IsKind(VBasic.SyntaxKind.PreserveKeyword); - - var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); - var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); - if (preserve && convertedBounds.Count == 1) { - bool isProperty = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true; - var arrayToResize = isProperty ? CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression) : default; - var resizeArg = isProperty ? (ExpressionSyntax)arrayToResize.Reference : csTargetArrayExpression; - - var argumentList = new[] { resizeArg, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); - var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList); - - if (isProperty) { - var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, arrayToResize.Reference); - return SyntaxFactory.List(new StatementSyntax[] { arrayToResize.Declaration, SyntaxFactory.ExpressionStatement(arrayResize), SyntaxFactory.ExpressionStatement(assignment) }); - } - - return SingleStatement(arrayResize); - } - var newArrayAssignment = CreateNewArrayAssignment(node.Expression, csTargetArrayExpression, convertedBounds); - if (!preserve) return SingleStatement(newArrayAssignment); - - var lastIdentifierText = node.Expression.DescendantNodesAndSelf().OfType().Last().Identifier.Text; - string variableNameBase = "old" + lastIdentifierText.ToPascalCase(); - var expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csTargetArrayExpression); - var (stmt, oldTargetExpression) = CreateLocalVariableWithUniqueName(node.Expression, variableNameBase, expr); - - var arrayCopyIfNotNull = CreateConditionalArrayCopy(node, oldTargetExpression, csTargetArrayExpression, convertedBounds); - - return SyntaxFactory.List(new[] { stmt, newArrayAssignment, arrayCopyIfNotNull}); - } - - /// - /// Cut down version of Microsoft.VisualBasic.CompilerServices.Utils.CopyArray - /// - private IfStatementSyntax CreateConditionalArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, - IdentifierNameSyntax sourceArrayExpression, - ExpressionSyntax targetArrayExpression, - List convertedBounds) - { - var sourceLength = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, sourceArrayExpression, ValidSyntaxFactory.IdentifierName("Length")); - var arrayCopyStatement = convertedBounds.Count == 1 - ? CreateArrayCopyWithMinOfLengths(sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds.Single()) - : CreateArrayCopy(originalVbNode, sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds); - - var oldTargetNotEqualToNull = CommonConversions.NotNothingComparison(sourceArrayExpression, true); - return SyntaxFactory.IfStatement(oldTargetNotEqualToNull, arrayCopyStatement); - } - - /// - /// Array copy for multiple array dimensions represented by - /// - /// - /// Exception cases will sometimes silently succeed in the converted code, - /// but existing VB code relying on the exception thrown from a multidimensional redim preserve on - /// different rank arrays is hopefully rare enough that it's worth saving a few lines of code - /// - private StatementSyntax CreateArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, - IdentifierNameSyntax sourceArrayExpression, - MemberAccessExpressionSyntax sourceLength, - ExpressionSyntax targetArrayExpression, ICollection convertedBounds) - { - var lastSourceLengthArgs = ExpressionSyntaxExtensions.CreateArgList(CommonConversions.Literal(convertedBounds.Count - 1)); - var sourceLastRankLength = SyntaxFactory.InvocationExpression( - SyntaxFactory.ParseExpression($"{sourceArrayExpression.Identifier}.GetLength"), lastSourceLengthArgs); - var targetLastRankLength = - SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression($"{targetArrayExpression}.GetLength"), - lastSourceLengthArgs); - var length = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(sourceLastRankLength, targetLastRankLength)); - - var loopVariableName = GetUniqueVariableNameInScope(originalVbNode, "i"); - var loopVariableIdentifier = ValidSyntaxFactory.IdentifierName(loopVariableName); - var sourceStartForThisIteration = - SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, sourceLastRankLength); - var targetStartForThisIteration = - SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, targetLastRankLength); - - var arrayCopy = CreateArrayCopyWithStartingPoints(sourceArrayExpression, sourceStartForThisIteration, targetArrayExpression, - targetStartForThisIteration, length); - - var sourceArrayCount = SyntaxFactory.BinaryExpression(SyntaxKind.SubtractExpression, - SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, sourceLength, sourceLastRankLength), CommonConversions.Literal(1)); - - return CreateForZeroToValueLoop(loopVariableIdentifier, arrayCopy, sourceArrayCount); - } - - private static ForStatementSyntax CreateForZeroToValueLoop(SimpleNameSyntax loopVariableIdentifier, StatementSyntax loopStatement, ExpressionSyntax inclusiveLoopUpperBound) - { - var loopVariableAssignment = CommonConversions.CreateVariableDeclarationAndAssignment(loopVariableIdentifier.Identifier.Text, CommonConversions.Literal(0)); - var lessThanSourceBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, - loopVariableIdentifier, inclusiveLoopUpperBound); - var incrementors = SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.PrefixUnaryExpression(SyntaxKind.PreIncrementExpression, loopVariableIdentifier)); - var forStatementSyntax = SyntaxFactory.ForStatement(loopVariableAssignment, - SyntaxFactory.SeparatedList(), - lessThanSourceBounds, incrementors, loopStatement); - return forStatementSyntax; - } - - private static ExpressionStatementSyntax CreateArrayCopyWithMinOfLengths( - IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceLength, - ExpressionSyntax targetExpression, ExpressionSyntax targetLength) - { - var minLength = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(targetLength, sourceLength)); - var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, targetExpression, minLength); - var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); - return SyntaxFactory.ExpressionStatement(arrayCopy); - } - - private static ExpressionStatementSyntax CreateArrayCopyWithStartingPoints( - IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceStart, - ExpressionSyntax targetExpression, ExpressionSyntax targetStart, ExpressionSyntax length) - { - var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, sourceStart, targetExpression, targetStart, length); - var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); - return SyntaxFactory.ExpressionStatement(arrayCopy); - } - - private ExpressionStatementSyntax CreateNewArrayAssignment(VBSyntax.ExpressionSyntax vbArrayExpression, - ExpressionSyntax csArrayExpression, List convertedBounds) - { - var convertedType = (IArrayTypeSymbol) _semanticModel.GetTypeInfo(vbArrayExpression).ConvertedType; - var arrayRankSpecifierSyntax = SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(convertedBounds)); - var rankSpecifiers = SyntaxFactory.SingletonList(arrayRankSpecifierSyntax); - while (convertedType.ElementType is IArrayTypeSymbol ats) { - convertedType = ats; - rankSpecifiers = rankSpecifiers.Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.OmittedArraySizeExpression()))); - }; - var typeSyntax = CommonConversions.GetTypeSyntax(convertedType.ElementType); - var arrayCreation = - SyntaxFactory.ArrayCreationExpression(SyntaxFactory.ArrayType(typeSyntax, rankSpecifiers)); - var assignmentExpressionSyntax = - SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csArrayExpression, arrayCreation); - var newArrayAssignment = SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); - return newArrayAssignment; - } - - public override async Task> VisitThrowStatement(VBSyntax.ThrowStatementSyntax node) - { - return SingleStatement(SyntaxFactory.ThrowStatement(await node.Expression.AcceptAsync(_expressionVisitor))); - } - - public override async Task> VisitReturnStatement(VBSyntax.ReturnStatementSyntax node) - { - if (IsIterator) - return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); - - var csExpression = await node.Expression.AcceptAsync(_expressionVisitor); - csExpression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csExpression); - return SingleStatement(SyntaxFactory.ReturnStatement(csExpression)); - } - - public override async Task> VisitContinueStatement(VBSyntax.ContinueStatementSyntax node) - { - var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); - return SyntaxFactory.List(_perScopeState.ConvertContinue(vbBlockKeywordKind)); - } - - public override async Task> VisitYieldStatement(VBSyntax.YieldStatementSyntax node) - { - return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, await node.Expression.AcceptAsync(_expressionVisitor))); - } - - public override async Task> VisitExitStatement(VBSyntax.ExitStatementSyntax node) - { - var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); - switch (vbBlockKeywordKind) { - case VBasic.SyntaxKind.SubKeyword: - case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) != true: - return SingleStatement(SyntaxFactory.ReturnStatement()); - case VBasic.SyntaxKind.FunctionKeyword: - case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) == true: - VBasic.VisualBasicSyntaxNode typeContainer = node.GetAncestor() - ?? (VBasic.VisualBasicSyntaxNode)node.GetAncestor() - ?? node.GetAncestor(); - var enclosingMethodInfo = typeContainer switch { - VBSyntax.LambdaExpressionSyntax e => _semanticModel.GetSymbolInfo(e).Symbol, - VBSyntax.MethodBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.SubOrFunctionStatement), - VBSyntax.AccessorBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.AccessorStatement), - _ => null - } as IMethodSymbol; - - if (IsIterator) return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); - - if (!enclosingMethodInfo.ReturnsVoidOrAsyncTask()) { - ExpressionSyntax expr = HasReturnVariable ? ReturnVariable : SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression); - return SingleStatement(SyntaxFactory.ReturnStatement(expr)); - } - - return SingleStatement(SyntaxFactory.ReturnStatement()); - default: - return SyntaxFactory.List(_perScopeState.ConvertExit(vbBlockKeywordKind)); - } - } - - public override async Task> VisitRaiseEventStatement(VBSyntax.RaiseEventStatementSyntax node) - { - var argumentListSyntax = await node.ArgumentList.AcceptAsync(_expressionVisitor) ?? SyntaxFactory.ArgumentList(); - - var symbolInfo = _semanticModel.GetSymbolInfo(node.Name).ExtractBestMatch(); - if (symbolInfo?.RaiseMethod != null) { - return SingleStatement(SyntaxFactory.InvocationExpression( - ValidSyntaxFactory.IdentifierName($"On{symbolInfo.Name}"), - argumentListSyntax)); - } - - var memberBindingExpressionSyntax = SyntaxFactory.MemberBindingExpression(ValidSyntaxFactory.IdentifierName("Invoke")); - var conditionalAccessExpressionSyntax = SyntaxFactory.ConditionalAccessExpression( - await node.Name.AcceptAsync(_expressionVisitor), - SyntaxFactory.InvocationExpression(memberBindingExpressionSyntax, argumentListSyntax) - ); - return SingleStatement( - conditionalAccessExpressionSyntax - ); - } - - public override async Task> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node) - { - var condition = await node.Condition.AcceptAsync(_expressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - ElseClauseSyntax elseClause = null; - - if (node.ElseClause != null) { - var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(node.ElseClause.Statements)); - elseClause = SyntaxFactory.ElseClause(elseBlock.UnpackNonNestedBlock()); - } - return SingleStatement(SyntaxFactory.IfStatement(condition, block.UnpackNonNestedBlock(), elseClause)); - } - - public override async Task> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node) - { - var condition = await node.IfStatement.Condition.AcceptAsync(_expressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - - var elseClause = await ConvertElseClauseAsync(node.ElseBlock); - elseClause = elseClause.WithVbSourceMappingFrom(node.ElseBlock); //Special case where explicit mapping is needed since block becomes clause so cannot be easily visited - - var elseIfBlocks = await node.ElseIfBlocks.SelectAsync(async elseIf => await ConvertElseIfAsync(elseIf)); - foreach (var elseIf in Enumerable.Reverse(elseIfBlocks)) { - var ifStmt = SyntaxFactory.IfStatement(elseIf.ElseIfCondition, elseIf.ElseBlock, elseClause); - elseClause = SyntaxFactory.ElseClause(ifStmt); - } - - return SingleStatement(SyntaxFactory.IfStatement(condition, block, elseClause)); - } - - private async Task<(ExpressionSyntax ElseIfCondition, BlockSyntax ElseBlock)> ConvertElseIfAsync(VBSyntax.ElseIfBlockSyntax elseIf) - { - var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements)); - var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); - elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: CommonConversions.KnownTypes.Boolean); - return (elseIfCondition, elseBlock); - } - - private async Task ConvertElseClauseAsync(VBSyntax.ElseBlockSyntax elseBlock) - { - if (elseBlock == null) return null; - - var csStatements = await ConvertStatementsAsync(elseBlock.Statements); - if (csStatements.TryUnpackSingleStatement(out var stmt) && stmt.IsKind(SyntaxKind.IfStatement)) { - // so that you get a neat "else if" at the end - return SyntaxFactory.ElseClause(stmt); - } - - return SyntaxFactory.ElseClause(SyntaxFactory.Block(csStatements)); - } - - private async Task ConvertStatementsAsync(SyntaxList statementSyntaxs) - { - return await statementSyntaxs.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); - } - - /// - /// See https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/for-next-statement#BKMK_Counter - /// - public override async Task> VisitForBlock(VBSyntax.ForBlockSyntax node) - { - var stmt = node.ForStatement; - VariableDeclarationSyntax declaration = null; - ExpressionSyntax id; - var controlVarSymbol = _semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol ?? (_semanticModel.GetOperation(stmt.ControlVariable) as IVariableDeclaratorOperation)?.Symbol; - - // If missing semantic info, the compiler just guesses object, let's try to improve on that guess: - var controlVarType = controlVarSymbol?.GetSymbolType().Yield().Concat( - new SyntaxNode[] {stmt.ControlVariable, stmt.FromValue, stmt.ToValue, stmt.StepClause?.StepValue} - .Select(exp => _semanticModel.GetTypeInfo(exp).Type) - ).FirstOrDefault(t => t != null && t.SpecialType != SpecialType.System_Object); - var startValue = await stmt.FromValue.AcceptAsync(_expressionVisitor); - startValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.FromValue, startValue?.SkipIntoParens(), forceTargetType: controlVarType); - - var initializers = new List(); - var controlVarTypeSyntax = CommonConversions.GetTypeSyntax(controlVarType); - if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax v) { - declaration = (await SplitVariableDeclarationsAsync(v)).Variables.Single().Decl; - declaration = declaration.WithVariables(SyntaxFactory.SingletonSeparatedList(declaration.Variables[0].WithInitializer(SyntaxFactory.EqualsValueClause(startValue)))); - id = ValidSyntaxFactory.IdentifierName(declaration.Variables[0].Identifier); - } else { - id = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); - - if (controlVarSymbol != null && controlVarSymbol.DeclaringSyntaxReferences.Any(r => r.Span.OverlapsWith(stmt.ControlVariable.Span))) { - declaration = CommonConversions.CreateVariableDeclarationAndAssignment(controlVarSymbol.Name, startValue, controlVarTypeSyntax); - } else { - startValue = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, id, startValue); - initializers.Add(startValue); - } - } - - - var preLoopStatements = new List(); - var csToValue = await stmt.ToValue.AcceptAsync(_expressionVisitor); - csToValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.ToValue, csToValue?.SkipIntoParens(), forceTargetType: controlVarType); - - // In Visual Basic, the To expression is only evaluated once, but in C# will be evaluated every loop. - // If it could evaluate differently or has side effects, it must be extracted as a variable - if (!_semanticModel.GetConstantValue(stmt.ToValue).HasValue) { - var loopToVariableName = GetUniqueVariableNameInScope(node, "loopTo"); - var toVariableId = ValidSyntaxFactory.IdentifierName(loopToVariableName); - - var loopToAssignment = CommonConversions.CreateVariableDeclarator(loopToVariableName, csToValue); - if (initializers.Any()) { - var loopEndDeclaration = SyntaxFactory.LocalDeclarationStatement( - CommonConversions.CreateVariableDeclarationAndAssignment(loopToVariableName, csToValue)); - // Does not do anything about porting newline trivia upwards to maintain spacing above the loop - preLoopStatements.Add(loopEndDeclaration); - } else { - declaration = declaration == null - ? SyntaxFactory.VariableDeclaration(controlVarTypeSyntax, - SyntaxFactory.SingletonSeparatedList(loopToAssignment)) - : declaration.AddVariables(loopToAssignment).WithType(controlVarTypeSyntax); - } - - csToValue = toVariableId; - } - - var (csCondition, csStep) = await ConvertConditionAndStepClauseAsync(stmt, id, csToValue, controlVarType); - - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - var forStatementSyntax = SyntaxFactory.ForStatement( - declaration, - SyntaxFactory.SeparatedList(initializers), - csCondition, - SyntaxFactory.SingletonSeparatedList(csStep), - block.UnpackNonNestedBlock()); - return SyntaxFactory.List(preLoopStatements.Concat(new[] { forStatementSyntax })); - } - - private async Task<(IReadOnlyCollection Variables, IReadOnlyCollection Methods)> SplitVariableDeclarationsAsync(VBSyntax.VariableDeclaratorSyntax v, bool preferExplicitType = false) - { - return await CommonConversions.SplitVariableDeclarationsAsync(v, _localsToInlineInLoop, preferExplicitType); - } - - private async Task<(ExpressionSyntax, ExpressionSyntax)> ConvertConditionAndStepClauseAsync(VBSyntax.ForStatementSyntax stmt, ExpressionSyntax id, ExpressionSyntax csToValue, ITypeSymbol controlVarType) - { - var vbStepValue = stmt.StepClause?.StepValue; - var csStepValue = await (stmt.StepClause?.StepValue).AcceptAsync(_expressionVisitor); - // For an enum, you need to add on an integer for example: - var forceStepType = controlVarType is INamedTypeSymbol nt && nt.IsEnumType() ? nt.EnumUnderlyingType : controlVarType; - csStepValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbStepValue, csStepValue?.SkipIntoParens(), forceTargetType: forceStepType); - - var nonNegativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, id, csToValue); - var negativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, id, csToValue); - if (csStepValue == null) { - return (nonNegativeCondition, SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, id)); - } - - ExpressionSyntax csCondition; - ExpressionSyntax csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.AddAssignmentExpression, id, csStepValue); - var vbStepConstValue = _semanticModel.GetConstantValue(vbStepValue); - var constValue = !vbStepConstValue.HasValue ? null : (dynamic)vbStepConstValue.Value; - if (constValue == null) { - var ifStepNonNegative = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, csStepValue, CommonConversions.Literal(0)); - csCondition = SyntaxFactory.ConditionalExpression(ifStepNonNegative, nonNegativeCondition, negativeCondition); - } else if (constValue < 0) { - csCondition = negativeCondition; - if (csStepValue is PrefixUnaryExpressionSyntax pues && pues.OperatorToken.IsKind(SyntaxKind.MinusToken)) { - csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.SubtractAssignmentExpression, id, pues.Operand); - } - } else { - csCondition = nonNegativeCondition; - } - - return (csCondition, csStep); - } - - public override async Task> VisitForEachBlock(VBSyntax.ForEachBlockSyntax node) - { - var stmt = node.ForEachStatement; - - TypeSyntax type; - SyntaxToken id; - List statements = new List(); - if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax vds) { - var declaration = (await SplitVariableDeclarationsAsync(vds)).Variables.Single().Decl; - type = declaration.Type; - id = declaration.Variables.Single().Identifier; - } else if (_semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol is { } varSymbol) { - var variableType = varSymbol.GetSymbolType(); - var explicitCastWouldHaveNoEffect = variableType?.SpecialType == SpecialType.System_Object || _semanticModel.GetTypeInfo(stmt.Expression).ConvertedType.IsEnumerableOfExactType(variableType); - type = CommonConversions.GetTypeSyntax(varSymbol.GetSymbolType(), explicitCastWouldHaveNoEffect); - var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); - if (_localsToInlineInLoop.Contains(varSymbol, SymbolEqualityComparer.IncludeNullability) && v is IdentifierNameSyntax vId) { - id = vId.Identifier; - } else { - id = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "current" + varSymbol.Name.ToPascalCase())); - statements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, v, ValidSyntaxFactory.IdentifierName(id)))); - } - } else { - var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); - id = v.Identifier; - type = ValidSyntaxFactory.VarType; - } - - var block = SyntaxFactory.Block(statements.Concat(await ConvertStatementsAsync(node.Statements))); - var csExpression = await stmt.Expression.AcceptAsync(_expressionVisitor); - return SingleStatement(SyntaxFactory.ForEachStatement( - type, - id, - CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.Expression, csExpression), - block.UnpackNonNestedBlock() - )); - } - - public override async Task> VisitLabelStatement(VBSyntax.LabelStatementSyntax node) - { - return SingleStatement(SyntaxFactory.LabeledStatement(CommonConversions.CsEscapedIdentifier(node.LabelToken.Text), SyntaxFactory.EmptyStatement())); - } - - public override async Task> VisitGoToStatement(VBSyntax.GoToStatementSyntax node) - { - return SingleStatement(SyntaxFactory.GotoStatement(SyntaxKind.GotoStatement, - ValidSyntaxFactory.IdentifierName((node.Label.LabelToken.Text)))); - } - - public override async Task> VisitSelectBlock(VBSyntax.SelectBlockSyntax node) - { - var vbExpr = node.SelectStatement.Expression; - var vbEquality = CommonConversions.VisualBasicEqualityComparison; - - var csSwitchExpr = await vbExpr.AcceptAsync(_expressionVisitor); - csSwitchExpr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csSwitchExpr); - var switchExprTypeInfo = _semanticModel.GetTypeInfo(vbExpr); - var isObjectComparison = switchExprTypeInfo.ConvertedType.SpecialType == SpecialType.System_Object; - var isStringComparison = !isObjectComparison && switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String || switchExprTypeInfo.ConvertedType?.IsArrayOf(SpecialType.System_Char) == true; - var caseInsensitiveStringComparison = vbEquality.OptionCompareTextCaseInsensitive && - isStringComparison; - if (isStringComparison) { - csSwitchExpr = vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo); - } - - var usedConstantValues = new HashSet(); - var sections = new List(); - foreach (var block in node.CaseBlocks) { - var labels = new List(); - foreach (var c in block.CaseStatement.Cases) { - if (c is VBSyntax.SimpleCaseClauseSyntax s) { - var originalExpressionSyntax = await s.Value.AcceptAsync(_expressionVisitor); - var caseTypeInfo = _semanticModel.GetTypeInfo(s.Value); - var typeConversionKind = CommonConversions.TypeConversionAnalyzer.AnalyzeConversion(s.Value); - var correctTypeExpressionSyntax = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(s.Value, originalExpressionSyntax, typeConversionKind, true, true); - var constantValue = _semanticModel.GetConstantValue(s.Value); - var notAlreadyUsed = !constantValue.HasValue || usedConstantValues.Add(constantValue.Value); - - // Pass both halves in case we can optimize away the check based on the switch expr - var wrapForStringComparison = isStringComparison && (caseInsensitiveStringComparison || - vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo, true, s.Value, originalExpressionSyntax, caseTypeInfo, false).rhs != originalExpressionSyntax); - - // CSharp requires an explicit cast from the base type (e.g. int) in most cases switching on an enum - var isBooleanCase = caseTypeInfo.Type?.SpecialType == SpecialType.System_Boolean; - bool enumRelated = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) || IsEnumOrNullableEnum(caseTypeInfo.Type); - bool convertingEnum = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) ^ IsEnumOrNullableEnum(caseTypeInfo.Type); - var csExpressionToUse = !isObjectComparison && !isBooleanCase && (convertingEnum || !enumRelated && correctTypeExpressionSyntax.IsConst) - ? correctTypeExpressionSyntax.Expr - : originalExpressionSyntax; - - var caseSwitchLabelSyntax = !isObjectComparison && !wrapForStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed - ? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(csExpressionToUse) - : WrapInCasePatternSwitchLabelSyntax(node, s.Value, csExpressionToUse, isBooleanCase); - labels.Add(caseSwitchLabelSyntax); - } else if (c is VBSyntax.ElseCaseClauseSyntax) { - labels.Add(SyntaxFactory.DefaultSwitchLabel()); - } else if (c is VBSyntax.RelationalCaseClauseSyntax relational) { - - var operatorKind = VBasic.VisualBasicExtensions.Kind(relational); - var csRelationalValue = await relational.Value.AcceptAsync(_expressionVisitor); - CasePatternSwitchLabelSyntax caseSwitchLabelSyntax; - if (isObjectComparison) { - caseSwitchLabelSyntax = WrapInCasePatternSwitchLabelSyntax(node, relational.Value, csRelationalValue, false, operatorKind); - } - else { - var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); - ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName); - csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue); - var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(), csLeft, csRelationalValue); - caseSwitchLabelSyntax = VarWhen(varName, binaryExp); - } - labels.Add(caseSwitchLabelSyntax); - } else if (c is VBSyntax.RangeCaseClauseSyntax range) { - var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); - ExpressionSyntax csCaseVar = ValidSyntaxFactory.IdentifierName(varName); - var lowerBound = await range.LowerBound.AcceptAsync(_expressionVisitor); - ExpressionSyntax lowerBoundCheck; - if (isObjectComparison) { - var caseTypeInfo = _semanticModel.GetTypeInfo(range.LowerBound); - lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual); - } else { - lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound); - lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar); - } - var upperBound = await range.UpperBound.AcceptAsync(_expressionVisitor); - ExpressionSyntax upperBoundCheck; - if (isObjectComparison) { - var caseTypeInfo = _semanticModel.GetTypeInfo(range.UpperBound); - upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual); - } else { - upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound); - upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound); - } - var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck); - labels.Add(VarWhen(varName, withinBounds)); - } else { - throw new NotSupportedException(c.Kind().ToString()); - } - } - - var csBlockStatements = (await ConvertStatementsAsync(block.Statements)).ToList(); - if (!DefinitelyExits(csBlockStatements.LastOrDefault())) { - csBlockStatements.Add(SyntaxFactory.BreakStatement()); - } - var list = SingleStatement(SyntaxFactory.Block(csBlockStatements)); - sections.Add(SyntaxFactory.SwitchSection(SyntaxFactory.List(labels), list)); - } - - var switchStatementSyntax = ValidSyntaxFactory.SwitchStatement(csSwitchExpr, sections); - return SingleStatement(switchStatementSyntax); - } - - private static bool DefinitelyExits(StatementSyntax statement) - { - if (statement == null) { - return false; - } - - StatementSyntax GetLastStatement(StatementSyntax node) => node is BlockSyntax block ? block.Statements.LastOrDefault() : node; - bool IsExitStatement(StatementSyntax node) - { - node = GetLastStatement(node); - return node != null && node.IsKind(SyntaxKind.ReturnStatement, SyntaxKind.BreakStatement, SyntaxKind.ThrowStatement, SyntaxKind.ContinueStatement); - } - - if (IsExitStatement(statement)) { - return true; - } - - if (statement is not IfStatementSyntax ifStatement) { - return false; - } - - while(ifStatement.Else != null) { - if (!IsExitStatement(ifStatement.Statement)) { - return false; - } - - if (ifStatement.Else.Statement is IfStatementSyntax x) { - ifStatement = x; - } else { - return IsExitStatement(ifStatement.Else.Statement); - } - } - - return false; - } - - private static bool IsEnumOrNullableEnum(ITypeSymbol convertedType) => - convertedType?.IsEnumType() == true || convertedType?.GetNullableUnderlyingType()?.IsEnumType() == true; - - private static CasePatternSwitchLabelSyntax VarWhen(SyntaxToken varName, ExpressionSyntax binaryExp) - { - var patternMatch = ValidSyntaxFactory.VarPattern(varName); - return SyntaxFactory.CasePatternSwitchLabel(patternMatch, - SyntaxFactory.WhenClause(binaryExp), SyntaxFactory.Token(SyntaxKind.ColonToken)); - } - - private async Task<(ExpressionSyntax Reusable, SyntaxList Statements, ExpressionSyntax SingleUse)> GetExpressionWithoutSideEffectsAsync(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase) - { - var expr = await vbExpr.AcceptAsync(_expressionVisitor); - expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, expr); - SyntaxList stmts = SyntaxFactory.List(); - ExpressionSyntax exprWithoutSideEffects; - ExpressionSyntax reusableExprWithoutSideEffects; - if (IsReusableReadOnlyLocalKind(_semanticModel.GetSymbolInfo(vbExpr).Symbol) || await CanEvaluateMultipleTimesAsync(vbExpr)) { - exprWithoutSideEffects = expr; - reusableExprWithoutSideEffects = expr.WithoutSourceMapping(); - } else { - TypeSyntax forceType = null; - if (_semanticModel.GetOperation(vbExpr.SkipIntoParens()).IsAssignableExpression()) { - forceType = SyntaxFactory.RefType(ValidSyntaxFactory.VarType); - expr = SyntaxFactory.RefExpression(expr); - } - - var (stmt, id) = CreateLocalVariableWithUniqueName(vbExpr, variableNameBase, expr, forceType); - stmts = stmts.Add(stmt); - reusableExprWithoutSideEffects = exprWithoutSideEffects = id; - } - - return (reusableExprWithoutSideEffects, stmts, exprWithoutSideEffects); - } - - private static bool IsReusableReadOnlyLocalKind(ISymbol symbol) => symbol is ILocalSymbol ls && (VBasic.VisualBasicExtensions.IsForEach(ls) || ls.IsUsing); - - private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) - { - var contextNode = vbExpr.GetAncestor() ?? (VBasic.VisualBasicSyntaxNode) vbExpr.Parent; - return CreateLocalVariableWithUniqueName(contextNode, variableNameBase, expr, forceType); - } - - private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VisualBasicSyntaxNode contextNode, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) - { - var varName = GetUniqueVariableNameInScope(contextNode, variableNameBase); - var stmt = CommonConversions.CreateLocalVariableDeclarationAndAssignment(varName, expr, forceType); - return (stmt, ValidSyntaxFactory.IdentifierName(varName)); - } - - private async Task CanEvaluateMultipleTimesAsync(VBSyntax.ExpressionSyntax vbExpr) - { - return _semanticModel.GetConstantValue(vbExpr).HasValue || vbExpr.IsKind(VBasic.SyntaxKind.MeExpression) || vbExpr.SkipIntoParens() is VBSyntax.NameSyntax ns && await IsNeverMutatedAsync(ns); - } - - private async Task IsNeverMutatedAsync(VBSyntax.NameSyntax ns) - { - var allowedLocation = Location.Create(ns.SyntaxTree, TextSpan.FromBounds(ns.GetAncestor().SpanStart, ns.Span.End)); - var symbol = _semanticModel.GetSymbolInfo(ns).Symbol; - //Perf optimization: Looking across the whole solution is expensive, so assume non-local symbols are written somewhere - return symbol.MatchesKind(SymbolKind.Parameter, SymbolKind.Local) && await CommonConversions.Document.Project.Solution.IsNeverWrittenAsync(symbol, allowedLocation); - } - - private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool treatAsBoolean = false, VBasic.SyntaxKind caseClauseKind = VBasic.SyntaxKind.CaseEqualsClause) - { - var typeInfo = _semanticModel.GetTypeInfo(node.SelectStatement.Expression); - - DeclarationPatternSyntax patternMatch; - if (typeInfo.ConvertedType.SpecialType == SpecialType.System_Boolean || treatAsBoolean) { - patternMatch = SyntaxFactory.DeclarationPattern( - SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), - SyntaxFactory.DiscardDesignation()); - } else { - var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); - patternMatch = ValidSyntaxFactory.VarPattern(varName); - ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName), csRight = expression; - var caseTypeInfo = _semanticModel.GetTypeInfo(vbCase); - expression = ComparisonAdjustedForStringComparison(node, vbCase, typeInfo, csLeft, csRight, caseTypeInfo, GetComparisonKind(caseClauseKind)); - } - - var colonToken = SyntaxFactory.Token(SyntaxKind.ColonToken); - return SyntaxFactory.CasePatternSwitchLabel(patternMatch, SyntaxFactory.WhenClause(expression), colonToken); - } - - private ComparisonKind GetComparisonKind(VBasic.SyntaxKind caseClauseKind) => caseClauseKind switch { - VBasic.SyntaxKind.CaseLessThanClause => ComparisonKind.LessThan, - VBasic.SyntaxKind.CaseLessThanOrEqualClause => ComparisonKind.LessThanOrEqual, - VBasic.SyntaxKind.CaseEqualsClause => ComparisonKind.Equals, - VBasic.SyntaxKind.CaseNotEqualsClause => ComparisonKind.NotEquals, - VBasic.SyntaxKind.CaseGreaterThanOrEqualClause => ComparisonKind.GreaterThanOrEqual, - VBasic.SyntaxKind.CaseGreaterThanClause => ComparisonKind.GreaterThan, - _ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null) - }; - - private ExpressionSyntax ComparisonAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo, ComparisonKind comparisonKind) - { - var vbEquality = CommonConversions.VisualBasicEqualityComparison; - switch (_visualBasicEqualityComparison.GetObjectEqualityType(lhsTypeInfo, rhsTypeInfo)) { - case VisualBasicEqualityComparison.RequiredType.Object: - return vbEquality.GetFullExpressionForVbObjectComparison(csLeft, csRight, comparisonKind); - case VisualBasicEqualityComparison.RequiredType.StringOnly: - // We know lhs isn't null, because we always coalesce it in the switch expression - (csLeft, csRight) = vbEquality - .AdjustForVbStringComparison(node.SelectStatement.Expression, csLeft, lhsTypeInfo, true, vbCase, csRight, rhsTypeInfo, false); - break; - } - return SyntaxFactory.BinaryExpression(GetSyntaxKind(comparisonKind), csLeft, csRight); - } - - private CS.SyntaxKind GetSyntaxKind(ComparisonKind comparisonKind) => comparisonKind switch { - ComparisonKind.LessThan => CS.SyntaxKind.LessThanExpression, - ComparisonKind.LessThanOrEqual => CS.SyntaxKind.LessThanOrEqualExpression, - ComparisonKind.Equals => CS.SyntaxKind.EqualsExpression, - ComparisonKind.NotEquals => CS.SyntaxKind.NotEqualsExpression, - ComparisonKind.GreaterThanOrEqual => CS.SyntaxKind.GreaterThanOrEqualExpression, - ComparisonKind.GreaterThan => CS.SyntaxKind.GreaterThanExpression, - _ => throw new ArgumentOutOfRangeException(nameof(comparisonKind), comparisonKind, null) - }; - - public override async Task> VisitWithBlock(VBSyntax.WithBlockSyntax node) - { - var (lhsExpression, prefixDeclarations, _) = await GetExpressionWithoutSideEffectsAsync(node.WithStatement.Expression, "withBlock"); - - _withBlockLhs.Push(lhsExpression); - try { - var statements = await ConvertStatementsAsync(node.Statements); - - var statementSyntaxs = SyntaxFactory.List(prefixDeclarations.Concat(statements)); - return prefixDeclarations.Any() - ? SingleStatement(SyntaxFactory.Block(statementSyntaxs)) - : statementSyntaxs; - } finally { - _withBlockLhs.Pop(); - } - } - - private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase) - { - return NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase); - } - - public override async Task> VisitTryBlock(VBSyntax.TryBlockSyntax node) - { - var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - return SingleStatement( - SyntaxFactory.TryStatement( - block, - SyntaxFactory.List(await node.CatchBlocks.SelectAsync(async c => await ConvertCatchBlockAsync(c))), - await ConvertFinallyBlockAsync(node.FinallyBlock) - ) - ); - - async Task ConvertCatchBlockAsync(VBasic.Syntax.CatchBlockSyntax node) - { - var stmt = node.CatchStatement; - CatchDeclarationSyntax catcher = null; - if (stmt.AsClause != null) { - catcher = SyntaxFactory.CatchDeclaration( - ConvertTypeSyntax(stmt.AsClause.Type), - CommonConversions.ConvertIdentifier(stmt.IdentifierName.Identifier) - ); - } - - var filter = await ConvertCatchFilterClauseAsync(stmt.WhenClause); - var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); - return SyntaxFactory.CatchClause( - catcher, - filter, - SyntaxFactory.Block(stmts) - ); - } - - async Task ConvertCatchFilterClauseAsync(VBasic.Syntax.CatchFilterClauseSyntax node) - { - if (node == null) return null; - return SyntaxFactory.CatchFilterClause(await node.Filter.AcceptAsync(_expressionVisitor)); - } - - async Task ConvertFinallyBlockAsync(VBasic.Syntax.FinallyBlockSyntax node) - { - if (node == null) return null; - var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); - return SyntaxFactory.FinallyClause(SyntaxFactory.Block(stmts)); - } - } - - private TypeSyntax ConvertTypeSyntax(VBSyntax.TypeSyntax vbType) - { - if (_semanticModel.GetSymbolInfo(vbType).Symbol is ITypeSymbol typeSymbol) - return CommonConversions.GetTypeSyntax(typeSymbol); - return SyntaxFactory.ParseTypeName(vbType.ToString()); - } - - public override async Task> VisitSyncLockBlock(VBSyntax.SyncLockBlockSyntax node) - { - return SingleStatement(SyntaxFactory.LockStatement( - await node.SyncLockStatement.Expression.AcceptAsync(_expressionVisitor), - SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() - )); - } - - public override async Task> VisitUsingBlock(VBSyntax.UsingBlockSyntax node) - { - var statementSyntax = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); - if (node.UsingStatement.Expression == null) { - StatementSyntax stmt = statementSyntax; - foreach (var v in node.UsingStatement.Variables.Reverse()) - foreach (var declaration in (await SplitVariableDeclarationsAsync(v)).Variables.Reverse()) - stmt = SyntaxFactory.UsingStatement(declaration.Decl, null, stmt); - return SingleStatement(stmt); - } - - var expr = await node.UsingStatement.Expression.AcceptAsync(_expressionVisitor); - var unpackPossiblyNestedBlock = statementSyntax.UnpackPossiblyNestedBlock(); // Allow reduced indentation for multiple usings in a row - return SingleStatement(SyntaxFactory.UsingStatement(null, expr, unpackPossiblyNestedBlock)); - } - - public override async Task> VisitWhileBlock(VBSyntax.WhileBlockSyntax node) - { - return SingleStatement(SyntaxFactory.WhileStatement( - await node.WhileStatement.Condition.AcceptAsync(_expressionVisitor), - SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() - )); - } - - public override async Task> VisitDoLoopBlock(VBSyntax.DoLoopBlockSyntax node) - { - var statements = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock(); - - if (node.DoStatement.WhileOrUntilClause != null) { - var stmt = node.DoStatement.WhileOrUntilClause; - if (stmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.WhileKeyword)) - return SingleStatement(SyntaxFactory.WhileStatement( - await stmt.Condition.AcceptAsync(_expressionVisitor), - statements - )); - return SingleStatement(SyntaxFactory.WhileStatement( - (await stmt.Condition.AcceptAsync(_expressionVisitor)).InvertCondition(), - statements - )); - } - - var whileOrUntilStmt = node.LoopStatement.WhileOrUntilClause; - ExpressionSyntax conditionExpression; - bool isUntilStmt; - if (whileOrUntilStmt != null) { - conditionExpression = await whileOrUntilStmt.Condition.AcceptAsync(_expressionVisitor); - isUntilStmt = whileOrUntilStmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.UntilKeyword); - } else { - conditionExpression = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression); - isUntilStmt = false; - } - - if (isUntilStmt) { - conditionExpression = conditionExpression.InvertCondition(); - } - - return SingleStatement(SyntaxFactory.DoStatement(statements, conditionExpression)); - } - - public override async Task> VisitCallStatement(VBSyntax.CallStatementSyntax node) - { - return SingleStatement(await node.Invocation.AcceptAsync(_expressionVisitor)); - } - - private static SyntaxList SingleStatement(StatementSyntax statement) - { - return SyntaxFactory.SingletonList(statement); - } - - private static SyntaxList SingleStatement(ExpressionSyntax expression) - { - return SyntaxFactory.SingletonList(SyntaxFactory.ExpressionStatement(expression)); - } +using System.Collections; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; +using static Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions; +using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using Microsoft.CodeAnalysis.Text; +using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ICSharpCode.CodeConverter.VB; +using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind; + +namespace ICSharpCode.CodeConverter.CSharp; + +/// +/// Executable statements - which includes executable blocks such as if statements +/// Maintains state relevant to the called method-like object. A fresh one must be used for each method, and the same one must be reused for statements in the same method +/// +internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVisitor>> +{ + private readonly VBasic.VisualBasicSyntaxNode _methodNode; + private readonly SemanticModel _semanticModel; + private readonly CommentConvertingVisitorWrapper _expressionVisitor; + private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; + private readonly Stack _withBlockLhs; + private readonly HashSet _extraUsingDirectives; + private readonly HandledEventsAnalysis _handledEventsAnalysis; + private readonly HashSet _generatedNames = new(); + private readonly HashSet _localsToInlineInLoop; + private readonly PerScopeState _perScopeState; + + public bool IsIterator { get; set; } + public IdentifierNameSyntax ReturnVariable { get; set; } + public bool HasReturnVariable => ReturnVariable != null; + public VBasic.VisualBasicSyntaxVisitor>> CommentConvertingVisitor { get; } + + private CommonConversions CommonConversions { get; } + + public static async Task CreateAsync(VisualBasicSyntaxNode node, SemanticModel semanticModel, + CommentConvertingVisitorWrapper triviaConvertingExpressionVisitor, CommonConversions commonConversions, VisualBasicEqualityComparison visualBasicEqualityComparison, + Stack withBlockLhs, HashSet extraUsingDirectives, + ITypeContext typeContext, bool isIterator, IdentifierNameSyntax csReturnVariable) + { + var solution = commonConversions.Document.Project.Solution; + var declarationsToInlineInLoop = await solution.GetDescendantsToInlineInLoopAsync(semanticModel, node); + return new MethodBodyExecutableStatementVisitor(node, semanticModel, triviaConvertingExpressionVisitor, commonConversions, visualBasicEqualityComparison, withBlockLhs, extraUsingDirectives, typeContext, declarationsToInlineInLoop) { + IsIterator = isIterator, + ReturnVariable = csReturnVariable, + }; + } + + private MethodBodyExecutableStatementVisitor(VisualBasicSyntaxNode methodNode, SemanticModel semanticModel, + CommentConvertingVisitorWrapper expressionVisitor, CommonConversions commonConversions, + VisualBasicEqualityComparison visualBasicEqualityComparison, + Stack withBlockLhs, HashSet extraUsingDirectives, + ITypeContext typeContext, HashSet localsToInlineInLoop) + { + _methodNode = methodNode; + _semanticModel = semanticModel; + _expressionVisitor = expressionVisitor; + _visualBasicEqualityComparison = visualBasicEqualityComparison; + CommonConversions = commonConversions; + _withBlockLhs = withBlockLhs; + _extraUsingDirectives = extraUsingDirectives; + _handledEventsAnalysis = typeContext.HandledEventsAnalysis; + _perScopeState = typeContext.PerScopeState; + var byRefParameterVisitor = new PerScopeStateVisitorDecorator(this, _perScopeState, semanticModel, _generatedNames); + CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor); + _localsToInlineInLoop = localsToInlineInLoop; + } + + public override async Task> DefaultVisit(SyntaxNode node) + { + throw new NotImplementedException($"Conversion for {VBasic.VisualBasicExtensions.Kind(node)} not implemented, please report this issue") + .WithNodeInformation(node); + } + + public override async Task> VisitStopOrEndStatement(VBSyntax.StopOrEndStatementSyntax node) + { + return SingleStatement(SyntaxFactory.ParseStatement(ConvertStopOrEndToCSharpStatementText(node))); + } + + private string ConvertStopOrEndToCSharpStatementText(VBSyntax.StopOrEndStatementSyntax node) + { + switch (VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword)) { + case VBasic.SyntaxKind.StopKeyword: + _extraUsingDirectives.Add("System.Diagnostics"); + return "Debugger.Break();"; + case VBasic.SyntaxKind.EndKeyword: + _extraUsingDirectives.Add("System"); + return "Environment.Exit(0);"; + default: + throw new NotImplementedException(VBasic.VisualBasicExtensions.Kind(node.StopOrEndKeyword) + " not implemented!"); + } + } + + public override async Task> VisitLocalDeclarationStatement(VBSyntax.LocalDeclarationStatementSyntax node) + { + var modifiers = CommonConversions.ConvertModifiers(node.Declarators[0].Names[0], node.Modifiers, TokenContext.Local); + var isConst = modifiers.Any(a => a.IsKind(SyntaxKind.ConstKeyword)); + var isVBStatic = node.Modifiers.Any(a => a.IsKind(VBasic.SyntaxKind.StaticKeyword)); + + var declarations = new List(); + + foreach (var declarator in node.Declarators) { + var (variables, methods) = await SplitVariableDeclarationsAsync(declarator, preferExplicitType: isConst || isVBStatic); + var localDeclarationStatementSyntaxs = variables.Select(declAndType => SyntaxFactory.LocalDeclarationStatement(modifiers, declAndType.Decl)); + if (isVBStatic) { + foreach (var decl in localDeclarationStatementSyntaxs) { + var variable = decl.Declaration.Variables.Single(); + var initializeValue = variable.Initializer?.Value; + string methodName; + SyntaxTokenList methodModifiers; + + MethodKind parentAccessorKind = MethodKind.Ordinary; + if (_methodNode is VBSyntax.MethodBlockSyntax methodBlock) { + var methodStatement = methodBlock.BlockStatement as VBSyntax.MethodStatementSyntax; + methodModifiers = methodStatement.Modifiers; + methodName = methodStatement.Identifier.Text; + } else if (_methodNode is VBSyntax.ConstructorBlockSyntax constructorBlock) { + methodModifiers = constructorBlock.BlockStatement.Modifiers; + methodName = null; + } else if (_methodNode is VBSyntax.AccessorBlockSyntax accessorBlock) { + var propertyBlock = accessorBlock.Parent as VBSyntax.PropertyBlockSyntax; + methodName = propertyBlock.PropertyStatement.Identifier.Text; + parentAccessorKind = accessorBlock.IsKind(VBasic.SyntaxKind.GetAccessorBlock) ? MethodKind.PropertyGet : MethodKind.PropertySet; + methodModifiers = propertyBlock.PropertyStatement.Modifiers; + } else { + throw new NotImplementedException(_methodNode.GetType() + " not implemented!"); + } + + var isVbShared = methodModifiers.Any(a => a.IsKind(VBasic.SyntaxKind.SharedKeyword)); + _perScopeState.HoistToTopLevel(new HoistedFieldFromVbStaticVariable(methodName, variable.Identifier.Text, parentAccessorKind, initializeValue, decl.Declaration.Type, isVbShared)); + } + } else { + var shouldPullVariablesBeforeLoop = _perScopeState.IsInsideLoop() && declarator.Initializer is null && declarator.AsClause is not VBSyntax.AsNewClauseSyntax; + if (shouldPullVariablesBeforeLoop) { + localDeclarationStatementSyntaxs = HoistVariablesBeforeLoopWhenNeeded(variables) + .Select(variableDecl => SyntaxFactory.LocalDeclarationStatement(modifiers, variableDecl)); + } + + declarations.AddRange(localDeclarationStatementSyntaxs); + } + var localFunctions = methods.Cast(); + declarations.AddRange(localFunctions); + } + + return SyntaxFactory.List(declarations); + } + + private IEnumerable HoistVariablesBeforeLoopWhenNeeded(IEnumerable variablesDeclarations) + { + foreach (var variablesDecl in variablesDeclarations) { + var variablesToRemove = new List(); + foreach (var (csVariable, vbVariable) in variablesDecl.Variables) { + var symbol = _semanticModel.GetDeclaredSymbol(vbVariable); + var assignedBeforeRead = _semanticModel.IsDefinitelyAssignedBeforeRead(symbol, vbVariable); + if (!assignedBeforeRead) { + _perScopeState.Hoist(new HoistedDefaultInitializedLoopVariable( + csVariable.Identifier.Text, + // e.g. "b As Boolean" has no initializer but can turn into "var b = default(bool)" + csVariable.Initializer?.Value, + variablesDecl.Decl.Type, + _perScopeState.IsInsideNestedLoop())); + variablesToRemove.Add(csVariable); + } + } + + var variablesToDeclareLocally = variablesDecl.Variables.Select(t => t.CsVar).Except(variablesToRemove).ToArray(); + if(variablesToDeclareLocally.Any()) { + yield return variablesDecl.Decl.WithVariables(SyntaxFactory.SeparatedList(variablesToDeclareLocally)); + } + } + } + + public override async Task> VisitAddRemoveHandlerStatement(VBSyntax.AddRemoveHandlerStatementSyntax node) + { + var syntaxKind = ConvertAddRemoveHandlerToCSharpSyntaxKind(node); + return SingleStatement(SyntaxFactory.AssignmentExpression(syntaxKind, + await node.EventExpression.AcceptAsync(_expressionVisitor), + await node.DelegateExpression.AcceptAsync(_expressionVisitor))); + } + + private static SyntaxKind ConvertAddRemoveHandlerToCSharpSyntaxKind(VBSyntax.AddRemoveHandlerStatementSyntax node) + { + switch (node.Kind()) { + case VBasic.SyntaxKind.AddHandlerStatement: + return SyntaxKind.AddAssignmentExpression; + case VBasic.SyntaxKind.RemoveHandlerStatement: + return SyntaxKind.SubtractAssignmentExpression; + default: + throw new NotImplementedException(node.Kind() + " not implemented!"); + } + } + + public override async Task> VisitExpressionStatement(VBSyntax.ExpressionStatementSyntax node) + { + if (node.Expression is VBSyntax.InvocationExpressionSyntax invoke && invoke.Expression is VBSyntax.MemberAccessExpressionSyntax access && access.Expression is VBSyntax.MyBaseExpressionSyntax && access.Name.Identifier.ValueText.Equals("Finalize", StringComparison.OrdinalIgnoreCase)) { + return new SyntaxList(); + } + + return SingleStatement(await node.Expression.AcceptAsync(_expressionVisitor)); + } + + public override async Task> VisitAssignmentStatement(VBSyntax.AssignmentStatementSyntax node) + { + if (node.IsKind(VBasic.SyntaxKind.MidAssignmentStatement) && node.Left is VBSyntax.MidExpressionSyntax mes) { + return await ConvertMidAssignmentAsync(node, mes); + } + + var lhs = await node.Left.AcceptAsync(_expressionVisitor); + var lOperation = _semanticModel.GetOperation(node.Left); + + var (parameterizedPropertyAccessMethod, _) = await CommonConversions.GetParameterizedPropertyAccessMethodAsync(lOperation); + + // 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 && + _methodNode is VBSyntax.MethodBlockSyntax mb && + HasReturnVariable && + id.Identifier.ValueText.Equals(mb.SubOrFunctionStatement.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)) { + lhs = ReturnVariable; + } + + if (node.IsKind(VBasic.SyntaxKind.ExponentiateAssignmentStatement)) { + rhs = SyntaxFactory.InvocationExpression( + ValidSyntaxFactory.MemberAccess(nameof(Math), nameof(Math.Pow)), + ExpressionSyntaxExtensions.CreateArgList(lhs, rhs)); + } + var kind = node.Kind().ConvertToken(); + + + var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left); + var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right); + + var typeConvertedRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); + + // Split out compound operator if type conversion needed on result + if (TypeConversionAnalyzer.GetNonCompoundOrNull(kind) is {} nonCompound) { + + var nonCompoundRhs = SyntaxFactory.BinaryExpression(nonCompound, lhs, typeConvertedRhs); + var typeConvertedNonCompoundRhs = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, nonCompoundRhs, forceSourceType: rhsTypeInfo.ConvertedType, forceTargetType: lhsTypeInfo.Type); + 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; + + if (setMethodName != null) { + if (lhs is InvocationExpressionSyntax ies) { + ExpressionSyntax exprToReplace = ies.Expression; + if (exprToReplace is MemberAccessExpressionSyntax {Name: IdentifierNameSyntax idn} maes) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn); + exprToReplace = maes.WithName(newName); + + if (maes.Expression is ThisExpressionSyntax) { + var skipParens = node.Left.SkipIntoParens(); + var isNonSelfQualified = skipParens is VBSyntax.MemberAccessExpressionSyntax { + Expression: not VBSyntax.MeExpressionSyntax + and not VBSyntax.MyClassExpressionSyntax + }; + if (!isNonSelfQualified) { + exprToReplace = newName.WithTriviaFrom(maes); + } + } + } else if (exprToReplace is IdentifierNameSyntax idn2) { + var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2); + exprToReplace = newName; + } + var newArg = SyntaxFactory.Argument(rhs); + var newArgs = ies.ArgumentList.Arguments.Add(newArg); + var newArgList = ies.ArgumentList.WithArguments(newArgs); + var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); + var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs); + var postAssign = GetPostAssignmentStatements(node); + return postAssign.Insert(0, invokeAssignment); + } + return SingleStatement(lhs); + } + + var assignment = SyntaxFactory.AssignmentExpression(kind, lhs, rhs); + var postAssignment = GetPostAssignmentStatements(node); + return postAssignment.Insert(0, SyntaxFactory.ExpressionStatement(assignment)); + } + + private async Task> ConvertMidAssignmentAsync(VBSyntax.AssignmentStatementSyntax node, VBSyntax.MidExpressionSyntax mes) + { + _extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices"); + var midFunction = ValidSyntaxFactory.MemberAccess("StringType", "MidStmtStr"); + var midArgList = await mes.ArgumentList.AcceptAsync(_expressionVisitor); + var (reusable, statements, _) = await GetExpressionWithoutSideEffectsAsync(node.Right, "midTmp"); + if (midArgList.Arguments.Count == 2) { + var length = ValidSyntaxFactory.MemberAccess(reusable, "Length"); + midArgList = midArgList.AddArguments(SyntaxFactory.Argument(length)); + } + midArgList = midArgList.AddArguments(SyntaxFactory.Argument(reusable)); + var invokeMid = SyntaxFactory.InvocationExpression(midFunction, midArgList); + return statements.Add(SyntaxFactory.ExpressionStatement(invokeMid)); + } + + /// + /// ensures we convert the property access to a field access + /// + private SyntaxList GetPostAssignmentStatements(VBSyntax.AssignmentStatementSyntax node) + { + var potentialPropertySymbol = _semanticModel.GetSymbolInfo(node.Left).ExtractBestMatch(); + return GetPostAssignmentStatements(node, potentialPropertySymbol); + } + + /// + /// Make winforms designer work: https://github.com/icsharpcode/CodeConverter/issues/321 + /// + public SyntaxList GetPostAssignmentStatements(Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax node, ISymbol potentialPropertySymbol) + { + if (CommonConversions.WinformsConversions.MayNeedToInlinePropertyAccess(node, potentialPropertySymbol)) { + return _handledEventsAnalysis.GetPostAssignmentStatements(potentialPropertySymbol); + } + + return SyntaxFactory.List(); + } + + public override async Task> VisitEraseStatement(VBSyntax.EraseStatementSyntax node) + { + var eraseStatements = await node.Expressions.SelectAsync(async arrayExpression => { + var lhs = await arrayExpression.AcceptAsync(_expressionVisitor); + var rhs = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); + var assignmentExpressionSyntax = + SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, lhs, + rhs); + return SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); + }); + return SyntaxFactory.List(eraseStatements); + } + + public override async Task> VisitReDimStatement(VBSyntax.ReDimStatementSyntax node) + { + return SyntaxFactory.List(await node.Clauses.SelectManyAsync(async arrayExpression => (IEnumerable) await ConvertRedimClauseAsync(arrayExpression))); + } + + /// + /// RedimClauseSyntax isn't an executable statement, therefore this isn't a "Visit" method. + /// Since it returns multiple statements it's easiest for it to be here in the current architecture. + /// + private async Task> ConvertRedimClauseAsync(VBSyntax.RedimClauseSyntax node) + { + bool preserve = node.Parent is VBSyntax.ReDimStatementSyntax rdss && rdss.PreserveKeyword.IsKind(VBasic.SyntaxKind.PreserveKeyword); + + var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); + var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); + if (preserve && convertedBounds.Count == 1) { + bool isProperty = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true; + var arrayToResize = isProperty ? CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression) : default; + var resizeArg = isProperty ? (ExpressionSyntax)arrayToResize.Reference : csTargetArrayExpression; + + var argumentList = new[] { resizeArg, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); + var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList); + + if (isProperty) { + var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, arrayToResize.Reference); + return SyntaxFactory.List(new StatementSyntax[] { arrayToResize.Declaration, SyntaxFactory.ExpressionStatement(arrayResize), SyntaxFactory.ExpressionStatement(assignment) }); + } + + return SingleStatement(arrayResize); + } + var newArrayAssignment = CreateNewArrayAssignment(node.Expression, csTargetArrayExpression, convertedBounds); + if (!preserve) return SingleStatement(newArrayAssignment); + + var lastIdentifierText = node.Expression.DescendantNodesAndSelf().OfType().Last().Identifier.Text; + string variableNameBase = "old" + lastIdentifierText.ToPascalCase(); + var expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csTargetArrayExpression); + var (stmt, oldTargetExpression) = CreateLocalVariableWithUniqueName(node.Expression, variableNameBase, expr); + + var arrayCopyIfNotNull = CreateConditionalArrayCopy(node, oldTargetExpression, csTargetArrayExpression, convertedBounds); + + return SyntaxFactory.List(new[] { stmt, newArrayAssignment, arrayCopyIfNotNull}); + } + + /// + /// Cut down version of Microsoft.VisualBasic.CompilerServices.Utils.CopyArray + /// + private IfStatementSyntax CreateConditionalArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, + IdentifierNameSyntax sourceArrayExpression, + ExpressionSyntax targetArrayExpression, + List convertedBounds) + { + var sourceLength = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, sourceArrayExpression, ValidSyntaxFactory.IdentifierName("Length")); + var arrayCopyStatement = convertedBounds.Count == 1 + ? CreateArrayCopyWithMinOfLengths(sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds.Single()) + : CreateArrayCopy(originalVbNode, sourceArrayExpression, sourceLength, targetArrayExpression, convertedBounds); + + var oldTargetNotEqualToNull = CommonConversions.NotNothingComparison(sourceArrayExpression, true); + return SyntaxFactory.IfStatement(oldTargetNotEqualToNull, arrayCopyStatement); + } + + /// + /// Array copy for multiple array dimensions represented by + /// + /// + /// Exception cases will sometimes silently succeed in the converted code, + /// but existing VB code relying on the exception thrown from a multidimensional redim preserve on + /// different rank arrays is hopefully rare enough that it's worth saving a few lines of code + /// + private StatementSyntax CreateArrayCopy(VBasic.VisualBasicSyntaxNode originalVbNode, + IdentifierNameSyntax sourceArrayExpression, + MemberAccessExpressionSyntax sourceLength, + ExpressionSyntax targetArrayExpression, ICollection convertedBounds) + { + var lastSourceLengthArgs = ExpressionSyntaxExtensions.CreateArgList(CommonConversions.Literal(convertedBounds.Count - 1)); + var sourceLastRankLength = SyntaxFactory.InvocationExpression( + SyntaxFactory.ParseExpression($"{sourceArrayExpression.Identifier}.GetLength"), lastSourceLengthArgs); + var targetLastRankLength = + SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression($"{targetArrayExpression}.GetLength"), + lastSourceLengthArgs); + var length = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(sourceLastRankLength, targetLastRankLength)); + + var loopVariableName = GetUniqueVariableNameInScope(originalVbNode, "i"); + var loopVariableIdentifier = ValidSyntaxFactory.IdentifierName(loopVariableName); + var sourceStartForThisIteration = + SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, sourceLastRankLength); + var targetStartForThisIteration = + SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, loopVariableIdentifier, targetLastRankLength); + + var arrayCopy = CreateArrayCopyWithStartingPoints(sourceArrayExpression, sourceStartForThisIteration, targetArrayExpression, + targetStartForThisIteration, length); + + var sourceArrayCount = SyntaxFactory.BinaryExpression(SyntaxKind.SubtractExpression, + SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, sourceLength, sourceLastRankLength), CommonConversions.Literal(1)); + + return CreateForZeroToValueLoop(loopVariableIdentifier, arrayCopy, sourceArrayCount); + } + + private static ForStatementSyntax CreateForZeroToValueLoop(SimpleNameSyntax loopVariableIdentifier, StatementSyntax loopStatement, ExpressionSyntax inclusiveLoopUpperBound) + { + var loopVariableAssignment = CommonConversions.CreateVariableDeclarationAndAssignment(loopVariableIdentifier.Identifier.Text, CommonConversions.Literal(0)); + var lessThanSourceBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, + loopVariableIdentifier, inclusiveLoopUpperBound); + var incrementors = SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.PrefixUnaryExpression(SyntaxKind.PreIncrementExpression, loopVariableIdentifier)); + var forStatementSyntax = SyntaxFactory.ForStatement(loopVariableAssignment, + SyntaxFactory.SeparatedList(), + lessThanSourceBounds, incrementors, loopStatement); + return forStatementSyntax; + } + + private static ExpressionStatementSyntax CreateArrayCopyWithMinOfLengths( + IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceLength, + ExpressionSyntax targetExpression, ExpressionSyntax targetLength) + { + var minLength = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Math.Min"), ExpressionSyntaxExtensions.CreateArgList(targetLength, sourceLength)); + var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, targetExpression, minLength); + var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); + return SyntaxFactory.ExpressionStatement(arrayCopy); + } + + private static ExpressionStatementSyntax CreateArrayCopyWithStartingPoints( + IdentifierNameSyntax sourceExpression, ExpressionSyntax sourceStart, + ExpressionSyntax targetExpression, ExpressionSyntax targetStart, ExpressionSyntax length) + { + var copyArgList = ExpressionSyntaxExtensions.CreateArgList(sourceExpression, sourceStart, targetExpression, targetStart, length); + var arrayCopy = SyntaxFactory.InvocationExpression(SyntaxFactory.ParseExpression("Array.Copy"), copyArgList); + return SyntaxFactory.ExpressionStatement(arrayCopy); + } + + private ExpressionStatementSyntax CreateNewArrayAssignment(VBSyntax.ExpressionSyntax vbArrayExpression, + ExpressionSyntax csArrayExpression, List convertedBounds) + { + var convertedType = (IArrayTypeSymbol) _semanticModel.GetTypeInfo(vbArrayExpression).ConvertedType; + var arrayRankSpecifierSyntax = SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(convertedBounds)); + var rankSpecifiers = SyntaxFactory.SingletonList(arrayRankSpecifierSyntax); + while (convertedType.ElementType is IArrayTypeSymbol ats) { + convertedType = ats; + rankSpecifiers = rankSpecifiers.Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.OmittedArraySizeExpression()))); + }; + var typeSyntax = CommonConversions.GetTypeSyntax(convertedType.ElementType); + var arrayCreation = + SyntaxFactory.ArrayCreationExpression(SyntaxFactory.ArrayType(typeSyntax, rankSpecifiers)); + var assignmentExpressionSyntax = + SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csArrayExpression, arrayCreation); + var newArrayAssignment = SyntaxFactory.ExpressionStatement(assignmentExpressionSyntax); + return newArrayAssignment; + } + + public override async Task> VisitThrowStatement(VBSyntax.ThrowStatementSyntax node) + { + return SingleStatement(SyntaxFactory.ThrowStatement(await node.Expression.AcceptAsync(_expressionVisitor))); + } + + public override async Task> VisitReturnStatement(VBSyntax.ReturnStatementSyntax node) + { + if (IsIterator) + return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); + + var csExpression = await node.Expression.AcceptAsync(_expressionVisitor); + csExpression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, csExpression); + return SingleStatement(SyntaxFactory.ReturnStatement(csExpression)); + } + + public override async Task> VisitContinueStatement(VBSyntax.ContinueStatementSyntax node) + { + var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); + return SyntaxFactory.List(_perScopeState.ConvertContinue(vbBlockKeywordKind)); + } + + public override async Task> VisitYieldStatement(VBSyntax.YieldStatementSyntax node) + { + return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldReturnStatement, await node.Expression.AcceptAsync(_expressionVisitor))); + } + + public override async Task> VisitExitStatement(VBSyntax.ExitStatementSyntax node) + { + var vbBlockKeywordKind = VBasic.VisualBasicExtensions.Kind(node.BlockKeyword); + switch (vbBlockKeywordKind) { + case VBasic.SyntaxKind.SubKeyword: + case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) != true: + return SingleStatement(SyntaxFactory.ReturnStatement()); + case VBasic.SyntaxKind.FunctionKeyword: + case VBasic.SyntaxKind.PropertyKeyword when node.GetAncestor()?.IsKind(VBasic.SyntaxKind.GetAccessorBlock) == true: + VBasic.VisualBasicSyntaxNode typeContainer = node.GetAncestor() + ?? (VBasic.VisualBasicSyntaxNode)node.GetAncestor() + ?? node.GetAncestor(); + var enclosingMethodInfo = typeContainer switch { + VBSyntax.LambdaExpressionSyntax e => _semanticModel.GetSymbolInfo(e).Symbol, + VBSyntax.MethodBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.SubOrFunctionStatement), + VBSyntax.AccessorBlockSyntax e => _semanticModel.GetDeclaredSymbol(e.AccessorStatement), + _ => null + } as IMethodSymbol; + + if (IsIterator) return SingleStatement(SyntaxFactory.YieldStatement(SyntaxKind.YieldBreakStatement)); + + if (!enclosingMethodInfo.ReturnsVoidOrAsyncTask()) { + ExpressionSyntax expr = HasReturnVariable ? ReturnVariable : SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression); + return SingleStatement(SyntaxFactory.ReturnStatement(expr)); + } + + return SingleStatement(SyntaxFactory.ReturnStatement()); + default: + return SyntaxFactory.List(_perScopeState.ConvertExit(vbBlockKeywordKind)); + } + } + + public override async Task> VisitRaiseEventStatement(VBSyntax.RaiseEventStatementSyntax node) + { + var argumentListSyntax = await node.ArgumentList.AcceptAsync(_expressionVisitor) ?? SyntaxFactory.ArgumentList(); + + var symbolInfo = _semanticModel.GetSymbolInfo(node.Name).ExtractBestMatch(); + if (symbolInfo?.RaiseMethod != null) { + return SingleStatement(SyntaxFactory.InvocationExpression( + ValidSyntaxFactory.IdentifierName($"On{symbolInfo.Name}"), + argumentListSyntax)); + } + + var memberBindingExpressionSyntax = SyntaxFactory.MemberBindingExpression(ValidSyntaxFactory.IdentifierName("Invoke")); + var conditionalAccessExpressionSyntax = SyntaxFactory.ConditionalAccessExpression( + await node.Name.AcceptAsync(_expressionVisitor), + SyntaxFactory.InvocationExpression(memberBindingExpressionSyntax, argumentListSyntax) + ); + return SingleStatement( + conditionalAccessExpressionSyntax + ); + } + + public override async Task> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node) + { + var condition = await node.Condition.AcceptAsync(_expressionVisitor); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + ElseClauseSyntax elseClause = null; + + if (node.ElseClause != null) { + var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(node.ElseClause.Statements)); + elseClause = SyntaxFactory.ElseClause(elseBlock.UnpackNonNestedBlock()); + } + return SingleStatement(SyntaxFactory.IfStatement(condition, block.UnpackNonNestedBlock(), elseClause)); + } + + public override async Task> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node) + { + var condition = await node.IfStatement.Condition.AcceptAsync(_expressionVisitor); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + + var elseClause = await ConvertElseClauseAsync(node.ElseBlock); + elseClause = elseClause.WithVbSourceMappingFrom(node.ElseBlock); //Special case where explicit mapping is needed since block becomes clause so cannot be easily visited + + var elseIfBlocks = await node.ElseIfBlocks.SelectAsync(async elseIf => await ConvertElseIfAsync(elseIf)); + foreach (var elseIf in Enumerable.Reverse(elseIfBlocks)) { + var ifStmt = SyntaxFactory.IfStatement(elseIf.ElseIfCondition, elseIf.ElseBlock, elseClause); + elseClause = SyntaxFactory.ElseClause(ifStmt); + } + + return SingleStatement(SyntaxFactory.IfStatement(condition, block, elseClause)); + } + + private async Task<(ExpressionSyntax ElseIfCondition, BlockSyntax ElseBlock)> ConvertElseIfAsync(VBSyntax.ElseIfBlockSyntax elseIf) + { + var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements)); + var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); + elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: CommonConversions.KnownTypes.Boolean); + return (elseIfCondition, elseBlock); + } + + private async Task ConvertElseClauseAsync(VBSyntax.ElseBlockSyntax elseBlock) + { + if (elseBlock == null) return null; + + var csStatements = await ConvertStatementsAsync(elseBlock.Statements); + if (csStatements.TryUnpackSingleStatement(out var stmt) && stmt.IsKind(SyntaxKind.IfStatement)) { + // so that you get a neat "else if" at the end + return SyntaxFactory.ElseClause(stmt); + } + + return SyntaxFactory.ElseClause(SyntaxFactory.Block(csStatements)); + } + + private async Task ConvertStatementsAsync(SyntaxList statementSyntaxs) + { + return await statementSyntaxs.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); + } + + /// + /// See https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/for-next-statement#BKMK_Counter + /// + public override async Task> VisitForBlock(VBSyntax.ForBlockSyntax node) + { + var stmt = node.ForStatement; + VariableDeclarationSyntax declaration = null; + ExpressionSyntax id; + var controlVarSymbol = _semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol ?? (_semanticModel.GetOperation(stmt.ControlVariable) as IVariableDeclaratorOperation)?.Symbol; + + // If missing semantic info, the compiler just guesses object, let's try to improve on that guess: + var controlVarType = controlVarSymbol?.GetSymbolType().Yield().Concat( + new SyntaxNode[] {stmt.ControlVariable, stmt.FromValue, stmt.ToValue, stmt.StepClause?.StepValue} + .Select(exp => _semanticModel.GetTypeInfo(exp).Type) + ).FirstOrDefault(t => t != null && t.SpecialType != SpecialType.System_Object); + var startValue = await stmt.FromValue.AcceptAsync(_expressionVisitor); + startValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.FromValue, startValue?.SkipIntoParens(), forceTargetType: controlVarType); + + var initializers = new List(); + var controlVarTypeSyntax = CommonConversions.GetTypeSyntax(controlVarType); + if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax v) { + declaration = (await SplitVariableDeclarationsAsync(v)).Variables.Single().Decl; + declaration = declaration.WithVariables(SyntaxFactory.SingletonSeparatedList(declaration.Variables[0].WithInitializer(SyntaxFactory.EqualsValueClause(startValue)))); + id = ValidSyntaxFactory.IdentifierName(declaration.Variables[0].Identifier); + } else { + id = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); + + if (controlVarSymbol != null && controlVarSymbol.DeclaringSyntaxReferences.Any(r => r.Span.OverlapsWith(stmt.ControlVariable.Span))) { + declaration = CommonConversions.CreateVariableDeclarationAndAssignment(controlVarSymbol.Name, startValue, controlVarTypeSyntax); + } else { + startValue = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, id, startValue); + initializers.Add(startValue); + } + } + + + var preLoopStatements = new List(); + var csToValue = await stmt.ToValue.AcceptAsync(_expressionVisitor); + csToValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.ToValue, csToValue?.SkipIntoParens(), forceTargetType: controlVarType); + + // In Visual Basic, the To expression is only evaluated once, but in C# will be evaluated every loop. + // If it could evaluate differently or has side effects, it must be extracted as a variable + if (!_semanticModel.GetConstantValue(stmt.ToValue).HasValue) { + var loopToVariableName = GetUniqueVariableNameInScope(node, "loopTo"); + var toVariableId = ValidSyntaxFactory.IdentifierName(loopToVariableName); + + var loopToAssignment = CommonConversions.CreateVariableDeclarator(loopToVariableName, csToValue); + if (initializers.Any()) { + var loopEndDeclaration = SyntaxFactory.LocalDeclarationStatement( + CommonConversions.CreateVariableDeclarationAndAssignment(loopToVariableName, csToValue)); + // Does not do anything about porting newline trivia upwards to maintain spacing above the loop + preLoopStatements.Add(loopEndDeclaration); + } else { + declaration = declaration == null + ? SyntaxFactory.VariableDeclaration(controlVarTypeSyntax, + SyntaxFactory.SingletonSeparatedList(loopToAssignment)) + : declaration.AddVariables(loopToAssignment).WithType(controlVarTypeSyntax); + } + + csToValue = toVariableId; + } + + var (csCondition, csStep) = await ConvertConditionAndStepClauseAsync(stmt, id, csToValue, controlVarType); + + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + var forStatementSyntax = SyntaxFactory.ForStatement( + declaration, + SyntaxFactory.SeparatedList(initializers), + csCondition, + SyntaxFactory.SingletonSeparatedList(csStep), + block.UnpackNonNestedBlock()); + return SyntaxFactory.List(preLoopStatements.Concat(new[] { forStatementSyntax })); + } + + private async Task<(IReadOnlyCollection Variables, IReadOnlyCollection Methods)> SplitVariableDeclarationsAsync(VBSyntax.VariableDeclaratorSyntax v, bool preferExplicitType = false) + { + return await CommonConversions.SplitVariableDeclarationsAsync(v, _localsToInlineInLoop, preferExplicitType); + } + + private async Task<(ExpressionSyntax, ExpressionSyntax)> ConvertConditionAndStepClauseAsync(VBSyntax.ForStatementSyntax stmt, ExpressionSyntax id, ExpressionSyntax csToValue, ITypeSymbol controlVarType) + { + var vbStepValue = stmt.StepClause?.StepValue; + var csStepValue = await (stmt.StepClause?.StepValue).AcceptAsync(_expressionVisitor); + // For an enum, you need to add on an integer for example: + var forceStepType = controlVarType is INamedTypeSymbol nt && nt.IsEnumType() ? nt.EnumUnderlyingType : controlVarType; + csStepValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbStepValue, csStepValue?.SkipIntoParens(), forceTargetType: forceStepType); + + var nonNegativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, id, csToValue); + var negativeCondition = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, id, csToValue); + if (csStepValue == null) { + return (nonNegativeCondition, SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, id)); + } + + ExpressionSyntax csCondition; + ExpressionSyntax csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.AddAssignmentExpression, id, csStepValue); + var vbStepConstValue = _semanticModel.GetConstantValue(vbStepValue); + var constValue = !vbStepConstValue.HasValue ? null : (dynamic)vbStepConstValue.Value; + if (constValue == null) { + var ifStepNonNegative = SyntaxFactory.BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, csStepValue, CommonConversions.Literal(0)); + csCondition = SyntaxFactory.ConditionalExpression(ifStepNonNegative, nonNegativeCondition, negativeCondition); + } else if (constValue < 0) { + csCondition = negativeCondition; + if (csStepValue is PrefixUnaryExpressionSyntax pues && pues.OperatorToken.IsKind(SyntaxKind.MinusToken)) { + csStep = SyntaxFactory.AssignmentExpression(SyntaxKind.SubtractAssignmentExpression, id, pues.Operand); + } + } else { + csCondition = nonNegativeCondition; + } + + return (csCondition, csStep); + } + + public override async Task> VisitForEachBlock(VBSyntax.ForEachBlockSyntax node) + { + var stmt = node.ForEachStatement; + + TypeSyntax type; + SyntaxToken id; + List statements = new List(); + if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax vds) { + var declaration = (await SplitVariableDeclarationsAsync(vds)).Variables.Single().Decl; + type = declaration.Type; + id = declaration.Variables.Single().Identifier; + } else if (_semanticModel.GetSymbolInfo(stmt.ControlVariable).Symbol is { } varSymbol) { + var variableType = varSymbol.GetSymbolType(); + var explicitCastWouldHaveNoEffect = variableType?.SpecialType == SpecialType.System_Object || _semanticModel.GetTypeInfo(stmt.Expression).ConvertedType.IsEnumerableOfExactType(variableType); + type = CommonConversions.GetTypeSyntax(varSymbol.GetSymbolType(), explicitCastWouldHaveNoEffect); + var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); + if (_localsToInlineInLoop.Contains(varSymbol, SymbolEqualityComparer.IncludeNullability) && v is IdentifierNameSyntax vId) { + id = vId.Identifier; + } else { + id = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "current" + varSymbol.Name.ToPascalCase())); + statements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, v, ValidSyntaxFactory.IdentifierName(id)))); + } + } else { + var v = await stmt.ControlVariable.AcceptAsync(_expressionVisitor); + id = v.Identifier; + type = ValidSyntaxFactory.VarType; + } + + var block = SyntaxFactory.Block(statements.Concat(await ConvertStatementsAsync(node.Statements))); + var csExpression = await stmt.Expression.AcceptAsync(_expressionVisitor); + return SingleStatement(SyntaxFactory.ForEachStatement( + type, + id, + CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(stmt.Expression, csExpression), + block.UnpackNonNestedBlock() + )); + } + + public override async Task> VisitLabelStatement(VBSyntax.LabelStatementSyntax node) + { + return SingleStatement(SyntaxFactory.LabeledStatement(CommonConversions.CsEscapedIdentifier(node.LabelToken.Text), SyntaxFactory.EmptyStatement())); + } + + public override async Task> VisitGoToStatement(VBSyntax.GoToStatementSyntax node) + { + return SingleStatement(SyntaxFactory.GotoStatement(SyntaxKind.GotoStatement, + ValidSyntaxFactory.IdentifierName((node.Label.LabelToken.Text)))); + } + + public override async Task> VisitSelectBlock(VBSyntax.SelectBlockSyntax node) + { + var vbExpr = node.SelectStatement.Expression; + var vbEquality = CommonConversions.VisualBasicEqualityComparison; + + var csSwitchExpr = await vbExpr.AcceptAsync(_expressionVisitor); + csSwitchExpr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csSwitchExpr); + var switchExprTypeInfo = _semanticModel.GetTypeInfo(vbExpr); + var isObjectComparison = switchExprTypeInfo.ConvertedType.SpecialType == SpecialType.System_Object; + var isStringComparison = !isObjectComparison && switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String || switchExprTypeInfo.ConvertedType?.IsArrayOf(SpecialType.System_Char) == true; + var caseInsensitiveStringComparison = vbEquality.OptionCompareTextCaseInsensitive && + isStringComparison; + if (isStringComparison) { + csSwitchExpr = vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo); + } + + var usedConstantValues = new HashSet(); + var sections = new List(); + foreach (var block in node.CaseBlocks) { + var labels = new List(); + foreach (var c in block.CaseStatement.Cases) { + if (c is VBSyntax.SimpleCaseClauseSyntax s) { + var originalExpressionSyntax = await s.Value.AcceptAsync(_expressionVisitor); + var caseTypeInfo = _semanticModel.GetTypeInfo(s.Value); + var typeConversionKind = CommonConversions.TypeConversionAnalyzer.AnalyzeConversion(s.Value); + var correctTypeExpressionSyntax = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(s.Value, originalExpressionSyntax, typeConversionKind, true, true); + var constantValue = _semanticModel.GetConstantValue(s.Value); + var notAlreadyUsed = !constantValue.HasValue || usedConstantValues.Add(constantValue.Value); + + // Pass both halves in case we can optimize away the check based on the switch expr + var wrapForStringComparison = isStringComparison && (caseInsensitiveStringComparison || + vbEquality.VbCoerceToNonNullString(vbExpr, csSwitchExpr, switchExprTypeInfo, true, s.Value, originalExpressionSyntax, caseTypeInfo, false).rhs != originalExpressionSyntax); + + // CSharp requires an explicit cast from the base type (e.g. int) in most cases switching on an enum + var isBooleanCase = caseTypeInfo.Type?.SpecialType == SpecialType.System_Boolean; + bool enumRelated = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) || IsEnumOrNullableEnum(caseTypeInfo.Type); + bool convertingEnum = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) ^ IsEnumOrNullableEnum(caseTypeInfo.Type); + var csExpressionToUse = !isObjectComparison && !isBooleanCase && (convertingEnum || !enumRelated && correctTypeExpressionSyntax.IsConst) + ? correctTypeExpressionSyntax.Expr + : originalExpressionSyntax; + + var caseSwitchLabelSyntax = !isObjectComparison && !wrapForStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed + ? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(csExpressionToUse) + : WrapInCasePatternSwitchLabelSyntax(node, s.Value, csExpressionToUse, isBooleanCase); + labels.Add(caseSwitchLabelSyntax); + } else if (c is VBSyntax.ElseCaseClauseSyntax) { + labels.Add(SyntaxFactory.DefaultSwitchLabel()); + } else if (c is VBSyntax.RelationalCaseClauseSyntax relational) { + + var operatorKind = VBasic.VisualBasicExtensions.Kind(relational); + var csRelationalValue = await relational.Value.AcceptAsync(_expressionVisitor); + CasePatternSwitchLabelSyntax caseSwitchLabelSyntax; + if (isObjectComparison) { + caseSwitchLabelSyntax = WrapInCasePatternSwitchLabelSyntax(node, relational.Value, csRelationalValue, false, operatorKind); + } + else { + var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); + ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName); + csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue); + var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(), csLeft, csRelationalValue); + caseSwitchLabelSyntax = VarWhen(varName, binaryExp); + } + labels.Add(caseSwitchLabelSyntax); + } else if (c is VBSyntax.RangeCaseClauseSyntax range) { + var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); + ExpressionSyntax csCaseVar = ValidSyntaxFactory.IdentifierName(varName); + var lowerBound = await range.LowerBound.AcceptAsync(_expressionVisitor); + ExpressionSyntax lowerBoundCheck; + if (isObjectComparison) { + var caseTypeInfo = _semanticModel.GetTypeInfo(range.LowerBound); + lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual); + } else { + lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound); + lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar); + } + var upperBound = await range.UpperBound.AcceptAsync(_expressionVisitor); + ExpressionSyntax upperBoundCheck; + if (isObjectComparison) { + var caseTypeInfo = _semanticModel.GetTypeInfo(range.UpperBound); + upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual); + } else { + upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound); + upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound); + } + var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck); + labels.Add(VarWhen(varName, withinBounds)); + } else { + throw new NotSupportedException(c.Kind().ToString()); + } + } + + var csBlockStatements = (await ConvertStatementsAsync(block.Statements)).ToList(); + if (!DefinitelyExits(csBlockStatements.LastOrDefault())) { + csBlockStatements.Add(SyntaxFactory.BreakStatement()); + } + var list = SingleStatement(SyntaxFactory.Block(csBlockStatements)); + sections.Add(SyntaxFactory.SwitchSection(SyntaxFactory.List(labels), list)); + } + + var switchStatementSyntax = ValidSyntaxFactory.SwitchStatement(csSwitchExpr, sections); + return SingleStatement(switchStatementSyntax); + } + + private static bool DefinitelyExits(StatementSyntax statement) + { + if (statement == null) { + return false; + } + + StatementSyntax GetLastStatement(StatementSyntax node) => node is BlockSyntax block ? block.Statements.LastOrDefault() : node; + bool IsExitStatement(StatementSyntax node) + { + node = GetLastStatement(node); + return node != null && node.IsKind(SyntaxKind.ReturnStatement, SyntaxKind.BreakStatement, SyntaxKind.ThrowStatement, SyntaxKind.ContinueStatement); + } + + if (IsExitStatement(statement)) { + return true; + } + + if (statement is not IfStatementSyntax ifStatement) { + return false; + } + + while(ifStatement.Else != null) { + if (!IsExitStatement(ifStatement.Statement)) { + return false; + } + + if (ifStatement.Else.Statement is IfStatementSyntax x) { + ifStatement = x; + } else { + return IsExitStatement(ifStatement.Else.Statement); + } + } + + return false; + } + + private static bool IsEnumOrNullableEnum(ITypeSymbol convertedType) => + convertedType?.IsEnumType() == true || convertedType?.GetNullableUnderlyingType()?.IsEnumType() == true; + + private static CasePatternSwitchLabelSyntax VarWhen(SyntaxToken varName, ExpressionSyntax binaryExp) + { + var patternMatch = ValidSyntaxFactory.VarPattern(varName); + return SyntaxFactory.CasePatternSwitchLabel(patternMatch, + SyntaxFactory.WhenClause(binaryExp), SyntaxFactory.Token(SyntaxKind.ColonToken)); + } + + private async Task<(ExpressionSyntax Reusable, SyntaxList Statements, ExpressionSyntax SingleUse)> GetExpressionWithoutSideEffectsAsync(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase) + { + var expr = await vbExpr.AcceptAsync(_expressionVisitor); + expr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, expr); + SyntaxList stmts = SyntaxFactory.List(); + ExpressionSyntax exprWithoutSideEffects; + ExpressionSyntax reusableExprWithoutSideEffects; + if (IsReusableReadOnlyLocalKind(_semanticModel.GetSymbolInfo(vbExpr).Symbol) || await CanEvaluateMultipleTimesAsync(vbExpr)) { + exprWithoutSideEffects = expr; + reusableExprWithoutSideEffects = expr.WithoutSourceMapping(); + } else { + TypeSyntax forceType = null; + if (_semanticModel.GetOperation(vbExpr.SkipIntoParens()).IsAssignableExpression()) { + forceType = SyntaxFactory.RefType(ValidSyntaxFactory.VarType); + expr = SyntaxFactory.RefExpression(expr); + } + + var (stmt, id) = CreateLocalVariableWithUniqueName(vbExpr, variableNameBase, expr, forceType); + stmts = stmts.Add(stmt); + reusableExprWithoutSideEffects = exprWithoutSideEffects = id; + } + + return (reusableExprWithoutSideEffects, stmts, exprWithoutSideEffects); + } + + private static bool IsReusableReadOnlyLocalKind(ISymbol symbol) => symbol is ILocalSymbol ls && (VBasic.VisualBasicExtensions.IsForEach(ls) || ls.IsUsing); + + private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VBSyntax.ExpressionSyntax vbExpr, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) + { + var contextNode = vbExpr.GetAncestor() ?? (VBasic.VisualBasicSyntaxNode) vbExpr.Parent; + return CreateLocalVariableWithUniqueName(contextNode, variableNameBase, expr, forceType); + } + + private (StatementSyntax Declaration, IdentifierNameSyntax Reference) CreateLocalVariableWithUniqueName(VisualBasicSyntaxNode contextNode, string variableNameBase, ExpressionSyntax expr, TypeSyntax forceType = null) + { + var varName = GetUniqueVariableNameInScope(contextNode, variableNameBase); + var stmt = CommonConversions.CreateLocalVariableDeclarationAndAssignment(varName, expr, forceType); + return (stmt, ValidSyntaxFactory.IdentifierName(varName)); + } + + private async Task CanEvaluateMultipleTimesAsync(VBSyntax.ExpressionSyntax vbExpr) + { + return _semanticModel.GetConstantValue(vbExpr).HasValue || vbExpr.IsKind(VBasic.SyntaxKind.MeExpression) || vbExpr.SkipIntoParens() is VBSyntax.NameSyntax ns && await IsNeverMutatedAsync(ns); + } + + private async Task IsNeverMutatedAsync(VBSyntax.NameSyntax ns) + { + var allowedLocation = Location.Create(ns.SyntaxTree, TextSpan.FromBounds(ns.GetAncestor().SpanStart, ns.Span.End)); + var symbol = _semanticModel.GetSymbolInfo(ns).Symbol; + //Perf optimization: Looking across the whole solution is expensive, so assume non-local symbols are written somewhere + return symbol.MatchesKind(SymbolKind.Parameter, SymbolKind.Local) && await CommonConversions.Document.Project.Solution.IsNeverWrittenAsync(symbol, allowedLocation); + } + + private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool treatAsBoolean = false, VBasic.SyntaxKind caseClauseKind = VBasic.SyntaxKind.CaseEqualsClause) + { + var typeInfo = _semanticModel.GetTypeInfo(node.SelectStatement.Expression); + + DeclarationPatternSyntax patternMatch; + if (typeInfo.ConvertedType.SpecialType == SpecialType.System_Boolean || treatAsBoolean) { + patternMatch = SyntaxFactory.DeclarationPattern( + SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), + SyntaxFactory.DiscardDesignation()); + } else { + var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); + patternMatch = ValidSyntaxFactory.VarPattern(varName); + ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName), csRight = expression; + var caseTypeInfo = _semanticModel.GetTypeInfo(vbCase); + expression = ComparisonAdjustedForStringComparison(node, vbCase, typeInfo, csLeft, csRight, caseTypeInfo, GetComparisonKind(caseClauseKind)); + } + + var colonToken = SyntaxFactory.Token(SyntaxKind.ColonToken); + return SyntaxFactory.CasePatternSwitchLabel(patternMatch, SyntaxFactory.WhenClause(expression), colonToken); + } + + private ComparisonKind GetComparisonKind(VBasic.SyntaxKind caseClauseKind) => caseClauseKind switch { + VBasic.SyntaxKind.CaseLessThanClause => ComparisonKind.LessThan, + VBasic.SyntaxKind.CaseLessThanOrEqualClause => ComparisonKind.LessThanOrEqual, + VBasic.SyntaxKind.CaseEqualsClause => ComparisonKind.Equals, + VBasic.SyntaxKind.CaseNotEqualsClause => ComparisonKind.NotEquals, + VBasic.SyntaxKind.CaseGreaterThanOrEqualClause => ComparisonKind.GreaterThanOrEqual, + VBasic.SyntaxKind.CaseGreaterThanClause => ComparisonKind.GreaterThan, + _ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null) + }; + + private ExpressionSyntax ComparisonAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo, ComparisonKind comparisonKind) + { + var vbEquality = CommonConversions.VisualBasicEqualityComparison; + switch (_visualBasicEqualityComparison.GetObjectEqualityType(lhsTypeInfo, rhsTypeInfo)) { + case VisualBasicEqualityComparison.RequiredType.Object: + return vbEquality.GetFullExpressionForVbObjectComparison(csLeft, csRight, comparisonKind); + case VisualBasicEqualityComparison.RequiredType.StringOnly: + // We know lhs isn't null, because we always coalesce it in the switch expression + (csLeft, csRight) = vbEquality + .AdjustForVbStringComparison(node.SelectStatement.Expression, csLeft, lhsTypeInfo, true, vbCase, csRight, rhsTypeInfo, false); + break; + } + return SyntaxFactory.BinaryExpression(GetSyntaxKind(comparisonKind), csLeft, csRight); + } + + private CS.SyntaxKind GetSyntaxKind(ComparisonKind comparisonKind) => comparisonKind switch { + ComparisonKind.LessThan => CS.SyntaxKind.LessThanExpression, + ComparisonKind.LessThanOrEqual => CS.SyntaxKind.LessThanOrEqualExpression, + ComparisonKind.Equals => CS.SyntaxKind.EqualsExpression, + ComparisonKind.NotEquals => CS.SyntaxKind.NotEqualsExpression, + ComparisonKind.GreaterThanOrEqual => CS.SyntaxKind.GreaterThanOrEqualExpression, + ComparisonKind.GreaterThan => CS.SyntaxKind.GreaterThanExpression, + _ => throw new ArgumentOutOfRangeException(nameof(comparisonKind), comparisonKind, null) + }; + + public override async Task> VisitWithBlock(VBSyntax.WithBlockSyntax node) + { + var (lhsExpression, prefixDeclarations, _) = await GetExpressionWithoutSideEffectsAsync(node.WithStatement.Expression, "withBlock"); + + _withBlockLhs.Push(lhsExpression); + try { + var statements = await ConvertStatementsAsync(node.Statements); + + var statementSyntaxs = SyntaxFactory.List(prefixDeclarations.Concat(statements)); + return prefixDeclarations.Any() + ? SingleStatement(SyntaxFactory.Block(statementSyntaxs)) + : statementSyntaxs; + } finally { + _withBlockLhs.Pop(); + } + } + + private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase) + { + return NameGenerator.CS.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase); + } + + public override async Task> VisitTryBlock(VBSyntax.TryBlockSyntax node) + { + var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + return SingleStatement( + SyntaxFactory.TryStatement( + block, + SyntaxFactory.List(await node.CatchBlocks.SelectAsync(async c => await ConvertCatchBlockAsync(c))), + await ConvertFinallyBlockAsync(node.FinallyBlock) + ) + ); + + async Task ConvertCatchBlockAsync(VBasic.Syntax.CatchBlockSyntax node) + { + var stmt = node.CatchStatement; + CatchDeclarationSyntax catcher = null; + if (stmt.AsClause != null) { + catcher = SyntaxFactory.CatchDeclaration( + ConvertTypeSyntax(stmt.AsClause.Type), + CommonConversions.ConvertIdentifier(stmt.IdentifierName.Identifier) + ); + } + + var filter = await ConvertCatchFilterClauseAsync(stmt.WhenClause); + var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); + return SyntaxFactory.CatchClause( + catcher, + filter, + SyntaxFactory.Block(stmts) + ); + } + + async Task ConvertCatchFilterClauseAsync(VBasic.Syntax.CatchFilterClauseSyntax node) + { + if (node == null) return null; + return SyntaxFactory.CatchFilterClause(await node.Filter.AcceptAsync(_expressionVisitor)); + } + + async Task ConvertFinallyBlockAsync(VBasic.Syntax.FinallyBlockSyntax node) + { + if (node == null) return null; + var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable)await s.Accept(CommentConvertingVisitor)); + return SyntaxFactory.FinallyClause(SyntaxFactory.Block(stmts)); + } + } + + private TypeSyntax ConvertTypeSyntax(VBSyntax.TypeSyntax vbType) + { + if (_semanticModel.GetSymbolInfo(vbType).Symbol is ITypeSymbol typeSymbol) + return CommonConversions.GetTypeSyntax(typeSymbol); + return SyntaxFactory.ParseTypeName(vbType.ToString()); + } + + public override async Task> VisitSyncLockBlock(VBSyntax.SyncLockBlockSyntax node) + { + return SingleStatement(SyntaxFactory.LockStatement( + await node.SyncLockStatement.Expression.AcceptAsync(_expressionVisitor), + SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() + )); + } + + public override async Task> VisitUsingBlock(VBSyntax.UsingBlockSyntax node) + { + var statementSyntax = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); + if (node.UsingStatement.Expression == null) { + StatementSyntax stmt = statementSyntax; + foreach (var v in node.UsingStatement.Variables.Reverse()) + foreach (var declaration in (await SplitVariableDeclarationsAsync(v)).Variables.Reverse()) + stmt = SyntaxFactory.UsingStatement(declaration.Decl, null, stmt); + return SingleStatement(stmt); + } + + var expr = await node.UsingStatement.Expression.AcceptAsync(_expressionVisitor); + var unpackPossiblyNestedBlock = statementSyntax.UnpackPossiblyNestedBlock(); // Allow reduced indentation for multiple usings in a row + return SingleStatement(SyntaxFactory.UsingStatement(null, expr, unpackPossiblyNestedBlock)); + } + + public override async Task> VisitWhileBlock(VBSyntax.WhileBlockSyntax node) + { + return SingleStatement(SyntaxFactory.WhileStatement( + await node.WhileStatement.Condition.AcceptAsync(_expressionVisitor), + SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock() + )); + } + + public override async Task> VisitDoLoopBlock(VBSyntax.DoLoopBlockSyntax node) + { + var statements = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)).UnpackNonNestedBlock(); + + if (node.DoStatement.WhileOrUntilClause != null) { + var stmt = node.DoStatement.WhileOrUntilClause; + if (stmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.WhileKeyword)) + return SingleStatement(SyntaxFactory.WhileStatement( + await stmt.Condition.AcceptAsync(_expressionVisitor), + statements + )); + return SingleStatement(SyntaxFactory.WhileStatement( + (await stmt.Condition.AcceptAsync(_expressionVisitor)).InvertCondition(), + statements + )); + } + + var whileOrUntilStmt = node.LoopStatement.WhileOrUntilClause; + ExpressionSyntax conditionExpression; + bool isUntilStmt; + if (whileOrUntilStmt != null) { + conditionExpression = await whileOrUntilStmt.Condition.AcceptAsync(_expressionVisitor); + isUntilStmt = whileOrUntilStmt.WhileOrUntilKeyword.IsKind(VBasic.SyntaxKind.UntilKeyword); + } else { + conditionExpression = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression); + isUntilStmt = false; + } + + if (isUntilStmt) { + conditionExpression = conditionExpression.InvertCondition(); + } + + return SingleStatement(SyntaxFactory.DoStatement(statements, conditionExpression)); + } + + public override async Task> VisitCallStatement(VBSyntax.CallStatementSyntax node) + { + return SingleStatement(await node.Invocation.AcceptAsync(_expressionVisitor)); + } + + private static SyntaxList SingleStatement(StatementSyntax statement) + { + return SyntaxFactory.SingletonList(statement); + } + + private static SyntaxList SingleStatement(ExpressionSyntax expression) + { + return SyntaxFactory.SingletonList(SyntaxFactory.ExpressionStatement(expression)); + } } \ No newline at end of file From 25b2ce9935a79ac475b357730267ce3964f20ef5 Mon Sep 17 00:00:00 2001 From: Graham Date: Wed, 11 Mar 2026 00:35:25 +0000 Subject: [PATCH 4/5] Single if statement --- .../MethodBodyExecutableStatementVisitor.cs | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 7271b682..bc48a7dc 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -270,31 +270,20 @@ _methodNode is VBSyntax.MethodBlockSyntax mb && if (setMethodName != null) { if (lhs is InvocationExpressionSyntax ies) { ExpressionSyntax exprToReplace = ies.Expression; - if (exprToReplace is MemberAccessExpressionSyntax {Name: IdentifierNameSyntax idn} maes) { - var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn); - exprToReplace = maes.WithName(newName); - - if (maes.Expression is ThisExpressionSyntax) { - var skipParens = node.Left.SkipIntoParens(); - var isNonSelfQualified = skipParens is VBSyntax.MemberAccessExpressionSyntax { - Expression: not VBSyntax.MeExpressionSyntax - and not VBSyntax.MyClassExpressionSyntax + 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) }; - if (!isNonSelfQualified) { - exprToReplace = newName.WithTriviaFrom(maes); - } - } - } else if (exprToReplace is IdentifierNameSyntax idn2) { - var newName = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(idn2); - exprToReplace = newName; + exprToReplace = stripThis ? newName.WithTriviaFrom(maes) : maes.WithName(newName); + } else if (exprToReplace is IdentifierNameSyntax) { + exprToReplace = SyntaxFactory.IdentifierName(setMethodName).WithTriviaFrom(exprToReplace); } - var newArg = SyntaxFactory.Argument(rhs); - var newArgs = ies.ArgumentList.Arguments.Add(newArg); - var newArgList = ies.ArgumentList.WithArguments(newArgs); + var newArgList = ies.ArgumentList.AddArguments(SyntaxFactory.Argument(rhs)); var newLhs = ies.WithExpression(exprToReplace).WithArgumentList(newArgList); - var invokeAssignment = SyntaxFactory.ExpressionStatement(newLhs); var postAssign = GetPostAssignmentStatements(node); - return postAssign.Insert(0, invokeAssignment); + return postAssign.Insert(0, SyntaxFactory.ExpressionStatement(newLhs)); } return SingleStatement(lhs); } From d0381686d2331e5a57cbd239a0dffc7ad7ac93d1 Mon Sep 17 00:00:00 2001 From: Graham Date: Wed, 11 Mar 2026 00:47:42 +0000 Subject: [PATCH 5/5] Fix newlines in assertion --- Tests/CSharp/StatementTests/MethodStatementTests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index e5de0265..bc20f218 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1756,7 +1756,6 @@ public partial class TestClass { private int[] _items = new int[] { 1 }; public int get_Item(int index) - { return _items[index]; } @@ -1765,10 +1764,8 @@ 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]; } @@ -1777,7 +1774,6 @@ public void set_StrItem(int index, string value) _strItems[index] = value; } - public void AllAssignmentOperators() { set_Item(0, get_Item(0) + 2);