From 6f4fdd00f7fa60a64ec69d8d65e66da11b04009b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 10 Apr 2011 20:58:07 +0200 Subject: [PATCH] Get rid of transparent identifiers in query expressions. --- .../Ast/Transforms/CombineQueryExpressions.cs | 124 ++++++++++++++++++ .../Transforms/IntroduceExtensionMethods.cs | 3 +- .../Transforms/IntroduceQueryExpressions.cs | 4 + .../Ast/Transforms/TransformationPipeline.cs | 1 + .../ICSharpCode.Decompiler.csproj | 1 + .../Tests/QueryExpressions.cs | 23 +++- 6 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs 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