Skip to content

Commit

Permalink
Improvements towards #298
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamTheCoder committed Jan 19, 2020
1 parent 0e9cbb8 commit f5466a9
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 5 deletions.
28 changes: 23 additions & 5 deletions ICSharpCode.CodeConverter/CSharp/QueryConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using ICSharpCode.CodeConverter.Util;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using CSSyntax = Microsoft.CodeAnalysis.CSharp.Syntax;
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;

Expand Down Expand Up @@ -155,8 +156,13 @@ private async Task<CSharpSyntaxNode> ConvertQueryWithContinuations(Queue<(Syntax
selectOrGroup = CreateDefaultSelectClause(fromClauseSyntax);
break;
case VBSyntax.GroupByClauseSyntax gcs:
var letGroupKey = SyntaxFactory.LetClause(GetGroupKeyIdentifiers(gcs).Single(), SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(GetGroupIdentifier(gcs)), SyntaxFactory.IdentifierName("Key")));
var continuationClauses = SyntaxFactory.List(new CSSyntax.QueryClauseSyntax[]{ letGroupKey});
var groupKeyIds = GetGroupKeyIdentifiers(gcs).ToList();

var continuationClauses = SyntaxFactory.List<CSSyntax.QueryClauseSyntax>();
if (groupKeyIds.Count == 1) {
var letGroupKey = SyntaxFactory.LetClause(groupKeyIds.First(), SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(GetGroupIdentifier(gcs)), SyntaxFactory.IdentifierName("Key")));
continuationClauses = continuationClauses.Add(letGroupKey);
}
if (!gcs.Items.Any()) {
var identifierNameSyntax =
SyntaxFactory.IdentifierName(CommonConversions.ConvertIdentifier(fromClauseSyntax.Identifier));
Expand All @@ -166,7 +172,7 @@ private async Task<CSharpSyntaxNode> ConvertQueryWithContinuations(Queue<(Syntax
var keyExpression = (CSSyntax.ExpressionSyntax) await gcs.Keys.Single().Expression.AcceptAsync(_triviaConvertingVisitor);
selectOrGroup = SyntaxFactory.GroupClause(item, keyExpression);
}
queryContinuation = CreateGroupByContinuation(gcs, continuationClauses, nestedClause);
queryContinuation = nestedClause != null ? CreateGroupByContinuation(gcs, continuationClauses, nestedClause) : null;
break;
case VBSyntax.SelectClauseSyntax scs:
selectOrGroup = await ConvertSelectClauseSyntax(scs);
Expand All @@ -180,7 +186,7 @@ private async Task<CSharpSyntaxNode> ConvertQueryWithContinuations(Queue<(Syntax

private CSSyntax.QueryContinuationSyntax CreateGroupByContinuation(VBSyntax.GroupByClauseSyntax gcs, SyntaxList<CSSyntax.QueryClauseSyntax> convertedClauses, CSSyntax.QueryBodySyntax body)
{
var queryBody = SyntaxFactory.QueryBody(convertedClauses, body?.SelectOrGroup, null);
var queryBody = convertedClauses.Any() ? SyntaxFactory.QueryBody(convertedClauses, body?.SelectOrGroup, null) : SyntaxFactory.QueryBody(body?.SelectOrGroup);
SyntaxToken groupName = GetGroupIdentifier(gcs);
return SyntaxFactory.QueryContinuation(groupName, queryBody);
}
Expand Down Expand Up @@ -287,7 +293,19 @@ private static CSSyntax.SelectClauseSyntax CreateDefaultSelectClause(CSSyntax.Fr

private async Task<CSSyntax.ExpressionSyntax> GetGroupExpression(VBSyntax.GroupByClauseSyntax gs)
{
return (CSSyntax.ExpressionSyntax) await gs.Keys.Single().Expression.AcceptAsync(_triviaConvertingVisitor);
var groupExpressions = (await gs.Keys.SelectAsync(async k => (vb: k.Expression, cs: (CSSyntax.ExpressionSyntax)await k.Expression.AcceptAsync(_triviaConvertingVisitor)))).ToList();
return (groupExpressions.Count == 1) ? groupExpressions.Single().cs : CreateAnonymousType(groupExpressions);
}

private CSSyntax.ExpressionSyntax CreateAnonymousType(List<(ExpressionSyntax vb, CSSyntax.ExpressionSyntax cs)> groupExpressions)
{
return SyntaxFactory.AnonymousObjectCreationExpression(SyntaxFactory.SeparatedList(groupExpressions.Select(CreateAnonymousMember)));
}

private static CSSyntax.AnonymousObjectMemberDeclaratorSyntax CreateAnonymousMember((ExpressionSyntax vb, CSSyntax.ExpressionSyntax cs) expr, int i)
{
var name = SyntaxFactory.Identifier(expr.vb.ExtractAnonymousTypeMemberName()?.Text ?? ("key" + i));
return SyntaxFactory.AnonymousObjectMemberDeclarator(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(name)), expr.cs);
}

private SyntaxToken GetGroupIdentifier(VBSyntax.GroupByClauseSyntax gs)
Expand Down
106 changes: 106 additions & 0 deletions ICSharpCode.CodeConverter/Util/ExpressionSyntaxExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,111 @@ private static bool IsIntegralType(SpecialType? specialType)
return false;
}
}

/// <summary>
/// This is a conversion heavily based on Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.SyntaxExtensions.ExtractAnonymousTypeMemberName from 1bbbfc28a8e4493b4057e171310343a4c7ba826c
/// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0
/// </summary>
public static SyntaxToken? ExtractAnonymousTypeMemberName(this VBSyntax.ExpressionSyntax input)
{
bool isNameDictionaryAccess;
Stack<VBSyntax.ConditionalAccessExpressionSyntax> conditionalAccessStack = null;
while (true) {
switch (input.Kind()) {
case VBasic.SyntaxKind.IdentifierName: {
return ((VBSyntax.IdentifierNameSyntax)input).Identifier;
}

case VBasic.SyntaxKind.XmlName: {
var xmlNameInferredFrom = (VBSyntax.XmlNameSyntax)input;
var name = xmlNameInferredFrom.LocalName;
// CONVERSION NOTE: Slightly skimped on the details here for brevity
return VBasic.SyntaxFacts.IsValidIdentifier(name.ToString()) ? name : default(SyntaxToken?);
}

case VBasic.SyntaxKind.XmlBracketedName: {
// handles something like <a-a>
var xmlNameInferredFrom = (VBSyntax.XmlBracketedNameSyntax)input;
input = xmlNameInferredFrom.Name;
continue;
}

case VBasic.SyntaxKind.SimpleMemberAccessExpression:
case VBasic.SyntaxKind.DictionaryAccessExpression: {
var memberAccess = (VBSyntax.MemberAccessExpressionSyntax)input;
var receiver = memberAccess.Expression ?? conditionalAccessStack.Pop();

if (input.Kind() == VBasic.SyntaxKind.SimpleMemberAccessExpression) {
// See if this is an identifier qualified with XmlElementAccessExpression or XmlDescendantAccessExpression
if (receiver != null) {
switch (receiver.Kind()) {
case VBasic.SyntaxKind.XmlElementAccessExpression:
case VBasic.SyntaxKind.XmlDescendantAccessExpression: {
input = receiver;
continue;
}
}
}
}

conditionalAccessStack = null;

isNameDictionaryAccess = input.Kind() == VBasic.SyntaxKind.DictionaryAccessExpression;
input = memberAccess.Name;
continue;
}

case VBasic.SyntaxKind.XmlElementAccessExpression:
case VBasic.SyntaxKind.XmlAttributeAccessExpression:
case VBasic.SyntaxKind.XmlDescendantAccessExpression: {
var xmlAccess = (VBSyntax.XmlMemberAccessExpressionSyntax)input;
conditionalAccessStack.Clear();

input = xmlAccess.Name;
continue;
}

case VBasic.SyntaxKind.InvocationExpression: {
var invocation = (VBSyntax.InvocationExpressionSyntax)input;
var target = invocation.Expression ?? conditionalAccessStack.Pop();

if (target == null)
break;

if (invocation.ArgumentList == null || invocation.ArgumentList.Arguments.Count == 0) {
input = target;
continue;
}

if (invocation.ArgumentList.Arguments.Count == 1) {
// See if this is an indexed XmlElementAccessExpression or XmlDescendantAccessExpression
switch (target.Kind()) {
case VBasic.SyntaxKind.XmlElementAccessExpression:
case VBasic.SyntaxKind.XmlDescendantAccessExpression: {
input = target;
continue;
}
}
}

break;
}

case VBasic.SyntaxKind.ConditionalAccessExpression: {
var access = (VBSyntax.ConditionalAccessExpressionSyntax)input;

if (conditionalAccessStack == null)
conditionalAccessStack = new Stack<VBSyntax.ConditionalAccessExpressionSyntax>();

conditionalAccessStack.Push(access);

input = access.WhenNotNull;
continue;
}
}

return null;
}
}
}
}
24 changes: 24 additions & 0 deletions Tests/CSharp/LinqExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,30 @@ join ord in orders on cust.CustomerID equals ord.CustomerID into CustomerOrders
}");
}

[Fact()]
public async Task LinqGroupByTwoThingsAnonymously()
{
//Can't auto-test comments when newlines get inserted
await TestConversionVisualBasicToCSharpWithoutComments(@"Public Class Class1
Sub Foo()
Dim xs As New List(Of String)
Dim y = From x In xs Group By x.Length, x.Count() Into Group
End Sub
End Class", @"using System.Collections.Generic;
using System.Linq;
public partial class Class1
{
public void Foo()
{
var xs = new List<string>();
var y = from x in xs
group x by new { x.Length, Count = x.Count() };
}
}");
// 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 LinqGroupByAnonymous()
{
Expand Down

0 comments on commit f5466a9

Please sign in to comment.