Browse Source

Add pretty tests for C# 14 first-class span types

The green FirstClassSpanTypes fixture pins overload-resolution behavior
the decompiler already gets right under the C# 14 implicit span
conversions: calls picking the new betterness winners (ReadOnlySpan
over Span/object/IEnumerable, ReadOnlySpan<string> over object[] and
ReadOnlySpan<object>, MemoryExtensions.Contains over
Enumerable.Contains) round-trip as plain calls, while calls picking the
losing overload keep their disambiguating casts and Enumerable.Contains
stays in static call form. Extension methods on span-convertible
receivers, generic inference through span conversions, params
betterness, and array-to-span returns are covered too. All winners were
verified by executing probes compiled at LangVersion 13 vs 14.

The FirstClassSpanConversions fixture is Assert.Ignore'd (#829): it
specs the desired folding of compiler-emitted span-conversion helpers
back into implicit conversions - MemoryExtensions.AsSpan(string),
ReadOnlySpan<T>.CastUp for span variance, and covariant-array/in-arg
conversions - which the decompiler currently renders as explicit helper
calls or casts (recompilable and semantics-preserving, just not
minimal). Both roslyn-latest configs compile the fixture and fail only
at the output comparison.

Assisted-by: Claude:claude-fable-5:Claude Code
pull/3930/head
Siegfried Pammer 2 months ago committed by Christoph Wille
parent
commit
6bd800fadd
  1. 13
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  2. 103
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs
  3. 186
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs

13
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -800,6 +800,19 @@ namespace ICSharpCode.Decompiler.Tests @@ -800,6 +800,19 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task FirstClassSpanTypes([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task FirstClassSpanConversions([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
Assert.Ignore("Implicit span conversions (C# 14 first-class span types) are not yet folded by the decompiler. See https://github.com/icsharpcode/ILSpy/issues/829");
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task ExpandParamsArgumentsDisabled([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{

103
ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanConversions.cs

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
// 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.
using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal static class FirstClassSpanConversions
{
internal class Base
{
}
internal class Derived : Base
{
}
public static void AcceptReadOnlySpanChar(ReadOnlySpan<char> s)
{
}
public static void AcceptReadOnlySpanBase(ReadOnlySpan<Base> s)
{
}
public static void AcceptInReadOnlySpan(in ReadOnlySpan<int> s)
{
}
public static void ObjectOrReadOnlySpanChar(object a)
{
}
public static void ObjectOrReadOnlySpanChar(ReadOnlySpan<char> a)
{
}
public static void StringArgument(string s)
{
AcceptReadOnlySpanChar(s);
ObjectOrReadOnlySpanChar(s);
s.ExtensionOnReadOnlySpanChar();
}
public static ReadOnlySpan<char> StringToReadOnlySpanCharReturn(string s)
{
return s;
}
public static int StringToReadOnlySpanCharLocal(string s)
{
ReadOnlySpan<char> readOnlySpan = s;
return readOnlySpan.Length;
}
public static void VarianceReadOnlySpan(ReadOnlySpan<Derived> s)
{
AcceptReadOnlySpanBase(s);
}
public static void VarianceSpan(Span<Derived> s)
{
AcceptReadOnlySpanBase(s);
}
public static ReadOnlySpan<Base> VarianceReturn(ReadOnlySpan<Derived> s)
{
return s;
}
public static void CovariantArrayToReadOnlySpan(Derived[] a)
{
AcceptReadOnlySpanBase(a);
}
public static void InArgument(int[] a)
{
AcceptInReadOnlySpan(a);
}
}
internal static class FirstClassSpanConversionsExtensions
{
public static void ExtensionOnReadOnlySpanChar(this ReadOnlySpan<char> s)
{
}
}
}

186
ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs

@ -0,0 +1,186 @@ @@ -0,0 +1,186 @@
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal static class FirstClassSpanTypes
{
public static void ArrayOrReadOnlySpan(int[] a)
{
}
public static void ArrayOrReadOnlySpan(ReadOnlySpan<int> a)
{
}
public static void ArrayOrSpan(int[] a)
{
}
public static void ArrayOrSpan(Span<int> a)
{
}
public static void SpanOrReadOnlySpan(Span<int> a)
{
}
public static void SpanOrReadOnlySpan(ReadOnlySpan<int> a)
{
}
public static void ObjectOrReadOnlySpan(object a)
{
}
public static void ObjectOrReadOnlySpan(ReadOnlySpan<int> a)
{
}
public static void ObjectOrReadOnlySpanChar(object a)
{
}
public static void ObjectOrReadOnlySpanChar(ReadOnlySpan<char> a)
{
}
public static void EnumerableOrReadOnlySpan(IEnumerable<int> a)
{
}
public static void EnumerableOrReadOnlySpan(ReadOnlySpan<int> a)
{
}
public static void CovariantArrayOrReadOnlySpan(object[] a)
{
}
public static void CovariantArrayOrReadOnlySpan(ReadOnlySpan<string> a)
{
}
public static void ReadOnlySpanOfObjectOrString(ReadOnlySpan<object> a)
{
}
public static void ReadOnlySpanOfObjectOrString(ReadOnlySpan<string> a)
{
}
public static void StringOrReadOnlySpanChar(string a)
{
}
public static void StringOrReadOnlySpanChar(ReadOnlySpan<char> a)
{
}
public static void ParamsArrayOrParamsReadOnlySpan(params int[] a)
{
}
public static void ParamsArrayOrParamsReadOnlySpan(params ReadOnlySpan<int> a)
{
}
public static void GenericArrayOrReadOnlySpan<T>(T[] a)
{
}
public static void GenericArrayOrReadOnlySpan<T>(ReadOnlySpan<T> a)
{
}
public static void InferFromReadOnlySpan<T>(ReadOnlySpan<T> a)
{
}
public static ReadOnlySpan<int> ArrayToReadOnlySpanReturn(int[] a)
{
return a;
}
public static ReadOnlySpan<int> TernaryArrayOrSpan(bool b, int[] a, Span<int> s)
{
#if OPT
if (!b)
{
return s;
}
return a;
#else
return b ? ((ReadOnlySpan<int>)a) : ((ReadOnlySpan<int>)s);
#endif
}
public static bool SpanExtensionContains(int[] a)
{
// binds to MemoryExtensions.Contains under C# 14 first-class span conversions
return a.Contains(2);
}
public static bool LinqContains(int[] a)
{
// Enumerable.Contains loses against MemoryExtensions.Contains under C# 14;
// extension method syntax must not be used here
return Enumerable.Contains(a, 2);
}
public static void CallWinners(int[] arr, Span<int> span, string str, string[] strArr)
{
ArrayOrReadOnlySpan(arr);
ArrayOrSpan(arr);
SpanOrReadOnlySpan(arr);
SpanOrReadOnlySpan(span);
ObjectOrReadOnlySpan(arr);
EnumerableOrReadOnlySpan(arr);
CovariantArrayOrReadOnlySpan(strArr);
ReadOnlySpanOfObjectOrString(strArr);
StringOrReadOnlySpanChar(str);
ParamsArrayOrParamsReadOnlySpan(arr);
GenericArrayOrReadOnlySpan(arr);
InferFromReadOnlySpan(arr);
InferFromReadOnlySpan(span);
arr.ExtensionOnReadOnlySpan();
}
public static void CallLosersWithExplicitConversions(int[] arr, string str)
{
ArrayOrReadOnlySpan((ReadOnlySpan<int>)arr);
ArrayOrSpan((Span<int>)arr);
SpanOrReadOnlySpan((Span<int>)arr);
ObjectOrReadOnlySpan((object)arr);
ObjectOrReadOnlySpanChar((object)str);
EnumerableOrReadOnlySpan((IEnumerable<int>)arr);
StringOrReadOnlySpanChar(str.AsSpan());
}
}
internal static class FirstClassSpanTypesExtensions
{
public static void ExtensionOnReadOnlySpan<T>(this ReadOnlySpan<T> s)
{
}
}
}
Loading…
Cancel
Save