Skip to content

Commit ed09554

Browse files
Convert foreach statement with tuple deconstruction - fixes #121
1 parent 26b8ce8 commit ed09554

5 files changed

Lines changed: 117 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Change Log
22
All notable changes to the code converter will be documented here.
33

4+
# 6.4.0 TBC
5+
6+
### C# -> VB
7+
* Tuples now converted
8+
49
# 6.3.0 05/02/2019
510
* VS 2019 support
611
* Breaking API change: Most library API names and return types are now async

ICSharpCode.CodeConverter/VB/CommonConversions.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ private SyntaxList<StatementSyntax> InsertRequiredDeclarations(
7979
SyntaxList<StatementSyntax> convertedStatements, CSharpSyntaxNode originaNode)
8080
{
8181
var descendantNodes = originaNode.DescendantNodes().ToList();
82-
var declarationExpressions = descendantNodes.OfType<DeclarationExpressionSyntax>().ToList();
82+
var declarationExpressions = descendantNodes.OfType<DeclarationExpressionSyntax>()
83+
.Where(e => !e.Parent.IsKind(CSSyntaxKind.ForEachVariableStatement)) //Handled inline for tuple loop
84+
.ToList();
8385
var isPatternExpressions = descendantNodes.OfType<IsPatternExpressionSyntax>().ToList();
8486
if (declarationExpressions.Any() || isPatternExpressions.Any()) {
8587
convertedStatements = convertedStatements.Insert(0, ConvertToDeclarationStatement(declarationExpressions, isPatternExpressions));
@@ -91,9 +93,16 @@ private SyntaxList<StatementSyntax> InsertRequiredDeclarations(
9193
private StatementSyntax ConvertToDeclarationStatement(List<DeclarationExpressionSyntax> des,
9294
List<IsPatternExpressionSyntax> isPatternExpressions)
9395
{
94-
var declarators = SyntaxFactory.SeparatedList(des.Select(ConvertToVariableDeclarator)
95-
.Concat(isPatternExpressions.Select(ConvertToVariableDeclarator)));
96-
return SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.DimKeyword)), declarators);
96+
IEnumerable<VariableDeclaratorSyntax> variableDeclaratorSyntaxs = des.Select(ConvertToVariableDeclarator)
97+
.Concat(isPatternExpressions.Select(ConvertToVariableDeclarator));
98+
return CreateLocalDeclarationStatement(variableDeclaratorSyntaxs.ToArray());
99+
}
100+
101+
public static StatementSyntax CreateLocalDeclarationStatement(params VariableDeclaratorSyntax[] variableDeclarators)
102+
{
103+
var syntaxTokenList = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.DimKeyword));
104+
var declarators = SyntaxFactory.SeparatedList(variableDeclarators);
105+
return SyntaxFactory.LocalDeclarationStatement(syntaxTokenList, declarators);
97106
}
98107

99108
private VariableDeclaratorSyntax ConvertToVariableDeclarator(DeclarationExpressionSyntax des)
@@ -149,28 +158,28 @@ public AccessorBlockSyntax ConvertAccessor(AccessorDeclarationSyntax node, out b
149158
Microsoft.CodeAnalysis.VisualBasic.Syntax.ParameterSyntax valueParam;
150159

151160
switch (CSharpExtensions.Kind(node)) {
152-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.GetAccessorDeclaration:
161+
case CSSyntaxKind.GetAccessorDeclaration:
153162
blockKind = SyntaxKind.GetAccessorBlock;
154163
stmt = SyntaxFactory.GetAccessorStatement(attributes, modifiers, null);
155164
endStmt = SyntaxFactory.EndGetStatement();
156165
break;
157-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.SetAccessorDeclaration:
166+
case CSSyntaxKind.SetAccessorDeclaration:
158167
blockKind = SyntaxKind.SetAccessorBlock;
159168
valueParam = SyntaxFactory.Parameter(SyntaxFactory.ModifiedIdentifier("value"))
160169
.WithAsClause(SyntaxFactory.SimpleAsClause((TypeSyntax)parent.Type.Accept(_nodesVisitor)))
161170
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ByValKeyword)));
162171
stmt = SyntaxFactory.SetAccessorStatement(attributes, modifiers, SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(valueParam)));
163172
endStmt = SyntaxFactory.EndSetStatement();
164173
break;
165-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.AddAccessorDeclaration:
174+
case CSSyntaxKind.AddAccessorDeclaration:
166175
blockKind = SyntaxKind.AddHandlerAccessorBlock;
167176
valueParam = SyntaxFactory.Parameter(SyntaxFactory.ModifiedIdentifier("value"))
168177
.WithAsClause(SyntaxFactory.SimpleAsClause((TypeSyntax)parent.Type.Accept(_nodesVisitor)))
169178
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ByValKeyword)));
170179
stmt = SyntaxFactory.AddHandlerAccessorStatement(attributes, modifiers, SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(valueParam)));
171180
endStmt = SyntaxFactory.EndAddHandlerStatement();
172181
break;
173-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.RemoveAccessorDeclaration:
182+
case CSSyntaxKind.RemoveAccessorDeclaration:
174183
blockKind = SyntaxKind.RemoveHandlerAccessorBlock;
175184
valueParam = SyntaxFactory.Parameter(SyntaxFactory.ModifiedIdentifier("value"))
176185
.WithAsClause(SyntaxFactory.SimpleAsClause((TypeSyntax)parent.Type.Accept(_nodesVisitor)))
@@ -219,7 +228,7 @@ public LambdaExpressionSyntax ConvertLambdaExpression(AnonymousFunctionExpressio
219228
if (body is BlockSyntax block) {
220229
statements = ConvertStatements(block.Statements);
221230

222-
} else if (body.Kind() == Microsoft.CodeAnalysis.CSharp.SyntaxKind.ThrowExpression) {
231+
} else if (body.Kind() == CSSyntaxKind.ThrowExpression) {
223232
var csThrowExpression = (ThrowExpressionSyntax)body;
224233
var vbThrowExpression = (ExpressionSyntax)csThrowExpression.Expression.Accept(_nodesVisitor);
225234
var vbThrowStatement = SyntaxFactory.ThrowStatement(SyntaxFactory.Token(SyntaxKind.ThrowKeyword), vbThrowExpression);
@@ -266,7 +275,7 @@ public void ConvertBaseList(BaseTypeDeclarationSyntax type, List<InheritsStateme
266275
{
267276
TypeSyntax[] arr;
268277
switch (type.Kind()) {
269-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.ClassDeclaration:
278+
case CSSyntaxKind.ClassDeclaration:
270279
var classOrInterface = type.BaseList?.Types.FirstOrDefault()?.Type;
271280
if (classOrInterface == null) return;
272281
var classOrInterfaceSymbol = ModelExtensions.GetSymbolInfo(_semanticModel, classOrInterface).Symbol;
@@ -281,12 +290,12 @@ public void ConvertBaseList(BaseTypeDeclarationSyntax type, List<InheritsStateme
281290
implements.Add(SyntaxFactory.ImplementsStatement(arr));
282291
}
283292
break;
284-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.StructDeclaration:
293+
case CSSyntaxKind.StructDeclaration:
285294
arr = type.BaseList?.Types.Select(t => (TypeSyntax)t.Type.Accept(_nodesVisitor)).ToArray();
286295
if (arr?.Length > 0)
287296
implements.Add(SyntaxFactory.ImplementsStatement(arr));
288297
break;
289-
case Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterfaceDeclaration:
298+
case CSSyntaxKind.InterfaceDeclaration:
290299
arr = type.BaseList?.Types.Select(t => (TypeSyntax)t.Type.Accept(_nodesVisitor)).ToArray();
291300
if (arr?.Length > 0)
292301
inherits.Add(SyntaxFactory.InheritsStatement(arr));
@@ -320,7 +329,7 @@ private static bool IgnoreInContext(SyntaxToken m, TokenContext context)
320329
switch (context) {
321330
case TokenContext.InterfaceOrModule:
322331
case TokenContext.MemberInModule:
323-
return m.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.StaticKeyword);
332+
return m.IsKind(CSSyntaxKind.StaticKeyword);
324333
}
325334
return false;
326335
}
@@ -508,5 +517,18 @@ private static string ConvertNumericLiteralValueText(string valueText)
508517

509518
return valueText;
510519
}
520+
521+
public static string GetTupleName(ParenthesizedVariableDesignationSyntax node)
522+
{
523+
return String.Join("", node.Variables.Select((v, i) => {
524+
var sourceText1 = v.ToString();
525+
return i > 0 ? UppercaseFirstLetter(sourceText1) : sourceText1;
526+
}));
527+
}
528+
529+
private static string UppercaseFirstLetter(string sourceText)
530+
{
531+
return sourceText.Substring(0, 1).ToUpper() + sourceText.Substring(1);
532+
}
511533
}
512534
}

ICSharpCode.CodeConverter/VB/MethodBodyVisitor.cs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -387,22 +387,52 @@ bool ConvertForToSimpleForNext(CSS.ForStatementSyntax node, out StatementSyntax
387387
return true;
388388
}
389389

390+
public override SyntaxList<StatementSyntax> VisitForEachVariableStatement(CSS.ForEachVariableStatementSyntax node)
391+
{
392+
var loopVar = node.Variable.Accept(_nodesVisitor);
393+
var extraStatements = new List<StatementSyntax>();
394+
if (node.Variable is CSS.DeclarationExpressionSyntax des && des.Designation is CSS.ParenthesizedVariableDesignationSyntax pv) {
395+
var tupleName = CommonConversions.GetTupleName(pv);
396+
extraStatements.AddRange(pv.Variables.Select((v, i) => {
397+
var initializer = SyntaxFactory.EqualsValue(SyntaxFactory.SimpleMemberAccessExpression(
398+
SyntaxFactory.IdentifierName(tupleName),
399+
SyntaxFactory.IdentifierName("Item" + (i + 1).ToString())));
400+
VariableDeclaratorSyntax variableDeclaratorSyntax = SyntaxFactory.VariableDeclarator(
401+
SyntaxFactory.ModifiedIdentifier(SyntaxFactory.Identifier(v.ToString())))
402+
.WithInitializer(initializer);
403+
return CommonConversions.CreateLocalDeclarationStatement(variableDeclaratorSyntax);
404+
}));
405+
}
406+
return CreateForEachStatement(loopVar, node.Expression, node.Statement, extraStatements.ToArray());
407+
}
408+
390409
public override SyntaxList<StatementSyntax> VisitForEachStatement(CSS.ForEachStatementSyntax node)
391410
{
392411
VisualBasicSyntaxNode variable;
393-
if (node.Type.IsVar) {
412+
if (node.Type.IsVar)
413+
{
394414
variable = SyntaxFactory.IdentifierName(CommonConversions.ConvertIdentifier(node.Identifier));
395-
} else {
415+
}
416+
else
417+
{
396418
variable = SyntaxFactory.VariableDeclarator(
397-
SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ModifiedIdentifier(CommonConversions.ConvertIdentifier(node.Identifier))),
398-
SyntaxFactory.SimpleAsClause((TypeSyntax)node.Type.Accept(_nodesVisitor)),
419+
SyntaxFactory.SingletonSeparatedList(
420+
SyntaxFactory.ModifiedIdentifier(CommonConversions.ConvertIdentifier(node.Identifier))),
421+
SyntaxFactory.SimpleAsClause((TypeSyntax) node.Type.Accept(_nodesVisitor)),
399422
null
400423
);
401424
}
402-
var expression = (ExpressionSyntax)node.Expression.Accept(_nodesVisitor);
403-
var stmt = ConvertBlock(node.Statement);
425+
426+
return CreateForEachStatement(variable, node.Expression, node.Statement);
427+
}
428+
429+
private SyntaxList<StatementSyntax> CreateForEachStatement(VisualBasicSyntaxNode vbVariable,
430+
CSS.ExpressionSyntax csExpression, CSS.StatementSyntax csStatement, params StatementSyntax[] prefixExtraVbStatements)
431+
{
432+
var expression = (ExpressionSyntax) csExpression.Accept(_nodesVisitor);
433+
var stmt = ConvertBlock(csStatement, prefixExtraVbStatements);
404434
var block = SyntaxFactory.ForEachBlock(
405-
SyntaxFactory.ForEachStatement(variable, expression),
435+
SyntaxFactory.ForEachStatement(vbVariable, expression),
406436
stmt,
407437
SyntaxFactory.NextStatement()
408438
);
@@ -518,14 +548,14 @@ public static MultiLineIfBlockSyntax CreateBlock(SyntaxList<StatementSyntax> sta
518548
return ifBlock;
519549
}
520550

521-
SyntaxList<StatementSyntax> ConvertBlock(CSS.StatementSyntax node)
551+
SyntaxList<StatementSyntax> ConvertBlock(CSS.StatementSyntax node, params StatementSyntax[] prefixExtraVbStatements)
522552
{
523-
if (node is CSS.BlockSyntax) {
524-
var b = (CSS.BlockSyntax)node;
525-
return SyntaxFactory.List(b.Statements.Where(s => !(s is CSS.EmptyStatementSyntax)).SelectMany(s => s.Accept(CommentConvertingVisitor)));
553+
if (node is CSS.BlockSyntax b) {
554+
return SyntaxFactory.List(prefixExtraVbStatements.Concat(b.Statements.Where(s => !(s is CSS.EmptyStatementSyntax))
555+
.SelectMany(s => s.Accept(CommentConvertingVisitor))));
526556
}
527557
if (node is CSS.EmptyStatementSyntax) {
528-
return SyntaxFactory.List<StatementSyntax>();
558+
return SyntaxFactory.List(prefixExtraVbStatements);
529559
}
530560
return node.Accept(CommentConvertingVisitor);
531561
}

ICSharpCode.CodeConverter/VB/NodesVisitor.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using ICSharpCode.CodeConverter.Shared;
77
using ICSharpCode.CodeConverter.Util;
88
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Text;
910
using Microsoft.CodeAnalysis.VisualBasic;
1011
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
1112
using ArgumentListSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.ArgumentListSyntax;
@@ -656,6 +657,13 @@ public override VisualBasicSyntaxNode VisitTupleExpression(CSS.TupleExpressionSy
656657
return SyntaxFactory.TupleExpression(SyntaxFactory.SeparatedList(args));
657658
}
658659

660+
661+
662+
public override VisualBasicSyntaxNode VisitParenthesizedVariableDesignation(CSS.ParenthesizedVariableDesignationSyntax node)
663+
{
664+
return SyntaxFactory.IdentifierName(CommonConversions.GetTupleName(node));
665+
}
666+
659667
public override VisualBasicSyntaxNode VisitParameter(CSS.ParameterSyntax node)
660668
{
661669
var id = CommonConversions.ConvertIdentifier(node.Identifier);
@@ -911,11 +919,6 @@ public override VisualBasicSyntaxNode VisitInvocationExpression(CSS.InvocationEx
911919
);
912920
}
913921

914-
public override VisualBasicSyntaxNode VisitParenthesizedVariableDesignation(CSS.ParenthesizedVariableDesignationSyntax node)
915-
{
916-
return base.VisitParenthesizedVariableDesignation(node);
917-
}
918-
919922
private bool IsNameOfExpression(CSS.InvocationExpressionSyntax node)
920923
{
921924
return node.Expression is CSS.IdentifierNameSyntax methodIdentifier

Tests/VB/StatementTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,33 @@ End Sub
736736
End Class");
737737
}
738738

739+
[Fact]
740+
public void ForTupleDeconstruction()
741+
{
742+
TestConversionCSharpToVisualBasic(@"public class SolutionConverter
743+
{
744+
private static string ApplyReplacements(string originalText, IEnumerable<(string, string)> replacements)
745+
{
746+
foreach (var (oldValue, newValue) in replacements)
747+
{
748+
originalText = Regex.Replace(originalText, oldValue, newValue, RegexOptions.IgnoreCase);
749+
}
750+
751+
return originalText;
752+
}
753+
}", @"Public Class SolutionConverter
754+
Private Shared Function ApplyReplacements(ByVal originalText As String, ByVal replacements As IEnumerable(Of (String, String))) As String
755+
For Each oldValueNewValue In replacements
756+
Dim oldValue = oldValueNewValue.Item1
757+
Dim newValue = oldValueNewValue.Item2
758+
originalText = Regex.Replace(originalText, oldValue, newValue, RegexOptions.IgnoreCase)
759+
Next
760+
761+
Return originalText
762+
End Function
763+
End Class");
764+
}
765+
739766
[Fact]
740767
public void LabeledAndForStatement()
741768
{

0 commit comments

Comments
 (0)