mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Add pretty tests for C# 14 simple lambda parameters with modifiers
The C# 14 feature is pure source sugar: a lambda parameter may carry ref/out/in/scoped/ref readonly without declaring a type, but the IL always has fully-typed parameters, so the decompiler's explicitly-typed output is the correct round-trip. LambdaParameterModifiers pins that this round-trip works: custom delegate types with each modifier (including scoped by-value, scoped ref, and mixed parameter lists), block- and expression-bodied lambdas, captures alongside ref parameters, generic ref delegates, lambda parameter attributes, call-site modifiers (including 'in' for 'ref readonly' parameters), unused-parameter anonymous methods, and a local-function contrast. CS140-gated branches compile the untyped sugar (and attributed sugar) under Roslyn latest to prove it produces the same decompilation. ScopedLambdaParameters is Assert.Ignore'd: when an anonymous function's 'scoped ref' parameter is unused, the decompiler drops the parameter list, losing the required 'scoped' modifier; the output then fails to recompile with CS8986 because the implicit parameters of a parameter-list-less anonymous method are unscoped. The fixture is the desired output (parameter list retained); all four Roslyn 4.14/latest debug/opt configs compile it 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 LambdaParameterModifiers([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
|
||||
{
|
||||
await RunForLibrary(cscOptions: cscOptions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ScopedLambdaParameters([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
|
||||
{
|
||||
Assert.Ignore("Anonymous functions with an unused 'scoped ref' parameter lose the required 'scoped' modifier when the parameter list is dropped. 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,157 @@
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
{
|
||||
internal class LambdaParameterModifiers
|
||||
{
|
||||
public delegate void RefAction(ref int x);
|
||||
|
||||
public delegate bool TryParseDelegate(string s, out int result);
|
||||
|
||||
public delegate int InFunc(in DateTime d);
|
||||
|
||||
public delegate int RefReadonlyFunc(ref readonly int x);
|
||||
|
||||
public delegate void RefGenericAction<T>(ref T item);
|
||||
|
||||
public delegate int ScopedSpanFunc(scoped Span<int> s);
|
||||
|
||||
public delegate Span<int> ScopedMixedFunc(scoped ref int x, ref int y);
|
||||
|
||||
public delegate void ScopedRefAction(scoped ref int x);
|
||||
|
||||
private int instanceField;
|
||||
|
||||
public RefAction RefBlockBody()
|
||||
{
|
||||
return delegate (ref int x) {
|
||||
x++;
|
||||
};
|
||||
}
|
||||
|
||||
public RefAction RefExpressionBody()
|
||||
{
|
||||
#if EXPECTED_OUTPUT
|
||||
return delegate (ref int x) {
|
||||
x *= 2;
|
||||
};
|
||||
#elif CS140
|
||||
// C# 14 simple lambda parameter with modifiers: no type needed
|
||||
return (ref x) => x *= 2;
|
||||
#else
|
||||
return (ref int x) => x *= 2;
|
||||
#endif
|
||||
}
|
||||
|
||||
public TryParseDelegate OutLambda()
|
||||
{
|
||||
#if EXPECTED_OUTPUT
|
||||
return delegate (string s, out int result) {
|
||||
return int.TryParse(s, out result);
|
||||
};
|
||||
#elif CS140
|
||||
// C# 14 simple lambda parameter with modifiers: no type needed
|
||||
return (s, out result) => int.TryParse(s, out result);
|
||||
#else
|
||||
return (string s, out int result) => int.TryParse(s, out result);
|
||||
#endif
|
||||
}
|
||||
|
||||
public InFunc InLambda()
|
||||
{
|
||||
return delegate (in DateTime d) {
|
||||
return d.Year;
|
||||
};
|
||||
}
|
||||
|
||||
public RefReadonlyFunc RefReadonlyLambda()
|
||||
{
|
||||
return delegate (ref readonly int x) {
|
||||
return x + 1;
|
||||
};
|
||||
}
|
||||
|
||||
public RefGenericAction<string> GenericRefLambda()
|
||||
{
|
||||
return delegate (ref string item) {
|
||||
item += "!";
|
||||
};
|
||||
}
|
||||
|
||||
public RefAction CaptureAlongsideRefParameter(int offset)
|
||||
{
|
||||
return delegate (ref int x) {
|
||||
x += offset + instanceField;
|
||||
};
|
||||
}
|
||||
|
||||
public ScopedSpanFunc ScopedSpanLambda()
|
||||
{
|
||||
return (scoped Span<int> s) => s.Length;
|
||||
}
|
||||
|
||||
public ScopedMixedFunc ScopedMixedLambda()
|
||||
{
|
||||
return delegate (scoped ref int x, ref int y) {
|
||||
x += y;
|
||||
return default(Span<int>);
|
||||
};
|
||||
}
|
||||
|
||||
public ScopedRefAction ScopedRefLambda()
|
||||
{
|
||||
return delegate (scoped ref int x) {
|
||||
x++;
|
||||
};
|
||||
}
|
||||
|
||||
public RefAction AttributedRefLambda()
|
||||
{
|
||||
#if EXPECTED_OUTPUT
|
||||
return ([ParamMod] ref int x) => {
|
||||
x += 10;
|
||||
};
|
||||
#elif CS140
|
||||
// C# 14 also allows attributes on simple lambda parameters
|
||||
return ([ParamMod] ref x) => x += 10;
|
||||
#else
|
||||
return ([ParamMod] ref int x) => x += 10;
|
||||
#endif
|
||||
}
|
||||
|
||||
public RefAction UnusedRefParameter()
|
||||
{
|
||||
return delegate {
|
||||
};
|
||||
}
|
||||
|
||||
public RefAction LocalFunctionWithRef()
|
||||
{
|
||||
return LocalRef;
|
||||
static void LocalRef(ref int x)
|
||||
{
|
||||
x -= 3;
|
||||
}
|
||||
}
|
||||
|
||||
public int InvokeAll()
|
||||
{
|
||||
int x = 1;
|
||||
RefBlockBody()(ref x);
|
||||
RefExpressionBody()(ref x);
|
||||
OutLambda()("42", out var result);
|
||||
DateTime d = DateTime.MinValue;
|
||||
int num = InLambda()(in d) + RefReadonlyLambda()(in x);
|
||||
string item = "a";
|
||||
GenericRefLambda()(ref item);
|
||||
CaptureAlongsideRefParameter(2)(ref x);
|
||||
LocalFunctionWithRef()(ref x);
|
||||
return x + result + num + item.Length;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
internal class ParamModAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
{
|
||||
internal class ScopedLambdaParameters
|
||||
{
|
||||
public delegate Span<int> ScopedRefFunc(scoped ref int x);
|
||||
|
||||
public ScopedRefFunc UnusedScopedRefParameter()
|
||||
{
|
||||
// The parameter list must not be dropped here: an anonymous function
|
||||
// without a parameter list has unscoped implicit parameters, which do
|
||||
// not match the delegate's "scoped ref" parameter (CS8986).
|
||||
return delegate (scoped ref int x) {
|
||||
return default(Span<int>);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user