Compare commits
10
Commits
197acf8cbe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a20c44d9 | ||
|
|
85285c1cea | ||
|
|
ef47a24511 | ||
|
|
72e7db13fa | ||
|
|
6e2842992c | ||
|
|
ffe7e7b3f1 | ||
|
|
8e3e41fedb | ||
|
|
3f1ef717b6 | ||
|
|
587f5c9583 | ||
|
|
999d3b48fa |
No files matched your search
@@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
|
||||||
|
{
|
||||||
|
class NullPropagation
|
||||||
|
{
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
new NullPropagation().TestNotCoalescing();
|
||||||
|
new NullPropagation().TestNullableBoolInChain();
|
||||||
|
#if CS72
|
||||||
|
new NullPropagation().TestRefStructNotCoalescing();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyClass
|
||||||
|
{
|
||||||
|
public string Text;
|
||||||
|
public bool? Flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestNotCoalescing()
|
||||||
|
{
|
||||||
|
Console.WriteLine("TestNotCoalescing:");
|
||||||
|
Console.WriteLine(NotCoalescing(null));
|
||||||
|
Console.WriteLine(NotCoalescing(new MyClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
string NotCoalescing(MyClass c)
|
||||||
|
{
|
||||||
|
return c != null ? c.Text : "Hello";
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestNullableBoolInChain()
|
||||||
|
{
|
||||||
|
Console.WriteLine("TestNullableBoolInChain:");
|
||||||
|
foreach (MyClass c in new[] { null, new MyClass(), new MyClass { Flag = false }, new MyClass { Flag = true } })
|
||||||
|
{
|
||||||
|
Console.WriteLine(EqualsTrue(c));
|
||||||
|
Console.WriteLine(GetValueOrDefaultToString(c) ?? "<null>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EqualsTrue(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.Flag == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetValueOrDefaultToString(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.Flag.GetValueOrDefault().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#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,710 @@
|
|||||||
|
// Copyright (c) 2026 Siegfried Pammer
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// An await shape that still decompiles to code that does not compile is tracked as #4017 (type
|
||||||
|
// parameter with an interface constraint); it is absent here because it has no correct output to
|
||||||
|
// pin yet.
|
||||||
|
|
||||||
|
#pragma warning disable 1998
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait
|
||||||
|
{
|
||||||
|
public class AwaitableContainer
|
||||||
|
{
|
||||||
|
public class NestedAwaitable
|
||||||
|
{
|
||||||
|
public TaskAwaiter GetAwaiter()
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The context the await sits in: how the translated expression has to be parenthesized, and
|
||||||
|
/// where the async state machine splits the surrounding statement.
|
||||||
|
/// </summary>
|
||||||
|
public class AwaitContexts
|
||||||
|
{
|
||||||
|
#if CS80 && !NET40
|
||||||
|
private sealed class AsyncDisposable : IAsyncDisposable
|
||||||
|
{
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private static Task<int> Get()
|
||||||
|
{
|
||||||
|
return Task.FromResult(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<string> GetString()
|
||||||
|
{
|
||||||
|
return Task.FromResult("s");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<Exception> GetException()
|
||||||
|
{
|
||||||
|
return Task.FromResult(new Exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Statement()
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Argument()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task BinaryOperator()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await Get() + await Get());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task UnaryOperator()
|
||||||
|
{
|
||||||
|
Console.WriteLine(-(await Get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task MemberAccessOnResult()
|
||||||
|
{
|
||||||
|
Console.WriteLine((await GetString()).Length);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task IndexerOnResult()
|
||||||
|
{
|
||||||
|
Console.WriteLine((await GetString())[0]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task CoalesceOnResult()
|
||||||
|
{
|
||||||
|
Console.WriteLine((await GetString()) ?? "null");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public async Task ThrowAwaitedException()
|
||||||
|
{
|
||||||
|
throw await GetException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task Checked()
|
||||||
|
{
|
||||||
|
Console.WriteLine(checked(await Get() + 1));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CS60
|
||||||
|
public async Task TryFinally()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public async Task Using()
|
||||||
|
{
|
||||||
|
using (new Disposable())
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS60
|
||||||
|
public async Task ConditionalAccessOnResult()
|
||||||
|
{
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
Console.WriteLine((await GetString())?.Length);
|
||||||
|
#else
|
||||||
|
object value = (await GetString())?.Length;
|
||||||
|
Console.WriteLine(value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CatchWithFilter()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex.Message.Length > 2)
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CS70 && !NET40
|
||||||
|
public async Task AwaitInTupleLiteral()
|
||||||
|
{
|
||||||
|
Console.WriteLine((await Get(), await GetString()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CS80 && !NET40
|
||||||
|
public async Task AwaitUsing()
|
||||||
|
{
|
||||||
|
await using (new AsyncDisposable())
|
||||||
|
{
|
||||||
|
await Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AwaitForeach(IAsyncEnumerable<int> source)
|
||||||
|
{
|
||||||
|
await foreach (int item in source)
|
||||||
|
{
|
||||||
|
Console.WriteLine(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AwaitForeachConfigured(IAsyncEnumerable<int> source)
|
||||||
|
{
|
||||||
|
await foreach (int item in source.ConfigureAwait(continueOnCapturedContext: false))
|
||||||
|
{
|
||||||
|
Console.WriteLine(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<int> AsyncIterator()
|
||||||
|
{
|
||||||
|
yield return await Get();
|
||||||
|
await Task.Yield();
|
||||||
|
yield return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<int> AsyncIteratorWithFinally()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
yield return await Get();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Console.WriteLine("cleanup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LocalFunction()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await Local());
|
||||||
|
static async Task<int> Local()
|
||||||
|
{
|
||||||
|
return await Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2 || OPT
|
||||||
|
public async Task AwaitInGenericMethod<T>(Task<T> task)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await task);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AwaiterExtensions
|
||||||
|
{
|
||||||
|
public static TaskAwaiter GetAwaiter(this IAwaitableMarker marker)
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TaskAwaiter GetAwaiter(this int millisecondsDelay)
|
||||||
|
{
|
||||||
|
return Task.Delay(millisecondsDelay).GetAwaiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TaskAwaiter GetAwaiter(this Action action)
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TaskAwaiter<T[]> GetAwaiter<T>(this IEnumerable<Task<T>> tasks)
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter<T[]>);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS70 && !NET40
|
||||||
|
public static TaskAwaiter<T> GetAwaiter<T>(this (Task<T>, string) taggedTask)
|
||||||
|
{
|
||||||
|
return taggedTask.Item1.GetAwaiter();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CS72
|
||||||
|
public static TaskAwaiter GetAwaiter(this in ByRefReceiver receiver)
|
||||||
|
{
|
||||||
|
return receiver.Self();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The operand side: the shape of the expression the await is applied to.
|
||||||
|
/// </summary>
|
||||||
|
public class AwaitOperands
|
||||||
|
{
|
||||||
|
private Task<int> taskField;
|
||||||
|
|
||||||
|
private StructAwaitable structField;
|
||||||
|
|
||||||
|
private readonly StructAwaitable readonlyStructField;
|
||||||
|
|
||||||
|
private Task<int> Property {
|
||||||
|
get {
|
||||||
|
Console.WriteLine("get_Property");
|
||||||
|
return taskField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<int> this[int index] {
|
||||||
|
get {
|
||||||
|
Console.WriteLine("get_Item");
|
||||||
|
return taskField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<int> Get()
|
||||||
|
{
|
||||||
|
return Task.FromResult(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DefaultOfStruct()
|
||||||
|
{
|
||||||
|
await default(StructAwaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Ternary(bool condition, Task first, Task second)
|
||||||
|
{
|
||||||
|
await (condition ? first : second);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Coalesce(Task first, Task second)
|
||||||
|
{
|
||||||
|
await (first ?? second);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS60
|
||||||
|
public async Task NullConditional(List<Task> tasks)
|
||||||
|
{
|
||||||
|
await (tasks?[0]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public async Task Cast(object obj)
|
||||||
|
{
|
||||||
|
await (Task)obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A null literal has no type, so the cast is what makes the operand awaitable. Note that
|
||||||
|
/// <c>default(Task)</c> compiles to the same `ldnull` and therefore decompiles to this same
|
||||||
|
/// cast; the two are indistinguishable in IL.
|
||||||
|
/// </summary>
|
||||||
|
public async Task NullLiteral()
|
||||||
|
{
|
||||||
|
await (Task)null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AsOperator(object obj)
|
||||||
|
{
|
||||||
|
await (obj as Task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task FieldAccess()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await taskField);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PropertyAccess()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await Property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task IndexerAccess()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await this[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StructField()
|
||||||
|
{
|
||||||
|
await structField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReadOnlyStructField()
|
||||||
|
{
|
||||||
|
await readonlyStructField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StructArrayElement(StructAwaitable[] awaitables)
|
||||||
|
{
|
||||||
|
await awaitables[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MethodCall()
|
||||||
|
{
|
||||||
|
Console.WriteLine(await Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DelegateInvocation(Func<Task<int>> factory)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await factory());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ArrayElement(Task<int>[] tasks)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await tasks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TernaryOfTasks(bool condition)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await (condition ? Get() : Get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The receiver ("expected type") side of ExpressionBuilder.VisitAwait: the awaited expression
|
||||||
|
/// is converted to the declaring type of the resolved GetAwaiter, or to its first parameter
|
||||||
|
/// type when GetAwaiter is an extension method.
|
||||||
|
/// </summary>
|
||||||
|
public class AwaitReceivers
|
||||||
|
{
|
||||||
|
public async Task InstanceAwaiterOnSelf(ClassAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AwaiterInheritedFromBaseClass(DerivedAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AwaiterThroughInterface(IAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AwaiterThroughBaseInterface(IDerivedAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cast carries the operand to the interface that declares GetAwaiter; without it the
|
||||||
|
/// explicit implementation is not accessible. A conversion that is merely implicit must not
|
||||||
|
/// be dropped here, even though a boxing conversion exists.
|
||||||
|
/// </summary>
|
||||||
|
public async Task ExplicitInterfaceImplementationOnStruct(ExplicitStructAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await (IAwaitable)awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The same shape on a class, i.e. it is not specific to the boxing conversion.
|
||||||
|
/// </summary>
|
||||||
|
public async Task ExplicitInterfaceImplementationOnClass(ExplicitClassAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await (IAwaitable)awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The await pattern does not apply user-defined conversions, so the cast that invokes
|
||||||
|
/// op_Implicit has to survive.
|
||||||
|
/// </summary>
|
||||||
|
public async Task UserDefinedConversionToAwaitable(ConvertsToAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await (ClassAwaitable)awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExtensionAwaiterOnClass(MarkerClass marker)
|
||||||
|
{
|
||||||
|
await marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExtensionAwaiterOnStruct(MarkerStruct marker)
|
||||||
|
{
|
||||||
|
await marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExtensionAwaiterOnPrimitive()
|
||||||
|
{
|
||||||
|
await 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExtensionAwaiterOnDelegate(Action action)
|
||||||
|
{
|
||||||
|
await action;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS72
|
||||||
|
/// <summary>
|
||||||
|
/// An extension GetAwaiter taking its receiver by 'in' makes the expected type a
|
||||||
|
/// ByReferenceType. Stripping the 'ref' must not leave a conversion that reaches the
|
||||||
|
/// managed reference back through a pointer.
|
||||||
|
/// </summary>
|
||||||
|
public async Task InReceiverExtensionAwaiter(ByRefReceiver receiver)
|
||||||
|
{
|
||||||
|
await receiver;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2
|
||||||
|
public async Task ExtensionAwaiterOverTaskArray(Task<int>[] tasks)
|
||||||
|
{
|
||||||
|
Console.WriteLine((await tasks)[0]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ROSLYN2
|
||||||
|
public async Task ExtensionAwaiterOverTaskList(List<Task<int>> tasks)
|
||||||
|
{
|
||||||
|
Console.WriteLine((await tasks)[0]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public async Task GenericAwaitableType(GenericAwaitable<string> awaitable)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task NestedAwaitableType(AwaitableContainer.NestedAwaitable awaitable)
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TypeParameterWithClassConstraint<T>(T awaitable) where T : ClassAwaitable
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TypeParameterWithStructConstraint<T>(T awaitable) where T : struct, IAwaitable
|
||||||
|
{
|
||||||
|
await awaitable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ConfiguredTaskAwaitable(Task<int> task)
|
||||||
|
{
|
||||||
|
#if ROSLYN2
|
||||||
|
Console.WriteLine(await task.ConfigureAwait(continueOnCapturedContext: false));
|
||||||
|
#else
|
||||||
|
Console.WriteLine(await task.ConfigureAwait(false));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS70 && !NET40
|
||||||
|
public async Task ExtensionAwaiterOnTuple(Task<int> task)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await (task, "tag"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CS80 && !NET40
|
||||||
|
public async Task ValueTaskAwaitable(ValueTask<int> task)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ConfiguredValueTaskAwaitable(ValueTask<int> task)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await task.ConfigureAwait(continueOnCapturedContext: false));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if NET80
|
||||||
|
public async Task ConfigureAwaitWithOptions(Task task)
|
||||||
|
{
|
||||||
|
await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public async Task AwaitOfAwait(Task<Task<int>> task)
|
||||||
|
{
|
||||||
|
Console.WriteLine(await (await task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS72
|
||||||
|
public struct ByRefReceiver
|
||||||
|
{
|
||||||
|
public long A;
|
||||||
|
|
||||||
|
public long B;
|
||||||
|
|
||||||
|
public TaskAwaiter Self()
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public class ClassAwaitable : IAwaitable
|
||||||
|
{
|
||||||
|
public TaskAwaiter GetAwaiter()
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConvertsToAwaitable
|
||||||
|
{
|
||||||
|
public static implicit operator ClassAwaitable(ConvertsToAwaitable value)
|
||||||
|
{
|
||||||
|
return new ClassAwaitable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DerivedAwaitable : ClassAwaitable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Disposable : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExplicitClassAwaitable : IAwaitable
|
||||||
|
{
|
||||||
|
TaskAwaiter IAwaitable.GetAwaiter()
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 1)]
|
||||||
|
public struct ExplicitStructAwaitable : IAwaitable
|
||||||
|
{
|
||||||
|
TaskAwaiter IAwaitable.GetAwaiter()
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericAwaitable<T>
|
||||||
|
{
|
||||||
|
public TaskAwaiter<T> GetAwaiter()
|
||||||
|
{
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter<T>);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAwaitable
|
||||||
|
{
|
||||||
|
TaskAwaiter GetAwaiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAwaitableMarker
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IBaseAwaitable
|
||||||
|
{
|
||||||
|
TaskAwaiter GetAwaiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDerivedAwaitable : IBaseAwaitable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MarkerClass : IAwaitableMarker
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 1)]
|
||||||
|
public struct MarkerStruct : IAwaitableMarker
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct StructAwaitable : IAwaitable
|
||||||
|
{
|
||||||
|
public int Counter;
|
||||||
|
|
||||||
|
public TaskAwaiter GetAwaiter()
|
||||||
|
{
|
||||||
|
Counter++;
|
||||||
|
#if CS71
|
||||||
|
return default;
|
||||||
|
#else
|
||||||
|
return default(TaskAwaiter);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -254,5 +254,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|||||||
// Explicit `unchecked` is necessary when casting oversized constant to nuint
|
// Explicit `unchecked` is necessary when casting oversized constant to nuint
|
||||||
return unchecked((nuint)9123123123123uL);
|
return unchecked((nuint)9123123123123uL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public nint ConditionalWithMixedBranchTypes(bool b, int i, nuint x, nint y)
|
||||||
|
{
|
||||||
|
return (b ? ((nint)i) : ((nint)x)) - y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
// 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 bool? NullableBool;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Issue3702(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.NullableBool.GetValueOrDefault() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Issue3702ToString(MyClass c)
|
||||||
|
{
|
||||||
|
return c?.NullableBool.GetValueOrDefault().ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,546 @@
|
|||||||
|
using System;
|
||||||
|
#if CS100
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
#endif
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||||
|
{
|
||||||
|
internal class RecordClasses
|
||||||
|
{
|
||||||
|
public record Base(string A);
|
||||||
|
|
||||||
|
public record CopyCtor(string A)
|
||||||
|
{
|
||||||
|
protected CopyCtor(CopyCtor _)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Derived(int B) : Base(B.ToString());
|
||||||
|
|
||||||
|
public record BaseRecordWithObject(object Id);
|
||||||
|
|
||||||
|
public record DerivedRecordWithString : BaseRecordWithObject
|
||||||
|
{
|
||||||
|
public DerivedRecordWithString(string Id)
|
||||||
|
: base(Id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Empty;
|
||||||
|
|
||||||
|
public record EmptyWithStaticField
|
||||||
|
{
|
||||||
|
public static readonly Empty X = new Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Fields
|
||||||
|
{
|
||||||
|
public int A;
|
||||||
|
public double B = 1.0;
|
||||||
|
public object C;
|
||||||
|
public dynamic D;
|
||||||
|
public string S = "abc";
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Interface(int B) : IRecord;
|
||||||
|
|
||||||
|
public interface IRecord
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Pair<A, B>
|
||||||
|
{
|
||||||
|
public A First { get; init; }
|
||||||
|
public B Second { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PairWithPrimaryCtor<A, B>(A First, B Second);
|
||||||
|
|
||||||
|
public record PrimaryCtor(int A, string B);
|
||||||
|
public record PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a);
|
||||||
|
public record PrimaryCtorWithField(int A, string B)
|
||||||
|
{
|
||||||
|
public double C = 1.0;
|
||||||
|
public string D = A + B;
|
||||||
|
}
|
||||||
|
public record PrimaryCtorWithInParameter(in int A, in string B);
|
||||||
|
public record PrimaryCtorWithProperty(int A, string B)
|
||||||
|
{
|
||||||
|
public double C { get; init; } = 1.0;
|
||||||
|
public string D { get; } = A + B;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Properties
|
||||||
|
{
|
||||||
|
public int A { get; set; }
|
||||||
|
public int B { get; }
|
||||||
|
public int C => 43;
|
||||||
|
public object O { get; set; }
|
||||||
|
public string S { get; set; }
|
||||||
|
public dynamic D { get; set; }
|
||||||
|
|
||||||
|
public Properties()
|
||||||
|
{
|
||||||
|
B = 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS110
|
||||||
|
public record WithRequiredMembers
|
||||||
|
{
|
||||||
|
public required Type ServiceType { get; init; }
|
||||||
|
|
||||||
|
public required object ServiceKey { get; init; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public class RecordTestAttribute : Attribute
|
||||||
|
{
|
||||||
|
public RecordTestAttribute(string name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record Sealed(string A);
|
||||||
|
|
||||||
|
public sealed record SealedDerived(int B) : Base(B.ToString());
|
||||||
|
|
||||||
|
public class WithExpressionTests
|
||||||
|
{
|
||||||
|
public Fields Test(Fields input)
|
||||||
|
{
|
||||||
|
return input with {
|
||||||
|
A = 42,
|
||||||
|
B = 3.141,
|
||||||
|
C = input
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public Fields Test2(Fields input)
|
||||||
|
{
|
||||||
|
return input with {
|
||||||
|
A = 42,
|
||||||
|
B = 3.141,
|
||||||
|
C = input with {
|
||||||
|
A = 43
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<PrimaryCtor> WithAwait(PrimaryCtor input)
|
||||||
|
{
|
||||||
|
return input with {
|
||||||
|
A = await Task.FromResult(42)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<Fields> WithAwaitAfterConstant(Fields input)
|
||||||
|
{
|
||||||
|
return input with {
|
||||||
|
A = 42,
|
||||||
|
B = await Task.FromResult(3.141)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record WithNestedRecords
|
||||||
|
{
|
||||||
|
public record A : WithNestedRecords
|
||||||
|
{
|
||||||
|
public override string AbstractProp => "A";
|
||||||
|
}
|
||||||
|
|
||||||
|
public record B : WithNestedRecords
|
||||||
|
{
|
||||||
|
public override string AbstractProp => "B";
|
||||||
|
|
||||||
|
public int? Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DerivedGeneric<T> : Pair<T, T?> where T : struct
|
||||||
|
{
|
||||||
|
public bool Flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string AbstractProp { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record BaseRecord
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public object Value { get; }
|
||||||
|
public bool Encode { get; }
|
||||||
|
|
||||||
|
protected BaseRecord(string name, object value, bool encode)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Value = value;
|
||||||
|
Encode = encode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DerivedRecord(string name, object value, bool encode = true) : BaseRecord(string.IsNullOrEmpty(name) ? "name" : name, value, encode);
|
||||||
|
|
||||||
|
public record DefaultValuesRecord() : DerivedRecord("default", 42, encode: false);
|
||||||
|
|
||||||
|
public record RecordWithProtectedMember(int Value)
|
||||||
|
{
|
||||||
|
protected int Double => Value * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record InheritedRecordWithAdditionalMember(int Value) : RecordWithProtectedMember(Value)
|
||||||
|
{
|
||||||
|
public int MoreData { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record InheritedRecordWithAdditionalParameter(int Value, int Value2) : RecordWithProtectedMember(Value);
|
||||||
|
|
||||||
|
public record BaseWithString(string S);
|
||||||
|
|
||||||
|
public record DerivedWithAdditionalInt(int I) : BaseWithString(I.ToString());
|
||||||
|
|
||||||
|
public record DerivedWithNoAdditionalProperty(string S) : BaseWithString(S);
|
||||||
|
|
||||||
|
public record DerivedWithAdditionalProperty(string S2) : BaseWithString(S2);
|
||||||
|
|
||||||
|
public record DerivedWithAdditionalPropertyDifferentAccessor(string S) : BaseWithString(S)
|
||||||
|
{
|
||||||
|
public string S2 { get; set; } = S;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MultipleCtorsChainedNoPrimaryCtor
|
||||||
|
{
|
||||||
|
public int A { get; init; }
|
||||||
|
public string B { get; init; }
|
||||||
|
|
||||||
|
public double C { get; init; }
|
||||||
|
|
||||||
|
public MultipleCtorsChainedNoPrimaryCtor(double c)
|
||||||
|
{
|
||||||
|
A = 0;
|
||||||
|
B = null;
|
||||||
|
C = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleCtorsChainedNoPrimaryCtor(int a)
|
||||||
|
: this(3.14)
|
||||||
|
{
|
||||||
|
A = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleCtorsChainedNoPrimaryCtor(string b)
|
||||||
|
: this(4.13)
|
||||||
|
{
|
||||||
|
B = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UnexpectedCodeInCtor
|
||||||
|
{
|
||||||
|
public int A { get; init; }
|
||||||
|
public string B { get; init; }
|
||||||
|
|
||||||
|
public UnexpectedCodeInCtor(int A, string B)
|
||||||
|
{
|
||||||
|
this.A = A;
|
||||||
|
this.B = B;
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnexpectedCodeInCtor(int A)
|
||||||
|
: this(A, null)
|
||||||
|
{
|
||||||
|
this.A = A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RecordWithMultipleInitializerAssignmentsInPrimaryCtor(string name, string? value, in object encode)
|
||||||
|
{
|
||||||
|
public string? Value { get; } = name;
|
||||||
|
|
||||||
|
public string Name { get; } = value;
|
||||||
|
|
||||||
|
private string? WebValue { get; } = (name != null) ? "111" : value;
|
||||||
|
|
||||||
|
private string? WebValue2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FieldBeforeProperty(int ID, string Text)
|
||||||
|
{
|
||||||
|
public int Field;
|
||||||
|
|
||||||
|
public int Property { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FieldsAndPropertiesInterleaved(int ID)
|
||||||
|
{
|
||||||
|
public int First;
|
||||||
|
|
||||||
|
public int Middle { get; set; }
|
||||||
|
|
||||||
|
public int Last;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PrivateFieldAndComputedProperty(int ID)
|
||||||
|
{
|
||||||
|
public int PublicField;
|
||||||
|
|
||||||
|
private int privateField;
|
||||||
|
|
||||||
|
public int Computed => privateField + PublicField;
|
||||||
|
|
||||||
|
public int Auto { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DerivedWithInterleavedMembers(int B) : Base(B.ToString())
|
||||||
|
{
|
||||||
|
public int Field;
|
||||||
|
|
||||||
|
public int Property { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CS100
|
||||||
|
internal class RecordStructs
|
||||||
|
{
|
||||||
|
public readonly record struct ReadonlyPoint(int X, int Y);
|
||||||
|
|
||||||
|
public record struct Base(string A);
|
||||||
|
|
||||||
|
public record CopyCtor(string A)
|
||||||
|
{
|
||||||
|
protected CopyCtor(CopyCtor _)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 1)]
|
||||||
|
public record struct Empty;
|
||||||
|
|
||||||
|
public record struct Fields
|
||||||
|
{
|
||||||
|
public int A;
|
||||||
|
public double B;
|
||||||
|
public object C;
|
||||||
|
public dynamic D;
|
||||||
|
public string S;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Interface(int B) : IRecord;
|
||||||
|
|
||||||
|
public interface IRecord
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Pair<A, B>
|
||||||
|
{
|
||||||
|
public A First { get; init; }
|
||||||
|
public B Second { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct PairWithPrimaryCtor<A, B>(A First, B Second);
|
||||||
|
|
||||||
|
public readonly record struct BoundsInfo(int Profile, Bounds Bounds);
|
||||||
|
|
||||||
|
public readonly record struct Bounds(ulong Small, ulong Large);
|
||||||
|
|
||||||
|
public record struct PrimaryCtor(int A, string B);
|
||||||
|
|
||||||
|
public record struct MultipleCtorsNoPrimaryCtor
|
||||||
|
{
|
||||||
|
public Guid Id { get; }
|
||||||
|
|
||||||
|
public MultipleCtorsNoPrimaryCtor()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleCtorsNoPrimaryCtor(Guid id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct MultipleCtorsChainedNoPrimaryCtor
|
||||||
|
{
|
||||||
|
public int A { get; init; }
|
||||||
|
public string B { get; init; }
|
||||||
|
|
||||||
|
public double C { get; init; }
|
||||||
|
|
||||||
|
public MultipleCtorsChainedNoPrimaryCtor(double c)
|
||||||
|
{
|
||||||
|
A = 0;
|
||||||
|
B = null;
|
||||||
|
C = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleCtorsChainedNoPrimaryCtor(int a)
|
||||||
|
: this(3.14)
|
||||||
|
{
|
||||||
|
A = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleCtorsChainedNoPrimaryCtor(string b)
|
||||||
|
: this(4.13)
|
||||||
|
{
|
||||||
|
B = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a);
|
||||||
|
public record struct PrimaryCtorWithField(int A, string B)
|
||||||
|
{
|
||||||
|
public double C = 1.0;
|
||||||
|
public string D = A + B;
|
||||||
|
}
|
||||||
|
public record struct PrimaryCtorWithInParameter(in int A, in string B);
|
||||||
|
public record struct PrimaryCtorWithProperty(int A, string B)
|
||||||
|
{
|
||||||
|
public double C { get; init; } = 1.0;
|
||||||
|
public string D { get; } = A + B;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Properties
|
||||||
|
{
|
||||||
|
public int A { get; set; }
|
||||||
|
public int B { get; }
|
||||||
|
public int C => 43;
|
||||||
|
public object O { get; set; }
|
||||||
|
public string S { get; set; }
|
||||||
|
public dynamic D { get; set; }
|
||||||
|
|
||||||
|
public Properties()
|
||||||
|
{
|
||||||
|
A = 41;
|
||||||
|
B = 42;
|
||||||
|
O = null;
|
||||||
|
S = "Hello";
|
||||||
|
D = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct PropertiesWithInitializers()
|
||||||
|
{
|
||||||
|
public int A { get; set; } = 41;
|
||||||
|
public int B { get; } = 42;
|
||||||
|
public int C => 43;
|
||||||
|
public object O { get; set; } = null;
|
||||||
|
public string S { get; set; } = "Hello";
|
||||||
|
public dynamic D { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public class RecordTestAttribute : Attribute
|
||||||
|
{
|
||||||
|
public RecordTestAttribute(string name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WithExpressionTests
|
||||||
|
{
|
||||||
|
public Fields Test(Fields input)
|
||||||
|
{
|
||||||
|
return input with {
|
||||||
|
A = 42,
|
||||||
|
B = 3.141,
|
||||||
|
C = input
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public Fields Test2(Fields input)
|
||||||
|
{
|
||||||
|
return input with {
|
||||||
|
A = 42,
|
||||||
|
B = 3.141,
|
||||||
|
C = input with {
|
||||||
|
A = 43
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if CS110
|
||||||
|
public record struct WithRequiredMembers
|
||||||
|
{
|
||||||
|
public int A { get; set; }
|
||||||
|
public required double B { get; set; }
|
||||||
|
public object C;
|
||||||
|
public required dynamic D;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
public record struct RecordWithMultipleCtors
|
||||||
|
{
|
||||||
|
public int A { get; set; }
|
||||||
|
|
||||||
|
public RecordWithMultipleCtors()
|
||||||
|
{
|
||||||
|
A = 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecordWithMultipleCtors(int A)
|
||||||
|
{
|
||||||
|
this.A = A;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecordWithMultipleCtors(string A)
|
||||||
|
{
|
||||||
|
this.A = int.Parse(A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct RecordCtorChain(int A, string B)
|
||||||
|
{
|
||||||
|
#if EXPECTED_OUTPUT
|
||||||
|
public double C = 0.0;
|
||||||
|
#else
|
||||||
|
public double C;
|
||||||
|
#endif
|
||||||
|
public RecordCtorChain(int A)
|
||||||
|
: this(A, "default")
|
||||||
|
{
|
||||||
|
C = 3.14;
|
||||||
|
}
|
||||||
|
public RecordCtorChain(string B)
|
||||||
|
: this(42, B)
|
||||||
|
{
|
||||||
|
C = 1.41;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BoundsInfo GetInfo()
|
||||||
|
{
|
||||||
|
return new BoundsInfo(1, new Bounds(2uL, 3uL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NestedDeconstruction()
|
||||||
|
{
|
||||||
|
var (value, (value2, value3)) = GetInfo();
|
||||||
|
Console.WriteLine(value);
|
||||||
|
Console.WriteLine(value2);
|
||||||
|
Console.WriteLine(value3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class RecordsWithCustomSynthesizedMembers
|
||||||
|
{
|
||||||
|
public record WithCustomToString(int A)
|
||||||
|
{
|
||||||
|
public sealed override string ToString()
|
||||||
|
{
|
||||||
|
return "custom";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WithCustomPrintMembers(int A)
|
||||||
|
{
|
||||||
|
protected virtual bool PrintMembers(StringBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Append("X");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ using System.Text;
|
|||||||
|
|
||||||
using ICSharpCode.Decompiler.CSharp.Resolver;
|
using ICSharpCode.Decompiler.CSharp.Resolver;
|
||||||
using ICSharpCode.Decompiler.CSharp.Syntax;
|
using ICSharpCode.Decompiler.CSharp.Syntax;
|
||||||
|
using ICSharpCode.Decompiler.CSharp.Transforms;
|
||||||
using ICSharpCode.Decompiler.IL;
|
using ICSharpCode.Decompiler.IL;
|
||||||
using ICSharpCode.Decompiler.IL.Transforms;
|
using ICSharpCode.Decompiler.IL.Transforms;
|
||||||
using ICSharpCode.Decompiler.Semantics;
|
using ICSharpCode.Decompiler.Semantics;
|
||||||
@@ -594,7 +595,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
|
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
|
||||||
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
|
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
|
||||||
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
|
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
|
||||||
&& argumentList.Length == 0)
|
&& argumentList.Length == 0
|
||||||
|
&& !IntroduceQueryExpressions.IsNullConditional(target))
|
||||||
{
|
{
|
||||||
argumentList.CheckNoNamedOrOptionalArguments();
|
argumentList.CheckNoNamedOrOptionalArguments();
|
||||||
return new BinaryOperatorExpression(
|
return new BinaryOperatorExpression(
|
||||||
@@ -1318,8 +1320,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
var firstArgument = argumentList.Arguments[0];
|
var firstArgument = argumentList.Arguments[0];
|
||||||
bool canWriteTypeArguments = !settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType());
|
bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0
|
||||||
bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0 && canWriteTypeArguments;
|
&& (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType()));
|
||||||
IType[] typeArguments = writeTypeArguments ? method.TypeArguments.ToArray() : Empty<IType>.Array;
|
IType[] typeArguments = writeTypeArguments ? method.TypeArguments.ToArray() : Empty<IType>.Array;
|
||||||
|
|
||||||
var directionExpression = firstArgument.Expression as DirectionExpression;
|
var directionExpression = firstArgument.Expression as DirectionExpression;
|
||||||
@@ -1342,20 +1344,10 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
string[]? remainingNames = argumentNames?.Take(actualArgumentCount).Skip(1).ToArray();
|
string[]? remainingNames = argumentNames?.Take(actualArgumentCount).Skip(1).ToArray();
|
||||||
if (remainingNames != null && remainingNames.All(name => name == null))
|
if (remainingNames != null && remainingNames.All(name => name == null))
|
||||||
remainingNames = null;
|
remainingNames = null;
|
||||||
var remainingArguments = argumentList.GetArgumentResolveResults(skipCount: 1).ToArray();
|
if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target,
|
||||||
if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target, remainingArguments, remainingNames))
|
argumentList.GetArgumentResolveResults(skipCount: 1).ToArray(), remainingNames))
|
||||||
{
|
{
|
||||||
// 'a?.M()' has no static call form to fall back to. Spelled-out type arguments rule out
|
return false;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression targetExpression;
|
Expression targetExpression;
|
||||||
|
|||||||
@@ -1825,6 +1825,22 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IType PreferNativeIntegerType(IType type)
|
||||||
|
{
|
||||||
|
if (settings.NativeIntegers)
|
||||||
|
{
|
||||||
|
if (type.IsKnownType(KnownTypeCode.IntPtr))
|
||||||
|
{
|
||||||
|
return SpecialType.NInt;
|
||||||
|
}
|
||||||
|
else if (type.IsKnownType(KnownTypeCode.UIntPtr))
|
||||||
|
{
|
||||||
|
return SpecialType.NUInt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a type used for performing arithmetic with the stack type and sign.
|
/// Gets a type used for performing arithmetic with the stack type and sign.
|
||||||
///
|
///
|
||||||
@@ -3525,16 +3541,9 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
{
|
{
|
||||||
IType targetType = inst.Type;
|
IType targetType = inst.Type;
|
||||||
var arg = Translate(inst.Argument, typeHint: targetType);
|
var arg = Translate(inst.Argument, typeHint: targetType);
|
||||||
if (settings.NativeIntegers && !arg.Type.Equals(targetType))
|
if (!arg.Type.Equals(targetType))
|
||||||
{
|
{
|
||||||
if (targetType.IsKnownType(KnownTypeCode.IntPtr))
|
targetType = PreferNativeIntegerType(targetType);
|
||||||
{
|
|
||||||
targetType = SpecialType.NInt;
|
|
||||||
}
|
|
||||||
else if (targetType.IsKnownType(KnownTypeCode.UIntPtr))
|
|
||||||
{
|
|
||||||
targetType = SpecialType.NUInt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
arg = arg.ConvertTo(targetType, this);
|
arg = arg.ConvertTo(targetType, this);
|
||||||
var obj = compilation.FindType(KnownTypeCode.Object);
|
var obj = compilation.FindType(KnownTypeCode.Object);
|
||||||
@@ -3784,7 +3793,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
{
|
{
|
||||||
var property = (IProperty)lastElement.Member;
|
var property = (IProperty)lastElement.Member;
|
||||||
Debug.Assert(property.Parameters.Count == indices);
|
Debug.Assert(property.Parameters.Count == indices);
|
||||||
Debug.Assert(property.Setter != null, $"Indexer property {property} has no setter");
|
DecompilerAssert.That(property.Setter != null, $"Indexer property {property} has no setter");
|
||||||
elementsStack.Peek().Add(
|
elementsStack.Peek().Add(
|
||||||
new CallBuilder(this, typeSystem, settings)
|
new CallBuilder(this, typeSystem, settings)
|
||||||
.BuildDictionaryInitializerExpression(lastElement.OpCode, property.Setter, initObjRR, GetIndices(lastElement.Indices, indexVariables).ToList(), info.Values!.Single())
|
.BuildDictionaryInitializerExpression(lastElement.OpCode, property.Setter, initObjRR, GetIndices(lastElement.Indices, indexVariables).ToList(), info.Values!.Single())
|
||||||
@@ -4051,13 +4060,22 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
|
|
||||||
TranslatedExpression TranslateWithInitializer(Block block)
|
TranslatedExpression TranslateWithInitializer(Block block)
|
||||||
{
|
{
|
||||||
var stloc = block.Instructions.FirstOrDefault() as StLoc;
|
if (block.Instructions.FirstOrDefault() is not StLoc stloc
|
||||||
var final = block.FinalInstruction as LdLoc;
|
|| block.FinalInstruction is not LdLoc final
|
||||||
if (stloc == null || final == null || stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget)
|
|| stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget)
|
||||||
|
{
|
||||||
throw new ArgumentException("given Block is invalid!");
|
throw new ArgumentException("given Block is invalid!");
|
||||||
|
}
|
||||||
|
ILInstruction oldValue = stloc.Value;
|
||||||
|
if (stloc.Variable.Type.IsReferenceType == true)
|
||||||
|
{
|
||||||
|
// reference types must involve a <Clone>$() call
|
||||||
|
Debug.Assert(TransformCollectionAndObjectInitializers.IsRecordCloneMethodCall((CallInstruction)stloc.Value));
|
||||||
|
oldValue = ((CallInstruction)stloc.Value).Arguments.Single();
|
||||||
|
}
|
||||||
|
|
||||||
WithInitializerExpression withInitializerExpression = new WithInitializerExpression();
|
WithInitializerExpression withInitializerExpression = new WithInitializerExpression();
|
||||||
withInitializerExpression.Expression = Translate(stloc.Value, stloc.Variable.Type);
|
withInitializerExpression.Expression = Translate(oldValue, stloc.Variable.Type);
|
||||||
withInitializerExpression.Initializer = BuildArrayInitializerExpression(block, new InitializedObjectResolveResult(stloc.Variable.Type));
|
withInitializerExpression.Initializer = BuildArrayInitializerExpression(block, new InitializedObjectResolveResult(stloc.Variable.Type));
|
||||||
|
|
||||||
return withInitializerExpression.WithILInstruction(block)
|
return withInitializerExpression.WithILInstruction(block)
|
||||||
@@ -4241,7 +4259,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
targetType = inst.InferType(compilation);
|
targetType = PreferNativeIntegerType(inst.InferType(compilation));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4539,7 +4557,12 @@ namespace ICSharpCode.Decompiler.CSharp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = Translate(inst.Value, typeHint: expectedType);
|
var operand = inst.Value;
|
||||||
|
if (operand is LdObjIfRef ldObjIfRef)
|
||||||
|
{
|
||||||
|
operand = ldObjIfRef.Target;
|
||||||
|
}
|
||||||
|
var value = Translate(operand, typeHint: expectedType);
|
||||||
if (value.Expression is DirectionExpression)
|
if (value.Expression is DirectionExpression)
|
||||||
{
|
{
|
||||||
// we can deference the managed reference by stripping away the 'ref'
|
// we can deference the managed reference by stripping away the 'ref'
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -668,12 +668,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|||||||
case OpCode.SwitchInstruction:
|
case OpCode.SwitchInstruction:
|
||||||
return true;
|
return true;
|
||||||
case OpCode.LdLoc:
|
case OpCode.LdLoc:
|
||||||
if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null)
|
var copiedVariable = ((LdLoc)inlinedExpression).Variable;
|
||||||
|
if (v.StateMachineField == null && copiedVariable.StateMachineField != null)
|
||||||
{
|
{
|
||||||
// Roslyn likes to put the result of fetching a state machine field into a temporary variable,
|
// Roslyn likes to put the result of fetching a state machine field into a temporary variable,
|
||||||
// so inline more aggressively in such cases.
|
// so inline more aggressively in such cases.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (IsHoistedCompilerTemporary(v) && IsHoistedCompilerTemporary(copiedVariable))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (inlinedExpression.ResultType == StackType.Ref)
|
if (inlinedExpression.ResultType == StackType.Ref)
|
||||||
@@ -773,6 +778,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsHoistedCompilerTemporary(ILVariable v)
|
||||||
|
{
|
||||||
|
string name = v.StateMachineField?.Name;
|
||||||
|
return name != null
|
||||||
|
&& (name.StartsWith("<>7__wrap", StringComparison.Ordinal) || name.StartsWith("<>s__", StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
|
/// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -942,10 +954,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|||||||
for (int i = 0; i < childIndex; ++i)
|
for (int i = 0; i < childIndex; ++i)
|
||||||
{
|
{
|
||||||
ILInstruction predecessor = inst.Parent.Children[i];
|
ILInstruction predecessor = inst.Parent.Children[i];
|
||||||
// The variable of a named argument only exists within its block,
|
|
||||||
// so only the stored value can interfere with the moved expression.
|
|
||||||
if (predecessor is StLoc { Variable.Kind: VariableKind.NamedArgument } namedArgument)
|
|
||||||
predecessor = namedArgument.Value;
|
|
||||||
if (!IsSafeForInlineOver(predecessor, expressionBeingMoved))
|
if (!IsSafeForInlineOver(predecessor, expressionBeingMoved))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Contributions to icsharpcode/ILSpy
|
||||||
|
|
||||||
|
.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
|
||||||
|
|
||||||
|
This repository holds only my commits to the upstream project, with their original author, date and diff.
|
||||||
|
Each of my commits is preceded by a snapshot of the upstream files it touched, so every diff shows exactly what I changed.
|
||||||
|
|
||||||
|
## Merged pull requests
|
||||||
|
|
||||||
|
- [Fix #4137: Keep nint in conditionals with mixed native int branches](https://github.com/icsharpcode/ILSpy/pull/4232) (2026-10-11)
|
||||||
|
- [Remove the delegate cache left by a discarded method-group conversion](https://github.com/icsharpcode/ILSpy/pull/4133) (2026-09-19)
|
||||||
|
- [Recognize a VB exception filter's type test](https://github.com/icsharpcode/ILSpy/pull/4134) (2026-09-15)
|
||||||
Reference in new issue
Block a user