diff --git a/ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs b/ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs
new file mode 100644
index 000000000..98af72df1
--- /dev/null
+++ b/ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs
@@ -0,0 +1,124 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using ICSharpCode.NRefactory.CSharp;
+using ICSharpCode.NRefactory.CSharp.PatternMatching;
+
+namespace ICSharpCode.Decompiler.Ast.Transforms
+{
+ ///
+ /// Combines query expressions and removes transparent identifiers.
+ ///
+ public class CombineQueryExpressions : IAstTransform
+ {
+ readonly DecompilerContext context;
+
+ public CombineQueryExpressions(DecompilerContext context)
+ {
+ this.context = context;
+ }
+
+ public void Run(AstNode compilationUnit)
+ {
+ if (!context.Settings.QueryExpressions)
+ return;
+ CombineQueries(compilationUnit);
+ }
+
+ void CombineQueries(AstNode node)
+ {
+ for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
+ CombineQueries(child);
+ }
+ QueryExpression query = node as QueryExpression;
+ if (query != null) {
+ QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
+ QueryExpression innerQuery = fromClause.Expression as QueryExpression;
+ if (innerQuery != null) {
+ if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery)) {
+ RemoveTransparentIdentifierReferences(query);
+ }
+ }
+ }
+ }
+
+ static readonly QuerySelectClause selectTransparentIdentifierPattern = new QuerySelectClause {
+ Expression = new ObjectCreateExpression {
+ Initializer = new ArrayInitializerExpression {
+ Elements = {
+ new NamedNode("nae1", new NamedArgumentExpression { Expression = new IdentifierExpression() }),
+ new NamedNode("nae2", new NamedArgumentExpression { Expression = new AnyNode() })
+ }
+ }
+ }};
+
+ bool IsTransparentIdentifier(string identifier)
+ {
+ return identifier.StartsWith("<>", StringComparison.Ordinal) && identifier.Contains("TransparentIdentifier");
+ }
+
+ bool TryRemoveTransparentIdentifier(QueryExpression query, QueryFromClause fromClause, QueryExpression innerQuery)
+ {
+ if (!IsTransparentIdentifier(fromClause.Identifier))
+ return false;
+ Match match = selectTransparentIdentifierPattern.Match(innerQuery.Clauses.Last());
+ if (match == null)
+ return false;
+ QuerySelectClause selectClause = (QuerySelectClause)innerQuery.Clauses.Last();
+ NamedArgumentExpression nae1 = match.Get("nae1").Single();
+ NamedArgumentExpression nae2 = match.Get("nae2").Single();
+ if (nae1.Identifier != ((IdentifierExpression)nae1.Expression).Identifier)
+ return false;
+ IdentifierExpression nae2IdentExpr = nae2.Expression as IdentifierExpression;
+ if (nae2IdentExpr != null && nae2.Identifier == nae2IdentExpr.Identifier) {
+ // from * in (from x in ... select new { x = x, y = y }) ...
+ // =>
+ // from x in ... ...
+ fromClause.Remove();
+ selectClause.Remove();
+ // Move clauses from innerQuery to query
+ QueryClause insertionPos = null;
+ foreach (var clause in innerQuery.Clauses) {
+ query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
+ }
+ } else {
+ // from * in (from x in ... select new { x = x, y = expr }) ...
+ // =>
+ // from x in ... let y = expr ...
+ fromClause.Remove();
+ selectClause.Remove();
+ // Move clauses from innerQuery to query
+ QueryClause insertionPos = null;
+ foreach (var clause in innerQuery.Clauses) {
+ query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
+ }
+ query.Clauses.InsertAfter(insertionPos, new QueryLetClause { Identifier = nae2.Identifier, Expression = nae2.Expression.Detach() });
+ }
+ return true;
+ }
+
+ ///
+ /// Removes all occurrences of transparent identifiers
+ ///
+ void RemoveTransparentIdentifierReferences(AstNode node)
+ {
+ foreach (AstNode child in node.Children) {
+ RemoveTransparentIdentifierReferences(child);
+ }
+ MemberReferenceExpression mre = node as MemberReferenceExpression;
+ if (mre != null) {
+ IdentifierExpression ident = mre.Target as IdentifierExpression;
+ if (ident != null && IsTransparentIdentifier(ident.Identifier)) {
+ IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName);
+ mre.TypeArguments.MoveTo(newIdent.TypeArguments);
+ newIdent.CopyAnnotationsFrom(mre);
+ newIdent.RemoveAnnotations(); // remove the reference to the property of the anonymous type
+ mre.ReplaceWith(newIdent);
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs
index 935a714e9..a1bdb0807 100644
--- a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs
+++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs
@@ -31,7 +31,8 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
foreach (var ca in d.CustomAttributes) {
if (ca.AttributeType.Name == "ExtensionAttribute" && ca.AttributeType.Namespace == "System.Runtime.CompilerServices") {
mre.Target = invocation.Arguments.First().Detach();
- mre.TypeArguments.Clear();
+ if (invocation.Arguments.Any())
+ mre.TypeArguments.Clear();
break;
}
}
diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs
index 264de8fa9..141217318 100644
--- a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs
+++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs
@@ -26,12 +26,16 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
if (!context.Settings.QueryExpressions)
return;
DecompileQueries(compilationUnit);
+ // After all queries were decompiled, detect degenerate queries (queries not property terminated with 'select' or 'group')
+ // and fix them, either by adding a degenerate select, or by combining them with another query.
foreach (QueryExpression query in compilationUnit.Descendants.OfType()) {
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
if (IsDegenerateQuery(query)) {
// introduce select for degenerate query
query.Clauses.Add(new QuerySelectClause { Expression = new IdentifierExpression(fromClause.Identifier) });
}
+ // See if the data source of this query is a degenerate query,
+ // and combine the queries if possible.
QueryExpression innerQuery = fromClause.Expression as QueryExpression;
while (IsDegenerateQuery(innerQuery)) {
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
diff --git a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs
index caf38da1f..7bcf7ab36 100644
--- a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs
+++ b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs
@@ -28,6 +28,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
new IntroduceUsingDeclarations(context),
new IntroduceExtensionMethods(context), // must run after IntroduceUsingDeclarations
new IntroduceQueryExpressions(context), // must run after IntroduceExtensionMethods
+ new CombineQueryExpressions(context),
};
}
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 87132e462..6e8d0eae8 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -58,6 +58,7 @@
+
diff --git a/ICSharpCode.Decompiler/Tests/QueryExpressions.cs b/ICSharpCode.Decompiler/Tests/QueryExpressions.cs
index 9cef2f145..010291d5e 100644
--- a/ICSharpCode.Decompiler/Tests/QueryExpressions.cs
+++ b/ICSharpCode.Decompiler/Tests/QueryExpressions.cs
@@ -43,7 +43,7 @@ public class QueryExpressions
select c;
}
- public object MultipleFromFollowedBySelect()
+ public object SelectManyFollowedBySelect()
{
return
from c in this.customers
@@ -51,7 +51,7 @@ public class QueryExpressions
select new { c.Name, o.OrderID, o.Total };
}
- public object MultipleFromFollowedByOrderBy()
+ public object SelectManyFollowedByOrderBy()
{
return
from c in this.customers
@@ -60,6 +60,25 @@ public class QueryExpressions
select new { c.Name, o.OrderID, o.Total };
}
+ public object MultipleSelectManyFollowedBySelect()
+ {
+ return
+ from c in this.customers
+ from o in c.Orders
+ from d in o.Details
+ select new { c.Name, o.OrderID, d.Quantity };
+ }
+
+ public object MultipleSelectManyFollowedByLet()
+ {
+ return
+ from c in this.customers
+ from o in c.Orders
+ from d in o.Details
+ let x = d.Quantity * d.UnitPrice
+ select new { c.Name, o.OrderID, x };
+ }
+
public object FromLetWhereSelect()
{
return