提交 13e22861 编写于 作者: C Charles Stoner 提交者: GitHub

Merge pull request #15839 from alrz/issue-13685

Use a linear algorithm to find the best candidate
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.CSharp
{
......@@ -799,7 +799,7 @@ private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol lef
// SPEC: Given the set of applicable candidate function members, the best function member in that set is located.
// SPEC: If the set contains only one function member, then that function member is the best function member.
if (result.GetValidCount() == 1)
if (result.SingleValid())
{
return;
}
......@@ -810,8 +810,23 @@ private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol lef
// SPEC: better than all other function members, then the function member invocation is ambiguous and a binding-time
// SPEC: error occurs.
// UNDONE: This is a naive quadratic algorithm; there is a linear algorithm that works. Consider using it.
var candidates = result.Results;
// Try to find a single best candidate
int bestIndex = GetTheBestCandidateIndex(left, right, candidates, ref useSiteDiagnostics);
if (bestIndex != -1)
{
// Mark all other candidates as worse
for (int index = 0; index < candidates.Count; ++index)
{
if (candidates[index].Kind != OperatorAnalysisResultKind.Inapplicable && index != bestIndex)
{
candidates[index] = candidates[index].Worse();
}
}
return;
}
for (int i = 1; i < candidates.Count; ++i)
{
if (candidates[i].Kind != OperatorAnalysisResultKind.Applicable)
......@@ -826,6 +841,7 @@ private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol lef
{
continue;
}
var better = BetterOperator(candidates[i].Signature, candidates[j].Signature, left, right, ref useSiteDiagnostics);
if (better == BetterResult.Left)
{
......@@ -839,12 +855,65 @@ private static LiftingResult UserDefinedBinaryOperatorCanBeLifted(TypeSymbol lef
}
}
private BetterResult BetterOperator(BinaryOperatorSignature op1, BinaryOperatorSignature op2, BoundExpression left, BoundExpression right, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
private int GetTheBestCandidateIndex(
BoundExpression left,
BoundExpression right,
ArrayBuilder<BinaryOperatorAnalysisResult> candidates,
ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
Debug.Assert(op1.Priority.HasValue == op2.Priority.HasValue);
int currentBestIndex = -1;
for (int index = 0; index < candidates.Count; index++)
{
if (candidates[index].Kind != OperatorAnalysisResultKind.Applicable)
{
continue;
}
// Assume that the current candidate is the best if we don't have any
if (currentBestIndex == -1)
{
currentBestIndex = index;
}
else
{
var better = BetterOperator(candidates[currentBestIndex].Signature, candidates[index].Signature, left, right, ref useSiteDiagnostics);
if (better == BetterResult.Right)
{
// The current best is worse
currentBestIndex = index;
}
else if (better != BetterResult.Left)
{
// The current best is not better
currentBestIndex = -1;
}
}
}
// Make sure that every candidate up to the current best is worse
for (int index = 0; index < currentBestIndex; index++)
{
if (candidates[index].Kind == OperatorAnalysisResultKind.Inapplicable)
{
continue;
}
var better = BetterOperator(candidates[currentBestIndex].Signature, candidates[index].Signature, left, right, ref useSiteDiagnostics);
if (better != BetterResult.Left)
{
// The current best is not better
return -1;
}
}
return currentBestIndex;
}
private BetterResult BetterOperator(BinaryOperatorSignature op1, BinaryOperatorSignature op2, BoundExpression left, BoundExpression right, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
// We use Priority as a tie-breaker to help match native compiler bugs.
if (op1.Priority.HasValue && op2.Priority.HasValue && op1.Priority.GetValueOrDefault() != op2.Priority.GetValueOrDefault())
Debug.Assert(op1.Priority.HasValue == op2.Priority.HasValue);
if (op1.Priority.HasValue && op1.Priority.GetValueOrDefault() != op2.Priority.GetValueOrDefault())
{
return (op1.Priority.GetValueOrDefault() < op2.Priority.GetValueOrDefault()) ? BetterResult.Left : BetterResult.Right;
}
......
......@@ -28,18 +28,23 @@ public bool AnyValid()
return false;
}
public int GetValidCount()
public bool SingleValid()
{
int count = 0;
bool oneValid = false;
foreach (var result in Results)
{
if (result.IsValid)
{
count++;
if (oneValid)
{
return false;
}
oneValid = true;
}
}
return count;
return oneValid;
}
public BinaryOperatorAnalysisResult Best
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.CSharp
{
......@@ -55,8 +55,6 @@ public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, BoundExpress
UnaryOperatorOverloadResolution(operand, result, ref useSiteDiagnostics);
}
// Takes a list of candidates and mutates the list to throw out the ones that are worse than
// another applicable candidate.
private void UnaryOperatorOverloadResolution(
......@@ -64,11 +62,10 @@ public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, BoundExpress
UnaryOperatorOverloadResolutionResult result,
ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
// SPEC: Given the set of applicable candidate function members, the best function
// member in that set is located. SPEC: If the set contains only one function member,
// then that function member is the best function member.
// SPEC: Given the set of applicable candidate function members, the best function member in that set is located.
// SPEC: If the set contains only one function member, then that function member is the best function member.
if (result.GetValidCount() == 1)
if (result.SingleValid())
{
return;
}
......@@ -79,8 +76,23 @@ public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, BoundExpress
// SPEC: better than all other function members, then the function member invocation is ambiguous and a binding-time
// SPEC: error occurs.
// UNDONE: This is a naive quadratic algorithm; there is a linear algorithm that works. Consider using it.
var candidates = result.Results;
// Try to find a single best candidate
int bestIndex = GetTheBestCandidateIndex(operand, candidates, ref useSiteDiagnostics);
if (bestIndex != -1)
{
// Mark all other candidates as worse
for (int index = 0; index < candidates.Count; ++index)
{
if (candidates[index].Kind != OperatorAnalysisResultKind.Inapplicable && index != bestIndex)
{
candidates[index] = candidates[index].Worse();
}
}
return;
}
for (int i = 1; i < candidates.Count; ++i)
{
if (candidates[i].Kind != OperatorAnalysisResultKind.Applicable)
......@@ -95,6 +107,7 @@ public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, BoundExpress
{
continue;
}
var better = BetterOperator(candidates[i].Signature, candidates[j].Signature, operand, ref useSiteDiagnostics);
if (better == BetterResult.Left)
{
......@@ -108,6 +121,59 @@ public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, BoundExpress
}
}
private int GetTheBestCandidateIndex(
BoundExpression operand,
ArrayBuilder<UnaryOperatorAnalysisResult> candidates,
ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
int currentBestIndex = -1;
for (int index = 0; index < candidates.Count; index++)
{
if (candidates[index].Kind != OperatorAnalysisResultKind.Applicable)
{
continue;
}
// Assume that the current candidate is the best if we don't have any
if (currentBestIndex == -1)
{
currentBestIndex = index;
}
else
{
var better = BetterOperator(candidates[currentBestIndex].Signature, candidates[index].Signature, operand, ref useSiteDiagnostics);
if (better == BetterResult.Right)
{
// The current best is worse
currentBestIndex = index;
}
else if (better != BetterResult.Left)
{
// The current best is not better
currentBestIndex = -1;
}
}
}
// Make sure that every candidate up to the current best is worse
for (int index = 0; index < currentBestIndex; index++)
{
if (candidates[index].Kind == OperatorAnalysisResultKind.Inapplicable)
{
continue;
}
var better = BetterOperator(candidates[currentBestIndex].Signature, candidates[index].Signature, operand, ref useSiteDiagnostics);
if (better != BetterResult.Left)
{
// The current best is not better
return -1;
}
}
return currentBestIndex;
}
private BetterResult BetterOperator(UnaryOperatorSignature op1, UnaryOperatorSignature op2, BoundExpression operand, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
// First we see if the conversion from the operand to one operand type is better than
......
......@@ -28,18 +28,23 @@ public bool AnyValid()
return false;
}
public int GetValidCount()
public bool SingleValid()
{
int count = 0;
bool oneValid = false;
foreach (var result in Results)
{
if (result.IsValid)
{
count++;
if (oneValid)
{
return false;
}
oneValid = true;
}
}
return count;
return oneValid;
}
public UnaryOperatorAnalysisResult Best
......
......@@ -82,6 +82,16 @@ public bool IsApplicable
}
}
internal MemberResolutionResult<TMember> Worse()
{
return new MemberResolutionResult<TMember>(Member, LeastOverriddenMember, MemberAnalysisResult.Worse());
}
internal MemberResolutionResult<TMember> Worst()
{
return new MemberResolutionResult<TMember>(Member, LeastOverriddenMember, MemberAnalysisResult.Worst());
}
internal bool HasUseSiteDiagnosticToReport
{
get
......
......@@ -54,19 +54,38 @@ private bool Strict
// We need an indexable collection of mappings from method candidates to their up-to-date
// overload resolution status. It must be fast and memory efficient, but it will very often
// contain just 1 candidate.
private static int RemainingCandidatesCount<TMember>(ArrayBuilder<MemberResolutionResult<TMember>> list)
private static bool AnyValidResult<TMember>(ArrayBuilder<MemberResolutionResult<TMember>> results)
where TMember : Symbol
{
var count = 0;
for (int i = 0; i < list.Count; i++)
foreach (var result in results)
{
if (list[i].Result.IsValid)
if (result.IsValid)
{
count += 1;
return true;
}
}
return count;
return false;
}
private static bool SingleValidResult<TMember>(ArrayBuilder<MemberResolutionResult<TMember>> results)
where TMember : Symbol
{
bool oneValid = false;
foreach (var result in results)
{
if (result.IsValid)
{
if (oneValid)
{
return false;
}
oneValid = true;
}
}
return oneValid;
}
// Perform overload resolution on the given method group, with the given arguments and
......@@ -176,22 +195,7 @@ private static bool OverloadResolutionResultIsValid<TMember>(ArrayBuilder<Member
return false;
}
bool oneValid = false;
foreach (var curResult in results)
{
if (curResult.Result.IsValid)
{
if (oneValid)
{
// We somehow have two valid results.
return false;
}
oneValid = true;
}
}
return oneValid;
return SingleValidResult(results);
}
// Perform method/indexer overload resolution, storing the results into "results". If
......@@ -255,7 +259,7 @@ private static bool OverloadResolutionResultIsValid<TMember>(ArrayBuilder<Member
// SPEC: If the resulting set of candidate methods is empty, then further processing along the following steps are abandoned,
// SPEC: and instead an attempt is made to process the invocation as an extension method invocation. If this fails, then no
// SPEC: applicable methods exist, and a binding-time error occurs.
if (RemainingCandidatesCount(results) == 0)
if (!AnyValidResult(results))
{
return;
}
......@@ -1011,6 +1015,66 @@ private static void ReportUseSiteDiagnostics<TMember>(ArrayBuilder<MemberResolut
}
}
private int GetTheBestCandidateIndex<TMember>(ArrayBuilder<MemberResolutionResult<TMember>> results, AnalyzedArguments arguments, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
where TMember : Symbol
{
int currentBestIndex = -1;
for (int index = 0; index < results.Count; index++)
{
if (!results[index].IsValid)
{
continue;
}
// Assume that the current candidate is the best if we don't have any
if (currentBestIndex == -1)
{
currentBestIndex = index;
}
else if (results[currentBestIndex].Member == results[index].Member)
{
currentBestIndex = -1;
}
else
{
var better = BetterFunctionMember(results[currentBestIndex], results[index], arguments.Arguments, ref useSiteDiagnostics);
if (better == BetterResult.Right)
{
// The current best is worse
currentBestIndex = index;
}
else if (better != BetterResult.Left)
{
// The current best is not better
currentBestIndex = -1;
}
}
}
// Make sure that every candidate up to the current best is worse
for (int index = 0; index < currentBestIndex; index++)
{
if (!results[index].IsValid)
{
continue;
}
if (results[currentBestIndex].Member == results[index].Member)
{
return -1;
}
var better = BetterFunctionMember(results[currentBestIndex], results[index], arguments.Arguments, ref useSiteDiagnostics);
if (better != BetterResult.Left)
{
// The current best is not better
return -1;
}
}
return currentBestIndex;
}
private void RemoveWorseMembers<TMember>(ArrayBuilder<MemberResolutionResult<TMember>> results, AnalyzedArguments arguments, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
where TMember : Symbol
{
......@@ -1034,90 +1098,84 @@ private void RemoveWorseMembers<TMember>(ArrayBuilder<MemberResolutionResult<TMe
// then we need to do no more work; we know it cannot win. But it is also possible that
// it is not worse than anything but not better than everything.
if (SingleValidResult(results))
{
return;
}
// See if we have a winner, otherwise we might need to perform additional analysis
// in order to improve diagnostics
int bestIndex = GetTheBestCandidateIndex(results, arguments, ref useSiteDiagnostics);
if (bestIndex != -1)
{
// Mark all other candidates as worse
for (int index = 0; index < results.Count; index++)
{
if (results[index].IsValid && index != bestIndex)
{
results[index] = results[index].Worse();
}
}
return;
}
const int unknown = 0;
const int worseThanSomething = 1;
const int notBetterThanEverything = 2;
const int betterThanEverything = 3;
var worse = ArrayBuilder<int>.GetInstance(results.Count, unknown);
int countOfNotBestCandidates = 0;
int notBestIdx = -1;
for (int c1Idx = 0; c1Idx < results.Count; c1Idx++)
{
var c1result = results[c1Idx];
var c1Result = results[c1Idx];
// If we already know this is worse than something else, no need to check again.
if (!c1result.Result.IsValid || worse[c1Idx] == worseThanSomething)
if (!c1Result.IsValid || worse[c1Idx] == worseThanSomething)
{
continue;
}
bool c1IsBetterThanEverything = true;
for (int c2Idx = 0; c2Idx < results.Count; c2Idx++)
{
var c2result = results[c2Idx];
if (!c2result.Result.IsValid)
{
continue;
}
if (c1result.Member == c2result.Member)
var c2Result = results[c2Idx];
if (!c2Result.IsValid || c1Idx == c2Idx || c1Result.Member == c2Result.Member)
{
continue;
}
var better = BetterFunctionMember(c1result, c2result, arguments.Arguments, ref useSiteDiagnostics);
var better = BetterFunctionMember(c1Result, c2Result, arguments.Arguments, ref useSiteDiagnostics);
if (better == BetterResult.Left)
{
worse[c2Idx] = worseThanSomething;
}
else
else if (better == BetterResult.Right)
{
c1IsBetterThanEverything = false;
if (better == BetterResult.Right)
{
worse[c1Idx] = worseThanSomething;
break;
}
worse[c1Idx] = worseThanSomething;
break;
}
}
if (worse[c1Idx] == unknown)
{
// c1 was not worse than anything. Was it better than everything?
worse[c1Idx] = c1IsBetterThanEverything ? betterThanEverything : notBetterThanEverything;
}
}
// See we have a winner, otherwise we might need to perform additional analysis
// in order to improve diagnostics
bool haveBestCandidate = false;
int countOfNotBestCandidates = 0;
int notBestIdx = -1;
for (int i = 0; i < worse.Count; ++i)
{
Debug.Assert(!results[i].Result.IsValid || worse[i] != unknown);
if (worse[i] == betterThanEverything)
{
haveBestCandidate = true;
break;
}
else if (worse[i] == notBetterThanEverything)
if (worse[c1Idx] == unknown)
{
// c1 was not worse than anything
worse[c1Idx] = notBetterThanEverything;
countOfNotBestCandidates++;
notBestIdx = i;
notBestIdx = c1Idx;
}
}
Debug.Assert(countOfNotBestCandidates == 0 || !haveBestCandidate);
if (haveBestCandidate || countOfNotBestCandidates == 0)
if (countOfNotBestCandidates == 0)
{
for (int i = 0; i < worse.Count; ++i)
{
Debug.Assert(!results[i].Result.IsValid || worse[i] != unknown);
if (worse[i] == worseThanSomething || worse[i] == notBetterThanEverything)
Debug.Assert(!results[i].IsValid || worse[i] != unknown);
if (worse[i] == worseThanSomething)
{
results[i] = new MemberResolutionResult<TMember>(results[i].Member, results[i].LeastOverriddenMember, MemberAnalysisResult.Worse());
results[i] = results[i].Worse();
}
}
}
......@@ -1125,14 +1183,12 @@ private void RemoveWorseMembers<TMember>(ArrayBuilder<MemberResolutionResult<TMe
{
for (int i = 0; i < worse.Count; ++i)
{
Debug.Assert(!results[i].Result.IsValid || worse[i] != unknown);
Debug.Assert(!results[i].IsValid || worse[i] != unknown);
if (worse[i] == worseThanSomething)
{
// Mark those candidates, that are worse than the single notBest candidate, as Worst in order to improve error reporting.
var analysisResult = BetterFunctionMember(results[notBestIdx], results[i], arguments.Arguments, ref useSiteDiagnostics) == BetterResult.Left ?
MemberAnalysisResult.Worst() :
MemberAnalysisResult.Worse();
results[i] = new MemberResolutionResult<TMember>(results[i].Member, results[i].LeastOverriddenMember, analysisResult);
results[i] = BetterResult.Left == BetterFunctionMember(results[notBestIdx], results[i], arguments.Arguments, ref useSiteDiagnostics)
? results[i].Worst() : results[i].Worse();
}
else
{
......@@ -1141,7 +1197,7 @@ private void RemoveWorseMembers<TMember>(ArrayBuilder<MemberResolutionResult<TMe
}
Debug.Assert(worse[notBestIdx] == notBetterThanEverything);
results[notBestIdx] = new MemberResolutionResult<TMember>(results[notBestIdx].Member, results[notBestIdx].LeastOverriddenMember, MemberAnalysisResult.Worse());
results[notBestIdx] = results[notBestIdx].Worse();
}
else
{
......@@ -1149,15 +1205,15 @@ private void RemoveWorseMembers<TMember>(ArrayBuilder<MemberResolutionResult<TMe
for (int i = 0; i < worse.Count; ++i)
{
Debug.Assert(!results[i].Result.IsValid || worse[i] != unknown);
Debug.Assert(!results[i].IsValid || worse[i] != unknown);
if (worse[i] == worseThanSomething)
{
// Mark those candidates, that are worse than something, as Worst in order to improve error reporting.
results[i] = new MemberResolutionResult<TMember>(results[i].Member, results[i].LeastOverriddenMember, MemberAnalysisResult.Worst());
results[i] = results[i].Worst();
}
else if (worse[i] == notBetterThanEverything)
{
results[i] = new MemberResolutionResult<TMember>(results[i].Member, results[i].LeastOverriddenMember, MemberAnalysisResult.Worse());
results[i] = results[i].Worse();
}
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using System.Linq;
using System.Text;
using Xunit;
......@@ -11,6 +12,58 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests
// test timeout, without shortcuts in overload resolution.
public class OverloadResolutionPerfTests : CSharpTestBase
{
[WorkItem(13685, "https://github.com/dotnet/roslyn/issues/13685")]
[Fact]
public void Overloads()
{
const int n = 3000;
var builder = new StringBuilder();
builder.AppendLine("class C");
builder.AppendLine("{");
builder.AppendLine($" static void F() {{ F(null); }}"); // matches n overloads: F(C0), F(C1), ...
for (int i = 0; i < n; i++)
{
builder.AppendLine($" static void F(C{i} c) {{ }}");
}
builder.AppendLine("}");
for (int i = 0; i < n; i++)
{
builder.AppendLine($"class C{i} {{ }}");
}
var source = builder.ToString();
var comp = CreateCompilationWithMscorlibAndSystemCore(source);
comp.VerifyDiagnostics(
// (3,23): error CS0121: The call is ambiguous between the following methods or properties: 'C.F(C0)' and 'C.F(C1)'
// static void F() { F(null); }
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("C.F(C0)", "C.F(C1)").WithLocation(3, 23));
}
[WorkItem(13685, "https://github.com/dotnet/roslyn/issues/13685")]
[Fact]
public void BinaryOperatorOverloads()
{
const int n = 3000;
var builder = new StringBuilder();
builder.AppendLine("class C");
builder.AppendLine("{");
builder.AppendLine($" static object F(C x) => x + null;"); // matches n overloads: +(C, C0), +(C, C1), ...
for (int i = 0; i < n; i++)
{
builder.AppendLine($" public static object operator+(C x, C{i} y) => null;");
}
builder.AppendLine("}");
for (int i = 0; i < n; i++)
{
builder.AppendLine($"class C{i} {{ }}");
}
var source = builder.ToString();
var comp = CreateCompilationWithMscorlibAndSystemCore(source);
comp.VerifyDiagnostics(
// (3,29): error CS0034: Operator '+' is ambiguous on operands of type 'C' and '<null>'
// static object F(C x) => x + null;
Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "x + null").WithArguments("+", "C", "<null>").WithLocation(3, 29));
}
[Fact]
public void StaticMethodsWithLambda()
{
......
......@@ -9257,5 +9257,61 @@ static class E
// a.F(o => {}, a);
Diagnostic(ErrorCode.ERR_BadInstanceArgType, "a").WithArguments("A", "F", "E.F(B, System.Action<object>, A)", "B").WithLocation(9, 9));
}
[Fact]
public void CircularImplicitConversions()
{
string source =
@"
class A
{
public static implicit operator B(A a) => null;
}
class B
{
public static implicit operator C(B b) => null;
}
class C
{
public static implicit operator A(C c) => null;
}
class D
{
public static implicit operator A(D d) => null;
public static implicit operator B(D d) => null;
public static implicit operator C(D d) => null;
}
class E
{
public static void F(A a) {}
public static void F(B b) {}
public static void F(C c) {}
}
public class Program
{
public static void Main() => E.F(new D());
}
";
var comp = CreateCompilationWithMscorlibAndSystemCore(source);
comp.VerifyDiagnostics(
// (28,36): error CS0121: The call is ambiguous between the following methods or properties: 'E.F(A)' and 'E.F(B)'
// public static void Main() => E.F(new D());
Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("E.F(A)", "E.F(B)").WithLocation(28, 36)
);
var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);
var callSyntax = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>().Single();
var symbolInfo = model.GetSymbolInfo(callSyntax);
Assert.Equal(CandidateReason.OverloadResolutionFailure, symbolInfo.CandidateReason);
var candidates = symbolInfo.CandidateSymbols;
Assert.Equal(3, candidates.Length);
Assert.Equal("void E.F(A a)", candidates[0].ToTestDisplayString());
Assert.Equal("void E.F(B b)", candidates[1].ToTestDisplayString());
Assert.Equal("void E.F(C c)", candidates[2].ToTestDisplayString());
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册