Skip to content

Commit 1315f16

Browse files
Add minimal support for params collections to Overload Resolution.
1 parent 4a9f5db commit 1315f16

File tree

1 file changed

+121
-8
lines changed

1 file changed

+121
-8
lines changed

ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs

Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ sealed class Candidate
4343
public readonly bool IsExpandedForm;
4444

4545
/// <summary>
46-
/// Gets the parameter types. In the first step, these are the types without any substition.
46+
/// Gets the parameter types. In the first step, these are the types without any substitution.
4747
/// After type inference, substitutions will be performed.
4848
/// </summary>
4949
public readonly IType[] ParameterTypes;
@@ -76,14 +76,32 @@ sealed class Candidate
7676
/// </summary>
7777
public Conversion[] ArgumentConversions;
7878

79+
/// <summary>
80+
/// Gets the type of the collection that is used for the 'params' parameter (before any substitution!).
81+
/// Otherwise returns SpecialType.UnknownType.
82+
/// </summary>
83+
public IType ParamsCollectionType {
84+
get {
85+
if (IsExpandedForm && Parameters.Count > 0)
86+
{
87+
IParameter lastParameter = Parameters[Parameters.Count - 1];
88+
if (lastParameter.IsParams)
89+
{
90+
return lastParameter.Type;
91+
}
92+
}
93+
return SpecialType.UnknownType;
94+
}
95+
}
96+
7997
public bool IsGenericMethod {
8098
get {
8199
IMethod method = Member as IMethod;
82100
return method != null && method.TypeParameters.Count > 0;
83101
}
84102
}
85103

86-
public int ArgumentsPassedToParamsArray {
104+
public int ArgumentsPassedToParams {
87105
get {
88106
int count = 0;
89107
if (IsExpandedForm)
@@ -104,7 +122,7 @@ public Candidate(IParameterizedMember member, bool isExpanded)
104122
this.Member = member;
105123
this.IsExpandedForm = isExpanded;
106124
IParameterizedMember memberDefinition = (IParameterizedMember)member.MemberDefinition;
107-
// For specificialized methods, go back to the original parameters:
125+
// For specialized methods, go back to the original parameters:
108126
// (without any type parameter substitution, not even class type parameters)
109127
// We'll re-substitute them as part of RunTypeInference().
110128
this.Parameters = memberDefinition.Parameters;
@@ -286,9 +304,12 @@ bool ResolveParameterTypes(Candidate candidate, bool useSpecializedParameters)
286304
}
287305
if (candidate.IsExpandedForm && i == candidate.Parameters.Count - 1)
288306
{
289-
ArrayType arrayType = type as ArrayType;
290-
if (arrayType != null && arrayType.Dimensions == 1)
307+
if (type is ArrayType arrayType && arrayType.Dimensions == 1)
291308
type = arrayType.ElementType;
309+
else if (type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) || type.IsKnownType(KnownTypeCode.SpanOfT))
310+
type = type.TypeArguments[0];
311+
else if (type.IsArrayInterfaceType())
312+
type = type.TypeArguments[0];
292313
else
293314
return false; // error: cannot unpack params-array. abort considering the expanded form for this candidate
294315
}
@@ -772,8 +793,8 @@ int BetterFunctionMember(Candidate c1, Candidate c2)
772793
else if (c1.IsExpandedForm && !c2.IsExpandedForm)
773794
return 2;
774795

775-
// prefer the member with less arguments mapped to the params-array
776-
int r = c1.ArgumentsPassedToParamsArray.CompareTo(c2.ArgumentsPassedToParamsArray);
796+
// prefer the member with less arguments mapped to the params-collection
797+
int r = c1.ArgumentsPassedToParams.CompareTo(c2.ArgumentsPassedToParams);
777798
if (r < 0)
778799
return 1;
779800
else if (r > 0)
@@ -797,13 +818,105 @@ int BetterFunctionMember(Candidate c1, Candidate c2)
797818
return 1;
798819
if (lift1 != null && lift2 == null)
799820
return 2;
821+
822+
// prefer by-value parameters over in-parameters
823+
r = BetterParameterPassingChoice(c1, c2);
824+
if (r != 0)
825+
return r;
826+
827+
if (c1.IsExpandedForm)
828+
{
829+
Debug.Assert(c2.IsExpandedForm);
830+
r = BetterParamsCollectionType(c1.ParamsCollectionType, c2.ParamsCollectionType);
831+
if (r != 0)
832+
return r;
833+
}
834+
}
835+
return 0;
836+
}
837+
838+
int BetterParamsCollectionType(IType paramsCollectionType1, IType paramsCollectionType2)
839+
{
840+
// see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-13.0/params-collections#better-function-member
841+
bool isSpan1 = paramsCollectionType1.IsKnownType(KnownTypeCode.SpanOfT) || paramsCollectionType1.IsKnownType(KnownTypeCode.ReadOnlySpanOfT);
842+
bool isSpan2 = paramsCollectionType2.IsKnownType(KnownTypeCode.SpanOfT) || paramsCollectionType2.IsKnownType(KnownTypeCode.ReadOnlySpanOfT);
843+
if (!isSpan1 && !isSpan2)
844+
{
845+
bool implicitConversion1to2 = conversions.ImplicitConversion(paramsCollectionType1, paramsCollectionType2).IsValid;
846+
bool implicitConversion2to1 = conversions.ImplicitConversion(paramsCollectionType2, paramsCollectionType1).IsValid;
847+
if (implicitConversion1to2 && !implicitConversion2to1)
848+
return 1;
849+
if (!implicitConversion1to2 && implicitConversion2to1)
850+
return 2;
851+
}
852+
else if (paramsCollectionType1.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) && paramsCollectionType2.IsKnownType(KnownTypeCode.SpanOfT))
853+
{
854+
if (conversions.IdentityConversion(paramsCollectionType1.TypeArguments[0], paramsCollectionType2.TypeArguments[0]))
855+
return 1; // ReadOnlySpan<T> is better than Span<T> if there exists an identity conversion between the element types
800856
}
857+
else if (paramsCollectionType2.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) && paramsCollectionType1.IsKnownType(KnownTypeCode.SpanOfT))
858+
{
859+
if (conversions.IdentityConversion(paramsCollectionType2.TypeArguments[0], paramsCollectionType1.TypeArguments[0]))
860+
return 2; // ReadOnlySpan<T> is better than Span<T> if there exists an identity conversion between the element types
861+
}
862+
else if (isSpan1 && IsArrayOrArrayInterfaceType(paramsCollectionType2, out var elementType2))
863+
{
864+
if (conversions.IdentityConversion(paramsCollectionType1.TypeArguments[0], elementType2))
865+
return 1; // Span<T> is better than an array type if there exists an identity conversion between the element types
866+
}
867+
else if (isSpan2 && IsArrayOrArrayInterfaceType(paramsCollectionType1, out var elementType1))
868+
{
869+
if (conversions.IdentityConversion(paramsCollectionType2.TypeArguments[0], elementType1))
870+
return 2; // Span<T> is better than an array type if there exists an identity conversion between the element types
871+
}
872+
return 0;
873+
}
874+
875+
bool IsArrayOrArrayInterfaceType(IType type, out IType elementType)
876+
{
877+
elementType = null;
878+
if (type is ArrayType arrayType)
879+
{
880+
elementType = arrayType.ElementType;
881+
return true;
882+
}
883+
if (type.IsArrayInterfaceType())
884+
{
885+
elementType = type.TypeArguments[0];
886+
return true;
887+
}
888+
return false;
889+
}
890+
891+
int BetterParameterPassingChoice(Candidate c1, Candidate c2)
892+
{
893+
Debug.Assert(c1.Parameters.Count == c2.Parameters.Count, "c1 and c2 must have the same number of parameters");
894+
895+
bool c1IsBetter = false;
896+
bool c2IsBetter = false;
897+
898+
for (int i = 0; i < c1.Parameters.Count; i++)
899+
{
900+
ReferenceKind refKind1 = c1.Parameters[i].ReferenceKind;
901+
ReferenceKind refKind2 = c2.Parameters[i].ReferenceKind;
902+
903+
if (refKind1 == ReferenceKind.None && refKind2 == ReferenceKind.In)
904+
c1IsBetter = true; // by-value is better than in
905+
if (refKind1 == ReferenceKind.In && refKind2 == ReferenceKind.None)
906+
c2IsBetter = true;
907+
}
908+
909+
if (c1IsBetter && !c2IsBetter)
910+
return 1;
911+
if (!c1IsBetter && c2IsBetter)
912+
return 2;
913+
801914
return 0;
802915
}
803916

804917
int MoreSpecificFormalParameters(Candidate c1, Candidate c2)
805918
{
806-
// prefer the member with more formal parmeters (in case both have different number of optional parameters)
919+
// prefer the member with more formal parameters (in case both have different number of optional parameters)
807920
int r = c1.Parameters.Count.CompareTo(c2.Parameters.Count);
808921
if (r > 0)
809922
return 1;

0 commit comments

Comments
 (0)