Skip to content

Commit

Permalink
Convert Select x = into let x = within Linq - fixes #717
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamTheCoder committed May 25, 2021
1 parent 841b220 commit 9411680
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 20 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### VB -> C#

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

### C# -> VB

Expand Down
3 changes: 3 additions & 0 deletions CodeConverter/CSharp/ExpressionNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,9 @@ await node.Name.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor)
);
}

public override async Task<CSharpSyntaxNode> VisitVariableNameEquals(VBSyntax.VariableNameEqualsSyntax node) =>
SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(ConvertIdentifier(node.Identifier.Identifier)));

public override async Task<CSharpSyntaxNode> VisitObjectCollectionInitializer(VBasic.Syntax.ObjectCollectionInitializerSyntax node)
{
return await node.Initializer.AcceptAsync<CSharpSyntaxNode>(TriviaConvertingExpressionVisitor); //Dictionary initializer comes through here despite the FROM keyword not being in the source code
Expand Down
44 changes: 25 additions & 19 deletions CodeConverter/CSharp/QueryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public async Task<CSharpSyntaxNode> ConvertClausesAsync(SyntaxList<VBSyntax.Quer
new Queue<(SyntaxList<CSSyntax.QueryClauseSyntax>, VBSyntax.QueryClauseSyntax)>();
while (vbBodyClauses.Any() && !RequiresMethodInvocation(vbBodyClauses.Peek())) {
var convertedClauses = new List<CSSyntax.QueryClauseSyntax>();
while (vbBodyClauses.Any() && !RequiredContinuation(vbBodyClauses.Peek())) {
while (vbBodyClauses.Any() && !RequiredContinuation(vbBodyClauses.Peek(), vbBodyClauses.Count - 1)) {
convertedClauses.Add(await ConvertQueryBodyClauseAsync(vbBodyClauses.Dequeue()));
}

Expand Down Expand Up @@ -233,11 +233,11 @@ private static bool RequiresMethodInvocation(VBSyntax.QueryClauseSyntax queryCla
|| queryClauseSyntax is VBSyntax.DistinctClauseSyntax;
}

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

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

private async Task<CSSyntax.QueryClauseSyntax> ConvertQueryBodyClauseAsync(VBSyntax.QueryClauseSyntax node)
private Task<CSSyntax.QueryClauseSyntax> ConvertQueryBodyClauseAsync(VBSyntax.QueryClauseSyntax node)
{
return await node
.TypeSwitch<VBSyntax.QueryClauseSyntax, VBSyntax.FromClauseSyntax, VBSyntax.JoinClauseSyntax,
VBSyntax.LetClauseSyntax, VBSyntax.OrderByClauseSyntax, VBSyntax.WhereClauseSyntax,
Task<CSSyntax.QueryClauseSyntax>>(
//(VBSyntax.AggregateClauseSyntax ags) => null,
async syntax => (CSSyntax.QueryClauseSyntax) await ConvertFromClauseSyntaxAsync(syntax),
ConvertJoinClauseAsync,
ConvertLetClauseAsync,
ConvertOrderByClauseAsync,
ConvertWhereClauseAsync,
_ => throw new NotImplementedException(
$"Conversion for query clause with kind '{node.Kind()}' not implemented"));
return node switch {
VBSyntax.FromClauseSyntax x => ConvertFromQueryClauseSyntaxAsync(x),
VBSyntax.JoinClauseSyntax x => ConvertJoinClauseAsync(x),
VBSyntax.SelectClauseSyntax x => ConvertSelectClauseAsync(x),
VBSyntax.LetClauseSyntax x => ConvertLetClauseAsync(x),
VBSyntax.OrderByClauseSyntax x => ConvertOrderByClauseAsync(x),
VBSyntax.WhereClauseSyntax x => ConvertWhereClauseAsync(x),
_ => throw new NotImplementedException($"Conversion for query clause with kind '{node.Kind()}' not implemented")
};

async Task<CSSyntax.QueryClauseSyntax> ConvertFromQueryClauseSyntaxAsync(VBSyntax.FromClauseSyntax x) => await ConvertFromClauseSyntaxAsync(x);
}

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

private async Task<CSSyntax.QueryClauseSyntax> ConvertSelectClauseAsync(VBSyntax.SelectClauseSyntax sc)
{
var singleVariable = sc.Variables.Single();
var identifier = CommonConversions.ConvertIdentifier(singleVariable.NameEquals.Identifier.Identifier);
return SyntaxFactory.LetClause(identifier, await singleVariable.Expression.AcceptAsync<CSSyntax.ExpressionSyntax>(_triviaConvertingVisitor));
}

private async Task<CSSyntax.QueryClauseSyntax> ConvertLetClauseAsync(VBSyntax.LetClauseSyntax ls)
{
var singleVariable = ls.Variables.Single();
Expand Down
41 changes: 41 additions & 0 deletions Tests/CSharp/ExpressionTests/LinqExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,47 @@ public void Foo()
// 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() }"
}

[Fact()]
public async Task LinqSelectVariableDeclarationAsync()
{
await TestConversionVisualBasicToCSharpAsync(@"Imports System
Imports System.Linq
Public Class Class717
Sub Main()
Dim arr(1) as Integer
arr(0) = 0
arr(1) = 1
Dim r = From e In arr
Select p = $""value: {e}""
Select l = p.Substring(1)
Select x = l
For each m In r
Console.WriteLine(m)
Next
End Sub
End Class", @"using System;
using System.Linq;
public partial class Class717
{
public void Main()
{
var arr = new int[2];
arr[0] = 0;
arr[1] = 1;
var r = from e in arr
let p = $""value: {e}""
let l = p.Substring(1)
select l;
foreach (var m in r)
Console.WriteLine(m);
}
}");
}

[Fact]
public async Task LinqGroupByAnonymousAsync()
{
Expand Down

0 comments on commit 9411680

Please sign in to comment.