Skip to content

Commit 2ad488f

Browse files
Push into LambdaConverter
1 parent 3a29928 commit 2ad488f

2 files changed

Lines changed: 108 additions & 104 deletions

File tree

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 9 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace ICSharpCode.CodeConverter.CSharp;
1515
internal partial class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSharpSyntaxNode>>
1616
{
1717
private static readonly Type ConvertType = typeof(Conversions);
18-
private CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor => CommonConversions.TriviaConvertingExpressionVisitor;
18+
public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor => CommonConversions.TriviaConvertingExpressionVisitor;
1919
private readonly SemanticModel _semanticModel;
2020
private readonly HashSet<string> _extraUsingDirectives;
2121
private readonly IOperatorConverter _operatorConverter;
@@ -38,7 +38,7 @@ public ExpressionNodeVisitor(SemanticModel semanticModel,
3838
_semanticModel = semanticModel;
3939
CommonConversions = commonConversions;
4040
commonConversions.TriviaConvertingExpressionVisitor = new CommentConvertingVisitorWrapper(this, _semanticModel.SyntaxTree);
41-
_lambdaConverter = new LambdaConverter(commonConversions, semanticModel);
41+
_lambdaConverter = new LambdaConverter(commonConversions, semanticModel, _withBlockLhs, extraUsingDirectives, typeContext);
4242
_visualBasicEqualityComparison = visualBasicEqualityComparison;
4343
_queryConverter = new QueryConverter(commonConversions, _semanticModel, TriviaConvertingExpressionVisitor);
4444
_typeContext = typeContext;
@@ -80,8 +80,8 @@ public override async Task<CSharpSyntaxNode> DefaultVisit(SyntaxNode node)
8080
public override Task<CSharpSyntaxNode> VisitXmlName(VBSyntax.XmlNameSyntax node) => _xmlExpressionConverter.ConvertXmlNameAsync(node);
8181
public override async Task<CSharpSyntaxNode> VisitSimpleArgument(VBasic.Syntax.SimpleArgumentSyntax node) => await _argumentConverter.ConvertSimpleArgumentAsync(node);
8282
public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax entryNode) => await _binaryExpressionConverter.ConvertBinaryExpressionAsync(entryNode);
83-
public override Task<CSharpSyntaxNode> VisitSingleLineLambdaExpression(VBasic.Syntax.SingleLineLambdaExpressionSyntax node) => ConvertSingleLineLambdaAsync(node);
84-
public override Task<CSharpSyntaxNode> VisitMultiLineLambdaExpression(VBasic.Syntax.MultiLineLambdaExpressionSyntax node) => ConvertMultiLineLambdaAsync(node);
83+
public override Task<CSharpSyntaxNode> VisitSingleLineLambdaExpression(VBasic.Syntax.SingleLineLambdaExpressionSyntax node) => _lambdaConverter.ConvertSingleLineLambdaAsync(node);
84+
public override Task<CSharpSyntaxNode> VisitMultiLineLambdaExpression(VBasic.Syntax.MultiLineLambdaExpressionSyntax node) => _lambdaConverter.ConvertMultiLineLambdaAsync(node);
8585

8686
public override async Task<CSharpSyntaxNode> VisitGetTypeExpression(VBasic.Syntax.GetTypeExpressionSyntax node)
8787
{
@@ -513,101 +513,6 @@ private CSharpSyntaxNode ConvertAddressOf(VBSyntax.UnaryExpressionSyntax node, E
513513
}
514514

515515

516-
517-
private async Task<CSharpSyntaxNode> ConvertSingleLineLambdaAsync(VBSyntax.SingleLineLambdaExpressionSyntax node)
518-
{
519-
var originalIsWithinQuery = TriviaConvertingExpressionVisitor.IsWithinQuery;
520-
TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node);
521-
try {
522-
return await ConvertInnerAsync();
523-
} finally {
524-
TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery;
525-
}
526-
527-
async Task<CSharpSyntaxNode> ConvertInnerAsync()
528-
{
529-
IReadOnlyCollection<StatementSyntax> convertedStatements;
530-
if (node.Body is VBasic.Syntax.StatementSyntax statement)
531-
{
532-
convertedStatements = await ConvertMethodBodyStatementsAsync(statement, statement.Yield().ToArray());
533-
}
534-
else
535-
{
536-
var csNode = await node.Body.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor);
537-
convertedStatements = new[] {SyntaxFactory.ExpressionStatement(csNode)};
538-
}
539-
540-
var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync<ParameterListSyntax>(TriviaConvertingExpressionVisitor);
541-
return await _lambdaConverter.ConvertAsync(node, param, convertedStatements);
542-
}
543-
}
544-
545-
546-
private async Task<CSharpSyntaxNode> ConvertMultiLineLambdaAsync(VBSyntax.MultiLineLambdaExpressionSyntax node)
547-
{
548-
var originalIsWithinQuery = TriviaConvertingExpressionVisitor.IsWithinQuery;
549-
TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node);
550-
try {
551-
return await ConvertInnerAsync();
552-
} finally {
553-
TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery;
554-
}
555-
556-
async Task<CSharpSyntaxNode> ConvertInnerAsync()
557-
{
558-
var body = await ConvertMethodBodyStatementsAsync(node, node.Statements);
559-
var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync<ParameterListSyntax>(TriviaConvertingExpressionVisitor);
560-
return await _lambdaConverter.ConvertAsync(node, param, body.ToList());
561-
}
562-
}
563-
564-
public async Task<IReadOnlyCollection<StatementSyntax>> ConvertMethodBodyStatementsAsync(VBasic.VisualBasicSyntaxNode node, IReadOnlyCollection<VBSyntax.StatementSyntax> statements, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null)
565-
{
566-
567-
var innerMethodBodyVisitor = await MethodBodyExecutableStatementVisitor.CreateAsync(node, _semanticModel, TriviaConvertingExpressionVisitor, CommonConversions, _visualBasicEqualityComparison, _withBlockLhs, _extraUsingDirectives, _typeContext, isIterator, csReturnVariable);
568-
return await GetWithConvertedGotosOrNull(statements) ?? await ConvertStatements(statements);
569-
570-
async Task<List<StatementSyntax>> ConvertStatements(IEnumerable<VBSyntax.StatementSyntax> readOnlyCollection)
571-
{
572-
return (await readOnlyCollection.SelectManyAsync(async s => (IEnumerable<StatementSyntax>)await s.Accept(innerMethodBodyVisitor.CommentConvertingVisitor))).ToList();
573-
}
574-
575-
async Task<IReadOnlyCollection<StatementSyntax>> GetWithConvertedGotosOrNull(IReadOnlyCollection<Microsoft.CodeAnalysis.VisualBasic.Syntax.StatementSyntax> statements)
576-
{
577-
var onlyIdentifierLabel = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.LabelStatement));
578-
var onlyOnErrorGotoStatement = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.OnErrorGoToLabelStatement));
579-
580-
// See https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/on-error-statement
581-
if (onlyIdentifierLabel != null && onlyOnErrorGotoStatement != null) {
582-
var statementsList = statements.ToList();
583-
var onlyIdentifierLabelIndex = statementsList.IndexOf(onlyIdentifierLabel);
584-
var onlyOnErrorGotoStatementIndex = statementsList.IndexOf(onlyOnErrorGotoStatement);
585-
586-
// Even this very simple case can generate compile errors if the error handling uses statements declared in the scope of the try block
587-
// For now, the user will have to fix these manually, in future it'd be possible to hoist any used declarations out of the try block
588-
if (onlyOnErrorGotoStatementIndex < onlyIdentifierLabelIndex) {
589-
var beforeStatements = await ConvertStatements(statements.Take(onlyOnErrorGotoStatementIndex));
590-
var tryBlockStatements = await ConvertStatements(statements.Take(onlyIdentifierLabelIndex).Skip(onlyOnErrorGotoStatementIndex + 1));
591-
var tryBlock = SyntaxFactory.Block(tryBlockStatements);
592-
var afterStatements = await ConvertStatements(statements.Skip(onlyIdentifierLabelIndex + 1));
593-
594-
var catchClauseSyntax = SyntaxFactory.CatchClause();
595-
596-
// Default to putting the statements after the catch block in case logic falls through, but if the last statement is a return, put them inside the catch block for neatness.
597-
if (tryBlockStatements.LastOrDefault().IsKind(SyntaxKind.ReturnStatement)) {
598-
catchClauseSyntax = catchClauseSyntax.WithBlock(SyntaxFactory.Block(afterStatements));
599-
afterStatements = new List<StatementSyntax>();
600-
}
601-
602-
var tryStatement = SyntaxFactory.TryStatement(SyntaxFactory.SingletonList(catchClauseSyntax)).WithBlock(tryBlock);
603-
return beforeStatements.Append(tryStatement).Concat(afterStatements).ToList();
604-
}
605-
}
606-
607-
return null;
608-
}
609-
}
610-
611516
public override async Task<CSharpSyntaxNode> VisitParameterList(VBSyntax.ParameterListSyntax node)
612517
{
613518
var parameters = await node.Parameters.SelectAsync(async p => await p.AcceptAsync<ParameterSyntax>(TriviaConvertingExpressionVisitor));
@@ -817,8 +722,9 @@ private static InvocationExpressionSyntax Invoke(ExpressionSyntax toInvoke, Expr
817722
);
818723
}
819724

820-
private SyntaxToken ConvertIdentifier(SyntaxToken identifierIdentifier, bool isAttribute = false)
821-
{
822-
return CommonConversions.ConvertIdentifier(identifierIdentifier, isAttribute);
823-
}
725+
private SyntaxToken ConvertIdentifier(SyntaxToken identifierIdentifier, bool isAttribute = false) =>
726+
CommonConversions.ConvertIdentifier(identifierIdentifier, isAttribute);
727+
728+
public Task<IReadOnlyCollection<StatementSyntax>> ConvertMethodBodyStatementsAsync(VBasic.VisualBasicSyntaxNode node, IReadOnlyCollection<VBSyntax.StatementSyntax> statements, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null) =>
729+
_lambdaConverter.ConvertMethodBodyStatementsAsync(node, statements, isIterator, csReturnVariable);
824730
}

CodeConverter/CSharp/LambdaConverter.cs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,68 @@ namespace ICSharpCode.CodeConverter.CSharp;
77
internal class LambdaConverter
88
{
99
private readonly SemanticModel _semanticModel;
10+
private readonly Stack<ExpressionSyntax> _withBlockLhs;
11+
private readonly HashSet<string> _extraUsingDirectives;
12+
private readonly ITypeContext _typeContext;
1013
private readonly Solution _solution;
1114

12-
public LambdaConverter(CommonConversions commonConversions, SemanticModel semanticModel)
15+
public LambdaConverter(CommonConversions commonConversions, SemanticModel semanticModel, Stack<ExpressionSyntax> withBlockLhs, HashSet<string> extraUsingDirectives, ITypeContext typeContext)
1316
{
1417
CommonConversions = commonConversions;
1518
_semanticModel = semanticModel;
19+
_withBlockLhs = withBlockLhs;
20+
_extraUsingDirectives = extraUsingDirectives;
21+
_typeContext = typeContext;
1622
_solution = CommonConversions.Document.Project.Solution;
1723
}
1824

1925
public CommonConversions CommonConversions { get; }
2026

27+
28+
29+
public async Task<CSharpSyntaxNode> ConvertMultiLineLambdaAsync(VBSyntax.MultiLineLambdaExpressionSyntax node)
30+
{
31+
var originalIsWithinQuery = CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery;
32+
CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node);
33+
try {
34+
return await ConvertInnerAsync();
35+
} finally {
36+
CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery;
37+
}
38+
39+
async Task<CSharpSyntaxNode> ConvertInnerAsync()
40+
{
41+
var body = await ConvertMethodBodyStatementsAsync(node, node.Statements);
42+
var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync<ParameterListSyntax>(CommonConversions.TriviaConvertingExpressionVisitor);
43+
return await ConvertAsync(node, param, body.ToList());
44+
}
45+
}
46+
47+
public async Task<CSharpSyntaxNode> ConvertSingleLineLambdaAsync(VBSyntax.SingleLineLambdaExpressionSyntax node)
48+
{
49+
var originalIsWithinQuery = CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery;
50+
CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = CommonConversions.IsLinqDelegateExpression(node);
51+
try {
52+
return await ConvertInnerAsync();
53+
} finally {
54+
CommonConversions.TriviaConvertingExpressionVisitor.IsWithinQuery = originalIsWithinQuery;
55+
}
56+
57+
async Task<CSharpSyntaxNode> ConvertInnerAsync()
58+
{
59+
IReadOnlyCollection<StatementSyntax> convertedStatements;
60+
if (node.Body is VBasic.Syntax.StatementSyntax statement) {
61+
convertedStatements = await ConvertMethodBodyStatementsAsync(statement, statement.Yield().ToArray());
62+
} else {
63+
var csNode = await node.Body.AcceptAsync<ExpressionSyntax>(CommonConversions.TriviaConvertingExpressionVisitor);
64+
convertedStatements = new[] { SyntaxFactory.ExpressionStatement(csNode) };
65+
}
66+
67+
var param = await node.SubOrFunctionHeader.ParameterList.AcceptAsync<ParameterListSyntax>(CommonConversions.TriviaConvertingExpressionVisitor);
68+
return await ConvertAsync(node, param, convertedStatements);
69+
}
70+
}
71+
2172
public async Task<CSharpSyntaxNode> ConvertAsync(VBSyntax.LambdaExpressionSyntax vbNode,
2273
ParameterListSyntax param, IReadOnlyCollection<StatementSyntax> convertedStatements)
2374
{
@@ -132,4 +183,51 @@ private LocalFunctionStatementSyntax CreateLocalFunction(IAnonymousFunctionOpera
132183

133184
return localFunc;
134185
}
186+
187+
public async Task<IReadOnlyCollection<StatementSyntax>> ConvertMethodBodyStatementsAsync(VBasic.VisualBasicSyntaxNode node, IReadOnlyCollection<VBSyntax.StatementSyntax> statements, bool isIterator = false, IdentifierNameSyntax csReturnVariable = null)
188+
{
189+
190+
var innerMethodBodyVisitor = await MethodBodyExecutableStatementVisitor.CreateAsync(node, _semanticModel, CommonConversions.TriviaConvertingExpressionVisitor, CommonConversions, CommonConversions.VisualBasicEqualityComparison, _withBlockLhs, _extraUsingDirectives, _typeContext, isIterator, csReturnVariable);
191+
return await GetWithConvertedGotosOrNull(statements) ?? await ConvertStatements(statements);
192+
193+
async Task<List<StatementSyntax>> ConvertStatements(IEnumerable<VBSyntax.StatementSyntax> readOnlyCollection)
194+
{
195+
return (await readOnlyCollection.SelectManyAsync(async s => (IEnumerable<StatementSyntax>)await s.Accept(innerMethodBodyVisitor.CommentConvertingVisitor))).ToList();
196+
}
197+
198+
async Task<IReadOnlyCollection<StatementSyntax>> GetWithConvertedGotosOrNull(IReadOnlyCollection<Microsoft.CodeAnalysis.VisualBasic.Syntax.StatementSyntax> statements)
199+
{
200+
var onlyIdentifierLabel = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.LabelStatement));
201+
var onlyOnErrorGotoStatement = statements.OnlyOrDefault(s => s.IsKind(VBasic.SyntaxKind.OnErrorGoToLabelStatement));
202+
203+
// See https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/on-error-statement
204+
if (onlyIdentifierLabel != null && onlyOnErrorGotoStatement != null) {
205+
var statementsList = statements.ToList();
206+
var onlyIdentifierLabelIndex = statementsList.IndexOf(onlyIdentifierLabel);
207+
var onlyOnErrorGotoStatementIndex = statementsList.IndexOf(onlyOnErrorGotoStatement);
208+
209+
// Even this very simple case can generate compile errors if the error handling uses statements declared in the scope of the try block
210+
// For now, the user will have to fix these manually, in future it'd be possible to hoist any used declarations out of the try block
211+
if (onlyOnErrorGotoStatementIndex < onlyIdentifierLabelIndex) {
212+
var beforeStatements = await ConvertStatements(statements.Take(onlyOnErrorGotoStatementIndex));
213+
var tryBlockStatements = await ConvertStatements(statements.Take(onlyIdentifierLabelIndex).Skip(onlyOnErrorGotoStatementIndex + 1));
214+
var tryBlock = SyntaxFactory.Block(tryBlockStatements);
215+
var afterStatements = await ConvertStatements(statements.Skip(onlyIdentifierLabelIndex + 1));
216+
217+
var catchClauseSyntax = SyntaxFactory.CatchClause();
218+
219+
// Default to putting the statements after the catch block in case logic falls through, but if the last statement is a return, put them inside the catch block for neatness.
220+
if (tryBlockStatements.LastOrDefault().IsKind(SyntaxKind.ReturnStatement)) {
221+
catchClauseSyntax = catchClauseSyntax.WithBlock(SyntaxFactory.Block(afterStatements));
222+
afterStatements = new List<StatementSyntax>();
223+
}
224+
225+
var tryStatement = SyntaxFactory.TryStatement(SyntaxFactory.SingletonList(catchClauseSyntax)).WithBlock(tryBlock);
226+
return beforeStatements.Append(tryStatement).Concat(afterStatements).ToList();
227+
}
228+
}
229+
230+
return null;
231+
}
232+
}
135233
}

0 commit comments

Comments
 (0)