From acb88a9692ee324ffeb1e35bc78e7add34bccbfb Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 14 Jul 2023 13:52:38 +0200 Subject: [PATCH 1/4] Allow default values for `ref readonly` parameters --- .../CSharp/Portable/CSharpResources.resx | 6 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../CSharp/Portable/Errors/ErrorFacts.cs | 2 + .../Generated/ErrorFacts.Generated.cs | 1 + .../Portable/Symbols/ParameterSymbol.cs | 2 +- .../Symbols/Source/ParameterHelpers.cs | 6 + .../Source/SourceComplexParameterSymbol.cs | 6 + .../Portable/xlf/CSharpResources.cs.xlf | 10 + .../Portable/xlf/CSharpResources.de.xlf | 10 + .../Portable/xlf/CSharpResources.es.xlf | 10 + .../Portable/xlf/CSharpResources.fr.xlf | 10 + .../Portable/xlf/CSharpResources.it.xlf | 10 + .../Portable/xlf/CSharpResources.ja.xlf | 10 + .../Portable/xlf/CSharpResources.ko.xlf | 10 + .../Portable/xlf/CSharpResources.pl.xlf | 10 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 10 + .../Portable/xlf/CSharpResources.ru.xlf | 10 + .../Portable/xlf/CSharpResources.tr.xlf | 10 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 10 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 10 + .../Semantics/RefReadonlyParameterTests.cs | 316 ++++++++++++++++++ .../Test/Syntax/Diagnostics/DiagnosticTest.cs | 1 + 22 files changed, 470 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 9042aedebfdac..6f0171e72cdbe 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4094,6 +4094,12 @@ You should consider suppressing the warning only if you're sure that you don't w The default value specified will have no effect because it applies to a member that is used in contexts that do not allow optional arguments + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + Error signing output with public key from file '{0}' -- {1} diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 074f5439ab3dc..d16dcb0e2c788 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2247,6 +2247,7 @@ internal enum ErrorCode ERR_BadArgExtraRefLangVersion = 9505, WRN_ArgExpectedIn = 9506, ERR_OutAttrOnRefReadonlyParam = 9520, + WRN_RefReadonlyParameterDefaultValue = 9521, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index ba5e92c22ce3a..e5029a4ab28bd 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -540,6 +540,7 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_ArgExpectedRefOrIn: case ErrorCode.WRN_RefReadonlyNotVariable: case ErrorCode.WRN_ArgExpectedIn: + case ErrorCode.WRN_RefReadonlyParameterDefaultValue: return 1; default: return 0; @@ -2370,6 +2371,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_BadArgExtraRefLangVersion: case ErrorCode.WRN_ArgExpectedIn: case ErrorCode.ERR_OutAttrOnRefReadonlyParam: + case ErrorCode.WRN_RefReadonlyParameterDefaultValue: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index df06e915998f5..981a3ae08e2e0 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -323,6 +323,7 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_ArgExpectedRefOrIn: case ErrorCode.WRN_RefReadonlyNotVariable: case ErrorCode.WRN_ArgExpectedIn: + case ErrorCode.WRN_RefReadonlyParameterDefaultValue: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index 9a8bc63dfdff8..fb44eb88b7d57 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -151,7 +151,7 @@ public bool IsOptional RefKind refKind; return !IsParams && IsMetadataOptional && ((refKind = RefKind) == RefKind.None || - (refKind == RefKind.In) || + (refKind is RefKind.In or RefKind.RefReadOnlyParameter) || (refKind == RefKind.Ref && ContainingSymbol.ContainingType.IsComImport)); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index ec1aeb35e97d7..b073706e6d953 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -853,6 +853,12 @@ internal static bool ReportDefaultParameterErrors( parameterSyntax.Identifier.ValueText); } + if (refKind == RefKind.RefReadOnlyParameter) + { + // A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + diagnostics.Add(ErrorCode.WRN_RefReadonlyParameterDefaultValue, parameterSyntax.Default.Value, parameterSyntax.Identifier.ValueText); + } + return hasErrors; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 96200b18a03c2..dafaddb810d11 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -891,6 +891,12 @@ private void DecodeDefaultParameterValueAttribute(AttributeDescription descripti if (!value.IsBad) { VerifyParamDefaultValueMatchesAttributeIfAny(value, syntax, diagnostics); + + if (this.RefKind == RefKind.RefReadOnlyParameter && this.IsOptional && this.CSharpSyntaxNode.Default is null) + { + // A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + diagnostics.Add(ErrorCode.WRN_RefReadonlyParameterDefaultValue, syntax, this.Name); + } } } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index bbd25dfbdd4cb..6bab7694bbae5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Tato funkce vrací místní {0} podle odkazu, ale nejedná se o místní odkaz diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index c9e1c24f47a61..0024ef334e2cc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Gibt die lokale Variable "{0}" als Verweis zurück, es handelt sich jedoch nicht um eine lokale ref-Variable. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index ebd017a2bcfd3..697e589a70d05 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Esto devuelve por referencia "{0}" de la variable local, pero no es una variable local de tipo ref diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 184eb373875c2..1569115d4a8cb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Retourne une variable locale '{0}' par référence, mais il ne s'agit pas d'une variable locale de référence diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index c2dbf446fcf2f..7b49cad393453 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local In questo modo viene restituito il valore locale '{0}' per riferimento, ma non è un riferimento locale diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 1b56c0aae557f..a829d6fdfb7eb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local これは、ローカル変数 '{0}' を参照渡しで返しますが、ref ローカル変数ではありません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index d0337d32ad23e..905a338e73b3f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local 참조로 로컬 '{0}'을(를) 반환하지만 참조 로컬이 아닙니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index d6537eddf7e38..ccdd144949d9d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Spowoduje to zwrócenie lokalnego elementu „{0}” przez odwołanie, ale nie jest to odwołanie lokalne diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index d1cdbfb0d1040..a6d582261db35 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Isso retorna o local '{0}' por referência, mas não é um local de ref diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index a43b0dfa94ae4..264d17835fe53 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Это возвращает local "{0}" по ссылке, но это не ref local diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index a96999e5056da..64d21e3092128 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local Bu, referans olarak yerel '{0}' döndürür, ancak bir ref yerel değildir diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 7f9423d1eb247..8bdc3f4add7ef 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local 这将按引用返回本地“{0}”,但它不是 ref 本地 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index b69e2af1a2672..07f6cab4802e3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -2712,6 +2712,16 @@ Argument should be a variable because it is passed to a 'ref readonly' parameter + + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + + + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + A default value is specified for 'ref readonly' parameter, but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + + This returns local '{0}' by reference but it is not a ref local 這會藉傳址方式傳回本機 '{0}',但其非參考本機 diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs index 227b82cc948bf..8b1ffd91d718c 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs @@ -2873,6 +2873,322 @@ static void Main() Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "in string", "ref readonly int").WithLocation(9, 14)); } + [Fact] + public void DefaultParameterValue_EqualsValue() + { + var source = """ + class C + { + static void M(ref readonly int i = 1) => System.Console.Write(i); + static void Main() + { + int x = 2; + M(); + M(x); + M(ref x); + M(in x); + } + static void M2() => M(); + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "1222").VerifyDiagnostics( + // (3,40): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M(ref readonly int i = 1) => System.Console.Write(i); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(3, 40), + // (8,11): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // M(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(8, 11)); + verifier.VerifyIL("C.M2", """ + { + // Code size 10 (0xa) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call "void C.M(ref readonly int)" + IL_0009: ret + } + """); + } + + [Fact] + public void DefaultParameterValue_Attribute() + { + var source = """ + using System.Runtime.InteropServices; + class C + { + static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); + static void Main() + { + int x = 2; + M(); + M(x); + M(ref x); + M(in x); + } + static void M2() => M(); + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "1222").VerifyDiagnostics( + // (4,30): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DefaultParameterValue(1)").WithArguments("i").WithLocation(4, 30), + // (9,11): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // M(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(9, 11)); + verifier.VerifyIL("C.M2", """ + { + // Code size 10 (0xa) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call "void C.M(ref readonly int)" + IL_0009: ret + } + """); + } + + [Fact] + public void DefaultParameterValue_AttributeAndEqualsValue() + { + var source = """ + using System.Runtime.InteropServices; + class C + { + static void M([DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (4,20): error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute + // static void M([DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.ERR_DefaultValueUsedWithAttributes, "DefaultParameterValue").WithLocation(4, 20), + // (4,67): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M([DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(4, 67), + // (4,67): error CS8017: The parameter has multiple distinct default values. + // static void M([DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.ERR_ParamDefaultValueDiffersFromAttribute, "1").WithLocation(4, 67)); + } + + [Fact] + public void DefaultParameterValue_OptionalAndEqualsValue() + { + var source = """ + using System.Runtime.InteropServices; + class C + { + static void M([Optional] ref readonly int i = 1) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (4,20): error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute + // static void M([Optional] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.ERR_DefaultValueUsedWithAttributes, "Optional").WithLocation(4, 20), + // (4,51): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M([Optional] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(4, 51)); + } + + [Fact] + public void DefaultParameterValue_All() + { + var source = """ + using System.Runtime.InteropServices; + class C + { + static void M([Optional, DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (4,20): error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute + // static void M([Optional, DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.ERR_DefaultValueUsedWithAttributes, "Optional").WithLocation(4, 20), + // (4,30): error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute + // static void M([Optional, DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.ERR_DefaultValueUsedWithAttributes, "DefaultParameterValue").WithLocation(4, 30), + // (4,77): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M([Optional, DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(4, 77), + // (4,77): error CS8017: The parameter has multiple distinct default values. + // static void M([Optional, DefaultParameterValue(1)] ref readonly int i = 1) => throw null; + Diagnostic(ErrorCode.ERR_ParamDefaultValueDiffersFromAttribute, "1").WithLocation(4, 77)); + } + + [Fact] + public void DefaultParameterValue_DecimalConstant_Valid() + { + var source = """ + using System; + using System.Globalization; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + class C + { + static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); + static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); + static void Main() + { + decimal x = 2.2m; + M1(); + M1(x); + M1(ref x); + M1(in x); + + decimal y = 3.3m; + M2(); + M2(y); + M2(ref y); + M2(in y); + } + static void M3() => M1(); + static void M4() => M2(); + } + """; + var verifier = CompileAndVerify(source, expectedOutput: """ + M1 1.1 + M1 2.2 + M1 2.2 + M1 2.2 + M2 1.1 + M2 3.3 + M2 3.3 + M2 3.3 + """).VerifyDiagnostics( + // (7,31): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DecimalConstant(1, 0, 0u, 0u, 11u)").WithArguments("d").WithLocation(7, 31), + // (8,45): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1.1m").WithArguments("d").WithLocation(8, 45), + // (13,12): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // M1(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(13, 12), + // (19,12): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // M2(y); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "y").WithArguments("1").WithLocation(19, 12)); + verifier.VerifyIL("C.M3", """ + { + // Code size 20 (0x14) + .maxstack 5 + .locals init (decimal V_0) + IL_0000: ldc.i4.s 11 + IL_0002: ldc.i4.0 + IL_0003: ldc.i4.0 + IL_0004: ldc.i4.0 + IL_0005: ldc.i4.1 + IL_0006: newobj "decimal..ctor(int, int, int, bool, byte)" + IL_000b: stloc.0 + IL_000c: ldloca.s V_0 + IL_000e: call "void C.M1(ref readonly decimal)" + IL_0013: ret + } + """); + verifier.VerifyIL("C.M4", """ + { + // Code size 20 (0x14) + .maxstack 5 + .locals init (decimal V_0) + IL_0000: ldc.i4.s 11 + IL_0002: ldc.i4.0 + IL_0003: ldc.i4.0 + IL_0004: ldc.i4.0 + IL_0005: ldc.i4.1 + IL_0006: newobj "decimal..ctor(int, int, int, bool, byte)" + IL_000b: stloc.0 + IL_000c: ldloca.s V_0 + IL_000e: call "void C.M2(ref readonly decimal)" + IL_0013: ret + } + """); + } + + [Fact] + public void DefaultParameterValue_DecimalConstant_Invalid() + { + var source = """ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + class C + { + static void M1([DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => throw null; + static void M2([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d = 1.1m) => throw null; + static void Main() + { + M1(); + M2(); + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,21): error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute + // static void M2([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d = 1.1m) => throw null; + Diagnostic(ErrorCode.ERR_DefaultValueUsedWithAttributes, "Optional").WithLocation(6, 21), + // (6,92): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M2([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d = 1.1m) => throw null; + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1.1m").WithArguments("d").WithLocation(6, 92), + // (9,9): error CS7036: There is no argument given that corresponds to the required parameter 'd' of 'C.M1(ref readonly decimal)' + // M1(); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "M1").WithArguments("d", "C.M1(ref readonly decimal)").WithLocation(9, 9)); + } + + [Fact] + public void DefaultParameterValue_DateTimeConstant_Valid() + { + var source = """ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + class C + { + static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); + static void Main() => M(); + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "100").VerifyDiagnostics( + // (6,30): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DateTimeConstant(100L)").WithArguments("d").WithLocation(6, 30)); + verifier.VerifyIL("C.Main", """ + { + // Code size 17 (0x11) + .maxstack 1 + .locals init (System.DateTime V_0) + IL_0000: ldc.i4.s 100 + IL_0002: conv.i8 + IL_0003: newobj "System.DateTime..ctor(long)" + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: call "void C.M(ref readonly System.DateTime)" + IL_0010: ret + } + """); + } + + [Fact] + public void DefaultParameterValue_DateTimeConstant_Invalid() + { + var source = """ + using System; + using System.Runtime.CompilerServices; + class C + { + static void M([DateTimeConstant(100L)] ref readonly DateTime d) => throw null; + static void Main() + { + M(); + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (8,9): error CS7036: There is no argument given that corresponds to the required parameter 'd' of 'C.M(ref readonly DateTime)' + // M(); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "M").WithArguments("d", "C.M(ref readonly System.DateTime)").WithLocation(8, 9)); + } + [Fact] public void Invocation_VirtualMethod() { diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index 52b46b59d144f..9cea534c354f0 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -312,6 +312,7 @@ public void WarningLevel_2() case ErrorCode.WRN_ArgExpectedRefOrIn: case ErrorCode.WRN_RefReadonlyNotVariable: case ErrorCode.WRN_ArgExpectedIn: + case ErrorCode.WRN_RefReadonlyParameterDefaultValue: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_MainIgnored: From d57df4d988a308b480160d45cb2fa6e2f147cafe Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Mon, 17 Jul 2023 16:10:41 +0200 Subject: [PATCH 2/4] Verify consuming default parameter values from metadata --- .../Semantics/RefReadonlyParameterTests.cs | 200 ++++++++++-------- 1 file changed, 116 insertions(+), 84 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs index 8b1ffd91d718c..8d0b513e34338 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs @@ -2873,32 +2873,40 @@ static void Main() Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "in string", "ref readonly int").WithLocation(9, 14)); } - [Fact] - public void DefaultParameterValue_EqualsValue() + [Theory, CombinatorialData] + public void DefaultParameterValue_EqualsValue(bool fromMetadata) { - var source = """ - class C + var source1 = """ + public class C + { + public static void M(ref readonly int i = 1) => System.Console.Write(i); + } + """; + var source2 = """ + class D { - static void M(ref readonly int i = 1) => System.Console.Write(i); static void Main() { int x = 2; - M(); - M(x); - M(ref x); - M(in x); - } - static void M2() => M(); - } - """; - var verifier = CompileAndVerify(source, expectedOutput: "1222").VerifyDiagnostics( - // (3,40): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // static void M(ref readonly int i = 1) => System.Console.Write(i); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(3, 40), - // (8,11): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword - // M(x); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(8, 11)); - verifier.VerifyIL("C.M2", """ + C.M(); + C.M(x); + C.M(ref x); + C.M(in x); + } + static void M2() => C.M(); + } + """; + var comp = fromMetadata + ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "1222").VerifyDiagnostics( + // (3,47): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M(ref readonly int i = 1) => System.Console.Write(i); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(3, 47), + // (7,13): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // C.M(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13)); + verifier.VerifyIL("D.M2", """ { // Code size 10 (0xa) .maxstack 1 @@ -2912,33 +2920,41 @@ .locals init (int V_0) """); } - [Fact] - public void DefaultParameterValue_Attribute() + [Theory, CombinatorialData] + public void DefaultParameterValue_Attribute(bool fromMetadata) { - var source = """ + var source1 = """ using System.Runtime.InteropServices; - class C + public class C + { + public static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); + } + """; + var source2 = """ + class D { - static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); static void Main() { int x = 2; - M(); - M(x); - M(ref x); - M(in x); - } - static void M2() => M(); - } - """; - var verifier = CompileAndVerify(source, expectedOutput: "1222").VerifyDiagnostics( - // (4,30): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DefaultParameterValue(1)").WithArguments("i").WithLocation(4, 30), - // (9,11): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword - // M(x); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(9, 11)); - verifier.VerifyIL("C.M2", """ + C.M(); + C.M(x); + C.M(ref x); + C.M(in x); + } + static void M2() => C.M(); + } + """; + var comp = fromMetadata + ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "1222").VerifyDiagnostics( + // (4,37): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DefaultParameterValue(1)").WithArguments("i").WithLocation(4, 37), + // (7,13): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // C.M(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13)); + verifier.VerifyIL("D.M2", """ { // Code size 10 (0xa) .maxstack 1 @@ -3018,37 +3034,45 @@ class C Diagnostic(ErrorCode.ERR_ParamDefaultValueDiffersFromAttribute, "1").WithLocation(4, 77)); } - [Fact] - public void DefaultParameterValue_DecimalConstant_Valid() + [Theory, CombinatorialData] + public void DefaultParameterValue_DecimalConstant_Valid(bool fromMetadata) { - var source = """ + var source1 = """ using System; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - class C + public class C + { + public static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); + public static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); + } + """; + var source2 = """ + class D { - static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); - static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); static void Main() { decimal x = 2.2m; - M1(); - M1(x); - M1(ref x); - M1(in x); + C.M1(); + C.M1(x); + C.M1(ref x); + C.M1(in x); decimal y = 3.3m; - M2(); - M2(y); - M2(ref y); - M2(in y); + C.M2(); + C.M2(y); + C.M2(ref y); + C.M2(in y); } - static void M3() => M1(); - static void M4() => M2(); + static void M3() => C.M1(); + static void M4() => C.M2(); } """; - var verifier = CompileAndVerify(source, expectedOutput: """ + var comp = fromMetadata + ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: """ M1 1.1 M1 2.2 M1 2.2 @@ -3058,19 +3082,19 @@ M2 3.3 M2 3.3 M2 3.3 """).VerifyDiagnostics( - // (7,31): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DecimalConstant(1, 0, 0u, 0u, 11u)").WithArguments("d").WithLocation(7, 31), - // (8,45): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1.1m").WithArguments("d").WithLocation(8, 45), - // (13,12): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword - // M1(x); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(13, 12), - // (19,12): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword - // M2(y); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "y").WithArguments("1").WithLocation(19, 12)); - verifier.VerifyIL("C.M3", """ + // (7,14): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // C.M1(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 14), + // (7,38): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DecimalConstant(1, 0, 0u, 0u, 11u)").WithArguments("d").WithLocation(7, 38), + // (8,52): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1.1m").WithArguments("d").WithLocation(8, 52), + // (13,14): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // C.M2(y); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "y").WithArguments("1").WithLocation(13, 14)); + verifier.VerifyIL("D.M3", """ { // Code size 20 (0x14) .maxstack 5 @@ -3087,7 +3111,7 @@ .locals init (decimal V_0) IL_0013: ret } """); - verifier.VerifyIL("C.M4", """ + verifier.VerifyIL("D.M4", """ { // Code size 20 (0x14) .maxstack 5 @@ -3135,24 +3159,32 @@ static void Main() Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "M1").WithArguments("d", "C.M1(ref readonly decimal)").WithLocation(9, 9)); } - [Fact] - public void DefaultParameterValue_DateTimeConstant_Valid() + [Theory, CombinatorialData] + public void DefaultParameterValue_DateTimeConstant_Valid(bool fromMetadata) { - var source = """ + var source1 = """ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - class C + public class C { - static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); - static void Main() => M(); + public static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); } """; - var verifier = CompileAndVerify(source, expectedOutput: "100").VerifyDiagnostics( - // (6,30): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DateTimeConstant(100L)").WithArguments("d").WithLocation(6, 30)); - verifier.VerifyIL("C.Main", """ + var source2 = """ + class D + { + static void Main() => C.M(); + } + """; + var comp = fromMetadata + ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "100").VerifyDiagnostics( + // (6,37): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DateTimeConstant(100L)").WithArguments("d").WithLocation(6, 37)); + verifier.VerifyIL("D.Main", """ { // Code size 17 (0x11) .maxstack 1 From a942f4001520757e2a2bae49d65957fe739fbddc Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 18 Jul 2023 10:52:02 +0200 Subject: [PATCH 3/4] Use `EmitToImageReference` for metadata default params --- .../Semantics/RefReadonlyParameterTests.cs | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs index 8d0b513e34338..aef0108a3d5e6 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs @@ -2882,6 +2882,10 @@ public class C public static void M(ref readonly int i = 1) => System.Console.Write(i); } """; + var warning1 = + // (3,47): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M(ref readonly int i = 1) => System.Console.Write(i); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(3, 47); var source2 = """ class D { @@ -2896,16 +2900,14 @@ static void Main() static void M2() => C.M(); } """; - var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); - var verifier = CompileAndVerify(comp, expectedOutput: "1222").VerifyDiagnostics( - // (3,47): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // public static void M(ref readonly int i = 1) => System.Console.Write(i); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1").WithArguments("i").WithLocation(3, 47), + var warning2 = // (7,13): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword // C.M(x); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13)); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13); + var comp = fromMetadata + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning2) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning1, warning2); + var verifier = CompileAndVerify(comp, expectedOutput: "1222"); verifier.VerifyIL("D.M2", """ { // Code size 10 (0xa) @@ -2930,6 +2932,10 @@ public class C public static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); } """; + var warning1 = + // (4,37): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DefaultParameterValue(1)").WithArguments("i").WithLocation(4, 37); var source2 = """ class D { @@ -2944,16 +2950,14 @@ static void Main() static void M2() => C.M(); } """; - var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); - var verifier = CompileAndVerify(comp, expectedOutput: "1222").VerifyDiagnostics( - // (4,37): warning CS9521: A default value is specified for 'ref readonly' parameter 'i', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // public static void M([Optional, DefaultParameterValue(1)] ref readonly int i) => System.Console.Write(i); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DefaultParameterValue(1)").WithArguments("i").WithLocation(4, 37), + var warning2 = // (7,13): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword // C.M(x); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13)); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13); + var comp = fromMetadata + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning2) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning1, warning2); + var verifier = CompileAndVerify(comp, expectedOutput: "1222"); verifier.VerifyIL("D.M2", """ { // Code size 10 (0xa) @@ -3048,6 +3052,15 @@ public class C public static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); } """; + var warnings1 = new[] + { + // (7,38): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DecimalConstant(1, 0, 0u, 0u, 11u)").WithArguments("d").WithLocation(7, 38), + // (8,52): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1.1m").WithArguments("d").WithLocation(8, 52), + }; var source2 = """ class D { @@ -3069,9 +3082,18 @@ static void Main() static void M4() => C.M2(); } """; + var warnings2 = new[] + { + // (7,14): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // C.M1(x); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 14), + // (13,14): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword + // C.M2(y); + Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "y").WithArguments("1").WithLocation(13, 14) + }; var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warnings1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warnings2) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warnings1.Concat(warnings2).ToArray()); var verifier = CompileAndVerify(comp, expectedOutput: """ M1 1.1 M1 2.2 @@ -3081,19 +3103,7 @@ M2 1.1 M2 3.3 M2 3.3 M2 3.3 - """).VerifyDiagnostics( - // (7,14): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword - // C.M1(x); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 14), - // (7,38): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // public static void M1([Optional, DecimalConstant(1, 0, 0u, 0u, 11u)] ref readonly decimal d) => Console.WriteLine("M1 " + d.ToString(CultureInfo.InvariantCulture)); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DecimalConstant(1, 0, 0u, 0u, 11u)").WithArguments("d").WithLocation(7, 38), - // (8,52): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // public static void M2(ref readonly decimal d = 1.1m) => Console.WriteLine("M2 " + d.ToString(CultureInfo.InvariantCulture)); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "1.1m").WithArguments("d").WithLocation(8, 52), - // (13,14): warning CS9503: Argument 1 should be passed with 'ref' or 'in' keyword - // C.M2(y); - Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "y").WithArguments("1").WithLocation(13, 14)); + """); verifier.VerifyIL("D.M3", """ { // Code size 20 (0x14) @@ -3171,6 +3181,10 @@ public class C public static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); } """; + var warning1 = + // (6,37): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. + // public static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); + Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DateTimeConstant(100L)").WithArguments("d").WithLocation(6, 37); var source2 = """ class D { @@ -3178,12 +3192,9 @@ class D } """; var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).ToMetadataReference() }, options: TestOptions.ReleaseExe) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); - var verifier = CompileAndVerify(comp, expectedOutput: "100").VerifyDiagnostics( - // (6,37): warning CS9521: A default value is specified for 'ref readonly' parameter 'd', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'. - // public static void M([Optional, DateTimeConstant(100L)] ref readonly DateTime d) => Console.Write(d.Ticks); - Diagnostic(ErrorCode.WRN_RefReadonlyParameterDefaultValue, "DateTimeConstant(100L)").WithArguments("d").WithLocation(6, 37)); + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics() + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning1); + var verifier = CompileAndVerify(comp, expectedOutput: "100"); verifier.VerifyIL("D.Main", """ { // Code size 17 (0x11) From e77ec26b14774fe1e3a8aba0b00cbcbc535b5652 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 18 Jul 2023 17:13:06 +0200 Subject: [PATCH 4/4] Verify default parameter diagnostics after emit --- .../Semantics/RefReadonlyParameterTests.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs index aef0108a3d5e6..e72f9c2d35e8c 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -2905,9 +2906,10 @@ static void Main() // C.M(x); Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13); var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning2) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning1, warning2); + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: "1222"); + verifier.VerifyDiagnostics(fromMetadata ? new[] { warning2 } : new[] { warning1, warning2 }); verifier.VerifyIL("D.M2", """ { // Code size 10 (0xa) @@ -2955,9 +2957,10 @@ static void Main() // C.M(x); Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "x").WithArguments("1").WithLocation(7, 13); var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning2) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning1, warning2); + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: "1222"); + verifier.VerifyDiagnostics(fromMetadata ? new[] { warning2 } : new[] { warning1, warning2 }); verifier.VerifyIL("D.M2", """ { // Code size 10 (0xa) @@ -3092,8 +3095,8 @@ static void Main() Diagnostic(ErrorCode.WRN_ArgExpectedRefOrIn, "y").WithArguments("1").WithLocation(13, 14) }; var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warnings1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warnings2) - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warnings1.Concat(warnings2).ToArray()); + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warnings1).EmitToImageReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: """ M1 1.1 M1 2.2 @@ -3104,6 +3107,7 @@ M2 3.3 M2 3.3 M2 3.3 """); + verifier.VerifyDiagnostics(fromMetadata ? warnings2 : warnings1.Concat(warnings2).ToArray()); verifier.VerifyIL("D.M3", """ { // Code size 20 (0x14) @@ -3192,9 +3196,10 @@ class D } """; var comp = fromMetadata - ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe).VerifyDiagnostics() - : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe).VerifyDiagnostics(warning1); + ? CreateCompilation(source2, new[] { CreateCompilation(source1).VerifyDiagnostics(warning1).EmitToImageReference() }, options: TestOptions.ReleaseExe) + : CreateCompilation(new[] { source1, source2 }, options: TestOptions.ReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: "100"); + verifier.VerifyDiagnostics(fromMetadata ? Array.Empty() : new[] { warning1 }); verifier.VerifyIL("D.Main", """ { // Code size 17 (0x11)