Skip to content

Commit bc6ee61

Browse files
Neaten up case when variable is only used within loop
1 parent 325115e commit bc6ee61

File tree

10 files changed

+264
-121
lines changed

10 files changed

+264
-121
lines changed

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public CommonConversions(Document document, SemanticModel semanticModel,
5858
}
5959

6060
public async Task<(IReadOnlyCollection<(VariableDeclarationSyntax Decl, ITypeSymbol Type)> Variables, IReadOnlyCollection<CSharpSyntaxNode> Methods)> SplitVariableDeclarations(
61-
VariableDeclaratorSyntax declarator, bool preferExplicitType = false)
61+
VariableDeclaratorSyntax declarator, HashSet<ILocalSymbol> symbolsToSkip = null, bool preferExplicitType = false)
6262
{
6363
var vbInitValue = GetInitializerToConvert(declarator);
6464
var initializerOrMethodDecl = await vbInitValue.AcceptAsync(TriviaConvertingExpressionVisitor);
@@ -81,6 +81,7 @@ public CommonConversions(Document document, SemanticModel semanticModel,
8181
foreach (var name in declarator.Names) {
8282

8383
var declaredSymbol = _semanticModel.GetDeclaredSymbol(name);
84+
if (symbolsToSkip?.Contains(declaredSymbol) == true) continue;
8485
var declaredSymbolType = declaredSymbol.GetSymbolType();
8586
var equalsValueClauseSyntax = await ConvertEqualsValueClauseSyntax(declarator, name, vbInitValue, declaredSymbolType, declaredSymbol, initializerOrMethodDecl);
8687
var v = SyntaxFactory.VariableDeclarator(ConvertIdentifier(name.Identifier), null, equalsValueClauseSyntax);

CodeConverter/CSharp/DeclarationNodeVisitor.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ internal class DeclarationNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSh
4949
private string _topAncestorNamespace;
5050

5151
private CommonConversions CommonConversions { get; }
52-
private Func<VisualBasicSyntaxNode, bool, IdentifierNameSyntax, VisualBasicSyntaxVisitor<Task<SyntaxList<StatementSyntax>>>> _createMethodBodyVisitor { get; }
52+
private Func<VisualBasicSyntaxNode, bool, IdentifierNameSyntax, Task<VisualBasicSyntaxVisitor<Task<SyntaxList<StatementSyntax>>>>> _createMethodBodyVisitorAsync { get; }
5353

5454
public DeclarationNodeVisitor(Document document, Compilation compilation, SemanticModel semanticModel,
5555
CSharpCompilation csCompilation, SyntaxGenerator csSyntaxGenerator)
@@ -66,13 +66,13 @@ public DeclarationNodeVisitor(Document document, Compilation compilation, Semant
6666
_additionalInitializers = new AdditionalInitializers();
6767
var expressionNodeVisitor = new ExpressionNodeVisitor(semanticModel, _visualBasicEqualityComparison, _additionalLocals, csCompilation, _methodsWithHandles, CommonConversions, _extraUsingDirectives);
6868
_triviaConvertingExpressionVisitor = expressionNodeVisitor.TriviaConvertingExpressionVisitor;
69-
_createMethodBodyVisitor = expressionNodeVisitor.CreateMethodBodyVisitor;
69+
_createMethodBodyVisitorAsync = expressionNodeVisitor.CreateMethodBodyVisitor;
7070
CommonConversions.TriviaConvertingExpressionVisitor = _triviaConvertingExpressionVisitor;
7171
}
7272

73-
public VBasic.VisualBasicSyntaxVisitor<Task<SyntaxList<StatementSyntax>>> CreateMethodBodyVisitor(VBasic.VisualBasicSyntaxNode node, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null)
73+
public async Task<VBasic.VisualBasicSyntaxVisitor<Task<SyntaxList<StatementSyntax>>>> CreateMethodBodyVisitor(VBasic.VisualBasicSyntaxNode node, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null)
7474
{
75-
return _createMethodBodyVisitor(node, isIterator, csReturnVariable);
75+
return await _createMethodBodyVisitorAsync(node, isIterator, csReturnVariable);
7676
}
7777

7878
public override async Task<CSharpSyntaxNode> DefaultVisit(SyntaxNode node)
@@ -809,7 +809,7 @@ public override async Task<CSharpSyntaxNode> VisitAccessorBlock(VBSyntax.Accesso
809809
Microsoft.CodeAnalysis.CSharp.SyntaxKind blockKind;
810810
bool isIterator = node.IsIterator();
811811
var csReturnVariableOrNull = CommonConversions.GetRetVariableNameOrNull(node);
812-
var convertedStatements = await ConvertStatements(node.Statements, CreateMethodBodyVisitor(node, isIterator));
812+
var convertedStatements = await ConvertStatements(node.Statements, await CreateMethodBodyVisitor(node, isIterator));
813813
var body = WithImplicitReturnStatements(node, convertedStatements, csReturnVariableOrNull);
814814
var attributes = await CommonConversions.ConvertAttributes(node.AccessorStatement.AttributeLists);
815815
var modifiers = CommonConversions.ConvertModifiers(node, node.AccessorStatement.Modifiers, TokenContext.Local);
@@ -889,7 +889,7 @@ public override async Task<CSharpSyntaxNode> VisitMethodBlock(VBSyntax.MethodBlo
889889
return methodBlock;
890890
}
891891
var csReturnVariableOrNull = CommonConversions.GetRetVariableNameOrNull(node);
892-
var visualBasicSyntaxVisitor = CreateMethodBodyVisitor(node, node.IsIterator(), csReturnVariableOrNull);
892+
var visualBasicSyntaxVisitor = await CreateMethodBodyVisitor(node, node.IsIterator(), csReturnVariableOrNull);
893893
var convertedStatements = await ConvertStatements(node.Statements, visualBasicSyntaxVisitor);
894894

895895
if (node.SubOrFunctionStatement.Identifier.Text == "InitializeComponent" && node.SubOrFunctionStatement.IsKind(VBasic.SyntaxKind.SubStatement) && declaredSymbol.ContainingType.IsDesignerGeneratedTypeWithInitializeComponent(_compilation)) {
@@ -1140,7 +1140,7 @@ public override async Task<CSharpSyntaxNode> VisitOperatorStatement(VBSyntax.Ope
11401140
var attributeList = SyntaxFactory.List(attributes);
11411141
var returnType = (TypeSyntax) await (node.AsClause?.Type).AcceptAsync(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.PredefinedType(SyntaxFactory.Token(Microsoft.CodeAnalysis.CSharp.SyntaxKind.VoidKeyword));
11421142
var parameterList = (ParameterListSyntax) await node.ParameterList.AcceptAsync(_triviaConvertingExpressionVisitor);
1143-
var methodBodyVisitor = CreateMethodBodyVisitor(node);
1143+
var methodBodyVisitor = await CreateMethodBodyVisitor(node);
11441144
var body = SyntaxFactory.Block(await containingBlock.Statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor)));
11451145
var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, GetMemberContext(node));
11461146

@@ -1181,7 +1181,7 @@ public override async Task<CSharpSyntaxNode> VisitConstructorBlock(VBSyntax.Cons
11811181
ctorCall = null;
11821182
}
11831183

1184-
var methodBodyVisitor = CreateMethodBodyVisitor(node);
1184+
var methodBodyVisitor = await CreateMethodBodyVisitor(node);
11851185
return SyntaxFactory.ConstructorDeclaration(
11861186
SyntaxFactory.List(attributes),
11871187
modifiers,

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public override async Task<CSharpSyntaxNode> VisitCatchBlock(VBasic.Syntax.Catch
179179
}
180180

181181
var filter = (CatchFilterClauseSyntax) await stmt.WhenClause.AcceptAsync(TriviaConvertingExpressionVisitor);
182-
var methodBodyVisitor = CreateMethodBodyVisitor(node); //Probably should actually be using the existing method body visitor in order to get variable name generation correct
182+
var methodBodyVisitor = await CreateMethodBodyVisitor(node); //Probably should actually be using the existing method body visitor in order to get variable name generation correct
183183
var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor));
184184
return SyntaxFactory.CatchClause(
185185
catcher,
@@ -202,7 +202,7 @@ public override async Task<CSharpSyntaxNode> VisitCatchFilterClause(VBasic.Synta
202202

203203
public override async Task<CSharpSyntaxNode> VisitFinallyBlock(VBasic.Syntax.FinallyBlockSyntax node)
204204
{
205-
var methodBodyVisitor = CreateMethodBodyVisitor(node); //Probably should actually be using the existing method body visitor in order to get variable name generation correct
205+
var methodBodyVisitor = await CreateMethodBodyVisitor(node); //Probably should actually be using the existing method body visitor in order to get variable name generation correct
206206
var stmts = await node.Statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor));
207207
return SyntaxFactory.FinallyClause(SyntaxFactory.Block(stmts));
208208
}
@@ -864,7 +864,7 @@ public override async Task<CSharpSyntaxNode> VisitSingleLineLambdaExpression(VBa
864864
{
865865
IReadOnlyCollection<StatementSyntax> convertedStatements;
866866
if (node.Body is VBasic.Syntax.StatementSyntax statement) {
867-
convertedStatements = await statement.Accept(CreateMethodBodyVisitor(node));
867+
convertedStatements = await statement.Accept(await CreateMethodBodyVisitor(node));
868868
} else {
869869
var csNode = await node.Body.AcceptAsync(TriviaConvertingExpressionVisitor);
870870
convertedStatements = new[] { SyntaxFactory.ExpressionStatement((ExpressionSyntax)csNode)};
@@ -875,7 +875,7 @@ public override async Task<CSharpSyntaxNode> VisitSingleLineLambdaExpression(VBa
875875

876876
public override async Task<CSharpSyntaxNode> VisitMultiLineLambdaExpression(VBasic.Syntax.MultiLineLambdaExpressionSyntax node)
877877
{
878-
var methodBodyVisitor = CreateMethodBodyVisitor(node);
878+
var methodBodyVisitor = await CreateMethodBodyVisitor(node);
879879
var body = await node.Statements.SelectManyAsync(async s => (IEnumerable<StatementSyntax>) await s.Accept(methodBodyVisitor));
880880
var param = (ParameterListSyntax) await node.SubOrFunctionHeader.ParameterList.AcceptAsync(TriviaConvertingExpressionVisitor);
881881
return await _lambdaConverter.Convert(node, param, body.ToList());
@@ -1170,12 +1170,9 @@ public override async Task<CSharpSyntaxNode> VisitTypeArgumentList(VBasic.Syntax
11701170
return SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(args));
11711171
}
11721172

1173-
public VBasic.VisualBasicSyntaxVisitor<Task<SyntaxList<StatementSyntax>>> CreateMethodBodyVisitor(VBasic.VisualBasicSyntaxNode node, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null)
1173+
public async Task<VBasic.VisualBasicSyntaxVisitor<Task<SyntaxList<StatementSyntax>>>> CreateMethodBodyVisitor(VBasic.VisualBasicSyntaxNode node, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null)
11741174
{
1175-
var methodBodyVisitor = new MethodBodyExecutableStatementVisitor(node, _semanticModel, TriviaConvertingExpressionVisitor, CommonConversions, _withBlockLhs, _extraUsingDirectives, _additionalLocals, _methodsWithHandles) {
1176-
IsIterator = isIterator,
1177-
ReturnVariable = csReturnVariable,
1178-
};
1175+
var methodBodyVisitor = await MethodBodyExecutableStatementVisitor.CreateAsync(node, _semanticModel, TriviaConvertingExpressionVisitor, CommonConversions, _withBlockLhs, _extraUsingDirectives, _additionalLocals, _methodsWithHandles, isIterator, csReturnVariable);
11791176
return methodBodyVisitor.CommentConvertingVisitor;
11801177
}
11811178

CodeConverter/CSharp/Extensions.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
6+
namespace ICSharpCode.CodeConverter.CSharp
7+
{
8+
internal static class Extensions
9+
{
10+
/// <summary>
11+
/// Returns the single statement in a block if it has no nested statements.
12+
/// If it has nested statements, and the surrounding block was removed, it could be ambiguous,
13+
/// e.g. if (...) { if (...) return null; } else return "";
14+
/// Unbundling the middle if statement would bind the else to it, rather than the outer if statement
15+
/// </summary>
16+
public static StatementSyntax UnpackNonNestedBlock(this BlockSyntax block)
17+
{
18+
return block.Statements.Count == 1 && !block.ContainsNestedStatements() ? block.Statements[0] : block;
19+
}
20+
21+
/// <summary>
22+
/// Returns the single statement in a block
23+
/// </summary>
24+
public static bool TryUnpackSingleStatement(this IReadOnlyCollection<StatementSyntax> statements, out StatementSyntax singleStatement)
25+
{
26+
singleStatement = statements.Count == 1 ? statements.Single() : null;
27+
if (singleStatement is BlockSyntax block && TryUnpackSingleStatement(block.Statements, out var s)) {
28+
singleStatement = s;
29+
}
30+
31+
return singleStatement != null;
32+
}
33+
34+
/// <summary>
35+
/// Returns the single expression in a statement
36+
/// </summary>
37+
public static bool TryUnpackSingleExpressionFromStatement(this StatementSyntax statement, out ExpressionSyntax singleExpression)
38+
{
39+
switch(statement){
40+
case BlockSyntax blockSyntax:
41+
singleExpression = null;
42+
return TryUnpackSingleStatement(blockSyntax.Statements, out var nestedStmt) &&
43+
TryUnpackSingleExpressionFromStatement(nestedStmt, out singleExpression);
44+
case ExpressionStatementSyntax expressionStatementSyntax:
45+
singleExpression = expressionStatementSyntax.Expression;
46+
return singleExpression != null;
47+
case ReturnStatementSyntax returnStatementSyntax:
48+
singleExpression = returnStatementSyntax.Expression;
49+
return singleExpression != null;
50+
default:
51+
singleExpression = null;
52+
return false;
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Only use this over <see cref="UnpackNonNestedBlock"/> in special cases where it will display more neatly and where you're sure nested statements don't introduce ambiguity
58+
/// </summary>
59+
public static StatementSyntax UnpackPossiblyNestedBlock(this BlockSyntax block)
60+
{
61+
SyntaxList<StatementSyntax> statementSyntaxs = block.Statements;
62+
return statementSyntaxs.Count == 1 ? statementSyntaxs[0] : block;
63+
}
64+
65+
private static bool ContainsNestedStatements(this BlockSyntax block)
66+
{
67+
return block.Statements.Any(HasDescendantCSharpStatement);
68+
}
69+
70+
private static bool HasDescendantCSharpStatement(this StatementSyntax c)
71+
{
72+
return c.DescendantNodes().OfType<StatementSyntax>().Any();
73+
}
74+
}
75+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using ICSharpCode.CodeConverter.Shared;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;
8+
using ICSharpCode.CodeConverter.Util.FromRoslyn;
9+
using Microsoft.CodeAnalysis.VisualBasic;
10+
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
11+
12+
namespace ICSharpCode.CodeConverter.CSharp
13+
{
14+
internal static class LocalVariableAnalyzer
15+
{
16+
public static async Task<HashSet<ILocalSymbol>> GetDescendantsToInlineInLoopAsync(this Solution solution, SemanticModel semanticModel, VisualBasicSyntaxNode methodNode)
17+
{
18+
var forEachControlVariables = await methodNode.DescendantNodes().OfType<VBSyntax.ForEachBlockSyntax>().SelectAsync(forEach => GetLoopVariablesToInlineAsync(solution, semanticModel, forEach));
19+
return new HashSet<ILocalSymbol>(forEachControlVariables.Where(f => f != null), SymbolEquivalenceComparer.Instance);
20+
}
21+
22+
private static async Task<ILocalSymbol> GetLoopVariablesToInlineAsync(Solution solution, SemanticModel semanticModel, ForEachBlockSyntax block)
23+
{
24+
if (semanticModel.GetSymbolInfo(block.ForEachStatement.ControlVariable).Symbol is ILocalSymbol varSymbol) {
25+
var usagesOutsideLoop = await solution.GetUsagesAsync(varSymbol, block.GetLocation());
26+
if (!usagesOutsideLoop.Any()) return varSymbol;
27+
}
28+
return null;
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)