Upstream icsharpcode/ILSpy state of the touched files before 53822318d9
This commit is contained in:
1 parent
3f1ef717b6
commit
8e3e41fedb
4 files changed
+908
-14
No files matched your search
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
|
||||
{
|
||||
class NullPropagation
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
new NullPropagation().TestNotCoalescing();
|
||||
#if CS72
|
||||
new NullPropagation().TestRefStructNotCoalescing();
|
||||
#endif
|
||||
}
|
||||
|
||||
class MyClass
|
||||
{
|
||||
public string Text;
|
||||
}
|
||||
|
||||
void TestNotCoalescing()
|
||||
{
|
||||
Console.WriteLine("TestNotCoalescing:");
|
||||
Console.WriteLine(NotCoalescing(null));
|
||||
Console.WriteLine(NotCoalescing(new MyClass()));
|
||||
}
|
||||
|
||||
string NotCoalescing(MyClass c)
|
||||
{
|
||||
return c != null ? c.Text : "Hello";
|
||||
}
|
||||
|
||||
#if CS72
|
||||
ref struct ByRefLikeStruct
|
||||
{
|
||||
public int Value;
|
||||
}
|
||||
|
||||
class RefStructHolder
|
||||
{
|
||||
public ByRefLikeStruct Get()
|
||||
{
|
||||
ByRefLikeStruct result = default(ByRefLikeStruct);
|
||||
result.Value = 42;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
void TestRefStructNotCoalescing()
|
||||
{
|
||||
Console.WriteLine("TestRefStructNotCoalescing:");
|
||||
Console.WriteLine(RefStructNotCoalescing(null).Value);
|
||||
Console.WriteLine(RefStructNotCoalescing(new RefStructHolder()).Value);
|
||||
}
|
||||
|
||||
// A by-ref-like return type cannot be turned into a ?. / ?? null-propagation: a ref struct
|
||||
// cannot be wrapped in Nullable<T>, so that form does not compile.
|
||||
ByRefLikeStruct RefStructNotCoalescing(RefStructHolder holder)
|
||||
{
|
||||
return holder != null ? holder.Get() : default(ByRefLikeStruct);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
// Copyright (c) 2018 Daniel Grunwald
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
{
|
||||
internal class NullPropagation
|
||||
{
|
||||
private class MyClass
|
||||
{
|
||||
public int IntVal;
|
||||
public readonly int ReadonlyIntVal;
|
||||
public MyStruct StructField;
|
||||
public readonly MyStruct ReadonlyStructField;
|
||||
public string Text;
|
||||
public MyClass Field;
|
||||
public MyClass Property { get; set; }
|
||||
public MyClass this[int index] => null;
|
||||
public MyClass Method(int arg)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool GetBool()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Done()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private struct MyStruct
|
||||
{
|
||||
public int IntVal;
|
||||
public readonly int ReadonlyIntVal;
|
||||
public MyClass Field;
|
||||
public MyStruct? Property1 => null;
|
||||
#if CS71
|
||||
public MyStruct Property2 => default;
|
||||
#else
|
||||
public MyStruct Property2 => default(MyStruct);
|
||||
#endif
|
||||
public MyStruct? this[int index] => null;
|
||||
public MyStruct? Method1(int arg)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public MyStruct Method2(int arg)
|
||||
{
|
||||
#if CS71
|
||||
return default;
|
||||
#else
|
||||
return default(MyStruct);
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool GetBool()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Done()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class Container<T1, T2>
|
||||
{
|
||||
public GenericStruct<T1, T2> Other;
|
||||
}
|
||||
|
||||
private struct GenericStruct<T1, T2>
|
||||
{
|
||||
public T1 Field1;
|
||||
public T2 Field2;
|
||||
public Container<T1, T2> Other;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "(" + Field1?.ToString() + ", " + Field2?.ToString() + ")";
|
||||
}
|
||||
|
||||
public int? GetTextLength()
|
||||
{
|
||||
return Field1?.ToString().Length + Field2?.ToString().Length + 4;
|
||||
}
|
||||
|
||||
public string Chain1()
|
||||
{
|
||||
return Other?.Other.Other?.Other.Field1?.ToString();
|
||||
}
|
||||
|
||||
public string Chain2()
|
||||
{
|
||||
return Other?.Other.Other?.Other.Field1?.ToString()?.GetType().Name;
|
||||
}
|
||||
|
||||
public int? Test2()
|
||||
{
|
||||
return Field1?.ToString().Length ?? 42;
|
||||
}
|
||||
|
||||
public int? GetTextLengthNRE()
|
||||
{
|
||||
return (Field1?.ToString()).Length;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITest
|
||||
{
|
||||
int Int();
|
||||
ITest Next();
|
||||
}
|
||||
|
||||
private int GetInt()
|
||||
{
|
||||
return 9;
|
||||
}
|
||||
|
||||
private string GetString()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private MyClass GetMyClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private MyStruct? GetMyStruct()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public string Substring()
|
||||
{
|
||||
return GetString()?.Substring(GetInt());
|
||||
}
|
||||
|
||||
public void CallSubstringAndIgnoreResult()
|
||||
{
|
||||
GetString()?.Substring(GetInt());
|
||||
}
|
||||
|
||||
private void Use<T>(T t)
|
||||
{
|
||||
}
|
||||
|
||||
public void CallDone()
|
||||
{
|
||||
GetMyClass()?.Done();
|
||||
GetMyClass()?.Field?.Done();
|
||||
GetMyClass()?.Field.Done();
|
||||
GetMyClass()?.Property?.Done();
|
||||
GetMyClass()?.Property.Done();
|
||||
GetMyClass()?.Method(GetInt())?.Done();
|
||||
GetMyClass()?.Method(GetInt()).Done();
|
||||
GetMyClass()?[GetInt()]?.Done();
|
||||
GetMyClass()?[GetInt()].Done();
|
||||
}
|
||||
|
||||
public void CallGetBool()
|
||||
{
|
||||
GetMyClass()?.GetBool();
|
||||
GetMyClass()?.Field?.GetBool();
|
||||
GetMyClass()?.Field.GetBool();
|
||||
GetMyClass()?.Property?.GetBool();
|
||||
GetMyClass()?.Property.GetBool();
|
||||
GetMyClass()?.Method(GetInt())?.GetBool();
|
||||
GetMyClass()?.Method(GetInt()).GetBool();
|
||||
GetMyClass()?[GetInt()]?.GetBool();
|
||||
GetMyClass()?[GetInt()].GetBool();
|
||||
}
|
||||
|
||||
public void CallDoneStruct()
|
||||
{
|
||||
GetMyStruct()?.Done();
|
||||
GetMyStruct()?.Field?.Done();
|
||||
GetMyStruct()?.Field.Done();
|
||||
GetMyStruct()?.Property1?.Done();
|
||||
GetMyStruct()?.Property2.Done();
|
||||
GetMyStruct()?.Method1(GetInt())?.Done();
|
||||
GetMyStruct()?.Method2(GetInt()).Done();
|
||||
GetMyStruct()?[GetInt()]?.Done();
|
||||
}
|
||||
|
||||
public void CallGetBoolStruct()
|
||||
{
|
||||
GetMyStruct()?.GetBool();
|
||||
GetMyStruct()?.Field?.GetBool();
|
||||
GetMyStruct()?.Field.GetBool();
|
||||
GetMyStruct()?.Property1?.GetBool();
|
||||
GetMyStruct()?.Property2.GetBool();
|
||||
GetMyStruct()?.Method1(GetInt())?.GetBool();
|
||||
GetMyStruct()?.Method2(GetInt()).GetBool();
|
||||
GetMyStruct()?[GetInt()]?.GetBool();
|
||||
}
|
||||
|
||||
public void RequiredParentheses()
|
||||
{
|
||||
(GetMyClass()?.Field).Done();
|
||||
(GetMyClass()?.Method(GetInt())).Done();
|
||||
(GetMyStruct()?.Property2)?.Done();
|
||||
}
|
||||
|
||||
public int?[] ChainsOnClass()
|
||||
{
|
||||
return new int?[9] {
|
||||
GetMyClass()?.IntVal,
|
||||
GetMyClass()?.Field.IntVal,
|
||||
GetMyClass()?.Field?.IntVal,
|
||||
GetMyClass()?.Property.IntVal,
|
||||
GetMyClass()?.Property?.IntVal,
|
||||
GetMyClass()?.Method(GetInt()).IntVal,
|
||||
GetMyClass()?.Method(GetInt())?.IntVal,
|
||||
GetMyClass()?[GetInt()].IntVal,
|
||||
GetMyClass()?[GetInt()]?.IntVal
|
||||
};
|
||||
}
|
||||
|
||||
public int?[] ChainsStruct()
|
||||
{
|
||||
return new int?[8] {
|
||||
GetMyStruct()?.IntVal,
|
||||
GetMyStruct()?.Field.IntVal,
|
||||
GetMyStruct()?.Field?.IntVal,
|
||||
GetMyStruct()?.Property2.IntVal,
|
||||
GetMyStruct()?.Property1?.IntVal,
|
||||
GetMyStruct()?.Method2(GetInt()).IntVal,
|
||||
GetMyStruct()?.Method1(GetInt())?.IntVal,
|
||||
GetMyStruct()?[GetInt()]?.IntVal
|
||||
};
|
||||
}
|
||||
|
||||
public int CoalescingReturn()
|
||||
{
|
||||
return GetMyClass()?.IntVal ?? 1;
|
||||
}
|
||||
|
||||
public void Coalescing()
|
||||
{
|
||||
Use(GetMyClass()?.IntVal ?? 1);
|
||||
}
|
||||
|
||||
public void CoalescingString()
|
||||
{
|
||||
Use(GetMyClass()?.Text ?? "Hello");
|
||||
}
|
||||
|
||||
public void CallOnValueTypeField()
|
||||
{
|
||||
Use(GetMyClass()?.IntVal.ToString());
|
||||
Use(GetMyStruct()?.IntVal.ToString());
|
||||
Use(GetMyClass()?.ReadonlyIntVal.ToString());
|
||||
Use(GetMyStruct()?.ReadonlyIntVal.ToString());
|
||||
GetMyClass()?.StructField.Done();
|
||||
GetMyClass()?.ReadonlyStructField.Done();
|
||||
}
|
||||
|
||||
public void InvokeDelegate(EventHandler eh)
|
||||
{
|
||||
eh?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public int? InvokeDelegate(Func<int> f)
|
||||
{
|
||||
return f?.Invoke();
|
||||
}
|
||||
|
||||
private void NotNullPropagation(MyClass c)
|
||||
{
|
||||
// don't decompile this to "(c?.IntVal ?? 0) != 0"
|
||||
if (c != null && c.IntVal != 0)
|
||||
{
|
||||
Console.WriteLine("non-zero");
|
||||
}
|
||||
if (c == null || c.IntVal == 0)
|
||||
{
|
||||
Console.WriteLine("null or zero");
|
||||
}
|
||||
Console.WriteLine("end of method");
|
||||
}
|
||||
|
||||
private void Setter(MyClass c)
|
||||
{
|
||||
if (c != null)
|
||||
{
|
||||
c.IntVal = 1;
|
||||
}
|
||||
Console.WriteLine();
|
||||
if (c != null)
|
||||
{
|
||||
c.Property = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int? GenericUnconstrainedInt<T>(T t) where T : ITest
|
||||
{
|
||||
return t?.Int();
|
||||
}
|
||||
|
||||
private static int? GenericClassConstraintInt<T>(T t) where T : class, ITest
|
||||
{
|
||||
return t?.Int();
|
||||
}
|
||||
|
||||
private static int? GenericStructConstraintInt<T>(T? t) where T : struct, ITest
|
||||
{
|
||||
return t?.Int();
|
||||
}
|
||||
|
||||
private static int? GenericRefUnconstrainedInt<T>(ref T t) where T : ITest
|
||||
{
|
||||
return t?.Int();
|
||||
}
|
||||
|
||||
private static int? GenericRefClassConstraintInt<T>(ref T t) where T : class, ITest
|
||||
{
|
||||
return t?.Int();
|
||||
}
|
||||
|
||||
private static int? GenericRefStructConstraintInt<T>(ref T? t) where T : struct, ITest
|
||||
{
|
||||
return t?.Int();
|
||||
}
|
||||
|
||||
public int? Issue1709(object obj)
|
||||
{
|
||||
return (obj as ICollection)?.Count + (obj as ICollection<int>)?.Count;
|
||||
}
|
||||
|
||||
private static void Issue1689(List<byte[]> setsOfNumbers)
|
||||
{
|
||||
Console.WriteLine(setsOfNumbers?[0]?[1].ToString() == "2");
|
||||
Console.WriteLine(setsOfNumbers?[1]?[1].ToString() == null);
|
||||
}
|
||||
|
||||
private static byte[] GetBytes()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ArrayLengthWithFallback()
|
||||
{
|
||||
Console.WriteLine(GetBytes()?.Length ?? 0);
|
||||
Console.WriteLine(GetBytes()?.LongLength ?? 0);
|
||||
}
|
||||
|
||||
private static dynamic DynamicNullProp(dynamic a)
|
||||
{
|
||||
return a?.b.c(1)?.d[10];
|
||||
}
|
||||
|
||||
private static string Issue3181()
|
||||
{
|
||||
#if EXPECTED_OUTPUT
|
||||
return ((int?)null)?.ToString();
|
||||
#else
|
||||
int? x = null;
|
||||
return x?.ToString();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1318,8 +1318,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
return false;
|
||||
|
||||
var firstArgument = argumentList.Arguments[0];
|
||||
bool canWriteTypeArguments = !settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType());
|
||||
bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0 && canWriteTypeArguments;
|
||||
bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0
|
||||
&& (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType()));
|
||||
IType[] typeArguments = writeTypeArguments ? method.TypeArguments.ToArray() : Empty<IType>.Array;
|
||||
|
||||
var directionExpression = firstArgument.Expression as DirectionExpression;
|
||||
@@ -1342,19 +1342,9 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
string[]? remainingNames = argumentNames?.Take(actualArgumentCount).Skip(1).ToArray();
|
||||
if (remainingNames != null && remainingNames.All(name => name == null))
|
||||
remainingNames = null;
|
||||
var remainingArguments = argumentList.GetArgumentResolveResults(skipCount: 1).ToArray();
|
||||
if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target, remainingArguments, remainingNames))
|
||||
if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target,
|
||||
argumentList.GetArgumentResolveResults(skipCount: 1).ToArray(), remainingNames))
|
||||
{
|
||||
// 'a?.M()' has no static call form to fall back to. Spelled-out type arguments rule out
|
||||
// extension methods of a different arity, which NullPropagationTransform relies on.
|
||||
if (writeTypeArguments || !canWriteTypeArguments || method.TypeArguments.Count == 0
|
||||
|| !IsNullConditional(firstArgument.Expression))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
writeTypeArguments = true;
|
||||
typeArguments = method.TypeArguments.ToArray();
|
||||
if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target, remainingArguments, remainingNames))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
using ICSharpCode.Decompiler.CSharp.Syntax;
|
||||
using ICSharpCode.Decompiler.IL;
|
||||
using ICSharpCode.Decompiler.Semantics;
|
||||
|
||||
namespace ICSharpCode.Decompiler.CSharp.Transforms
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompiles query expressions.
|
||||
/// Based on C# 4.0 spec, §7.16.2 Query expression translation
|
||||
/// </summary>
|
||||
public class IntroduceQueryExpressions : IAstTransform
|
||||
{
|
||||
[AllowNull]
|
||||
TransformContext context;
|
||||
|
||||
public void Run(AstNode rootNode, TransformContext context)
|
||||
{
|
||||
if (!context.Settings.QueryExpressions)
|
||||
return;
|
||||
this.context = context;
|
||||
DecompileQueries(rootNode);
|
||||
// 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 rootNode.Descendants.OfType<QueryExpression>())
|
||||
{
|
||||
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First();
|
||||
if (IsDegenerateQuery(query))
|
||||
{
|
||||
// introduce select for degenerate query
|
||||
context.Step("Add degenerate query select clause", query);
|
||||
query.Clauses.Add(new QuerySelectClause { Expression = new IdentifierExpression(fromClause.Identifier).CopyAnnotationsFrom(fromClause) });
|
||||
}
|
||||
// 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 (innerQuery != null && IsDegenerateQuery(innerQuery))
|
||||
{
|
||||
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First();
|
||||
ILVariable? innerVariable = innerFromClause.Annotation<ILVariableResolveResult>()?.Variable;
|
||||
ILVariable? rangeVariable = fromClause.Annotation<ILVariableResolveResult>()?.Variable;
|
||||
context.Step("Combine nested query clauses", fromClause);
|
||||
// Replace the fromClause with all clauses from the inner query
|
||||
fromClause.Remove();
|
||||
QueryClause? insertionPos = null;
|
||||
foreach (var clause in innerQuery.Clauses)
|
||||
{
|
||||
CombineRangeVariables(clause, innerVariable, rangeVariable);
|
||||
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach());
|
||||
}
|
||||
context.EndStep(innerFromClause);
|
||||
fromClause = innerFromClause;
|
||||
innerQuery = fromClause.Expression as QueryExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CombineRangeVariables(QueryClause clause, ILVariable? oldVariable, ILVariable? newVariable)
|
||||
{
|
||||
if (oldVariable == null || newVariable == null)
|
||||
return;
|
||||
foreach (var identifier in clause.DescendantNodes().OfType<Identifier>())
|
||||
{
|
||||
var parent = identifier.Parent;
|
||||
if (parent == null)
|
||||
continue;
|
||||
var variable = parent.Annotation<ILVariableResolveResult>()?.Variable;
|
||||
if (variable == oldVariable)
|
||||
{
|
||||
context.Step("Combine query range variables", identifier);
|
||||
parent.RemoveAnnotations<ILVariableResolveResult>();
|
||||
parent.AddAnnotation(new ILVariableResolveResult(newVariable));
|
||||
var newIdentifier = Identifier.Create(newVariable.Name!);
|
||||
identifier.ReplaceWith(newIdentifier);
|
||||
context.EndStep(newIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDegenerateQuery(QueryExpression? query)
|
||||
{
|
||||
if (query == null)
|
||||
return false;
|
||||
var lastClause = query.Clauses.LastOrDefault();
|
||||
return !(lastClause is QuerySelectClause || lastClause is QueryGroupClause);
|
||||
}
|
||||
|
||||
void DecompileQueries(AstNode node)
|
||||
{
|
||||
Expression? query = DecompileQuery(node as InvocationExpression);
|
||||
if (query != null)
|
||||
{
|
||||
if (node.Parent is ExpressionStatement && CanUseDiscardAssignment())
|
||||
query = new AssignmentExpression(new IdentifierExpression("_"), query);
|
||||
node.ReplaceWith(query);
|
||||
context.EndStep(query);
|
||||
}
|
||||
|
||||
AstNode? next;
|
||||
for (AstNode? child = (query ?? node).FirstChild; child != null; child = next)
|
||||
{
|
||||
// store reference to next child before transformation
|
||||
next = child.NextSibling;
|
||||
DecompileQueries(child);
|
||||
}
|
||||
}
|
||||
|
||||
bool CanUseDiscardAssignment()
|
||||
{
|
||||
// TODO : check whether there exists a variable named '_' in scope.
|
||||
return context.Settings.Discards;
|
||||
}
|
||||
|
||||
QueryExpression? DecompileQuery(InvocationExpression? invocation)
|
||||
{
|
||||
if (invocation == null)
|
||||
return null;
|
||||
MemberReferenceExpression? mre = invocation.Target as MemberReferenceExpression;
|
||||
if (mre == null || IsNullConditional(mre.Target))
|
||||
return null;
|
||||
if (mre.TypeArguments.Count > 0)
|
||||
return null;
|
||||
switch (mre.MemberName)
|
||||
{
|
||||
case "Select":
|
||||
{
|
||||
if (invocation.Arguments.Count != 1)
|
||||
return null;
|
||||
if (!IsComplexQuery(mre))
|
||||
return null;
|
||||
Expression expr = invocation.Arguments.Single();
|
||||
if (MatchSimpleLambda(expr, out var parameter, out var body))
|
||||
{
|
||||
context.Step("Build select query", invocation);
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
|
||||
query.Clauses.Add(new QuerySelectClause { Expression = WrapExpressionInParenthesesIfNecessary(body.Detach(), parameter.Name!) }.CopyAnnotationsFrom(expr));
|
||||
return query;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "GroupBy":
|
||||
{
|
||||
if (invocation.Arguments.Count == 2)
|
||||
{
|
||||
Expression keyLambda = invocation.Arguments.ElementAt(0);
|
||||
Expression projectionLambda = invocation.Arguments.ElementAt(1);
|
||||
if (MatchSimpleLambda(keyLambda, out var parameter1, out var keySelector)
|
||||
&& MatchSimpleLambda(projectionLambda, out var parameter2, out var elementSelector)
|
||||
&& parameter1.Name == parameter2.Name)
|
||||
{
|
||||
context.Step("Build group query", invocation);
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(parameter1, mre.Target.Detach()));
|
||||
var queryGroupClause = new QueryGroupClause {
|
||||
Projection = elementSelector.Detach(),
|
||||
Key = keySelector.Detach()
|
||||
};
|
||||
// The matched group-by lambdas always carry their ILFunction annotation.
|
||||
queryGroupClause.AddAnnotation(new QueryGroupClauseAnnotation(keyLambda.Annotation<IL.ILFunction>()!, projectionLambda.Annotation<IL.ILFunction>()!));
|
||||
query.Clauses.Add(queryGroupClause);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
else if (invocation.Arguments.Count == 1)
|
||||
{
|
||||
Expression lambda = invocation.Arguments.Single();
|
||||
if (MatchSimpleLambda(lambda, out var parameter, out var keySelector))
|
||||
{
|
||||
context.Step("Build group query", invocation);
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
|
||||
query.Clauses.Add(new QueryGroupClause { Projection = new IdentifierExpression(parameter.Name!).CopyAnnotationsFrom(parameter), Key = keySelector.Detach() });
|
||||
return query;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "SelectMany":
|
||||
{
|
||||
if (invocation.Arguments.Count != 2)
|
||||
return null;
|
||||
var fromExpressionLambda = invocation.Arguments.ElementAt(0);
|
||||
if (!MatchSimpleLambda(fromExpressionLambda, out var parameter, out var collectionSelector))
|
||||
return null;
|
||||
if (IsNullConditional(collectionSelector))
|
||||
return null;
|
||||
LambdaExpression? lambda = invocation.Arguments.ElementAt(1) as LambdaExpression;
|
||||
if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression)
|
||||
{
|
||||
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
|
||||
ParameterDeclaration p2 = lambda.Parameters.ElementAt(1);
|
||||
if (p1.Name == parameter.Name)
|
||||
{
|
||||
context.Step("Build select-many query", invocation);
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(p1, mre.Target.Detach()));
|
||||
query.Clauses.Add(MakeFromClause(p2, collectionSelector.Detach()).CopyAnnotationsFrom(fromExpressionLambda));
|
||||
query.Clauses.Add(new QuerySelectClause { Expression = WrapExpressionInParenthesesIfNecessary(((Expression)lambda.Body).Detach(), parameter.Name!) });
|
||||
return query;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "Where":
|
||||
{
|
||||
if (invocation.Arguments.Count != 1)
|
||||
return null;
|
||||
if (!IsComplexQuery(mre))
|
||||
return null;
|
||||
Expression expr = invocation.Arguments.Single();
|
||||
if (MatchSimpleLambda(expr, out var parameter, out var body))
|
||||
{
|
||||
context.Step("Build where query", invocation);
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
|
||||
query.Clauses.Add(new QueryWhereClause { Condition = body.Detach() }.CopyAnnotationsFrom(expr));
|
||||
return query;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "OrderBy":
|
||||
case "OrderByDescending":
|
||||
case "ThenBy":
|
||||
case "ThenByDescending":
|
||||
{
|
||||
if (invocation.Arguments.Count != 1)
|
||||
return null;
|
||||
if (!IsComplexQuery(mre))
|
||||
return null;
|
||||
var lambda = invocation.Arguments.Single();
|
||||
if (MatchSimpleLambda(lambda, out var parameter, out var orderExpression))
|
||||
{
|
||||
if (ValidateThenByChain(invocation, parameter.Name!))
|
||||
{
|
||||
context.Step("Build order query", invocation);
|
||||
QueryOrderClause orderClause = new QueryOrderClause();
|
||||
while (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending")
|
||||
{
|
||||
// insert new ordering at beginning
|
||||
orderClause.Orderings.InsertAfter(
|
||||
null, new QueryOrdering {
|
||||
Expression = orderExpression.Detach(),
|
||||
Direction = (mre.MemberName == "ThenBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending)
|
||||
}.CopyAnnotationsFrom(lambda));
|
||||
|
||||
InvocationExpression tmp = (InvocationExpression)mre.Target;
|
||||
mre = (MemberReferenceExpression)tmp.Target;
|
||||
lambda = tmp.Arguments.Single();
|
||||
// ValidateThenByChain already confirmed every clause in the chain is a simple
|
||||
// lambda, so this match always succeeds.
|
||||
bool matched = MatchSimpleLambda(lambda, out parameter, out orderExpression);
|
||||
Debug.Assert(matched);
|
||||
Debug.Assert(orderExpression != null);
|
||||
}
|
||||
// The chain was validated and every iteration re-matched a simple lambda,
|
||||
// so the final range-variable parameter is set.
|
||||
Debug.Assert(parameter != null);
|
||||
// insert new ordering at beginning
|
||||
orderClause.Orderings.InsertAfter(
|
||||
null, new QueryOrdering {
|
||||
Expression = orderExpression.Detach(),
|
||||
Direction = (mre.MemberName == "OrderBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending)
|
||||
}.CopyAnnotationsFrom(lambda));
|
||||
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(parameter, mre.Target.Detach()));
|
||||
query.Clauses.Add(orderClause);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "Join":
|
||||
case "GroupJoin":
|
||||
{
|
||||
if (invocation.Arguments.Count != 4)
|
||||
return null;
|
||||
Expression source1 = mre.Target;
|
||||
Expression source2 = invocation.Arguments.ElementAt(0);
|
||||
if (IsNullConditional(source2))
|
||||
return null;
|
||||
Expression outerLambda = invocation.Arguments.ElementAt(1);
|
||||
if (!MatchSimpleLambda(outerLambda, out var element1, out var key1))
|
||||
return null;
|
||||
Expression innerLambda = invocation.Arguments.ElementAt(2);
|
||||
if (!MatchSimpleLambda(innerLambda, out var element2, out var key2))
|
||||
return null;
|
||||
LambdaExpression? lambda = invocation.Arguments.ElementAt(3) as LambdaExpression;
|
||||
if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression)
|
||||
{
|
||||
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0);
|
||||
ParameterDeclaration p2 = lambda.Parameters.ElementAt(1);
|
||||
if (ValidateParameter(p1) && ValidateParameter(p2)
|
||||
&& p1.Name == element1.Name && (p2.Name == element2.Name || mre.MemberName == "GroupJoin"))
|
||||
{
|
||||
context.Step(mre.MemberName == "GroupJoin" ? "Build group join query" : "Build join query", invocation);
|
||||
QueryExpression query = new QueryExpression();
|
||||
query.Clauses.Add(MakeFromClause(element1, source1.Detach()));
|
||||
QueryJoinClause joinClause = new QueryJoinClause();
|
||||
joinClause.JoinIdentifier = element2.Name!; // join elementName2
|
||||
joinClause.JoinIdentifierToken.CopyAnnotationsFrom(element2);
|
||||
joinClause.InExpression = source2.Detach(); // in source2
|
||||
joinClause.OnExpression = key1.Detach(); // on key1
|
||||
joinClause.EqualsExpression = key2.Detach(); // equals key2
|
||||
if (mre.MemberName == "GroupJoin")
|
||||
{
|
||||
joinClause.IntoIdentifier = p2.Name; // into p2.Name
|
||||
joinClause.IntoIdentifierToken!.CopyAnnotationsFrom(p2);
|
||||
}
|
||||
// The matched join key-selector lambdas always carry their ILFunction annotation.
|
||||
joinClause.AddAnnotation(new QueryJoinClauseAnnotation(outerLambda.Annotation<IL.ILFunction>()!, innerLambda.Annotation<IL.ILFunction>()!));
|
||||
query.Clauses.Add(joinClause);
|
||||
query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() }.CopyAnnotationsFrom(lambda));
|
||||
return query;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsComplexQuery(MemberReferenceExpression mre)
|
||||
{
|
||||
return (mre.Target is InvocationExpression && mre.Parent is InvocationExpression) || mre.Parent?.Parent is QueryClause;
|
||||
}
|
||||
|
||||
QueryFromClause MakeFromClause(ParameterDeclaration parameter, Expression body)
|
||||
{
|
||||
QueryFromClause fromClause = new QueryFromClause {
|
||||
Identifier = parameter.Name!,
|
||||
Expression = body
|
||||
};
|
||||
fromClause.CopyAnnotationsFrom(parameter);
|
||||
return fromClause;
|
||||
}
|
||||
|
||||
class ApplyAnnotationVisitor : DepthFirstAstVisitor<AstNode>
|
||||
{
|
||||
private LetIdentifierAnnotation annotation;
|
||||
private string identifier;
|
||||
|
||||
public ApplyAnnotationVisitor(LetIdentifierAnnotation annotation, string identifier)
|
||||
{
|
||||
this.annotation = annotation;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public override AstNode VisitIdentifier(Identifier identifier)
|
||||
{
|
||||
if (identifier.Name == this.identifier)
|
||||
identifier.AddAnnotation(annotation);
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsNullConditional(Expression target) => target switch {
|
||||
UnaryOperatorExpression { Operator: UnaryOperatorType.NullConditional } => true,
|
||||
MemberReferenceExpression member => IsNullConditional(member.Target),
|
||||
InvocationExpression invocation => IsNullConditional(invocation.Target),
|
||||
IndexerExpression { Target: { } indexerTarget } => IsNullConditional(indexerTarget),
|
||||
_ => false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// This fixes #437: Decompilation of query expression loses material parentheses
|
||||
/// We wrap the expression in parentheses if:
|
||||
/// - the Select-call is explicit (see caller(s))
|
||||
/// - the expression is a plain identifier matching the parameter name
|
||||
/// </summary>
|
||||
Expression WrapExpressionInParenthesesIfNecessary(Expression expression, string parameterName)
|
||||
{
|
||||
if (expression is IdentifierExpression ident && parameterName.Equals(ident.Identifier, StringComparison.Ordinal))
|
||||
return new ParenthesizedExpression(expression);
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation.
|
||||
/// </summary>
|
||||
bool ValidateThenByChain(InvocationExpression? invocation, string expectedParameterName)
|
||||
{
|
||||
if (invocation == null || invocation.Arguments.Count != 1)
|
||||
return false;
|
||||
if (!(invocation.Target is MemberReferenceExpression mre))
|
||||
return false;
|
||||
if (!MatchSimpleLambda(invocation.Arguments.Single(), out var parameter, out _))
|
||||
return false;
|
||||
if (parameter.Name != expectedParameterName)
|
||||
return false;
|
||||
if (mre.TypeArguments.Count > 0)
|
||||
return false;
|
||||
|
||||
if (mre.MemberName == "OrderBy" || mre.MemberName == "OrderByDescending")
|
||||
return !IsNullConditional(mre.Target);
|
||||
else if (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending")
|
||||
return ValidateThenByChain(mre.Target as InvocationExpression, expectedParameterName);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Matches simple lambdas of the form "a => b"</summary>
|
||||
bool MatchSimpleLambda(Expression expr, [NotNullWhen(true)] out ParameterDeclaration? parameter, [NotNullWhen(true)] out Expression? body)
|
||||
{
|
||||
if (expr is LambdaExpression lambda && lambda.Parameters.Count == 1 && lambda.Body is Expression)
|
||||
{
|
||||
if (lambda.GetResolveResult() is DecompiledLambdaResolveResult { AttemptedConversionWithTypeMismatch: true })
|
||||
{
|
||||
parameter = null;
|
||||
body = null;
|
||||
return false;
|
||||
}
|
||||
ParameterDeclaration p = lambda.Parameters.Single();
|
||||
if (ValidateParameter(p))
|
||||
{
|
||||
parameter = p;
|
||||
body = (Expression)lambda.Body;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
parameter = null;
|
||||
body = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ValidateParameter(ParameterDeclaration p)
|
||||
{
|
||||
return p.ParameterModifier == Decompiler.TypeSystem.ReferenceKind.None && p.Attributes.Count == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user