Skip to content

Commit 6196b9f

Browse files
authored
Implement "non-generic method is better" rule in operator overload resolution (#78633)
1 parent 0b0a222 commit 6196b9f

File tree

4 files changed

+255
-1
lines changed

4 files changed

+255
-1
lines changed

src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,19 @@ private BetterResult BetterOperator(BinaryOperatorSignature op1, BinaryOperatorS
12341234
if (Conversions.HasIdentityConversion(op1.LeftType, op2.LeftType) &&
12351235
Conversions.HasIdentityConversion(op1.RightType, op2.RightType))
12361236
{
1237+
// SPEC: If Mp is a non-generic method and Mq is a generic method, then Mp is better than Mq.
1238+
if (op1.Method?.GetMemberArityIncludingExtension() is null or 0)
1239+
{
1240+
if (op2.Method?.GetMemberArityIncludingExtension() > 0)
1241+
{
1242+
return BetterResult.Left;
1243+
}
1244+
}
1245+
else if (op2.Method?.GetMemberArityIncludingExtension() is null or 0)
1246+
{
1247+
return BetterResult.Right;
1248+
}
1249+
12371250
// NOTE: The native compiler does not follow these rules; effectively, the native
12381251
// compiler checks for liftedness first, and then for specificity. For example:
12391252
// struct S<T> where T : struct {

src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,19 @@ private BetterResult BetterOperator(UnaryOperatorSignature op1, UnaryOperatorSig
428428

429429
if (Conversions.HasIdentityConversion(op1.OperandType, op2.OperandType))
430430
{
431+
// SPEC: If Mp is a non-generic method and Mq is a generic method, then Mp is better than Mq.
432+
if (op1.Method?.GetMemberArityIncludingExtension() is null or 0)
433+
{
434+
if (op2.Method?.GetMemberArityIncludingExtension() > 0)
435+
{
436+
return BetterResult.Left;
437+
}
438+
}
439+
else if (op2.Method?.GetMemberArityIncludingExtension() is null or 0)
440+
{
441+
return BetterResult.Right;
442+
}
443+
431444
// SPEC: If Mp has more specific parameter types than Mq then Mp is better than Mq.
432445

433446
// Under what circumstances can two unary operators with identical signatures be "more specific"

src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,120 @@ static void Main()
11781178
CompileAndVerify(comp, expectedOutput: "System.Int32System.Int32System.Int32:").VerifyDiagnostics();
11791179
}
11801180

1181+
[Fact]
1182+
public void Unary_020_Consumption_Generic_Worse()
1183+
{
1184+
var src = $$$"""
1185+
public static class Extensions1
1186+
{
1187+
extension<T>(S1<T>)
1188+
{
1189+
public static S1<T> operator +(S1<T> x)
1190+
{
1191+
System.Console.Write("[S1<T>]");
1192+
return x;
1193+
}
1194+
}
1195+
1196+
extension<T>(S1<T>?)
1197+
{
1198+
public static S1<T>? operator +(S1<T>? x)
1199+
{
1200+
System.Console.Write("[S1<T>?]");
1201+
return x;
1202+
}
1203+
}
1204+
1205+
extension(S1<int>)
1206+
{
1207+
public static S1<int> operator +(S1<int> x)
1208+
{
1209+
System.Console.Write("[S1<int>]");
1210+
return x;
1211+
}
1212+
}
1213+
1214+
extension<T>(S2<T>)
1215+
{
1216+
public static S2<T> operator +(in S2<T> x) => throw null;
1217+
1218+
public static S2<T> operator +(S2<T> x)
1219+
{
1220+
System.Console.Write("[S2<T>]");
1221+
return x;
1222+
}
1223+
}
1224+
1225+
extension(S2<int>)
1226+
{
1227+
public static S2<int> operator +(in S2<int> x)
1228+
{
1229+
System.Console.Write("[in S2<int>]");
1230+
return x;
1231+
}
1232+
}
1233+
}
1234+
1235+
public struct S1<T>
1236+
{}
1237+
1238+
public struct S2<T>
1239+
{}
1240+
1241+
class Program
1242+
{
1243+
static void Main()
1244+
{
1245+
var s11 = new S1<int>();
1246+
s11 = +s11;
1247+
Extensions1.op_UnaryPlus(s11);
1248+
1249+
System.Console.WriteLine();
1250+
1251+
var s12 = new S1<byte>();
1252+
s12 = +s12;
1253+
Extensions1.op_UnaryPlus(s12);
1254+
1255+
System.Console.WriteLine();
1256+
1257+
var s21 = new S2<int>();
1258+
s21 = +s21;
1259+
Extensions1.op_UnaryPlus(s21);
1260+
1261+
System.Console.WriteLine();
1262+
1263+
var s22 = new S2<byte>();
1264+
s22 = +s22;
1265+
Extensions1.op_UnaryPlus(s22);
1266+
1267+
System.Console.WriteLine();
1268+
1269+
S1<int>? s13 = new S1<int>();
1270+
s13 = +s13;
1271+
s13 = null;
1272+
s13 = +s13;
1273+
1274+
System.Console.WriteLine();
1275+
1276+
S1<byte>? s14 = new S1<byte>();
1277+
s14 = +s14;
1278+
s14 = null;
1279+
s14 = +s14;
1280+
}
1281+
}
1282+
""";
1283+
1284+
var comp = CreateCompilation(src, options: TestOptions.DebugExe);
1285+
CompileAndVerify(comp, expectedOutput: @"
1286+
[S1<int>][S1<int>]
1287+
[S1<T>][S1<T>]
1288+
[in S2<int>][in S2<int>]
1289+
[S2<T>][S2<T>]
1290+
[S1<int>]
1291+
[S1<T>?][S1<T>?]
1292+
").VerifyDiagnostics();
1293+
}
1294+
11811295
[Fact]
11821296
public void Unary_021_Consumption_Generic_ConstraintsViolation()
11831297
{
@@ -4695,6 +4809,120 @@ static void Main()
46954809
CompileAndVerify(comp, expectedOutput: "System.Int32System.Int32System.Int32:").VerifyDiagnostics();
46964810
}
46974811

4812+
[Fact]
4813+
public void Binary_027_Consumption_Generic_Worse()
4814+
{
4815+
var src = $$$"""
4816+
public static class Extensions1
4817+
{
4818+
extension<T>(S1<T>)
4819+
{
4820+
public static S1<T> operator +(S1<T> x, S1<T> y)
4821+
{
4822+
System.Console.Write("[S1<T>]");
4823+
return x;
4824+
}
4825+
}
4826+
4827+
extension<T>(S1<T>?)
4828+
{
4829+
public static S1<T>? operator +(S1<T>? x, S1<T>? y)
4830+
{
4831+
System.Console.Write("[S1<T>?]");
4832+
return x;
4833+
}
4834+
}
4835+
4836+
extension(S1<int>)
4837+
{
4838+
public static S1<int> operator +(S1<int> x, S1<int> y)
4839+
{
4840+
System.Console.Write("[S1<int>]");
4841+
return x;
4842+
}
4843+
}
4844+
4845+
extension<T>(S2<T>)
4846+
{
4847+
public static S2<T> operator +(in S2<T> x, S2<T> y) => throw null;
4848+
4849+
public static S2<T> operator +(S2<T> x, S2<T> y)
4850+
{
4851+
System.Console.Write("[S2<T>]");
4852+
return x;
4853+
}
4854+
}
4855+
4856+
extension(S2<int>)
4857+
{
4858+
public static S2<int> operator +(in S2<int> x, S2<int> y)
4859+
{
4860+
System.Console.Write("[in S2<int>]");
4861+
return x;
4862+
}
4863+
}
4864+
}
4865+
4866+
public struct S1<T>
4867+
{}
4868+
4869+
public struct S2<T>
4870+
{}
4871+
4872+
class Program
4873+
{
4874+
static void Main()
4875+
{
4876+
var s11 = new S1<int>();
4877+
s11 = s11 + s11;
4878+
Extensions1.op_Addition(s11, s11);
4879+
4880+
System.Console.WriteLine();
4881+
4882+
var s12 = new S1<byte>();
4883+
s12 = s12 + s12;
4884+
Extensions1.op_Addition(s12, s12);
4885+
4886+
System.Console.WriteLine();
4887+
4888+
var s21 = new S2<int>();
4889+
s21 = s21 + s21;
4890+
Extensions1.op_Addition(s21, s21);
4891+
4892+
System.Console.WriteLine();
4893+
4894+
var s22 = new S2<byte>();
4895+
s22 = s22 + s22;
4896+
Extensions1.op_Addition(s22, s22);
4897+
4898+
System.Console.WriteLine();
4899+
4900+
S1<int>? s13 = new S1<int>();
4901+
s13 = s13 + s13;
4902+
s13 = null;
4903+
s13 = s13 + s13;
4904+
4905+
System.Console.WriteLine();
4906+
4907+
S1<byte>? s14 = new S1<byte>();
4908+
s14 = s14 + s14;
4909+
s14 = null;
4910+
s14 = s14 + s14;
4911+
}
4912+
}
4913+
""";
4914+
4915+
var comp = CreateCompilation(src, options: TestOptions.DebugExe);
4916+
CompileAndVerify(comp, expectedOutput: @"
4917+
[S1<int>][S1<int>]
4918+
[S1<T>][S1<T>]
4919+
[in S2<int>][in S2<int>]
4920+
[S2<T>][S2<T>]
4921+
[S1<int>]
4922+
[S1<T>?][S1<T>?]
4923+
").VerifyDiagnostics();
4924+
}
4925+
46984926
[Fact]
46994927
public void Binary_028_Consumption_Generic()
47004928
{

src/Compilers/Test/Core/CompilationVerifier.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ public void EmitAndVerify(
384384
output = output.Trim();
385385
}
386386

387-
Assert.Equal(expectedOutput, output);
387+
AssertEx.Equal(expectedOutput, output);
388388
Assert.Empty(errorOutput);
389389
}
390390
}

0 commit comments

Comments
 (0)