diff --git a/ICSharpCode.Decompiler/CSharp/Disambiguator.cs b/ICSharpCode.Decompiler/CSharp/Disambiguator.cs
index 6ca7dff76cc..6df0c15c05b 100644
--- a/ICSharpCode.Decompiler/CSharp/Disambiguator.cs
+++ b/ICSharpCode.Decompiler/CSharp/Disambiguator.cs
@@ -587,6 +587,45 @@ bool TryApply(ReferenceTransformation step)
return true;
}
+ ///
+ /// Stage one: what the name binds to from this target, looked up the way this form of
+ /// reference is written. A null target is the unqualified lookup, not the absence of one.
+ ///
+ static ResolveResult? LookUpName(ExpressionBuilder expressionBuilder, ResolveResult? target,
+ string name, IReadOnlyList typeArguments, bool invocation)
+ {
+ CSharpResolver resolver = expressionBuilder.resolver;
+ return target == null
+ ? resolver.ResolveSimpleName(name, typeArguments, isInvocationTarget: invocation)
+ : CreateLookup(resolver).Lookup(target, name, typeArguments, isInvocation: invocation);
+ }
+
+ ///
+ /// Stage three: did overload resolution settle on the member meant? An empty candidate
+ /// set carries no error of its own - there is no best candidate to hold one - so it is
+ /// reported here.
+ ///
+ static OverloadResolutionErrors CheckBestCandidate(OverloadResolution or,
+ ExpectedTargetDetails expectedTargetDetails, IMember expected, out IParameterizedMember? found)
+ {
+ if (or.BestCandidateErrors != OverloadResolutionErrors.None)
+ {
+ found = null;
+ return or.BestCandidateErrors;
+ }
+ if (or.IsAmbiguous)
+ {
+ found = null;
+ return OverloadResolutionErrors.AmbiguousMatch;
+ }
+ found = or.GetBestCandidateWithSubstitutedTypeArguments();
+ if (found == null)
+ return OverloadResolutionErrors.AmbiguousMatch;
+ return IsAppropriateCallTarget(expectedTargetDetails, expected, found)
+ ? OverloadResolutionErrors.None
+ : OverloadResolutionErrors.AmbiguousMatch;
+ }
+
static MemberLookup CreateLookup(CSharpResolver resolver)
{
return new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentModule);
@@ -674,36 +713,19 @@ internal static OverloadResolutionErrors IsUnambiguousCall(ExpressionBuilder exp
or.AddCandidate(m);
}
}
- else if (target == null)
- {
- var result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: true)
- as MethodGroupResolveResult;
- if (result == null)
- return OverloadResolutionErrors.AmbiguousMatch;
- or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray());
- }
else
{
- var result = lookup.Lookup(target, method.Name, typeArguments, isInvocation: true) as MethodGroupResolveResult;
- if (result == null)
+ if (LookUpName(expressionBuilder, target, method.Name, typeArguments, invocation: true)
+ is not MethodGroupResolveResult methodGroup)
return OverloadResolutionErrors.AmbiguousMatch;
- or.AddMethodLists(result.MethodsGroupedByDeclaringType.ToArray());
+ or.AddMethodLists(methodGroup.MethodsGroupedByDeclaringType.ToArray());
}
bestCandidateIsExpandedForm = or.BestCandidateIsExpandedForm;
- if (or.BestCandidateErrors != OverloadResolutionErrors.None)
- return or.BestCandidateErrors;
- if (or.IsAmbiguous)
- return OverloadResolutionErrors.AmbiguousMatch;
- foundMember = or.GetBestCandidateWithSubstitutedTypeArguments();
- if (foundMember == null)
- {
- // Overload resolution reports no error for an empty candidate set - there is no
- // best candidate to carry one - so a call that matched nothing has to be reported
- // as unresolvable here.
- return OverloadResolutionErrors.AmbiguousMatch;
- }
- if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMember))
- return OverloadResolutionErrors.AmbiguousMatch;
+ var resolutionErrors = CheckBestCandidate(or, expectedTargetDetails, method, out foundMember);
+ if (resolutionErrors != OverloadResolutionErrors.None)
+ return resolutionErrors;
+ // Reporting no error means a candidate was found.
+ Debug.Assert(foundMember != null);
var map = or.GetArgumentToParameterMap();
for (int i = 0; i < arguments.Length; i++)
{
@@ -727,42 +749,26 @@ bool IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveRes
Log.WriteLine("IsUnambiguousAccess: Performing overload resolution for " + method);
Log.WriteCollection(" Arguments: ", arguments.Select(a => a.ResolveResult));
- foundMember = null;
- if (target == null)
+ IMember accessorOwner = method.AccessorOwner!;
+ // An indexer has no name to look up, so its candidates come from the indexer list and
+ // overload resolution picks among them; everything else binds by name.
+ if (target != null && accessorOwner.SymbolKind == SymbolKind.Indexer)
{
- var result = resolver.ResolveSimpleName(method.AccessorOwner!.Name,
- EmptyList.Instance,
- isInvocationTarget: false) as MemberResolveResult;
- if (result == null || result.IsError)
- return false;
- foundMember = result.Member;
+ var or = CreateOverloadResolution(resolver, arguments.SelectArray(a => a.ResolveResult),
+ argumentNames, Empty.Array);
+ or.AddMethodLists(CreateLookup(resolver).LookupIndexers(target));
+ var errors = CheckBestCandidate(or, expectedTargetDetails, accessorOwner, out var best);
+ foundMember = best;
+ return errors == OverloadResolutionErrors.None;
}
- else
+ if (LookUpName(expressionBuilder, target, accessorOwner.Name, EmptyList.Instance,
+ invocation: false) is not MemberResolveResult { IsError: false } resolved)
{
- var lookup = CreateLookup(resolver);
- if (method.AccessorOwner!.SymbolKind == SymbolKind.Indexer)
- {
- var or = CreateOverloadResolution(resolver, arguments.SelectArray(a => a.ResolveResult),
- argumentNames, Empty.Array);
- or.AddMethodLists(lookup.LookupIndexers(target));
- if (or.BestCandidateErrors != OverloadResolutionErrors.None)
- return false;
- if (or.IsAmbiguous)
- return false;
- foundMember = or.GetBestCandidateWithSubstitutedTypeArguments();
- }
- else
- {
- var result = lookup.Lookup(target,
- method.AccessorOwner!.Name,
- EmptyList.Instance,
- isInvocation: false) as MemberResolveResult;
- if (result == null || result.IsError)
- return false;
- foundMember = result.Member;
- }
+ foundMember = null;
+ return false;
}
- return foundMember != null && IsAppropriateCallTarget(expectedTargetDetails, method.AccessorOwner, foundMember);
+ foundMember = resolved.Member;
+ return IsAppropriateCallTarget(expectedTargetDetails, accessorOwner, foundMember);
}
bool IsUnambiguousMethodReference(ExpectedTargetDetails expectedTargetDetails, IMethod method, ResolveResult? target, IReadOnlyList typeArguments, bool isExtensionMethodReference, [NotNullWhen(true)] out ResolveResult? result)
@@ -790,22 +796,16 @@ bool IsUnambiguousMethodReference(ExpectedTargetDetails expectedTargetDetails, I
or = CreateOverloadResolution(resolver,
method.Parameters.SelectReadOnlyArray(p => new TypeResolveResult(p.Type)),
argumentNames: null, typeArguments.ToArray());
- if (target == null)
- {
- result = resolver.ResolveSimpleName(method.Name, typeArguments, isInvocationTarget: false);
- if (!(result is MethodGroupResolveResult mgrr))
- return false;
- or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray());
- }
- else
- {
- result = lookup.Lookup(target, method.Name, typeArguments, isInvocation: false);
- if (!(result is MethodGroupResolveResult mgrr))
- return false;
- or.AddMethodLists(mgrr.MethodsGroupedByDeclaringType.ToArray());
- }
+ result = LookUpName(expressionBuilder, target, method.Name, typeArguments, invocation: false);
+ if (result is not MethodGroupResolveResult methodGroup)
+ return false;
+ or.AddMethodLists(methodGroup.MethodsGroupedByDeclaringType.ToArray());
}
+ // Deliberately not CheckBestCandidate: unlike the other two checks this one does not
+ // reject BestCandidateErrors or an ambiguous result, and a method group carries no
+ // arguments to be ambiguous over. Whether that holds for the type arguments too is
+ // untested, so it is left as it was rather than tightened blind.
var foundMethod = or.GetBestCandidateWithSubstitutedTypeArguments();
if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMethod))
return false;