From 5ca60d1b6180ef143516daeb4be339fc336f7e5a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 30 Jul 2026 21:15:22 +0200 Subject: [PATCH] Pin params, ref and out interaction with span conversions The remaining parameter-modifier dimension of the C# 14 rules: an expanded params call prefers the params ReadOnlySpan overload over params array (the C# 13 better-params-collection rule) while the normal form keeps the exact array overload; and a span conversion never binds a ref or out parameter, so a keyword-less argument picks the by-value overload and the ref/out keyword must survive decompilation to keep binding the by-ref one. All expectations come from compiling probes (the negative directions are CS1503) and everything was green as written - these are lock-downs, not fixes. Part of #829. Assisted-by: Claude:claude-fable-5:Claude Code --- .../Semantics/OverloadResolutionTests.cs | 30 +++++++++++++++++-- .../TestCases/Pretty/FirstClassSpanTypes.cs | 30 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs index 500f7981d..4d72b7cdf 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs @@ -402,17 +402,20 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.That(r.IsAmbiguous); } - static IMethod MakeInMethodIn(ICompilation c, Type parameterType) + static IMethod MakeByRefMethodIn(ICompilation c, Type parameterType, ReferenceKind kind) { var m = new FakeMethod(c, SymbolKind.Method); m.Name = "Method"; m.Parameters = new List { new DefaultParameter(new ByReferenceType(c.FindType(parameterType)), string.Empty, - owner: m, referenceKind: ReferenceKind.In) + owner: m, referenceKind: kind) }; return m; } + static IMethod MakeInMethodIn(ICompilation c, Type parameterType) + => MakeByRefMethodIn(c, parameterType, ReferenceKind.In); + [Test] public void InReadOnlySpanParameter_BindsAnArrayWithoutInButNotWithIn() { @@ -472,6 +475,29 @@ namespace ICSharpCode.Decompiler.Tests.Semantics } } + [Test] + public void RefAndOutParametersNeverBindThroughASpanConversion() + { + // Roslyn: CS1503 for both 'M(ref arr)' against 'ref ReadOnlySpan' and + // 'M(out arr)' against 'out ReadOnlySpan' - ref and out demand the + // parameter's own type; the span conversion does not apply. A value argument + // without the keyword is a passing-mode mismatch regardless of conversions. + var c = spanCompilation.Value; + var arrayArg = new ResolveResult(new ArrayType(c, c.FindType(KnownTypeCode.Int32))); + foreach (var kind in new[] { ReferenceKind.Ref, ReferenceKind.Out }) + { + var byRefArgument = new OverloadResolution(c, new[] { + new ByReferenceResolveResult(arrayArg, kind) + }); + Assert.That(byRefArgument.AddCandidate(MakeByRefMethodIn(c, typeof(ReadOnlySpan), kind)), + Is.Not.EqualTo(OverloadResolutionErrors.None), kind.ToString()); + + var valueArgument = new OverloadResolution(c, new[] { arrayArg }); + Assert.That(valueArgument.AddCandidate(MakeByRefMethodIn(c, typeof(ReadOnlySpan), kind)), + Is.Not.EqualTo(OverloadResolutionErrors.None), kind.ToString()); + } + } + [Test] public void InOverloadIsTheOnlyCandidateWithInAtTheCall() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs index 3b01982ca..5bfdd89ed 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs @@ -104,6 +104,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { } + public static void RefSpanOrByValue(ref ReadOnlySpan s) + { + } + + public static void RefSpanOrByValue(ReadOnlySpan s) + { + } + + public static void OutSpanOrByValue(out ReadOnlySpan s) + { + s = default(ReadOnlySpan); + } + + public static void OutSpanOrByValue(ReadOnlySpan s) + { + } + public static void GenericArrayOrReadOnlySpan(T[] a) { } @@ -159,12 +176,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty ReadOnlySpanOfObjectOrString(strArr); StringOrReadOnlySpanChar(str); ParamsArrayOrParamsReadOnlySpan(arr); + ParamsArrayOrParamsReadOnlySpan(1, 2, 3); GenericArrayOrReadOnlySpan(arr); InferFromReadOnlySpan(arr); InferFromReadOnlySpan(span); arr.ExtensionOnReadOnlySpan(); } + public static void CallRefOutOrByValue(int[] arr) + { + // A span conversion never binds a ref or out parameter: without the keyword the + // by-value overload wins, with the keyword only the ref/out overload is + // applicable and the keyword must survive decompilation. + ReadOnlySpan s = arr; + RefSpanOrByValue(arr); + RefSpanOrByValue(ref s); + OutSpanOrByValue(arr); + OutSpanOrByValue(out s); + } + public static void CallLosersWithExplicitConversions(int[] arr, string str) { ArrayOrReadOnlySpan((ReadOnlySpan)arr);