mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Add pretty tests for C# 13 ref/unsafe in iterators and async methods
C# 13 allows ref locals, ref-struct (Span) locals, and unsafe blocks inside iterators and async methods as long as they do not cross a suspension point. Two suites, both gated on Roslyn 4.14/latest (whose -langversion:latest is C# 13/14; C# 12 rejects every case with CS9202): RefLocalsInIteratorsAndAsync is green and pins what already works: stackalloc and array-backed Span locals in async methods and async iterators, ReadOnlySpan in iterators, ref locals in iterators, and local functions (async with stackalloc, iterator with ref local, static with ref local) nested in such methods. Ref locals whose uses are all foldable (generic ref, ref readonly, ref reassignment, ref into a span) decompile to equivalent direct element accesses; those cases pin the folded form via EXPECTED_OUTPUT. UnsafeInIteratorsAndAsync is Assert.Ignore'd; the fixture is the desired output. The decompiler currently emits the raw pinned-local lowering (int[]/*pinned*/ plus Unsafe.AsPointer, or a GetPinnableReference sequence for fixed over string) instead of reconstructing fixed statements inside MoveNext, and marks the methods unsafe instead of wrapping the pointer region in an unsafe block. That output does not recompile: unsafe async methods fail with CS4004 (cannot await in an unsafe context), and pointer code in iterator bodies fails with CS0214 because an iterator body remains a safe context in C# 13 even when the method is declared unsafe. All four Roslyn 4.14/latest debug/opt configs compile the fixture and fail only at the output comparison. Assisted-by: Claude:claude-fable-5:Claude Code
This commit is contained in:
@@ -794,6 +794,19 @@ namespace ICSharpCode.Decompiler.Tests
|
||||
await RunForLibrary(cscOptions: cscOptions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RefLocalsInIteratorsAndAsync([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
|
||||
{
|
||||
await RunForLibrary(cscOptions: cscOptions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UnsafeInIteratorsAndAsync([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
|
||||
{
|
||||
Assert.Ignore("Unsafe blocks and fixed statements in iterators and async methods (C# 13) are not yet decompiled into valid C#. See https://github.com/icsharpcode/ILSpy/issues/829");
|
||||
await RunForLibrary(cscOptions: cscOptions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ExpandParamsArgumentsDisabled([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
{
|
||||
public class RefLocalsInIteratorsAndAsync
|
||||
{
|
||||
public async Task<int> SpanStackAllocInAsync(int length)
|
||||
{
|
||||
await Task.Yield();
|
||||
Span<int> span = stackalloc int[length];
|
||||
int result = 0;
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
span[i] = i * i;
|
||||
result += span[i];
|
||||
}
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> SpanFromArrayInAsync(int[] array)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
Span<int> span = array.AsSpan(1, 2);
|
||||
span[0] = 42;
|
||||
int result = span[0] + span[1];
|
||||
await Task.Delay(10);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> ConstStackAllocAsync()
|
||||
{
|
||||
await Task.Yield();
|
||||
Span<byte> span = stackalloc byte[8];
|
||||
span[7] = 42;
|
||||
int result = span[7];
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerable<int> RefLocalInIterator(int[] array)
|
||||
{
|
||||
yield return array[0];
|
||||
ref int reference = ref array[1];
|
||||
reference += 10;
|
||||
yield return reference;
|
||||
yield return array[1];
|
||||
}
|
||||
|
||||
public async Task<int> RefReassignInAsync(int[] array)
|
||||
{
|
||||
await Task.Yield();
|
||||
#if EXPECTED_OUTPUT
|
||||
array[0] = 1;
|
||||
array[1] = 2;
|
||||
#else
|
||||
ref int reference = ref array[0];
|
||||
reference = 1;
|
||||
reference = ref array[1];
|
||||
reference = 2;
|
||||
#endif
|
||||
int result = array[0] + array[1];
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<T> RefLocalGenericAsync<T>(T[] array)
|
||||
{
|
||||
await Task.Yield();
|
||||
#if EXPECTED_OUTPUT
|
||||
T result = array[0];
|
||||
#else
|
||||
ref T reference = ref array[0];
|
||||
T result = reference;
|
||||
#endif
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> RefIntoSpanAsync(int[] array)
|
||||
{
|
||||
await Task.Yield();
|
||||
Span<int> span = array.AsSpan();
|
||||
#if EXPECTED_OUTPUT
|
||||
span[1] += 5;
|
||||
#else
|
||||
ref int reference = ref span[1];
|
||||
reference += 5;
|
||||
#endif
|
||||
int result = span[1];
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerable<int> RefReadonlyInIterator(int[] array)
|
||||
{
|
||||
yield return 1;
|
||||
#if EXPECTED_OUTPUT
|
||||
yield return array[0] + 1;
|
||||
#else
|
||||
ref readonly int reference = ref array[0];
|
||||
yield return reference + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
public IEnumerable<int> LocalFunctionWithRefInIterator(int[] array)
|
||||
{
|
||||
yield return Increment(array);
|
||||
yield return Increment(array);
|
||||
static int Increment(int[] a)
|
||||
{
|
||||
ref int reference = ref a[0];
|
||||
reference++;
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<int> CallsAsyncLocalFunction(int length)
|
||||
{
|
||||
return Compute(length);
|
||||
static async Task<int> Compute(int size)
|
||||
{
|
||||
await Task.Yield();
|
||||
Span<int> span = stackalloc int[size];
|
||||
span[0] = size;
|
||||
int result = span[0];
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> CallsIteratorLocalFunction(int[] array)
|
||||
{
|
||||
return Iterate(array);
|
||||
static IEnumerable<int> Iterate(int[] source)
|
||||
{
|
||||
yield return source[0];
|
||||
ref int reference = ref source[1];
|
||||
reference++;
|
||||
yield return reference;
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<int> SpanInAsyncIterator(int[] array)
|
||||
{
|
||||
await Task.Yield();
|
||||
Span<int> span = array.AsSpan();
|
||||
int result = span[0] + span[span.Length - 1];
|
||||
yield return result;
|
||||
await Task.Yield();
|
||||
yield return result * 2;
|
||||
}
|
||||
|
||||
public IEnumerable<char> ReadOnlySpanInIterator(string text)
|
||||
{
|
||||
yield return 'a';
|
||||
ReadOnlySpan<char> readOnlySpan = text.AsSpan();
|
||||
yield return readOnlySpan[readOnlySpan.Length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
{
|
||||
public class UnsafeInIteratorsAndAsync
|
||||
{
|
||||
public async Task<int> UnsafeBlockInAsync(int[] array)
|
||||
{
|
||||
await Task.Yield();
|
||||
int result;
|
||||
unsafe
|
||||
{
|
||||
fixed (int* ptr = array)
|
||||
{
|
||||
result = *ptr + ptr[1];
|
||||
}
|
||||
}
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> PointerStackAllocInAsync()
|
||||
{
|
||||
await Task.Yield();
|
||||
int result;
|
||||
unsafe
|
||||
{
|
||||
int* ptr = stackalloc int[4];
|
||||
*ptr = 1;
|
||||
ptr[3] = 4;
|
||||
result = *ptr + ptr[3];
|
||||
}
|
||||
await Task.Yield();
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerable<int> FixedInIterator(int[] array)
|
||||
{
|
||||
yield return 1;
|
||||
int value;
|
||||
unsafe
|
||||
{
|
||||
fixed (int* ptr = array)
|
||||
{
|
||||
value = *ptr;
|
||||
}
|
||||
}
|
||||
yield return value;
|
||||
}
|
||||
|
||||
public IEnumerable<char> FixedStringInIterator(string text)
|
||||
{
|
||||
yield return 'x';
|
||||
char value;
|
||||
unsafe
|
||||
{
|
||||
fixed (char* ptr = text)
|
||||
{
|
||||
value = *ptr;
|
||||
}
|
||||
}
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user