提交 3f37d97b 编写于 作者: A AlekseyTs

Merge pull request #2305 from AlekseyTs/Bug1157097

Bring tie breaking rules around optional parameters and params parameters closer to native behavior.
......@@ -1261,16 +1261,20 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
int m1ParameterCount;
int m2ParameterCount;
int m1ParametersUsedIncludingExpansionAndOptional;
int m2ParametersUsedIncludingExpansionAndOptional;
if (!allSame)
GetParameterCounts(m1, arguments, out m1ParameterCount, out m1ParametersUsedIncludingExpansionAndOptional);
GetParameterCounts(m2, arguments, out m2ParameterCount, out m2ParametersUsedIncludingExpansionAndOptional);
// SPEC VIOLATION: When checking for matching parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN},
// native compiler includes types of optinal parameters. We partially duplicate this behavior
// here by comparing the number of parameters used taking params expansion and
// optional parameters into account.
if (!allSame || m1ParametersUsedIncludingExpansionAndOptional != m2ParametersUsedIncludingExpansionAndOptional)
{
// SPEC VIOLATION: Even when parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are
// not equivalent, we have tie-breaking rules when optional parameters are involved:
// 1. A candidate Mp that uses optional parameters and is not applicable only in expanded
// form is better than a candidate Mq that is applicable only in expanded form.
// 2. A candidate Mp that does not use optional parameters and is not applicable only in
// expanded form is better than a candidate Mq that uses optional parameters and is
// not applicable only in expanded form.
// not equivalent, we have tie-breaking rules.
//
// Relevant code in the native compiler is at the end of
// BetterTypeEnum ExpressionBinder::WhichMethodIsBetter(
......@@ -1279,38 +1283,34 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
// Type* pTypeThrough,
// ArgInfos*args)
//
m1ParameterCount = m1.Member.GetParameterCount();
m2ParameterCount = m2.Member.GetParameterCount();
if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
if (m1ParametersUsedIncludingExpansionAndOptional != m2ParametersUsedIncludingExpansionAndOptional)
{
if (m2.Result.Kind != MemberResolutionKind.ApplicableInExpandedForm && m2ParameterCount != arguments.Count)
if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
// Optionals used
return BetterResult.Right;
if (m2.Result.Kind != MemberResolutionKind.ApplicableInExpandedForm)
{
return BetterResult.Right;
}
}
}
else if (m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
if (m1.Result.Kind != MemberResolutionKind.ApplicableInExpandedForm && m1ParameterCount != arguments.Count)
else if (m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
// Optionals used
Debug.Assert(m1.Result.Kind != MemberResolutionKind.ApplicableInExpandedForm);
return BetterResult.Left;
}
}
else if (m1ParameterCount == arguments.Count)
{
if (m2ParameterCount != arguments.Count)
// Here, if both methods needed to use optionals to fill in the signatures,
// then we are ambiguous. Otherwise, take the one that didn't need any
// optionals.
if (m1ParametersUsedIncludingExpansionAndOptional == arguments.Count)
{
// Optionals used
return BetterResult.Left;
}
}
else if (m2ParameterCount == arguments.Count)
{
Debug.Assert(m1ParameterCount != arguments.Count);
// Optionals used
return BetterResult.Right;
else if (m2ParametersUsedIncludingExpansionAndOptional == arguments.Count)
{
return BetterResult.Right;
}
}
return BetterResult.Neither;
......@@ -1353,9 +1353,6 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
// Otherwise, if both methods have params arrays and are applicable only in their
// expanded forms, and if MP has more declared parameters than MQ, then MP is better than MQ.
m1ParameterCount = m1.Member.GetParameterCount();
m2ParameterCount = m2.Member.GetParameterCount();
if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
if (m1ParameterCount > m2ParameterCount)
......@@ -1448,6 +1445,28 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
return BetterResult.Neither;
}
private static void GetParameterCounts<TMember>(MemberResolutionResult<TMember> m, ArrayBuilder<BoundExpression> arguments, out int declaredParameterCount, out int parametersUsedIncludingExpansionAndOptional) where TMember : Symbol
{
declaredParameterCount = m.Member.GetParameterCount();
if (m.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
if (arguments.Count < declaredParameterCount)
{
// params parameter isn't used (see ExpressionBinder::TryGetExpandedParams in the native compiler)
parametersUsedIncludingExpansionAndOptional = declaredParameterCount - 1;
}
else
{
parametersUsedIncludingExpansionAndOptional = arguments.Count;
}
}
else
{
parametersUsedIncludingExpansionAndOptional = declaredParameterCount;
}
}
private static BetterResult MoreSpecificType(ArrayBuilder<TypeSymbol> t1, ArrayBuilder<TypeSymbol> t2, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
Debug.Assert(t1.Count == t2.Count);
......
......@@ -602,24 +602,10 @@ public static void M()
}
[Fact]
[Fact, WorkItem(1157097, "DevDiv"), WorkItem(2298, "https://github.com/dotnet/roslyn/issues/2298")]
public void TestOverloadResolutionTiebreaker()
{
// The native compiler gets this one wrong, giving an ambiguity error.
// The correct analysis is that there are four candidates:
// 0: X(params string[]) in normal form
// 1: X(params string[]) in expanded form
// 2: X<string>(string)
// 3: X(string, object) with default value for second parameter
//
// Candidate 0 is obviously inapplicable. The other three have identical formal parameter types
// corresponding to the given arguments, so we must go to tiebreaker round. The tiebreakers are:
// * Nongeneric beats generic, and then
// * applicable in normal form beats applicable only in expanded form, then
// * more parameters declared beats fewer parameters declared
// By these rules: 1 beats 2, 3 beats 1, 3 beats 2. 3 is unbeaten and beats at least one
// other candidate. No other candidate has this property, so candidate 3 should win.
// Testing that we get the same ambiguity error as the one reported by the native compiler.
string source = @"
class C
{
......@@ -632,11 +618,16 @@ public void M()
}
}";
TestOverloadResolutionWithDiff(source);
}
[Fact]
var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll);
compilation.VerifyDiagnostics(
// (9,9): error CS0121: The call is ambiguous between the following methods or properties: 'C.X(params string[])' and 'C.X<T>(T)'
// X((string)null); //-C.X(string, object)
Diagnostic(ErrorCode.ERR_AmbigCall, "X").WithArguments("C.X(params string[])", "C.X<T>(T)").WithLocation(9, 9)
);
}
[Fact]
private void TestConstraintViolationApplicabilityErrors()
{
// The rules for constraint satisfaction during overload resolution are a bit odd. If a constraint
......@@ -6221,7 +6212,7 @@ public enum E { }
CreateCompilationWithMscorlibAndSystemCore(source, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics();
}
[Fact, WorkItem(598032, "DevDiv")]
[Fact, WorkItem(598032, "DevDiv"), WorkItem(1157097, "DevDiv"), WorkItem(2298, "https://github.com/dotnet/roslyn/issues/2298")]
public void GenericVsOptionalParameter()
{
string source = @"
......@@ -6231,17 +6222,15 @@ class C
public static int Foo(int x, string y = null) { return 1; }
public static int Foo<T>(T x) { return 0; }
public static int M()
public static void Main()
{
return Foo(0); //-C.Foo(int, string)
System.Console.WriteLine(Foo(0));
}
}
";
// Roslyn is correctly following the spec here. Dev11 compares the two methods' parameter lists without first
// removing the parameters for which optional arguments are used. This is incorrect. It then concludes that the
// parameter lists are different and decides not to use part of the tie-breaker rules,
// but does use the "least number of optional args" tie-breaker.
TestOverloadResolutionWithDiff(source);
var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.DebugExe);
CompileAndVerify(compilation, expectedOutput: "0");
}
[WorkItem(598029, "DevDiv")]
......@@ -7625,5 +7614,227 @@ public static void Main(string[] args)
Diagnostic(ErrorCode.ERR_MethodNameExpected, "a1").WithLocation(14, 36)
);
}
[Fact, WorkItem(1157097, "DevDiv"), WorkItem(2298, "https://github.com/dotnet/roslyn/issues/2298")]
public void ParamsAndOptionals()
{
string source1 = @"
using System;
using System.Collections.Generic;
using VS2015CompilerBug;
public static class Extensions
{
//extension with params keyword
public static int Properties(this IFirstInterface source, params int[] x)
{
System.Console.WriteLine(""int Properties(this IFirstInterface source, params int[] x)"");
return 0;
}
public static bool Properties(this ISecondInterface source, int x = 0, params int[] y)
{
System.Console.WriteLine(""bool Properties(this ISecondInterface source, int x = 0, params int[] y)"");
return true;
}
//extension without params keyword
public static int Properties2(this IFirstInterface source)
{
System.Console.WriteLine(""int Properties2(this IFirstInterface source)"");
return 0;
}
public static bool Properties2(this ISecondInterface source, int x = 0)
{
System.Console.WriteLine(""bool Properties2(this ISecondInterface source, int x = 0)"");
return true;
}
}
namespace VS2015CompilerBug
{
public interface IFirstInterface
{
}
public interface ISecondInterface
{
}
public interface IFinalInterface : ISecondInterface, IFirstInterface
{
}
public class VS2015CompilerBug
{
public static void Main()
{
IFinalInterface x = default(IFinalInterface);
var properties = x.Properties();
var properties2 = x.Properties2();
(new VS2015CompilerBug()).Test2();
}
private void Test2(int x = 5, params int[] y)
{
System.Console.WriteLine(""void Test2(int x = 5, params int[] y)"");
}
private void Test2(params int[] x)
{
System.Console.WriteLine(""void Test2(params int[] x)"");
}
}
}
";
var compilation = CreateCompilationWithMscorlib(source1, new[] { SystemCoreRef }, options: TestOptions.DebugExe);
CompileAndVerify(compilation, expectedOutput:
@"int Properties(this IFirstInterface source, params int[] x)
int Properties2(this IFirstInterface source)
void Test2(params int[] x)");
}
[Fact, WorkItem(1157097, "DevDiv"), WorkItem(2298, "https://github.com/dotnet/roslyn/issues/2298")]
public void TieBreakOnNumberOfDeclaredParameters_01()
{
string source1 = @"
namespace VS2015CompilerBug
{
public class VS2015CompilerBug
{
public static void Main()
{
(new VS2015CompilerBug()).Test2(1);
(new VS2015CompilerBug()).Test2(1, 2);
(new VS2015CompilerBug()).Test2(1, 2, 3);
(new VS2015CompilerBug()).Test3(1, 2);
(new VS2015CompilerBug()).Test3(1, 2, 3);
(new VS2015CompilerBug()).Test3(1, 2, 3, 4);
}
private void Test2(int x, params int[] y)
{
System.Console.WriteLine(""void Test2(int x, params int[] y)"");
}
private void Test2(params int[] x)
{
System.Console.WriteLine(""void Test2(params int[] x)"");
}
private void Test3(int x, int y, params int[] z)
{
System.Console.WriteLine(""void Test3(int x, int y, params int[] z)"");
}
private void Test3(int x, params int[] y)
{
System.Console.WriteLine(""void Test3(int x, params int[] y)"");
}
}
}
";
var compilation = CreateCompilationWithMscorlib(source1, new[] { SystemCoreRef }, options: TestOptions.DebugExe);
CompileAndVerify(compilation, expectedOutput:
@"void Test2(int x, params int[] y)
void Test2(int x, params int[] y)
void Test2(int x, params int[] y)
void Test3(int x, int y, params int[] z)
void Test3(int x, int y, params int[] z)
void Test3(int x, int y, params int[] z)");
}
[Fact, WorkItem(1157097, "DevDiv"), WorkItem(2298, "https://github.com/dotnet/roslyn/issues/2298")]
public void TieBreakOnNumberOfDeclaredParameters_02()
{
string source1 = @"
namespace VS2015CompilerBug
{
public class VS2015CompilerBug
{
public static void Main()
{
(new VS2015CompilerBug()).Test2(1, 2);
(new VS2015CompilerBug()).Test3(1, 2, 3);
}
private void Test2(int x = 0, int y = 0)
{
System.Console.WriteLine(""void Test2(int x = 0, int y = 0)"");
}
private void Test2(int x, int y = 0, int z = 0)
{
System.Console.WriteLine(""void Test2(int x, int y = 0, int z = 0)"");
}
private void Test3(int x, int y, int z = 0, int u = 0)
{
System.Console.WriteLine(""void Test3(int x, int y, int z = 0, int u = 0)"");
}
private void Test3(int x, int y = 0, int z = 0)
{
System.Console.WriteLine(""void Test3(int x, int y = 0, int z = 0)"");
}
}
}
";
var compilation = CreateCompilationWithMscorlib(source1, new[] { SystemCoreRef }, options: TestOptions.DebugExe);
CompileAndVerify(compilation, expectedOutput:
@"void Test2(int x = 0, int y = 0)
void Test3(int x, int y = 0, int z = 0)");
}
[Fact, WorkItem(1157097, "DevDiv"), WorkItem(2298, "https://github.com/dotnet/roslyn/issues/2298")]
public void TieBreakOnNumberOfDeclaredParameters_03()
{
string source1 = @"
namespace VS2015CompilerBug
{
public class VS2015CompilerBug
{
public static void Main()
{
(new VS2015CompilerBug()).Test2(1);
(new VS2015CompilerBug()).Test3(1, 2);
}
private void Test2(int x = 0, int y = 0)
{
}
private void Test2(int x, int y = 0, int z = 0)
{
}
private void Test3(int x, int y, int z = 0, int u = 0)
{
}
private void Test3(int x, int y = 0, int z = 0)
{
}
}
}
";
var compilation = CreateCompilationWithMscorlib(source1, new[] { SystemCoreRef }, options: TestOptions.DebugExe);
compilation.VerifyDiagnostics(
// (9,39): error CS0121: The call is ambiguous between the following methods or properties: 'VS2015CompilerBug.Test2(int, int)' and 'VS2015CompilerBug.Test2(int, int, int)'
// (new VS2015CompilerBug()).Test2(1);
Diagnostic(ErrorCode.ERR_AmbigCall, "Test2").WithArguments("VS2015CompilerBug.VS2015CompilerBug.Test2(int, int)", "VS2015CompilerBug.VS2015CompilerBug.Test2(int, int, int)").WithLocation(9, 39),
// (10,39): error CS0121: The call is ambiguous between the following methods or properties: 'VS2015CompilerBug.Test3(int, int, int, int)' and 'VS2015CompilerBug.Test3(int, int, int)'
// (new VS2015CompilerBug()).Test3(1, 2);
Diagnostic(ErrorCode.ERR_AmbigCall, "Test3").WithArguments("VS2015CompilerBug.VS2015CompilerBug.Test3(int, int, int, int)", "VS2015CompilerBug.VS2015CompilerBug.Test3(int, int, int)").WithLocation(10, 39)
);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册