From 919b606f53975172f4b8c2d930e2afa8a32c5cee Mon Sep 17 00:00:00 2001 From: Omar Tawfik Date: Fri, 8 Dec 2017 14:56:13 -0800 Subject: [PATCH] Prefer by-val methods over in methods in overload resolution (#23122) * Prefer by-val methods over in methdos in overload resolution * More tests * Moved check to the end of OR chain * Address PR comments * Operators overloading * Address PR Comments * Clean up --- .../BinaryOperatorOverloadResolution.cs | 41 +- .../Operators/BinaryOperatorSignature.cs | 45 +- .../UnaryOperatorOverloadResolution.cs | 10 + .../Operators/UnaryOperatorSignature.cs | 26 +- .../OverloadResolution/OverloadResolution.cs | 112 +- .../Semantics/OverloadResolutionTests.cs | 1068 ++++++++++++++++- 6 files changed, 1201 insertions(+), 101 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs index 4a4554e828bac..a441637654025 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs @@ -1003,7 +1003,46 @@ private BetterResult BetterOperator(BinaryOperatorSignature op1, BinaryOperatorS } } - return BetterResult.Neither; + // Always prefer operators with val parameters over operators with in parameters: + BetterResult valOverInPreference; + + if (op1.LeftRefKind == RefKind.None && op2.LeftRefKind == RefKind.In) + { + valOverInPreference = BetterResult.Left; + } + else if (op2.LeftRefKind == RefKind.None && op1.LeftRefKind == RefKind.In) + { + valOverInPreference = BetterResult.Right; + } + else + { + valOverInPreference = BetterResult.Neither; + } + + if (op1.RightRefKind == RefKind.None && op2.RightRefKind == RefKind.In) + { + if (valOverInPreference == BetterResult.Right) + { + return BetterResult.Neither; + } + else + { + valOverInPreference = BetterResult.Left; + } + } + else if (op2.RightRefKind == RefKind.None && op1.RightRefKind == RefKind.In) + { + if (valOverInPreference == BetterResult.Left) + { + return BetterResult.Neither; + } + else + { + valOverInPreference = BetterResult.Right; + } + } + + return valOverInPreference; } private BetterResult MoreSpecificOperator(BinaryOperatorSignature op1, BinaryOperatorSignature op2, ref HashSet useSiteDiagnostics) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs index 4d872610e83df..f37e8ec7c43c1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorSignature.cs @@ -1,7 +1,8 @@ // 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.Symbols; using System; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -35,7 +36,7 @@ public BinaryOperatorSignature(BinaryOperatorKind kind, TypeSymbol leftType, Typ public override string ToString() { - return $"kind: {this.Kind} left: {this.LeftType} right: {this.RightType} return: {this.ReturnType}"; + return $"kind: {this.Kind} leftType: {this.LeftType} leftRefKind: {this.LeftRefKind} rightType: {this.RightType} rightRefKind: {this.RightRefKind} return: {this.ReturnType}"; } public bool Equals(BinaryOperatorSignature other) @@ -70,5 +71,45 @@ public override int GetHashCode() Hash.Combine(RightType, Hash.Combine(Method, (int)Kind)))); } + + public RefKind LeftRefKind + { + get + { + if ((object)Method != null) + { + Debug.Assert(Method.ParameterCount == 2); + + if (!Method.ParameterRefKinds.IsDefaultOrEmpty) + { + Debug.Assert(Method.ParameterRefKinds.Length == 2); + + return Method.ParameterRefKinds[0]; + } + } + + return RefKind.None; + } + } + + public RefKind RightRefKind + { + get + { + if ((object)Method != null) + { + Debug.Assert(Method.ParameterCount == 2); + + if (!Method.ParameterRefKinds.IsDefaultOrEmpty) + { + Debug.Assert(Method.ParameterRefKinds.Length == 2); + + return Method.ParameterRefKinds[1]; + } + } + + return RefKind.None; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs index 72f34a7ca5b97..806d90b2c8b6b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorOverloadResolution.cs @@ -219,6 +219,16 @@ private BetterResult BetterOperator(UnaryOperatorSignature op1, UnaryOperatorSig } } + // Always prefer operators with val parameters over operators with in parameters: + if (op1.RefKind == RefKind.None && op2.RefKind == RefKind.In) + { + return BetterResult.Left; + } + else if (op2.RefKind == RefKind.None && op1.RefKind == RefKind.In) + { + return BetterResult.Right; + } + return BetterResult.Neither; } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorSignature.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorSignature.cs index 7001b7ea978f4..b822f2be1fc4d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorSignature.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/UnaryOperatorSignature.cs @@ -1,8 +1,8 @@ // 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.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp { @@ -25,7 +25,27 @@ public UnaryOperatorSignature(UnaryOperatorKind kind, TypeSymbol operandType, Ty public override string ToString() { - return $"kind: {this.Kind} operand: {this.OperandType} return: {this.ReturnType}"; + return $"kind: {this.Kind} operandType: {this.OperandType} operandRefKind: {this.RefKind} return: {this.ReturnType}"; + } + + public RefKind RefKind + { + get + { + if ((object)Method != null) + { + Debug.Assert(Method.ParameterCount == 1); + + if (!Method.ParameterRefKinds.IsDefaultOrEmpty) + { + Debug.Assert(Method.ParameterRefKinds.Length == 1); + + return Method.ParameterRefKinds.Single(); + } + } + + return RefKind.None; + } } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 03a411297654f..4ed48bd45b8a1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -1234,20 +1234,11 @@ private void RemoveWorseMembers(ArrayBuilder parameters) + /// + /// Returns the parameter type (considering params). + /// + private static TypeSymbol GetParameterType(ParameterSymbol parameter, MemberAnalysisResult result) { - RefKind discarded; - return GetParameterType(argIndex, result, parameters, out discarded); - } - - // Return the parameter type corresponding to the given argument index. - private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult result, ImmutableArray parameters, out RefKind refKind) - { - int paramIndex = result.ParameterFromArgument(argIndex); - ParameterSymbol parameter = parameters[paramIndex]; - refKind = parameter.RefKind; - if (result.Kind == MemberResolutionKind.ApplicableInExpandedForm && parameter.IsParams && parameter.Type.IsSZArray()) { @@ -1259,6 +1250,15 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re } } + /// + /// Returns the parameter corresponding to the given argument index. + /// + private static ParameterSymbol GetParameter(int argIndex, MemberAnalysisResult result, ImmutableArray parameters) + { + int paramIndex = result.ParameterFromArgument(argIndex); + return parameters[paramIndex]; + } + private BetterResult BetterFunctionMember( MemberResolutionResult m1, MemberResolutionResult m2, @@ -1320,6 +1320,9 @@ private BetterResult BetterFunctionMember( // implicit conversion from EX to PX, and for at least one argument, the conversion from // EX to PX is better than the conversion from EX to QX. + var m1LeastOverridenParameters = m1.LeastOverriddenMember.GetParameters(); + var m2LeastOverridenParameters = m2.LeastOverriddenMember.GetParameters(); + bool allSame = true; // Are all parameter types equivalent by identify conversions, ignoring Task-like differences? int i; for (i = 0; i < arguments.Count; ++i) @@ -1335,9 +1338,11 @@ private BetterResult BetterFunctionMember( continue; } - RefKind refKind1, refKind2; - var type1 = GetParameterType(i, m1.Result, m1.LeastOverriddenMember.GetParameters(), out refKind1); - var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2); + var parameter1 = GetParameter(i, m1.Result, m1LeastOverridenParameters); + var type1 = GetParameterType(parameter1, m1.Result); + + var parameter2 = GetParameter(i, m2.Result, m2LeastOverridenParameters); + var type2 = GetParameterType(parameter2, m2.Result); bool okToDowngradeToNeither; BetterResult r; @@ -1345,10 +1350,10 @@ private BetterResult BetterFunctionMember( r = BetterConversionFromExpression(arguments[i], type1, m1.Result.ConversionForArg(i), - refKind1, + parameter1.RefKind, type2, m2.Result.ConversionForArg(i), - refKind2, + parameter2.RefKind, considerRefKinds, ref useSiteDiagnostics, out okToDowngradeToNeither); @@ -1466,11 +1471,12 @@ private BetterResult BetterFunctionMember( continue; } - RefKind refKind1, refKind2; - var type1 = GetParameterType(i, m1.Result, m1.LeastOverriddenMember.GetParameters(), out refKind1); - var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2); - + var parameter1 = GetParameter(i, m1.Result, m1LeastOverridenParameters); + var type1 = GetParameterType(parameter1, m1.Result); var type1Normalized = type1.NormalizeTaskTypes(Compilation); + + var parameter2 = GetParameter(i, m2.Result, m2LeastOverridenParameters); + var type2 = GetParameterType(parameter2, m2.Result); var type2Normalized = type2.NormalizeTaskTypes(Compilation); if (Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity) @@ -1527,7 +1533,7 @@ private BetterResult BetterFunctionMember( } } - return BetterResult.Neither; + return PreferValOverInParameters(arguments, m1, m1LeastOverridenParameters, m2, m2LeastOverridenParameters); } // If MP is a non-generic method and MQ is a generic method, then MP is better than MQ. @@ -1620,8 +1626,11 @@ private BetterResult BetterFunctionMember( continue; } - uninst1.Add(GetParameterType(i, m1.Result, m1Original)); - uninst2.Add(GetParameterType(i, m2.Result, m2Original)); + var parameter1 = GetParameter(i, m1.Result, m1Original); + uninst1.Add(GetParameterType(parameter1, m1.Result)); + + var parameter2 = GetParameter(i, m2.Result, m2Original); + uninst2.Add(GetParameterType(parameter2, m2.Result)); } result = MoreSpecificType(uninst1, uninst2, ref useSiteDiagnostics); @@ -1636,7 +1645,7 @@ private BetterResult BetterFunctionMember( // UNDONE: Otherwise if one member is a non-lifted operator and the other is a lifted // operator, the non-lifted one is better. - // The penultimate rule: Position in interactive submission chain. The last definition wins. + // Otherwise: Position in interactive submission chain. The last definition wins. if (m1.Member.ContainingType.TypeKind == TypeKind.Submission && m2.Member.ContainingType.TypeKind == TypeKind.Submission) { // script class is always defined in source: @@ -1656,7 +1665,7 @@ private BetterResult BetterFunctionMember( } } - // Finally, if one has fewer custom modifiers, that is better + // Otherwise, if one has fewer custom modifiers, that is better int m1ModifierCount = m1.LeastOverriddenMember.CustomModifierCount(); int m2ModifierCount = m2.LeastOverriddenMember.CustomModifierCount(); if (m1ModifierCount != m2ModifierCount) @@ -1664,8 +1673,53 @@ private BetterResult BetterFunctionMember( return (m1ModifierCount < m2ModifierCount) ? BetterResult.Left : BetterResult.Right; } - // Otherwise, neither function member is better. - return BetterResult.Neither; + // Otherwise, prefer methods with 'val' parameters over 'in' parameters. + return PreferValOverInParameters(arguments, m1, m1LeastOverridenParameters, m2, m2LeastOverridenParameters); + } + + private static BetterResult PreferValOverInParameters( + ArrayBuilder arguments, + MemberResolutionResult m1, + ImmutableArray parameters1, + MemberResolutionResult m2, + ImmutableArray parameters2) + where TMember : Symbol + { + BetterResult valOverInPreference = BetterResult.Neither; + + for (int i = 0; i < arguments.Count; ++i) + { + if (arguments[i].Kind != BoundKind.ArgListOperator) + { + var p1 = GetParameter(i, m1.Result, parameters1); + var p2 = GetParameter(i, m2.Result, parameters2); + + if (p1.RefKind == RefKind.None && p2.RefKind == RefKind.In) + { + if (valOverInPreference == BetterResult.Right) + { + return BetterResult.Neither; + } + else + { + valOverInPreference = BetterResult.Left; + } + } + else if (p2.RefKind == RefKind.None && p1.RefKind == RefKind.In) + { + if (valOverInPreference == BetterResult.Left) + { + return BetterResult.Neither; + } + else + { + valOverInPreference = BetterResult.Right; + } + } + } + } + + return valOverInPreference; } private static void GetParameterCounts(MemberResolutionResult m, ArrayBuilder arguments, out int declaredParameterCount, out int parametersUsedIncludingExpansionAndOptional) where TMember : Symbol diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs index 126834c6fff78..98e017b3e3ab2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs @@ -9456,6 +9456,765 @@ public void PassingInArgumentsOverloadedOnIn() { var code = @" public static class Program +{ + public static void Method(in int x) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Method(int x) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Main() + { + int x = 5; + Method(in x); + Method(x); + Method(5); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +in: 5 +val: 5 +val: 5 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_Inverse() + { + var code = @" +public static class Program +{ + public static void Method(int x) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Method(in int x) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Main() + { + int x = 5; + Method(in x); + Method(x); + Method(5); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +in: 5 +val: 5 +val: 5 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_BinaryOperators() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(Test a, Test b) => ""val""; + public static string operator +(in Test a, in Test b) => ""in""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_BinaryOperators_Inverse() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(in Test a, in Test b) => ""in""; + public static string operator +(Test a, Test b) => ""val""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_UnaryOperators() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator !(Test a) => ""val""; + public static string operator !(in Test a) => ""in""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + Console.WriteLine(!a); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_UnaryOperators_Inverse() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator !(in Test a) => ""in""; + public static string operator !(Test a) => ""val""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + Console.WriteLine(!a); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_FirstArgument() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int ignore) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Method(int x, int ignore) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Main() + { + int x = 5; + Method(in x, 0); + Method(x, 0); + Method(5, 0); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +in: 5 +val: 5 +val: 5 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_FirstArgument_Inverse() + { + var code = @" +public static class Program +{ + public static void Method(int x, int ignore) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Method(in int x, int ignore) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Main() + { + int x = 5; + Method(in x, 0); + Method(x, 0); + Method(5, 0); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +in: 5 +val: 5 +val: 5 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_FirstArgument_BinaryOperators() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(Test a, Test b) => ""val""; + public static string operator +(in Test a, Test b) => ""in""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_FirstArgument_BinaryOperators_Inverse() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(in Test a, Test b) => ""in""; + public static string operator +(Test a, Test b) => ""val""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_SecondArgument() + { + var code = @" +public static class Program +{ + public static void Method(int ignore, in int x) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Method(int ignore, int x) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Main() + { + int x = 5; + Method(0, in x); + Method(0, x); + Method(0, 5); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +in: 5 +val: 5 +val: 5 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_SecondArgument_Inverse() + { + var code = @" +public static class Program +{ + public static void Method(int ignore, int x) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Method(int ignore, in int x) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Main() + { + int x = 5; + Method(0, in x); + Method(0, x); + Method(0, 5); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +in: 5 +val: 5 +val: 5 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_SecondArgument_BinaryOperators() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(Test a, Test b) => ""val""; + public static string operator +(Test a, in Test b) => ""in""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_SecondArgument_BinaryOperators_Inverse() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(Test a, in Test b) => ""in""; + public static string operator +(Test a, Test b) => ""val""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ConflictingParameters() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int y) + { + System.Console.WriteLine($""in {x} | val {y}""); + } + + public static void Method(int x, in int y) + { + System.Console.WriteLine($""val {x} | in {y}""); + } + + public static void Main() + { + int x = 1, y = 2; + + Method(x, in y); + Method(in x, y); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +val 1 | in 2 +in 1 | val 2 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ConflictingParameters_Inverse() + { + var code = @" +public static class Program +{ + public static void Method(int x, in int y) + { + System.Console.WriteLine($""val {x} | in {y}""); + } + + public static void Method(in int x, int y) + { + System.Console.WriteLine($""in {x} | val {y}""); + } + + public static void Main() + { + int x = 1, y = 2; + + Method(x, in y); + Method(in x, y); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +val 1 | in 2 +in 1 | val 2 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ConflictingParameters_Error() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int y) + { + System.Console.WriteLine($""in {x} val {y}""); + } + + public static void Method(int x, in int y) + { + System.Console.WriteLine($""val {x} in {y}""); + } + + public static void Main() + { + int x = 1, y = 2; + + Method(x, y); + Method(3, 4); + } +}"; + + CreateStandardCompilation(code).VerifyDiagnostics( + // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int, int)' and 'Program.Method(int, in int)' + // Method(x, y); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int, int)", "Program.Method(int, in int)").WithLocation(18, 9), + // (19,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int, int)' and 'Program.Method(int, in int)' + // Method(3, 4); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int, int)", "Program.Method(int, in int)").WithLocation(19, 9)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ConflictingParameters_Error_Inverse() + { + var code = @" +public static class Program +{ + public static void Method(int x, in int y) + { + System.Console.WriteLine($""val {x} in {y}""); + } + + public static void Method(in int x, int y) + { + System.Console.WriteLine($""in {x} val {y}""); + } + + public static void Main() + { + int x = 1, y = 2; + + Method(x, y); + Method(3, 4); + } +}"; + + CreateStandardCompilation(code).VerifyDiagnostics( + // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(int, in int)' and 'Program.Method(in int, int)' + // Method(x, y); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(int, in int)", "Program.Method(in int, int)").WithLocation(18, 9), + // (19,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(int, in int)' and 'Program.Method(in int, int)' + // Method(3, 4); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(int, in int)", "Program.Method(in int, int)").WithLocation(19, 9)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ThreeConflictingParameters_Error() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int y, in int z) + { + System.Console.WriteLine($""in {x} val {y} in {z}""); + } + + public static void Method(int x, in int y, int z) + { + System.Console.WriteLine($""val {x} in {y} val {z}""); + } + + public static void Main() + { + int x = 1, y = 2, z = 3; + + Method(x, y, z); + Method(4, 5, 6); + } +}"; + + CreateStandardCompilation(code).VerifyDiagnostics( + // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int, int, in int)' and 'Program.Method(int, in int, int)' + // Method(x, y, z); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int, int, in int)", "Program.Method(int, in int, int)").WithLocation(18, 9), + // (19,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int, int, in int)' and 'Program.Method(int, in int, int)' + // Method(4, 5, 6); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int, int, in int)", "Program.Method(int, in int, int)").WithLocation(19, 9)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ThreeConflictingParameters_Error_Inverse() + { + var code = @" +public static class Program +{ + public static void Method(int x, in int y, int z) + { + System.Console.WriteLine($""val {x} in {y} val {z}""); + } + + public static void Method(in int x, int y, in int z) + { + System.Console.WriteLine($""in {x} val {y} in {z}""); + } + + public static void Main() + { + int x = 1, y = 2, z = 3; + + Method(x, y, z); + Method(4, 5, 6); + } +}"; + + CreateStandardCompilation(code).VerifyDiagnostics( + // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(int, in int, int)' and 'Program.Method(in int, int, in int)' + // Method(x, y, z); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(int, in int, int)", "Program.Method(in int, int, in int)").WithLocation(18, 9), + // (19,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(int, in int, int)' and 'Program.Method(in int, int, in int)' + // Method(4, 5, 6); + Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(int, in int, int)", "Program.Method(in int, int, in int)").WithLocation(19, 9)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ConflictingParameters_Error_BinaryOperators() + { + CreateStandardCompilation(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(in Test a, Test b) => ""left""; + public static string operator +(Test a, in Test b) => ""right""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}").VerifyDiagnostics( + // (15,27): error CS0034: Operator '+' is ambiguous on operands of type 'Test' and 'Test' + // Console.WriteLine(a + b); + Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "a + b").WithArguments("+", "Test", "Test").WithLocation(15, 27)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_ConflictingParameters_Error_BinaryOperators_Inverse() + { + CreateStandardCompilation(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(Test a, in Test b) => ""right""; + public static string operator +(in Test a, Test b) => ""left""; +} +class Program +{ + static void Main() + { + var a = new Test { Value = 1 }; + var b = new Test { Value = 2 }; + Console.WriteLine(a + b); + } +}").VerifyDiagnostics( + // (15,27): error CS0034: Operator '+' is ambiguous on operands of type 'Test' and 'Test' + // Console.WriteLine(a + b); + Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "a + b").WithArguments("+", "Test", "Test").WithLocation(15, 27)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_UnusedConflictingParameters() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int y = 0) + { + System.Console.WriteLine($""in: {x}""); + } + + public static void Method(int x, in int y = 0) + { + System.Console.WriteLine($""val: {x}""); + } + + public static void Main() + { + int x = 1; + + Method(x); + Method(in x); + Method(2); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +val: 1 +in: 1 +val: 2"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_UnorderedNamedParameters() + { + var code = @" +public static class Program +{ + public static void Method(int a, int b) + { + System.Console.WriteLine($""val a: {a} | val b: {b}""); + } + + public static void Method(in int b, int a) + { + System.Console.WriteLine($""in b: {b} | val a: {a}""); + } + + public static void Main() + { + int a = 1, b = 2; + Method(b: b, a: a); + Method(a: a, b: in b); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +val a: 1 | val b: 2 +in b: 2 | val a: 1"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_OptionalParameters() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int op1 = 0, int op2 = 0) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Method(int x, int op1 = 0, int op2 = 0, int op3 = 0) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Main() + { + int x = 1; + + Method(x); + Method(in x); + Method(1); + + x = 2; + + Method(x, 0); + Method(in x, 0); + Method(2, 0); + + x = 3; + + Method(x, op3: 0); + } +}"; + + CompileAndVerify(code, expectedOutput: @" +val: 1 +in: 1 +val: 1 +val: 2 +in: 2 +val: 2 +val: 3 +"); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_OptionalParameters_Error() + { + var code = @" +public static class Program +{ + public static void Method(in int x, int op1 = 0, int op2 = 0) + { + System.Console.WriteLine(""in: "" + x); + } + + public static void Method(int x, int op1 = 0, int op2 = 0, int op3 = 0) + { + System.Console.WriteLine(""val: "" + x); + } + + public static void Main() + { + int x = 1; + Method(in x, op3: 0); // ERROR + } +}"; + + CreateStandardCompilation(code).VerifyDiagnostics( + // (17,19): error CS1615: Argument 1 may not be passed with the 'in' keyword + // Method(in x, op3: 0); // ERROR + Diagnostic(ErrorCode.ERR_BadArgExtraRef, "x").WithArguments("1", "in").WithLocation(17, 19)); + } + + [Fact] + public void PassingInArgumentsOverloadedOnIn_Named() + { + var code = @" +public static class Program { public static void Method(in int inP) { @@ -9514,12 +10273,6 @@ public static void Main() // (17,19): error CS1503: Argument 1: cannot convert from 'in byte' to 'in int' // Method(in x); Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "in byte", "in int").WithLocation(17, 19), - // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int)' and 'Program.Method(int)' - // Method('Q'); - Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int)", "Program.Method(int)").WithLocation(18, 9), - // (19,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int)' and 'Program.Method(int)' - // Method(3); - Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int)", "Program.Method(int)").WithLocation(19, 9), // (20,26): error CS1510: A ref or out value must be an assignable variable // Method(valP: out 2); Diagnostic(ErrorCode.ERR_RefLvalueExpected, "2").WithLocation(20, 26), @@ -9558,6 +10311,8 @@ public static void Main() var p = new Program(); int x = 5; + _ = p[0]; + _ = p[x]; _ = p[in x]; _ = p[valP: 3]; _ = p[inP: 2]; @@ -9566,6 +10321,8 @@ public static void Main() "; CompileAndVerify(code, expectedOutput: @" +val: 0 +val: 5 in: 5 val: 3 in: 2 @@ -9613,12 +10370,6 @@ public static void Main() // (27,18): error CS1503: Argument 1: cannot convert from 'in byte' to 'in int' // _ = p[in x]; Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "in byte", "in int").WithLocation(27, 18), - // (28,13): error CS0121: The call is ambiguous between the following methods or properties: 'Program.this[in int]' and 'Program.this[int]' - // _ = p['Q']; - Diagnostic(ErrorCode.ERR_AmbigCall, "p['Q']").WithArguments("Program.this[in int]", "Program.this[int]").WithLocation(28, 13), - // (29,13): error CS0121: The call is ambiguous between the following methods or properties: 'Program.this[in int]' and 'Program.this[int]' - // _ = p[3]; - Diagnostic(ErrorCode.ERR_AmbigCall, "p[3]").WithArguments("Program.this[in int]", "Program.this[int]").WithLocation(29, 13), // (30,25): error CS1510: A ref or out value must be an assignable variable // _ = p[valP: out 2]; Diagnostic(ErrorCode.ERR_RefLvalueExpected, "2").WithLocation(30, 25), @@ -9627,31 +10378,6 @@ public static void Main() Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "2").WithLocation(31, 23)); } - [Fact] - public void PassingInArgumentsOverloadedOnIOperatorErr() - { - var code = @" -class C -{ - public static int operator+(C a, C b) => 0; - public static int operator+(in C a, in C b) => 0; -} -public class Program -{ - public static void Main() - { - var a = new C(); - var b = new C(); - var result = a + b; - } -}"; - - CreateStandardCompilation(code).VerifyDiagnostics( - // (13,22): error CS0034: Operator '+' is ambiguous on operands of type 'C' and 'C' - // var result = a + b; - Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "a + b").WithArguments("+", "C", "C").WithLocation(13, 22)); - } - [Fact] public void PassingInArgumentsOverloadedOnInOptionalParameters() { @@ -9670,59 +10396,59 @@ public static void Method(int valP = 0) public static void Main() { - int x = 5; - Method(in x); - Method(valP: 3); + Method(valP: 1); Method(inP: 2); + + int x = 3; + Method(in x); } }"; CompileAndVerify(code, expectedOutput: @" -in: 5 -val: 3 +val: 1 in: 2 +in: 3 "); } [Fact] - public void PassingInArgumentsOverloadedOnInErrOptionalParametersAmbiguous() + public void PassingInArgumentsOverloadedOnInParams() { var code = @" -public static class Program +using System; +class Program { - public static void Method(in int inP = 0) - { - System.Console.WriteLine(""in: "" + inP); - } + void M(in int x) { Console.WriteLine(""in: "" + x); } + void M(params int[] p) { Console.WriteLine(""params: "" + p.Length); } - public static void Method(int valP = 0) + static void Main() { - System.Console.WriteLine(""val: "" + valP); - } + var p = new Program(); - public static void Main() - { - int x = 0; + p.M(); + p.M(1); + p.M(1, 2); - Method(); // Should be an error - Method(in x); // Should be OK + int x = 3; + p.M(in x); } }"; - CreateStandardCompilation(code).VerifyDiagnostics( - // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Method(in int)' and 'Program.Method(int)' - // Method(); // Should be an error - Diagnostic(ErrorCode.ERR_AmbigCall, "Method").WithArguments("Program.Method(in int)", "Program.Method(int)").WithLocation(18, 9)); + CompileAndVerify(code, expectedOutput: +@"params: 0 +in: 1 +params: 2 +in: 3"); } [Fact] - public void PassingInArgumentsOverloadedOnInParams() + public void PassingInArgumentsOverloadedOnInParams_Array() { var code = @" using System; class Program { - void M(in int x) { Console.WriteLine(""in: "" + x); } + void M(in int[] p) { Console.WriteLine(""in: "" + p.Length); } void M(params int[] p) { Console.WriteLine(""params: "" + p.Length); } static void Main() @@ -9733,16 +10459,226 @@ static void Main() p.M(1); p.M(1, 2); - int x = 3; + var x = new int[] { }; + p.M(x); + p.M(in x); + p.M(new int[] { }); + + x = new int[] { 1 }; + p.M(x); p.M(in x); + p.M(new int[] { 1 }); } }"; - CompileAndVerify(code, expectedOutput: + CompileAndVerify(code, expectedOutput: @"params: 0 -in: 1 +params: 1 params: 2 -in: 3"); +params: 0 +in: 0 +params: 0 +params: 1 +in: 1 +params: 1"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_ExtensionMethods() + { + CompileAndVerify(@" +using System; +static class Extensions +{ + public static void M(this Program instance, in int x) { Console.WriteLine(""in: "" + x); } +} +class Program +{ + void M(int x) { Console.WriteLine(""val: "" + x); } + + static void Main() + { + var instance = new Program(); + + int x = 1; + instance.M(x); + + x = 2; + instance.M(in x); + + instance.M(3); + } +}", + additionalRefs: new[] { SystemCoreRef }, + expectedOutput: +@"val: 1 +in: 2 +val: 3"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_Indexers() + { + CompileAndVerify(@" +using System; +class Program +{ + public string this[int x] => ""val: "" + x; + public string this[in int x] => ""in: "" + x; + static void Main() + { + var instance = new Program(); + + int x = 1; + Console.WriteLine(instance[x]); + + x = 2; + Console.WriteLine(instance[in x]); + + Console.WriteLine(instance[3]); + } +}", + additionalRefs: new[] { SystemCoreRef }, + expectedOutput: +@"val: 1 +in: 2 +val: 3"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_TypeConversions_In() + { + CompileAndVerify(@" +using System; +class Program +{ + static void M(in byte x) { Console.WriteLine(""in: "" + x); } + static void M(int x) { Console.WriteLine(""val: "" + x); } + + static void Main() + { + M(0); + + int intX = 1; + byte byteX = 1; + + M(intX); + M(byteX); + + M((int)2); + M((byte)2); + } +}", + expectedOutput:@" +val: 0 +val: 1 +in: 1 +val: 2 +in: 2"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_TypeConversions_Val() + { + CompileAndVerify(@" +using System; +class Program +{ + static void M(byte x) { Console.WriteLine(""val: "" + x); } + static void M(in int x) { Console.WriteLine(""in: "" + x); } + + static void Main() + { + M(0); + + int intX = 1; + byte byteX = 1; + + M(intX); + M(byteX); + + M((int)2); + M((byte)2); + } +}", + expectedOutput: @" +in: 0 +in: 1 +val: 1 +in: 2 +val: 2"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_TypeConversions_BinaryOperators() + { + CompileAndVerify(@" +using System; +class Test +{ + public int Value { get; set; } + public static string operator +(int a, Test b) => ""val""; + public static string operator +(in byte a, Test b) => ""in""; +} +class Program +{ + static void Main() + { + int intX = 1; + byte byteX = 1; + var b = new Test { Value = 2 }; + + Console.WriteLine(intX + b); + Console.WriteLine(byteX + b); + Console.WriteLine(1 + b); + Console.WriteLine(((byte)1) + b); + } +}", + expectedOutput: @" +val +in +val +in"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_TypeConversions_NonConvertible() + { + CompileAndVerify(@" +using System; +using System.Text; +class Program +{ + static void M(string x) { Console.WriteLine(""val""); } + static void M(in StringBuilder x) { Console.WriteLine(""in""); } + + static void Main() + { + M(null); + } +}", + expectedOutput: "val"); + } + + [Fact] + public void PassingArgumentsToOverloadsOfByValAndInParameters_TypeConversions_NonConvertible_Error() + { + CreateStandardCompilation(@" +using System; +using System.Text; +class Program +{ + static void M(string x) { Console.WriteLine(""val""); } + static void M(StringBuilder x) { Console.WriteLine(""in""); } + + static void Main() + { + M(null); + } +}").VerifyDiagnostics( + // (11,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M(string)' and 'Program.M(StringBuilder)' + // M(null); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("Program.M(string)", "Program.M(System.Text.StringBuilder)").WithLocation(11, 9)); } [Fact]