Browse Source

Take lambda params and default values from the delegate's Invoke

An anonymous function's own method does not reliably carry ParamArrayAttribute
- Roslyn 4.14 omits it - while the delegate's Invoke method always describes
the full signature, and it is Invoke that call sites bind against. Reading the
modifiers from the anonymous function alone therefore dropped 'params' from
the parameter list while the call was still decompiled in expanded form,
which does not compile: the natural type of the re-emitted lambda is a plain
Func<int[], int>, taking exactly one argument.

Sourcing both modifiers from Invoke also makes the pre-C# 12 downgrade to
[ParamArray]/[Optional] fire on every compiler rather than only the newest.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/4004/head
Siegfried Pammer 1 month ago
parent
commit
d0d1da972f
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/LambdaNaturalType.cs
  2. 20
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LambdaOptionalAndParamsParameters.cs
  3. 8
      ICSharpCode.Decompiler.Tests/UglyTestRunner.cs
  4. 20
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

4
ICSharpCode.Decompiler.Tests/TestCases/Correctness/LambdaNaturalType.cs

@ -74,12 +74,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -74,12 +74,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
var optionalParameter = (int x = 5) => x * 2;
Console.WriteLine("optional parameter: " + optionalParameter() + " " + optionalParameter(7));
#if CS140
// Roslyn 4.14 emits no ParamArrayAttribute on a lambda's method, so 'params' cannot
// be recovered there and the decompiled call would no longer bind in expanded form.
var paramsParameter = (params int[] xs) => xs.Length;
Console.WriteLine("params parameter: " + paramsParameter(1, 2, 3));
#endif
}
}
}

20
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LambdaOptionalAndParamsParameters.cs

@ -12,35 +12,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -12,35 +12,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public ParamsAction ParamsStatementBody()
{
#if CS140
return (params int[] xs) => {
total += xs.Length;
Console.WriteLine(xs.Length);
};
#elif EXPECTED_OUTPUT
// Roslyn 4.14 does not emit ParamArrayAttribute on the lambda's method, so the
// 'params' modifier cannot be recovered from metadata.
return delegate (int[] xs) {
total += xs.Length;
Console.WriteLine(xs.Length);
};
#else
return (params int[] xs) => {
total += xs.Length;
Console.WriteLine(xs.Length);
};
#endif
}
public ParamsAction ParamsExpressionBody()
{
#if EXPECTED_OUTPUT && !CS140
return delegate (int[] xs) {
Console.WriteLine(xs.Length);
};
#else
return (params int[] xs) => Console.WriteLine(xs.Length);
#endif
}
public OptionalFunc OptionalStatementBody()

8
ICSharpCode.Decompiler.Tests/UglyTestRunner.cs

@ -104,16 +104,16 @@ namespace ICSharpCode.Decompiler.Tests @@ -104,16 +104,16 @@ namespace ICSharpCode.Decompiler.Tests
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
});
// Roslyn 4.14 does not emit ParamArrayAttribute on synthesized lambda methods, so tests
// pinning its round-trip need the current compiler only.
static readonly CompilerOptions[] roslynLatestOnlyOptions = Tester.SupportedOnCurrentPlatform(new[]
static readonly CompilerOptions[] roslyn4OrNewerOptions = Tester.SupportedOnCurrentPlatform(new[]
{
CompilerOptions.UseRoslyn4_14_0,
CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0,
CompilerOptions.UseRoslynLatest,
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
});
[Test]
public async Task NoLambdaOptionalAndParamsParameters([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions)
public async Task NoLambdaOptionalAndParamsParameters([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp11_0) {
FileScopedNamespaces = false

20
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2543,6 +2543,26 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2543,6 +2543,26 @@ namespace ICSharpCode.Decompiler.CSharp
AnonymousMethodExpression ame = new AnonymousMethodExpression();
ame.IsAsync = function.IsAsync;
ame.Parameters.AddRange(MakeParameters(function.Parameters, function));
// 'params' and parameter default values are part of the delegate's signature, and it
// is the delegate's Invoke method that call sites bind against. An anonymous function's
// own method does not reliably carry them - Roslyn 4.14 emits no ParamArrayAttribute
// there - so take them from Invoke, keeping declaration and call sites consistent.
var invokeParameters = delegateType.GetDelegateInvokeMethod()?.Parameters;
if (invokeParameters?.Count == ame.Parameters.Count)
{
int parameterIndex = 0;
foreach (var pd in ame.Parameters)
{
var invokeParameter = invokeParameters[parameterIndex++];
pd.IsParams |= invokeParameter.IsParams;
if (pd.DefaultExpression is null
&& astBuilder.ConvertParameter(invokeParameter).DefaultExpression is { } defaultValue)
{
defaultValue.Detach();
pd.DefaultExpression = defaultValue;
}
}
}
var builder = new StatementBuilder(
typeSystem,
this.decompilationContext,

Loading…
Cancel
Save