Skip to content

Commit 9411680

Browse files
Convert Select x = into let x = within Linq - fixes #717
1 parent 841b220 commit 9411680

File tree

4 files changed

+71
-20
lines changed

4 files changed

+71
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010

1111
### VB -> C#
1212

13-
* Prevent overrides and overloads appearing on the same property (https://github.com/icsharpcode/CodeConverter/issues/681)
13+
* Prevent overrides and overloads appearing on the same property [#681](https://github.com/icsharpcode/CodeConverter/issues/681)
14+
* Convert `Select x = ` into `let x = ` within Linq [#717](https://github.com/icsharpcode/CodeConverter/issues/717)
1415

1516
### C# -> VB
1617

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,9 @@ await node.Name.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor)
652652
);
653653
}
654654

655+
public override async Task<CSharpSyntaxNode> VisitVariableNameEquals(VBSyntax.VariableNameEqualsSyntax node) =>
656+
SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(ConvertIdentifier(node.Identifier.Identifier)));
657+
655658
public override async Task<CSharpSyntaxNode> VisitObjectCollectionInitializer(VBasic.Syntax.ObjectCollectionInitializerSyntax node)
656659
{
657660
return await node.Initializer.AcceptAsync<CSharpSyntaxNode>(TriviaConvertingExpressionVisitor); //Dictionary initializer comes through here despite the FROM keyword not being in the source code

CodeConverter/CSharp/QueryConverter.cs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public async Task<CSharpSyntaxNode> ConvertClausesAsync(SyntaxList<VBSyntax.Quer
9393
new Queue<(SyntaxList<CSSyntax.QueryClauseSyntax>, VBSyntax.QueryClauseSyntax)>();
9494
while (vbBodyClauses.Any() && !RequiresMethodInvocation(vbBodyClauses.Peek())) {
9595
var convertedClauses = new List<CSSyntax.QueryClauseSyntax>();
96-
while (vbBodyClauses.Any() && !RequiredContinuation(vbBodyClauses.Peek())) {
96+
while (vbBodyClauses.Any() && !RequiredContinuation(vbBodyClauses.Peek(), vbBodyClauses.Count - 1)) {
9797
convertedClauses.Add(await ConvertQueryBodyClauseAsync(vbBodyClauses.Dequeue()));
9898
}
9999

@@ -233,11 +233,11 @@ private static bool RequiresMethodInvocation(VBSyntax.QueryClauseSyntax queryCla
233233
|| queryClauseSyntax is VBSyntax.DistinctClauseSyntax;
234234
}
235235

236-
private static bool RequiredContinuation(VBSyntax.QueryClauseSyntax queryClauseSyntax)
237-
{
238-
return queryClauseSyntax is VBSyntax.GroupByClauseSyntax
239-
|| queryClauseSyntax is VBSyntax.SelectClauseSyntax;
240-
}
236+
/// <summary>
237+
/// In VB, multiple selects work like Let clauses, but the last one needs to become the actual select (its name is discarded)
238+
/// </summary>
239+
private static bool RequiredContinuation(VBSyntax.QueryClauseSyntax queryClauseSyntax, int clausesAfter) => queryClauseSyntax is VBSyntax.GroupByClauseSyntax
240+
|| queryClauseSyntax is VBSyntax.SelectClauseSyntax sc && (sc.Variables.Any(v => v.NameEquals is null) || clausesAfter == 0);
241241

242242
private async Task<CSSyntax.FromClauseSyntax> ConvertFromClauseSyntaxAsync(VBSyntax.FromClauseSyntax vbFromClause)
243243
{
@@ -284,20 +284,19 @@ private static CSSyntax.SelectClauseSyntax CreateDefaultSelectClause(SyntaxToken
284284
return SyntaxFactory.SelectClause(SyntaxFactory.IdentifierName(reusableCsFromId));
285285
}
286286

287-
private async Task<CSSyntax.QueryClauseSyntax> ConvertQueryBodyClauseAsync(VBSyntax.QueryClauseSyntax node)
287+
private Task<CSSyntax.QueryClauseSyntax> ConvertQueryBodyClauseAsync(VBSyntax.QueryClauseSyntax node)
288288
{
289-
return await node
290-
.TypeSwitch<VBSyntax.QueryClauseSyntax, VBSyntax.FromClauseSyntax, VBSyntax.JoinClauseSyntax,
291-
VBSyntax.LetClauseSyntax, VBSyntax.OrderByClauseSyntax, VBSyntax.WhereClauseSyntax,
292-
Task<CSSyntax.QueryClauseSyntax>>(
293-
//(VBSyntax.AggregateClauseSyntax ags) => null,
294-
async syntax => (CSSyntax.QueryClauseSyntax) await ConvertFromClauseSyntaxAsync(syntax),
295-
ConvertJoinClauseAsync,
296-
ConvertLetClauseAsync,
297-
ConvertOrderByClauseAsync,
298-
ConvertWhereClauseAsync,
299-
_ => throw new NotImplementedException(
300-
$"Conversion for query clause with kind '{node.Kind()}' not implemented"));
289+
return node switch {
290+
VBSyntax.FromClauseSyntax x => ConvertFromQueryClauseSyntaxAsync(x),
291+
VBSyntax.JoinClauseSyntax x => ConvertJoinClauseAsync(x),
292+
VBSyntax.SelectClauseSyntax x => ConvertSelectClauseAsync(x),
293+
VBSyntax.LetClauseSyntax x => ConvertLetClauseAsync(x),
294+
VBSyntax.OrderByClauseSyntax x => ConvertOrderByClauseAsync(x),
295+
VBSyntax.WhereClauseSyntax x => ConvertWhereClauseAsync(x),
296+
_ => throw new NotImplementedException($"Conversion for query clause with kind '{node.Kind()}' not implemented")
297+
};
298+
299+
async Task<CSSyntax.QueryClauseSyntax> ConvertFromQueryClauseSyntaxAsync(VBSyntax.FromClauseSyntax x) => await ConvertFromClauseSyntaxAsync(x);
301300
}
302301

303302
private async Task<CSSyntax.ExpressionSyntax> GetGroupExpressionAsync(VBSyntax.GroupByClauseSyntax gs)
@@ -337,6 +336,13 @@ private IEnumerable<string> GetGroupKeyIdentifiers(VBSyntax.GroupByClauseSyntax
337336
return SyntaxFactory.WhereClause(await ws.Condition.AcceptAsync<CSSyntax.ExpressionSyntax>(_triviaConvertingVisitor));
338337
}
339338

339+
private async Task<CSSyntax.QueryClauseSyntax> ConvertSelectClauseAsync(VBSyntax.SelectClauseSyntax sc)
340+
{
341+
var singleVariable = sc.Variables.Single();
342+
var identifier = CommonConversions.ConvertIdentifier(singleVariable.NameEquals.Identifier.Identifier);
343+
return SyntaxFactory.LetClause(identifier, await singleVariable.Expression.AcceptAsync<CSSyntax.ExpressionSyntax>(_triviaConvertingVisitor));
344+
}
345+
340346
private async Task<CSSyntax.QueryClauseSyntax> ConvertLetClauseAsync(VBSyntax.LetClauseSyntax ls)
341347
{
342348
var singleVariable = ls.Variables.Single();

Tests/CSharp/ExpressionTests/LinqExpressionTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,47 @@ public void Foo()
332332
// Current characterization is slightly wrong, I think it still needs this on the end "into g select new { Length = g.Key.Length, Count = g.Key.Count, Group = g.AsEnumerable() }"
333333
}
334334

335+
[Fact()]
336+
public async Task LinqSelectVariableDeclarationAsync()
337+
{
338+
await TestConversionVisualBasicToCSharpAsync(@"Imports System
339+
Imports System.Linq
340+
341+
Public Class Class717
342+
Sub Main()
343+
Dim arr(1) as Integer
344+
arr(0) = 0
345+
arr(1) = 1
346+
347+
Dim r = From e In arr
348+
Select p = $""value: {e}""
349+
Select l = p.Substring(1)
350+
Select x = l
351+
352+
For each m In r
353+
Console.WriteLine(m)
354+
Next
355+
End Sub
356+
End Class", @"using System;
357+
using System.Linq;
358+
359+
public partial class Class717
360+
{
361+
public void Main()
362+
{
363+
var arr = new int[2];
364+
arr[0] = 0;
365+
arr[1] = 1;
366+
var r = from e in arr
367+
let p = $""value: {e}""
368+
let l = p.Substring(1)
369+
select l;
370+
foreach (var m in r)
371+
Console.WriteLine(m);
372+
}
373+
}");
374+
}
375+
335376
[Fact]
336377
public async Task LinqGroupByAnonymousAsync()
337378
{

0 commit comments

Comments
 (0)