Browse Source

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
pull/3930/head
Siegfried Pammer 2 months ago committed by Christoph Wille
parent
commit
5ca60d1b61
  1. 30
      ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs
  2. 30
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FirstClassSpanTypes.cs

30
ICSharpCode.Decompiler.Tests/Semantics/OverloadResolutionTests.cs

@ -402,17 +402,20 @@ namespace ICSharpCode.Decompiler.Tests.Semantics @@ -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<IParameter> {
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 @@ -472,6 +475,29 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
}
}
[Test]
public void RefAndOutParametersNeverBindThroughASpanConversion()
{
// Roslyn: CS1503 for both 'M(ref arr)' against 'ref ReadOnlySpan<int>' and
// 'M(out arr)' against 'out ReadOnlySpan<int>' - 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<int>), kind)),
Is.Not.EqualTo(OverloadResolutionErrors.None), kind.ToString());
var valueArgument = new OverloadResolution(c, new[] { arrayArg });
Assert.That(valueArgument.AddCandidate(MakeByRefMethodIn(c, typeof(ReadOnlySpan<int>), kind)),
Is.Not.EqualTo(OverloadResolutionErrors.None), kind.ToString());
}
}
[Test]
public void InOverloadIsTheOnlyCandidateWithInAtTheCall()
{

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

@ -104,6 +104,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -104,6 +104,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
}
public static void RefSpanOrByValue(ref ReadOnlySpan<int> s)
{
}
public static void RefSpanOrByValue(ReadOnlySpan<int> s)
{
}
public static void OutSpanOrByValue(out ReadOnlySpan<int> s)
{
s = default(ReadOnlySpan<int>);
}
public static void OutSpanOrByValue(ReadOnlySpan<int> s)
{
}
public static void GenericArrayOrReadOnlySpan<T>(T[] a)
{
}
@ -159,12 +176,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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<int> s = arr;
RefSpanOrByValue(arr);
RefSpanOrByValue(ref s);
OutSpanOrByValue(arr);
OutSpanOrByValue(out s);
}
public static void CallLosersWithExplicitConversions(int[] arr, string str)
{
ArrayOrReadOnlySpan((ReadOnlySpan<int>)arr);

Loading…
Cancel
Save