mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
967 lines
37 KiB
967 lines
37 KiB
// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using ICSharpCode.Decompiler.Semantics; |
|
using ICSharpCode.Decompiler.TypeSystem; |
|
using ICSharpCode.Decompiler.TypeSystem.Implementation; |
|
using ICSharpCode.Decompiler.Util; |
|
|
|
namespace ICSharpCode.Decompiler.CSharp.Resolver |
|
{ |
|
/// <summary> |
|
/// C# overload resolution (C# 4.0 spec: §7.5). |
|
/// </summary> |
|
public class OverloadResolution |
|
{ |
|
sealed class Candidate |
|
{ |
|
public readonly IParameterizedMember Member; |
|
|
|
/// <summary> |
|
/// Returns the normal form candidate, if this is an expanded candidate. |
|
/// </summary> |
|
public readonly bool IsExpandedForm; |
|
|
|
/// <summary> |
|
/// Gets the parameter types. In the first step, these are the types without any substition. |
|
/// After type inference, substitutions will be performed. |
|
/// </summary> |
|
public readonly IType[] ParameterTypes; |
|
|
|
/// <summary> |
|
/// argument index -> parameter index; -1 for arguments that could not be mapped |
|
/// </summary> |
|
public int[] ArgumentToParameterMap; |
|
|
|
public OverloadResolutionErrors Errors; |
|
public int ErrorCount; |
|
|
|
public bool HasUnmappedOptionalParameters; |
|
|
|
public IType[] InferredTypes; |
|
|
|
/// <summary> |
|
/// Gets the original member parameters (before any substitution!) |
|
/// </summary> |
|
public readonly IReadOnlyList<IParameter> Parameters; |
|
|
|
/// <summary> |
|
/// Gets the original method type parameters (before any substitution!) |
|
/// </summary> |
|
public readonly IReadOnlyList<ITypeParameter> TypeParameters; |
|
|
|
/// <summary> |
|
/// Conversions applied to the arguments. |
|
/// This field is set by the CheckApplicability step. |
|
/// </summary> |
|
public Conversion[] ArgumentConversions; |
|
|
|
public bool IsGenericMethod { |
|
get { |
|
IMethod method = Member as IMethod; |
|
return method != null && method.TypeParameters.Count > 0; |
|
} |
|
} |
|
|
|
public int ArgumentsPassedToParamsArray { |
|
get { |
|
int count = 0; |
|
if (IsExpandedForm) { |
|
int paramsParameterIndex = this.Parameters.Count - 1; |
|
foreach (int parameterIndex in ArgumentToParameterMap) { |
|
if (parameterIndex == paramsParameterIndex) |
|
count++; |
|
} |
|
} |
|
return count; |
|
} |
|
} |
|
|
|
public Candidate(IParameterizedMember member, bool isExpanded) |
|
{ |
|
this.Member = member; |
|
this.IsExpandedForm = isExpanded; |
|
IParameterizedMember memberDefinition = (IParameterizedMember)member.MemberDefinition; |
|
// For specificialized methods, go back to the original parameters: |
|
// (without any type parameter substitution, not even class type parameters) |
|
// We'll re-substitute them as part of RunTypeInference(). |
|
this.Parameters = memberDefinition.Parameters; |
|
IMethod methodDefinition = memberDefinition as IMethod; |
|
if (methodDefinition != null && methodDefinition.TypeParameters.Count > 0) { |
|
this.TypeParameters = methodDefinition.TypeParameters; |
|
} |
|
this.ParameterTypes = new IType[this.Parameters.Count]; |
|
} |
|
|
|
public void AddError(OverloadResolutionErrors newError) |
|
{ |
|
this.Errors |= newError; |
|
if (!IsApplicable(newError)) |
|
this.ErrorCount++; |
|
} |
|
} |
|
|
|
readonly ICompilation compilation; |
|
readonly ResolveResult[] arguments; |
|
readonly string[] argumentNames; |
|
readonly CSharpConversions conversions; |
|
//List<Candidate> candidates = new List<Candidate>(); |
|
Candidate bestCandidate; |
|
Candidate bestCandidateAmbiguousWith; |
|
IType[] explicitlyGivenTypeArguments; |
|
bool bestCandidateWasValidated; |
|
OverloadResolutionErrors bestCandidateValidationResult; |
|
|
|
#region Constructor |
|
public OverloadResolution(ICompilation compilation, ResolveResult[] arguments, string[] argumentNames = null, IType[] typeArguments = null, CSharpConversions conversions = null) |
|
{ |
|
if (compilation == null) |
|
throw new ArgumentNullException("compilation"); |
|
if (arguments == null) |
|
throw new ArgumentNullException("arguments"); |
|
if (argumentNames == null) |
|
argumentNames = new string[arguments.Length]; |
|
else if (argumentNames.Length != arguments.Length) |
|
throw new ArgumentException("argumentsNames.Length must be equal to arguments.Length"); |
|
this.compilation = compilation; |
|
this.arguments = arguments; |
|
this.argumentNames = argumentNames; |
|
|
|
// keep explicitlyGivenTypeArguments==null when no type arguments were specified |
|
if (typeArguments != null && typeArguments.Length > 0) |
|
this.explicitlyGivenTypeArguments = typeArguments; |
|
|
|
this.conversions = conversions ?? CSharpConversions.Get(compilation); |
|
this.AllowExpandingParams = true; |
|
this.AllowOptionalParameters = true; |
|
} |
|
#endregion |
|
|
|
#region Input Properties |
|
/// <summary> |
|
/// Gets/Sets whether the methods are extension methods that are being called using extension method syntax. |
|
/// </summary> |
|
/// <remarks> |
|
/// Setting this property to true restricts the possible conversions on the first argument to |
|
/// implicit identity, reference, or boxing conversions. |
|
/// </remarks> |
|
public bool IsExtensionMethodInvocation { get; set; } |
|
|
|
/// <summary> |
|
/// Gets/Sets whether expanding 'params' into individual elements is allowed. |
|
/// The default value is true. |
|
/// </summary> |
|
public bool AllowExpandingParams { get; set; } |
|
|
|
/// <summary> |
|
/// Gets/Sets whether optional parameters may be left at their default value. |
|
/// The default value is true. |
|
/// If this property is set to false, optional parameters will be treated like regular parameters. |
|
/// </summary> |
|
public bool AllowOptionalParameters { get; set; } |
|
|
|
/// <summary> |
|
/// Gets/Sets whether ConversionResolveResults created by this OverloadResolution |
|
/// instance apply overflow checking. |
|
/// The default value is false. |
|
/// </summary> |
|
public bool CheckForOverflow { get; set; } |
|
|
|
/// <summary> |
|
/// Gets the arguments for which this OverloadResolution instance was created. |
|
/// </summary> |
|
public IList<ResolveResult> Arguments { |
|
get { return arguments; } |
|
} |
|
#endregion |
|
|
|
#region AddCandidate |
|
/// <summary> |
|
/// Adds a candidate to overload resolution. |
|
/// </summary> |
|
/// <param name="member">The candidate member to add.</param> |
|
/// <returns>The errors that prevent the member from being applicable, if any. |
|
/// Note: this method does not return errors that do not affect applicability.</returns> |
|
public OverloadResolutionErrors AddCandidate(IParameterizedMember member) |
|
{ |
|
return AddCandidate(member, OverloadResolutionErrors.None); |
|
} |
|
|
|
/// <summary> |
|
/// Adds a candidate to overload resolution. |
|
/// </summary> |
|
/// <param name="member">The candidate member to add.</param> |
|
/// <param name="additionalErrors">Additional errors that apply to the candidate. |
|
/// This is used to represent errors during member lookup (e.g. OverloadResolutionErrors.Inaccessible) |
|
/// in overload resolution.</param> |
|
/// <returns>The errors that prevent the member from being applicable, if any. |
|
/// Note: this method does not return errors that do not affect applicability.</returns> |
|
public OverloadResolutionErrors AddCandidate(IParameterizedMember member, OverloadResolutionErrors additionalErrors) |
|
{ |
|
if (member == null) |
|
throw new ArgumentNullException("member"); |
|
|
|
Candidate c = new Candidate(member, false); |
|
c.AddError(additionalErrors); |
|
if (CalculateCandidate(c)) { |
|
//candidates.Add(c); |
|
} |
|
|
|
if (this.AllowExpandingParams && member.Parameters.Count > 0 |
|
&& member.Parameters[member.Parameters.Count - 1].IsParams) |
|
{ |
|
Candidate expandedCandidate = new Candidate(member, true); |
|
expandedCandidate.AddError(additionalErrors); |
|
// consider expanded form only if it isn't obviously wrong |
|
if (CalculateCandidate(expandedCandidate)) { |
|
//candidates.Add(expandedCandidate); |
|
|
|
if (expandedCandidate.ErrorCount < c.ErrorCount) |
|
return expandedCandidate.Errors; |
|
} |
|
} |
|
return c.Errors; |
|
} |
|
|
|
/// <summary> |
|
/// Calculates applicability etc. for the candidate. |
|
/// </summary> |
|
/// <returns>True if the calculation was successful, false if the candidate should be removed without reporting an error</returns> |
|
bool CalculateCandidate(Candidate candidate) |
|
{ |
|
if (!ResolveParameterTypes(candidate, false)) |
|
return false; |
|
MapCorrespondingParameters(candidate); |
|
RunTypeInference(candidate); |
|
CheckApplicability(candidate); |
|
ConsiderIfNewCandidateIsBest(candidate); |
|
return true; |
|
} |
|
|
|
bool ResolveParameterTypes(Candidate candidate, bool useSpecializedParameters) |
|
{ |
|
for (int i = 0; i < candidate.Parameters.Count; i++) { |
|
IType type; |
|
if (useSpecializedParameters) { |
|
// Use the parameter type of the specialized non-generic method or indexer |
|
Debug.Assert(!candidate.IsGenericMethod); |
|
type = candidate.Member.Parameters[i].Type; |
|
} else { |
|
// Use the type of the original formal parameter |
|
type = candidate.Parameters[i].Type; |
|
} |
|
if (candidate.IsExpandedForm && i == candidate.Parameters.Count - 1) { |
|
ArrayType arrayType = type as ArrayType; |
|
if (arrayType != null && arrayType.Dimensions == 1) |
|
type = arrayType.ElementType; |
|
else |
|
return false; // error: cannot unpack params-array. abort considering the expanded form for this candidate |
|
} |
|
candidate.ParameterTypes[i] = type; |
|
} |
|
return true; |
|
} |
|
#endregion |
|
|
|
#region AddMethodLists |
|
/// <summary> |
|
/// Adds all candidates from the method lists. |
|
/// |
|
/// This method implements the logic that causes applicable methods in derived types to hide |
|
/// all methods in base types. |
|
/// </summary> |
|
/// <param name="methodLists">The methods, grouped by declaring type. Base types must come first in the list.</param> |
|
public void AddMethodLists(IReadOnlyList<MethodListWithDeclaringType> methodLists) |
|
{ |
|
if (methodLists == null) |
|
throw new ArgumentNullException("methodLists"); |
|
// Base types come first, so go through the list backwards (derived types first) |
|
bool[] isHiddenByDerivedType; |
|
if (methodLists.Count > 1) |
|
isHiddenByDerivedType = new bool[methodLists.Count]; |
|
else |
|
isHiddenByDerivedType = null; |
|
for (int i = methodLists.Count - 1; i >= 0; i--) { |
|
if (isHiddenByDerivedType != null && isHiddenByDerivedType[i]) { |
|
Log.WriteLine(" Skipping methods in {0} because they are hidden by an applicable method in a derived type", methodLists[i].DeclaringType); |
|
continue; |
|
} |
|
|
|
MethodListWithDeclaringType methodList = methodLists[i]; |
|
bool foundApplicableCandidateInCurrentList = false; |
|
|
|
for (int j = 0; j < methodList.Count; j++) { |
|
IParameterizedMember method = methodList[j]; |
|
Log.Indent(); |
|
OverloadResolutionErrors errors = AddCandidate(method); |
|
Log.Unindent(); |
|
LogCandidateAddingResult(" Candidate", method, errors); |
|
|
|
foundApplicableCandidateInCurrentList |= IsApplicable(errors); |
|
} |
|
|
|
if (foundApplicableCandidateInCurrentList && i > 0) { |
|
foreach (IType baseType in methodList.DeclaringType.GetAllBaseTypes()) { |
|
for (int j = 0; j < i; j++) { |
|
if (!isHiddenByDerivedType[j] && baseType.Equals(methodLists[j].DeclaringType)) |
|
isHiddenByDerivedType[j] = true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
[Conditional("DEBUG")] |
|
internal void LogCandidateAddingResult(string text, IParameterizedMember method, OverloadResolutionErrors errors) |
|
{ |
|
#if DEBUG |
|
Log.WriteLine(string.Format("{0} {1} = {2}{3}", |
|
text, method, |
|
errors == OverloadResolutionErrors.None ? "Success" : errors.ToString(), |
|
this.BestCandidate == method ? " (best candidate so far)" : |
|
this.BestCandidateAmbiguousWith == method ? " (ambiguous)" : "" |
|
)); |
|
#endif |
|
} |
|
#endregion |
|
|
|
#region MapCorrespondingParameters |
|
void MapCorrespondingParameters(Candidate candidate) |
|
{ |
|
// C# 4.0 spec: §7.5.1.1 Corresponding parameters |
|
// Updated for C# 7.2 non-trailing named arguments |
|
candidate.ArgumentToParameterMap = new int[arguments.Length]; |
|
bool hasPositionalArgument = false; |
|
// go backwards, so that hasPositionalArgument tells us whether there |
|
// are non-trailing named arguments |
|
for (int i = arguments.Length - 1; i >= 0; i--) { |
|
candidate.ArgumentToParameterMap[i] = -1; |
|
if (argumentNames[i] == null || hasPositionalArgument) { |
|
hasPositionalArgument = true; |
|
// positional argument or non-trailing named argument |
|
if (i < candidate.ParameterTypes.Length) { |
|
candidate.ArgumentToParameterMap[i] = i; |
|
if (argumentNames[i] != null && argumentNames[i] != candidate.Parameters[i].Name) { |
|
// non-trailing named argument must match name |
|
candidate.AddError(OverloadResolutionErrors.NoParameterFoundForNamedArgument); |
|
} |
|
} else if (candidate.IsExpandedForm) { |
|
candidate.ArgumentToParameterMap[i] = candidate.ParameterTypes.Length - 1; |
|
if (argumentNames[i] != null) { |
|
// can't use non-trailing named argument here |
|
candidate.AddError(OverloadResolutionErrors.NoParameterFoundForNamedArgument); |
|
} |
|
} else { |
|
candidate.AddError(OverloadResolutionErrors.TooManyPositionalArguments); |
|
} |
|
} else { |
|
// (trailing) named argument |
|
for (int j = 0; j < candidate.Parameters.Count; j++) { |
|
if (argumentNames[i] == candidate.Parameters[j].Name) { |
|
candidate.ArgumentToParameterMap[i] = j; |
|
} |
|
} |
|
if (candidate.ArgumentToParameterMap[i] < 0) |
|
candidate.AddError(OverloadResolutionErrors.NoParameterFoundForNamedArgument); |
|
} |
|
} |
|
} |
|
#endregion |
|
|
|
#region RunTypeInference |
|
void RunTypeInference(Candidate candidate) |
|
{ |
|
if (candidate.TypeParameters == null) { |
|
if (explicitlyGivenTypeArguments != null) { |
|
// method does not expect type arguments, but was given some |
|
candidate.AddError(OverloadResolutionErrors.WrongNumberOfTypeArguments); |
|
} |
|
// Grab new parameter types: |
|
ResolveParameterTypes(candidate, true); |
|
return; |
|
} |
|
ParameterizedType parameterizedDeclaringType = candidate.Member.DeclaringType as ParameterizedType; |
|
IReadOnlyList<IType> classTypeArguments; |
|
if (parameterizedDeclaringType != null) { |
|
classTypeArguments = parameterizedDeclaringType.TypeArguments; |
|
} else { |
|
classTypeArguments = null; |
|
} |
|
// The method is generic: |
|
if (explicitlyGivenTypeArguments != null) { |
|
if (explicitlyGivenTypeArguments.Length == candidate.TypeParameters.Count) { |
|
candidate.InferredTypes = explicitlyGivenTypeArguments; |
|
} else { |
|
candidate.AddError(OverloadResolutionErrors.WrongNumberOfTypeArguments); |
|
// wrong number of type arguments given, so truncate the list or pad with UnknownType |
|
candidate.InferredTypes = new IType[candidate.TypeParameters.Count]; |
|
for (int i = 0; i < candidate.InferredTypes.Length; i++) { |
|
if (i < explicitlyGivenTypeArguments.Length) |
|
candidate.InferredTypes[i] = explicitlyGivenTypeArguments[i]; |
|
else |
|
candidate.InferredTypes[i] = SpecialType.UnknownType; |
|
} |
|
} |
|
} else { |
|
TypeInference ti = new TypeInference(compilation, conversions); |
|
bool success; |
|
candidate.InferredTypes = ti.InferTypeArguments(candidate.TypeParameters, arguments, candidate.ParameterTypes, out success, classTypeArguments); |
|
if (!success) |
|
candidate.AddError(OverloadResolutionErrors.TypeInferenceFailed); |
|
} |
|
// Now substitute in the formal parameters: |
|
var substitution = new ConstraintValidatingSubstitution(classTypeArguments, candidate.InferredTypes, this); |
|
for (int i = 0; i < candidate.ParameterTypes.Length; i++) { |
|
candidate.ParameterTypes[i] = candidate.ParameterTypes[i].AcceptVisitor(substitution); |
|
} |
|
if (!substitution.ConstraintsValid) |
|
candidate.AddError(OverloadResolutionErrors.ConstructedTypeDoesNotSatisfyConstraint); |
|
} |
|
|
|
sealed class ConstraintValidatingSubstitution : TypeParameterSubstitution |
|
{ |
|
readonly CSharpConversions conversions; |
|
public bool ConstraintsValid = true; |
|
|
|
public ConstraintValidatingSubstitution(IReadOnlyList<IType> classTypeArguments, IReadOnlyList<IType> methodTypeArguments, OverloadResolution overloadResolution) |
|
: base(classTypeArguments, methodTypeArguments) |
|
{ |
|
this.conversions = overloadResolution.conversions; |
|
} |
|
|
|
public override IType VisitParameterizedType(ParameterizedType type) |
|
{ |
|
IType newType = base.VisitParameterizedType(type); |
|
if (newType != type && ConstraintsValid) { |
|
// something was changed, so we need to validate the constraints |
|
ParameterizedType newParameterizedType = newType as ParameterizedType; |
|
if (newParameterizedType != null) { |
|
// C# 4.0 spec: §4.4.4 Satisfying constraints |
|
var typeParameters = newParameterizedType.TypeParameters; |
|
var substitution = newParameterizedType.GetSubstitution(); |
|
for (int i = 0; i < typeParameters.Count; i++) { |
|
if (!ValidateConstraints(typeParameters[i], newParameterizedType.GetTypeArgument(i), substitution, conversions)) { |
|
ConstraintsValid = false; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
return newType; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Validate Constraints |
|
OverloadResolutionErrors ValidateMethodConstraints(Candidate candidate) |
|
{ |
|
// If type inference already failed, we won't check the constraints: |
|
if ((candidate.Errors & OverloadResolutionErrors.TypeInferenceFailed) != 0) |
|
return OverloadResolutionErrors.None; |
|
|
|
if (candidate.TypeParameters == null || candidate.TypeParameters.Count == 0) |
|
return OverloadResolutionErrors.None; // the method isn't generic |
|
var substitution = GetSubstitution(candidate); |
|
for (int i = 0; i < candidate.TypeParameters.Count; i++) { |
|
if (!ValidateConstraints(candidate.TypeParameters[i], substitution.MethodTypeArguments[i], substitution)) |
|
return OverloadResolutionErrors.MethodConstraintsNotSatisfied; |
|
} |
|
return OverloadResolutionErrors.None; |
|
} |
|
|
|
/// <summary> |
|
/// Validates whether the given type argument satisfies the constraints for the given type parameter. |
|
/// </summary> |
|
/// <param name="typeParameter">The type parameter.</param> |
|
/// <param name="typeArgument">The type argument.</param> |
|
/// <param name="substitution">The substitution that defines how type parameters are replaced with type arguments. |
|
/// The substitution is used to check constraints that depend on other type parameters (or recursively on the same type parameter). |
|
/// May be null if no substitution should be used.</param> |
|
/// <returns>True if the constraints are satisfied; false otherwise.</returns> |
|
public static bool ValidateConstraints(ITypeParameter typeParameter, IType typeArgument, TypeVisitor substitution = null) |
|
{ |
|
if (typeParameter == null) |
|
throw new ArgumentNullException("typeParameter"); |
|
if (typeArgument == null) |
|
throw new ArgumentNullException("typeArgument"); |
|
return ValidateConstraints(typeParameter, typeArgument, substitution, CSharpConversions.Get(typeParameter.Owner.Compilation)); |
|
} |
|
|
|
internal static bool ValidateConstraints(ITypeParameter typeParameter, IType typeArgument, TypeVisitor substitution, CSharpConversions conversions) |
|
{ |
|
switch (typeArgument.Kind) { // void, null, and pointers cannot be used as type arguments |
|
case TypeKind.Void: |
|
case TypeKind.Null: |
|
case TypeKind.Pointer: |
|
return false; |
|
} |
|
if (typeParameter.HasReferenceTypeConstraint) { |
|
if (typeArgument.IsReferenceType != true) |
|
return false; |
|
} |
|
if (typeParameter.HasValueTypeConstraint) { |
|
if (!NullableType.IsNonNullableValueType(typeArgument)) |
|
return false; |
|
} |
|
if (typeParameter.HasDefaultConstructorConstraint) { |
|
ITypeDefinition def = typeArgument.GetDefinition(); |
|
if (def != null && def.IsAbstract) |
|
return false; |
|
var ctors = typeArgument.GetConstructors( |
|
m => m.Parameters.Count == 0 && m.Accessibility == Accessibility.Public, |
|
GetMemberOptions.IgnoreInheritedMembers | GetMemberOptions.ReturnMemberDefinitions |
|
); |
|
if (!ctors.Any()) |
|
return false; |
|
} |
|
foreach (IType constraintType in typeParameter.DirectBaseTypes) { |
|
IType c = constraintType; |
|
if (substitution != null) |
|
c = c.AcceptVisitor(substitution); |
|
if (!conversions.IsConstraintConvertible(typeArgument, c)) |
|
return false; |
|
} |
|
return true; |
|
} |
|
#endregion |
|
|
|
#region CheckApplicability |
|
/// <summary> |
|
/// Returns whether a candidate with the given errors is still considered to be applicable. |
|
/// </summary> |
|
public static bool IsApplicable(OverloadResolutionErrors errors) |
|
{ |
|
const OverloadResolutionErrors errorsThatDoNotMatterForApplicability = |
|
OverloadResolutionErrors.AmbiguousMatch | OverloadResolutionErrors.MethodConstraintsNotSatisfied; |
|
return (errors & ~errorsThatDoNotMatterForApplicability) == OverloadResolutionErrors.None; |
|
} |
|
|
|
void CheckApplicability(Candidate candidate) |
|
{ |
|
// C# 4.0 spec: §7.5.3.1 Applicable function member |
|
|
|
// Test whether parameters were mapped the correct number of arguments: |
|
int[] argumentCountPerParameter = new int[candidate.ParameterTypes.Length]; |
|
foreach (int parameterIndex in candidate.ArgumentToParameterMap) { |
|
if (parameterIndex >= 0) |
|
argumentCountPerParameter[parameterIndex]++; |
|
} |
|
for (int i = 0; i < argumentCountPerParameter.Length; i++) { |
|
if (candidate.IsExpandedForm && i == argumentCountPerParameter.Length - 1) |
|
continue; // any number of arguments is fine for the params-array |
|
if (argumentCountPerParameter[i] == 0) { |
|
if (this.AllowOptionalParameters && candidate.Parameters[i].IsOptional) |
|
candidate.HasUnmappedOptionalParameters = true; |
|
else |
|
candidate.AddError(OverloadResolutionErrors.MissingArgumentForRequiredParameter); |
|
} else if (argumentCountPerParameter[i] > 1) { |
|
candidate.AddError(OverloadResolutionErrors.MultipleArgumentsForSingleParameter); |
|
} |
|
} |
|
|
|
candidate.ArgumentConversions = new Conversion[arguments.Length]; |
|
// Test whether argument passing mode matches the parameter passing mode |
|
for (int i = 0; i < arguments.Length; i++) { |
|
int parameterIndex = candidate.ArgumentToParameterMap[i]; |
|
if (parameterIndex < 0) { |
|
candidate.ArgumentConversions[i] = Conversion.None; |
|
continue; |
|
} |
|
|
|
ByReferenceResolveResult brrr = arguments[i] as ByReferenceResolveResult; |
|
if (brrr != null) { |
|
if ((brrr.IsOut && !candidate.Parameters[parameterIndex].IsOut) || (brrr.IsRef && !candidate.Parameters[parameterIndex].IsRef)) |
|
candidate.AddError(OverloadResolutionErrors.ParameterPassingModeMismatch); |
|
} else { |
|
if (candidate.Parameters[parameterIndex].IsOut || candidate.Parameters[parameterIndex].IsRef) |
|
candidate.AddError(OverloadResolutionErrors.ParameterPassingModeMismatch); |
|
} |
|
IType parameterType = candidate.ParameterTypes[parameterIndex]; |
|
Conversion c = conversions.ImplicitConversion(arguments[i], parameterType); |
|
candidate.ArgumentConversions[i] = c; |
|
if (IsExtensionMethodInvocation && parameterIndex == 0) { |
|
// First parameter to extension method must be an identity, reference or boxing conversion |
|
if (!(c == Conversion.IdentityConversion || c == Conversion.ImplicitReferenceConversion || c == Conversion.BoxingConversion)) |
|
candidate.AddError(OverloadResolutionErrors.ArgumentTypeMismatch); |
|
} else { |
|
if ((!c.IsValid && !c.IsUserDefined && !c.IsMethodGroupConversion) && parameterType.Kind != TypeKind.Unknown) |
|
candidate.AddError(OverloadResolutionErrors.ArgumentTypeMismatch); |
|
} |
|
} |
|
} |
|
#endregion |
|
|
|
#region BetterFunctionMember |
|
/// <summary> |
|
/// Returns 1 if c1 is better than c2; 2 if c2 is better than c1; or 0 if neither is better. |
|
/// </summary> |
|
int BetterFunctionMember(Candidate c1, Candidate c2) |
|
{ |
|
// prefer applicable members (part of heuristic that produces a best candidate even if none is applicable) |
|
if (c1.ErrorCount == 0 && c2.ErrorCount > 0) |
|
return 1; |
|
if (c1.ErrorCount > 0 && c2.ErrorCount == 0) |
|
return 2; |
|
|
|
// C# 4.0 spec: §7.5.3.2 Better function member |
|
bool c1IsBetter = false; |
|
bool c2IsBetter = false; |
|
bool parameterTypesEqual = true; |
|
for (int i = 0; i < arguments.Length; i++) { |
|
int p1 = c1.ArgumentToParameterMap[i]; |
|
int p2 = c2.ArgumentToParameterMap[i]; |
|
if (p1 >= 0 && p2 < 0) { |
|
c1IsBetter = true; |
|
} else if (p1 < 0 && p2 >= 0) { |
|
c2IsBetter = true; |
|
} else if (p1 >= 0 && p2 >= 0) { |
|
if (!conversions.IdentityConversion(c1.ParameterTypes[p1], c2.ParameterTypes[p2])) |
|
parameterTypesEqual = false; |
|
switch (conversions.BetterConversion(arguments[i], c1.ParameterTypes[p1], c2.ParameterTypes[p2])) { |
|
case 1: |
|
c1IsBetter = true; |
|
break; |
|
case 2: |
|
c2IsBetter = true; |
|
break; |
|
} |
|
} |
|
} |
|
if (c1IsBetter && !c2IsBetter) |
|
return 1; |
|
if (!c1IsBetter && c2IsBetter) |
|
return 2; |
|
|
|
// prefer members with less errors (part of heuristic that produces a best candidate even if none is applicable) |
|
if (c1.ErrorCount < c2.ErrorCount) return 1; |
|
if (c1.ErrorCount > c2.ErrorCount) return 2; |
|
|
|
if (!c1IsBetter && !c2IsBetter && parameterTypesEqual) { |
|
// we need the tie-breaking rules |
|
|
|
// non-generic methods are better |
|
if (!c1.IsGenericMethod && c2.IsGenericMethod) |
|
return 1; |
|
else if (c1.IsGenericMethod && !c2.IsGenericMethod) |
|
return 2; |
|
|
|
// non-expanded members are better |
|
if (!c1.IsExpandedForm && c2.IsExpandedForm) |
|
return 1; |
|
else if (c1.IsExpandedForm && !c2.IsExpandedForm) |
|
return 2; |
|
|
|
// prefer the member with less arguments mapped to the params-array |
|
int r = c1.ArgumentsPassedToParamsArray.CompareTo(c2.ArgumentsPassedToParamsArray); |
|
if (r < 0) return 1; |
|
else if (r > 0) return 2; |
|
|
|
// prefer the member where no default values need to be substituted |
|
if (!c1.HasUnmappedOptionalParameters && c2.HasUnmappedOptionalParameters) |
|
return 1; |
|
else if (c1.HasUnmappedOptionalParameters && !c2.HasUnmappedOptionalParameters) |
|
return 2; |
|
|
|
// compare the formal parameters |
|
r = MoreSpecificFormalParameters(c1, c2); |
|
if (r != 0) |
|
return r; |
|
|
|
// prefer non-lifted operators |
|
ILiftedOperator lift1 = c1.Member as ILiftedOperator; |
|
ILiftedOperator lift2 = c2.Member as ILiftedOperator; |
|
if (lift1 == null && lift2 != null) |
|
return 1; |
|
if (lift1 != null && lift2 == null) |
|
return 2; |
|
} |
|
return 0; |
|
} |
|
|
|
int MoreSpecificFormalParameters(Candidate c1, Candidate c2) |
|
{ |
|
// prefer the member with more formal parmeters (in case both have different number of optional parameters) |
|
int r = c1.Parameters.Count.CompareTo(c2.Parameters.Count); |
|
if (r > 0) return 1; |
|
else if (r < 0) return 2; |
|
|
|
return MoreSpecificFormalParameters(c1.Parameters.Select(p => p.Type), c2.Parameters.Select(p => p.Type)); |
|
} |
|
|
|
static int MoreSpecificFormalParameters(IEnumerable<IType> t1, IEnumerable<IType> t2) |
|
{ |
|
bool c1IsBetter = false; |
|
bool c2IsBetter = false; |
|
foreach (var pair in t1.Zip(t2, (a,b) => new { Item1 = a, Item2 = b })) { |
|
switch (MoreSpecificFormalParameter(pair.Item1, pair.Item2)) { |
|
case 1: |
|
c1IsBetter = true; |
|
break; |
|
case 2: |
|
c2IsBetter = true; |
|
break; |
|
} |
|
} |
|
if (c1IsBetter && !c2IsBetter) |
|
return 1; |
|
if (!c1IsBetter && c2IsBetter) |
|
return 2; |
|
return 0; |
|
} |
|
|
|
static int MoreSpecificFormalParameter(IType t1, IType t2) |
|
{ |
|
if ((t1 is ITypeParameter) && !(t2 is ITypeParameter)) |
|
return 2; |
|
if ((t2 is ITypeParameter) && !(t1 is ITypeParameter)) |
|
return 1; |
|
|
|
ParameterizedType p1 = t1 as ParameterizedType; |
|
ParameterizedType p2 = t2 as ParameterizedType; |
|
if (p1 != null && p2 != null && p1.TypeParameterCount == p2.TypeParameterCount) { |
|
int r = MoreSpecificFormalParameters(p1.TypeArguments, p2.TypeArguments); |
|
if (r > 0) |
|
return r; |
|
} |
|
TypeWithElementType tew1 = t1 as TypeWithElementType; |
|
TypeWithElementType tew2 = t2 as TypeWithElementType; |
|
if (tew1 != null && tew2 != null) { |
|
return MoreSpecificFormalParameter(tew1.ElementType, tew2.ElementType); |
|
} |
|
return 0; |
|
} |
|
#endregion |
|
|
|
#region ConsiderIfNewCandidateIsBest |
|
void ConsiderIfNewCandidateIsBest(Candidate candidate) |
|
{ |
|
if (bestCandidate == null) { |
|
bestCandidate = candidate; |
|
bestCandidateWasValidated = false; |
|
} else { |
|
switch (BetterFunctionMember(candidate, bestCandidate)) { |
|
case 0: |
|
// Overwrite 'bestCandidateAmbiguousWith' so that API users can |
|
// detect the set of all ambiguous methods if they look at |
|
// bestCandidateAmbiguousWith after each step. |
|
bestCandidateAmbiguousWith = candidate; |
|
break; |
|
case 1: |
|
bestCandidate = candidate; |
|
bestCandidateWasValidated = false; |
|
bestCandidateAmbiguousWith = null; |
|
break; |
|
// case 2: best candidate stays best |
|
} |
|
} |
|
} |
|
#endregion |
|
|
|
#region Output Properties |
|
public IParameterizedMember BestCandidate { |
|
get { return bestCandidate != null ? bestCandidate.Member : null; } |
|
} |
|
|
|
/// <summary> |
|
/// Returns the errors that apply to the best candidate. |
|
/// This includes additional errors that do not affect applicability (e.g. AmbiguousMatch, MethodConstraintsNotSatisfied) |
|
/// </summary> |
|
public OverloadResolutionErrors BestCandidateErrors { |
|
get { |
|
if (bestCandidate == null) |
|
return OverloadResolutionErrors.None; |
|
if (!bestCandidateWasValidated) { |
|
bestCandidateValidationResult = ValidateMethodConstraints(bestCandidate); |
|
bestCandidateWasValidated = true; |
|
} |
|
OverloadResolutionErrors err = bestCandidate.Errors | bestCandidateValidationResult; |
|
if (bestCandidateAmbiguousWith != null) |
|
err |= OverloadResolutionErrors.AmbiguousMatch; |
|
return err; |
|
} |
|
} |
|
|
|
public bool FoundApplicableCandidate { |
|
get { return bestCandidate != null && IsApplicable(bestCandidate.Errors); } |
|
} |
|
|
|
public IParameterizedMember BestCandidateAmbiguousWith { |
|
get { return bestCandidateAmbiguousWith != null ? bestCandidateAmbiguousWith.Member : null; } |
|
} |
|
|
|
public bool BestCandidateIsExpandedForm { |
|
get { return bestCandidate != null ? bestCandidate.IsExpandedForm : false; } |
|
} |
|
|
|
public bool IsAmbiguous { |
|
get { return bestCandidateAmbiguousWith != null; } |
|
} |
|
|
|
public IReadOnlyList<IType> InferredTypeArguments { |
|
get { |
|
if (bestCandidate != null && bestCandidate.InferredTypes != null) |
|
return bestCandidate.InferredTypes; |
|
else |
|
return EmptyList<IType>.Instance; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the implicit conversions that are being applied to the arguments. |
|
/// </summary> |
|
public IList<Conversion> ArgumentConversions { |
|
get { |
|
if (bestCandidate != null && bestCandidate.ArgumentConversions != null) |
|
return bestCandidate.ArgumentConversions; |
|
else |
|
return Enumerable.Repeat(Conversion.None, arguments.Length).ToList(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets an array that maps argument indices to parameter indices. |
|
/// For arguments that could not be mapped to any parameter, the value will be -1. |
|
/// |
|
/// parameterIndex = GetArgumentToParameterMap()[argumentIndex] |
|
/// </summary> |
|
public IReadOnlyList<int> GetArgumentToParameterMap() |
|
{ |
|
if (bestCandidate != null) |
|
return bestCandidate.ArgumentToParameterMap; |
|
else |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Returns the arguments for the method call in the order they were provided (not in the order of the parameters). |
|
/// Arguments are wrapped in a <see cref="ConversionResolveResult"/> if an implicit conversion is being applied |
|
/// to them when calling the method. |
|
/// </summary> |
|
public IList<ResolveResult> GetArgumentsWithConversions() |
|
{ |
|
if (bestCandidate == null) |
|
return arguments; |
|
else |
|
return GetArgumentsWithConversions(null, null); |
|
} |
|
|
|
/// <summary> |
|
/// Returns the arguments for the method call in the order they were provided (not in the order of the parameters). |
|
/// Arguments are wrapped in a <see cref="ConversionResolveResult"/> if an implicit conversion is being applied |
|
/// to them when calling the method. |
|
/// For arguments where an explicit argument name was provided, the argument will |
|
/// be wrapped in a <see cref="NamedArgumentResolveResult"/>. |
|
/// </summary> |
|
public IList<ResolveResult> GetArgumentsWithConversionsAndNames() |
|
{ |
|
if (bestCandidate == null) |
|
return arguments; |
|
else |
|
return GetArgumentsWithConversions(null, GetBestCandidateWithSubstitutedTypeArguments()); |
|
} |
|
|
|
IList<ResolveResult> GetArgumentsWithConversions(ResolveResult targetResolveResult, IParameterizedMember bestCandidateForNamedArguments) |
|
{ |
|
var conversions = this.ArgumentConversions; |
|
ResolveResult[] args = new ResolveResult[arguments.Length]; |
|
for (int i = 0; i < args.Length; i++) { |
|
var argument = arguments[i]; |
|
if (this.IsExtensionMethodInvocation && i == 0 && targetResolveResult != null) |
|
argument = targetResolveResult; |
|
int parameterIndex = bestCandidate.ArgumentToParameterMap[i]; |
|
if (parameterIndex >= 0 && conversions[i] != Conversion.IdentityConversion) { |
|
// Wrap argument in ConversionResolveResult |
|
IType parameterType = bestCandidate.ParameterTypes[parameterIndex]; |
|
if (parameterType.Kind != TypeKind.Unknown) { |
|
if (arguments[i].IsCompileTimeConstant && conversions[i].IsValid && !conversions[i].IsUserDefined) { |
|
argument = new CSharpResolver(compilation).WithCheckForOverflow(CheckForOverflow).ResolveCast(parameterType, argument); |
|
} else { |
|
argument = new ConversionResolveResult(parameterType, argument, conversions[i], CheckForOverflow); |
|
} |
|
} |
|
} |
|
if (bestCandidateForNamedArguments != null && argumentNames[i] != null) { |
|
// Wrap argument in NamedArgumentResolveResult |
|
if (parameterIndex >= 0) { |
|
argument = new NamedArgumentResolveResult(bestCandidateForNamedArguments.Parameters[parameterIndex], argument, bestCandidateForNamedArguments); |
|
} else { |
|
argument = new NamedArgumentResolveResult(argumentNames[i], argument); |
|
} |
|
} |
|
args[i] = argument; |
|
} |
|
return args; |
|
} |
|
|
|
public IParameterizedMember GetBestCandidateWithSubstitutedTypeArguments() |
|
{ |
|
if (bestCandidate == null) |
|
return null; |
|
IMethod method = bestCandidate.Member as IMethod; |
|
if (method != null && method.TypeParameters.Count > 0) { |
|
return ((IMethod)method.MemberDefinition).Specialize(GetSubstitution(bestCandidate)); |
|
} else { |
|
return bestCandidate.Member; |
|
} |
|
} |
|
|
|
TypeParameterSubstitution GetSubstitution(Candidate candidate) |
|
{ |
|
// Do not compose the substitutions, but merge them. |
|
// This is required for InvocationTests.SubstituteClassAndMethodTypeParametersAtOnce |
|
return new TypeParameterSubstitution(candidate.Member.Substitution.ClassTypeArguments, candidate.InferredTypes); |
|
} |
|
|
|
/// <summary> |
|
/// Creates a ResolveResult representing the result of overload resolution. |
|
/// </summary> |
|
/// <param name="targetResolveResult"> |
|
/// The target expression of the call. May be <c>null</c> for static methods/constructors. |
|
/// </param> |
|
/// <param name="initializerStatements"> |
|
/// Statements for Objects/Collections initializer. |
|
/// <see cref="InvocationResolveResult.InitializerStatements"/> |
|
/// </param> |
|
/// <param name="returnTypeOverride"> |
|
/// If not null, use this instead of the ReturnType of the member as the type of the created resolve result. |
|
/// </param> |
|
public CSharpInvocationResolveResult CreateResolveResult(ResolveResult targetResolveResult, IList<ResolveResult> initializerStatements = null, IType returnTypeOverride = null) |
|
{ |
|
IParameterizedMember member = GetBestCandidateWithSubstitutedTypeArguments(); |
|
if (member == null) |
|
throw new InvalidOperationException(); |
|
|
|
return new CSharpInvocationResolveResult( |
|
this.IsExtensionMethodInvocation ? new TypeResolveResult(member.DeclaringType ?? SpecialType.UnknownType) : targetResolveResult, |
|
member, |
|
GetArgumentsWithConversions(targetResolveResult, member), |
|
this.BestCandidateErrors, |
|
this.IsExtensionMethodInvocation, |
|
this.BestCandidateIsExpandedForm, |
|
isDelegateInvocation: false, |
|
argumentToParameterMap: this.GetArgumentToParameterMap(), |
|
initializerStatements: initializerStatements, |
|
returnTypeOverride: returnTypeOverride); |
|
} |
|
#endregion |
|
} |
|
}
|
|
|