diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index f5d4d50..40d8b24 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -932,6 +932,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task Issue3180([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task AssemblyCustomAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3180.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3180.cs new file mode 100644 index 0000000..dd07f00 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3180.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Issue3180 +{ + internal static class Extensions + { + public static int Count(this IEnumerable source) + { + return 5; + } + } + + internal class NullConditional + { + public static int StaticCall(IEnumerable s) + { + return Enumerable.Count(s); + } + + public static int CountOrZero(IEnumerable s) + { +#if EXPECTED_OUTPUT + return s?.Count() ?? 0; +#else + return (s != null) ? Enumerable.Count(s) : 0; +#endif + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index bad908d..1797541 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1318,8 +1318,8 @@ namespace ICSharpCode.Decompiler.CSharp return false; var firstArgument = argumentList.Arguments[0]; - bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0 - && (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType())); + bool canWriteTypeArguments = !settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType()); + bool writeTypeArguments = (transform & ReferenceTransformation.RequireTypeArguments) != 0 && canWriteTypeArguments; IType[] typeArguments = writeTypeArguments ? method.TypeArguments.ToArray() : Empty.Array; var directionExpression = firstArgument.Expression as DirectionExpression; @@ -1342,10 +1342,20 @@ namespace ICSharpCode.Decompiler.CSharp string[]? remainingNames = argumentNames?.Take(actualArgumentCount).Skip(1).ToArray(); if (remainingNames != null && remainingNames.All(name => name == null)) remainingNames = null; - if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target, - argumentList.GetArgumentResolveResults(skipCount: 1).ToArray(), remainingNames)) + var remainingArguments = argumentList.GetArgumentResolveResults(skipCount: 1).ToArray(); + if (!resolver.CanTransformToExtensionMethodCall(method, typeArguments, target, remainingArguments, remainingNames)) { - return false; + // '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; } Expression targetExpression;