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
|
||||
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.Syntax;
|
||||
using ICSharpCode.Decompiler.CSharp.Transforms;
|
||||
using ICSharpCode.Decompiler.IL;
|
||||
using ICSharpCode.Decompiler.IL.Transforms;
|
||||
using ICSharpCode.Decompiler.Semantics;
|
||||
@@ -594,7 +595,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
|
||||
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
|
||||
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)
|
||||
&& argumentList.Length == 0)
|
||||
&& argumentList.Length == 0
|
||||
&& !IntroduceQueryExpressions.IsNullConditional(target))
|
||||
{
|
||||
argumentList.CheckNoNamedOrOptionalArguments();
|
||||
return new BinaryOperatorExpression(
|
||||
@@ -1318,8 +1320,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,20 +1344,10 @@ 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;
|
||||
return false;
|
||||
}
|
||||
|
||||
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>
|
||||
/// 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;
|
||||
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 = SpecialType.NInt;
|
||||
}
|
||||
else if (targetType.IsKnownType(KnownTypeCode.UIntPtr))
|
||||
{
|
||||
targetType = SpecialType.NUInt;
|
||||
}
|
||||
targetType = PreferNativeIntegerType(targetType);
|
||||
}
|
||||
arg = arg.ConvertTo(targetType, this);
|
||||
var obj = compilation.FindType(KnownTypeCode.Object);
|
||||
@@ -3784,7 +3793,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
{
|
||||
var property = (IProperty)lastElement.Member;
|
||||
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(
|
||||
new CallBuilder(this, typeSystem, settings)
|
||||
.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)
|
||||
{
|
||||
var stloc = block.Instructions.FirstOrDefault() as StLoc;
|
||||
var final = block.FinalInstruction as LdLoc;
|
||||
if (stloc == null || final == null || stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget)
|
||||
if (block.Instructions.FirstOrDefault() is not StLoc stloc
|
||||
|| block.FinalInstruction is not LdLoc final
|
||||
|| stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget)
|
||||
{
|
||||
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.Expression = Translate(stloc.Value, stloc.Variable.Type);
|
||||
withInitializerExpression.Expression = Translate(oldValue, stloc.Variable.Type);
|
||||
withInitializerExpression.Initializer = BuildArrayInitializerExpression(block, new InitializedObjectResolveResult(stloc.Variable.Type));
|
||||
|
||||
return withInitializerExpression.WithILInstruction(block)
|
||||
@@ -4241,7 +4259,7 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
}
|
||||
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)
|
||||
{
|
||||
// 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:
|
||||
return true;
|
||||
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,
|
||||
// so inline more aggressively in such cases.
|
||||
return true;
|
||||
}
|
||||
if (IsHoistedCompilerTemporary(v) && IsHoistedCompilerTemporary(copiedVariable))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
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>
|
||||
/// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
|
||||
/// </summary>
|
||||
@@ -942,10 +954,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
for (int i = 0; i < childIndex; ++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))
|
||||
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