diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 4d01fb83c4289c81543b0693ecf80fcb1244a642..d254744d1d8f850f3c5a3c21361b429ec1ffd43b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -438,8 +438,6 @@ internal bool CheckValueKind(SyntaxNode node, BoundExpression expr, BindValueKin return CheckLocalValueKind(node, local, valueKind, checkingReceiver, diagnostics); case BoundKind.ThisReference: - var thisref = (BoundThisReference)expr; - // `this` is never ref assignable if (RequiresRefAssignableVariable(valueKind)) { @@ -457,14 +455,12 @@ internal bool CheckValueKind(SyntaxNode node, BoundExpression expr, BindValueKin // SPEC: of a struct, it is classified as a variable. // Note: RValueOnly is checked at the beginning of this method. Since we are here we need more than readable. - //"this" is readonly in members of readonly structs, unless we are in a constructor. - if (!thisref.Type.IsValueType || - (RequiresAssignableVariable(valueKind) && - thisref.Type.IsReadOnly && - (this.ContainingMemberOrLambda as MethodSymbol)?.MethodKind != MethodKind.Constructor)) + // "this" is readonly in members marked "readonly" and in members of readonly structs, unless we are in a constructor. + var isValueType = ((BoundThisReference)expr).Type.IsValueType; + if (!isValueType || (RequiresAssignableVariable(valueKind) && (this.ContainingMemberOrLambda as MethodSymbol)?.IsEffectivelyReadOnly == true)) { // CONSIDER: the Dev10 name has angle brackets (i.e. "") - Error(diagnostics, GetThisLvalueError(valueKind), node, ThisParameterSymbol.SymbolName); + Error(diagnostics, GetThisLvalueError(valueKind, isValueType), node, ThisParameterSymbol.SymbolName); return false; } @@ -994,7 +990,8 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV ReportDiagnosticsIfObsolete(diagnostics, setMethod, node, receiver?.Kind == BoundKind.BaseReference); - if (RequiresVariableReceiver(receiver, setMethod) && !CheckIsValidReceiverForVariable(node, receiver, BindValueKind.Assignable, diagnostics)) + var setValueKind = setMethod.IsEffectivelyReadOnly ? BindValueKind.RValue : BindValueKind.Assignable; + if (RequiresVariableReceiver(receiver, setMethod) && !CheckIsValidReceiverForVariable(node, receiver, setValueKind, diagnostics)) { return false; } @@ -1041,6 +1038,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV return false; } + CheckImplicitThisCopyInReadOnlyMember(receiver, getMethod, diagnostics); ReportDiagnosticsIfObsolete(diagnostics, getMethod, node, receiver?.Kind == BoundKind.BaseReference); } } @@ -1518,7 +1516,7 @@ private static void ReportReadonlyLocalError(SyntaxNode node, LocalSymbol local, Error(diagnostics, ReadOnlyLocalErrors[index], node, local, cause.Localize()); } - static private ErrorCode GetThisLvalueError(BindValueKind kind) + static private ErrorCode GetThisLvalueError(BindValueKind kind, bool isValueType) { switch (kind) { @@ -1533,7 +1531,7 @@ static private ErrorCode GetThisLvalueError(BindValueKind kind) return ErrorCode.ERR_InvalidAddrOp; case BindValueKind.IncrementDecrement: - return ErrorCode.ERR_IncrementLvalueExpected; + return isValueType ? ErrorCode.ERR_AssgReadonlyLocal : ErrorCode.ERR_IncrementLvalueExpected; case BindValueKind.RefReturn: case BindValueKind.ReadonlyRef: @@ -2969,7 +2967,7 @@ internal enum AddressKind if (!IsAnyReadOnly(addressKind) && method.IsEffectivelyReadOnly) { - return method.MethodKind == MethodKind.Constructor; + return false; } return true; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index c347b23f8d876e902026f68ff0421ab111a45ab0..808d376abf7b2f31c80764a8418f54909b288d2c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -142,6 +142,11 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia checkedVariables, out conversion); + if (conversion.Method != null) + { + CheckImplicitThisCopyInReadOnlyMember(boundRHS, conversion.Method, diagnostics); + } + FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 28095859578bb3e784862ec11377a380b0678829..56e2c1284617f15906ce98da09553c9aa1d8d7ad 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -5775,6 +5775,9 @@ private bool IsUsingAliasInScope(string name) } else if ((object)leftType != null) { + // NB: We don't know if we really only need RValue access, or if we are actually + // passing the receiver implicitly by ref (e.g. in a struct instance method invocation). + // These checks occur later. boundLeft = CheckValue(boundLeft, BindValueKind.RValue, diagnostics); return BindInstanceMemberAccess(node, right, boundLeft, rightName, rightArity, typeArgumentsSyntax, typeArguments, invoked, indexed, diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 2d407320a224743aa3d16b1d5f823b241b5ec34d..e8ce99e6c877f6dd86ff53180293505987cfcd0c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1010,6 +1010,8 @@ private void CheckRestrictedTypeReceiver(BoundExpression expression, Compilation // (i.e. the first argument, if invokedAsExtensionMethod). var gotError = MemberGroupFinalValidation(receiver, method, expression, diagnostics, invokedAsExtensionMethod); + CheckImplicitThisCopyInReadOnlyMember(receiver, method, diagnostics); + if (invokedAsExtensionMethod) { BoundExpression receiverArgument = analyzedArguments.Argument(0); @@ -1135,30 +1137,27 @@ private void CheckRestrictedTypeReceiver(BoundExpression expression, Compilation argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, binderOpt: this, type: returnType, hasErrors: gotError); } - private bool IsBindingModuleLevelAttribute() + /// + /// Returns false if an implicit 'this' copy will occur due to an instance member invocation in a readonly member. + /// + internal bool CheckImplicitThisCopyInReadOnlyMember(BoundExpression receiver, MethodSymbol method, DiagnosticBag diagnostics) { - if ((this.Flags & BinderFlags.InContextualAttributeBinder) == 0) + // For now we are warning only in implicit copy scenarios that are only possible with readonly members. + // Eventually we will warn on implicit value copies in more scenarios. See https://github.com/dotnet/roslyn/issues/33968. + if (receiver is BoundThisReference && + receiver.Type.IsValueType && + ContainingMemberOrLambda is MethodSymbol containingMethod && + containingMethod.IsEffectivelyReadOnly && + // Ignore calls to base members. + TypeSymbol.Equals(containingMethod.ContainingType, method.ContainingType, TypeCompareKind.ConsiderEverything) && + !method.IsEffectivelyReadOnly && + !method.IsStatic) { + Error(diagnostics, ErrorCode.WRN_ImplicitCopyInReadOnlyMember, receiver.Syntax, method.Name, ThisParameterSymbol.SymbolName); return false; } - var current = this; - - do - { - var contextualAttributeBinder = current as ContextualAttributeBinder; - - if (contextualAttributeBinder != null) - { - return (object)contextualAttributeBinder.AttributeTarget != null && - contextualAttributeBinder.AttributeTarget.Kind == SymbolKind.NetModule; - } - - current = current.Next; - } - while (current != null); - - throw ExceptionUtilities.Unreachable; + return true; } /// Invocation syntax node. diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index f804fcbda8cce258524ff4ad9774b79ae54dfcb7..c598dbda47b2d432cb83b9d03ca14fac3a438fa2 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -207,6 +207,10 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, AwaitableInfo awaitInfo = null; MethodSymbol getEnumeratorMethod = builder.GetEnumeratorMethod; + if (getEnumeratorMethod != null) + { + originalBinder.CheckImplicitThisCopyInReadOnlyMember(collectionExpr, getEnumeratorMethod, diagnostics); + } if (IsAsync) { var placeholder = new BoundAwaitableValuePlaceholder(_syntax.Expression, builder.MoveNextMethod?.ReturnType ?? CreateErrorType()); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index dee1627254694516173dd39e5563bdc533b8bff3..03c8977d578f04701068028172c50866c0877104 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.CSharp { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class CSharpResources { @@ -14355,6 +14355,24 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'.. + /// + internal static string WRN_ImplicitCopyInReadOnlyMember { + get { + return ResourceManager.GetString("WRN_ImplicitCopyInReadOnlyMember", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Call to non-readonly member from a 'readonly' member results in an implicit copy.. + /// + internal static string WRN_ImplicitCopyInReadOnlyMember_Title { + get { + return ResourceManager.GetString("WRN_ImplicitCopyInReadOnlyMember_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Assignment in conditional expression is always constant; did you mean to use == instead of = ?. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 0962f512fb8f806674ec845b5b2689d430110abb..a3564a73c65c8d24752547b7e3f38699d0f87d30 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5421,6 +5421,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A null literal introduces a null value for a type parameter. + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Argument of type '{0}' cannot be used for parameter '{2}' of type '{1}' in '{3}' due to differences in the nullability of reference types. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 14282f970663dec690744a83d456fc2162f3ebf1..c7f0b2ba60340c5bf5f84da468e9668a1cddb354 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1683,6 +1683,7 @@ internal enum ErrorCode ERR_FeatureInPreview = 8652, WRN_DefaultExpressionMayIntroduceNullT = 8653, WRN_NullLiteralMayIntroduceNullT = 8654, + WRN_ImplicitCopyInReadOnlyMember = 8655, #endregion diagnostics introduced for C# 8.0 diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 272aace97be355c26e39fef47c49df9667fa790e..f16ee76b3f011971e9c10ecd660991916766ac4c 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -404,6 +404,7 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_NullLiteralMayIntroduceNullT: case ErrorCode.WRN_ConditionalAccessMayReturnNull: case ErrorCode.WRN_AsOperatorMayReturnNull: + case ErrorCode.WRN_ImplicitCopyInReadOnlyMember: return 1; default: return 0; diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 13ecfb21cbdea834873018f78022d53541455945..6dc84176087227fa99d85bd912345b791d6511a6 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -222,6 +222,7 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList: case ErrorCode.WRN_DefaultExpressionMayIntroduceNullT: case ErrorCode.WRN_NullLiteralMayIntroduceNullT: + case ErrorCode.WRN_ImplicitCopyInReadOnlyMember: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index ce9de4c231657b9c0e4f9d49aebf2a9dfa62a7ac..0de3356fd12254a5eda0bf9af71dc6487f9a4b6f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -315,7 +315,7 @@ internal virtual bool IsExplicitInterfaceImplementation /// Indicates whether the method is effectively readonly, /// by either the method or the containing type being marked readonly. /// - internal bool IsEffectivelyReadOnly => IsDeclaredReadOnly || ContainingType?.IsReadOnly == true; + internal bool IsEffectivelyReadOnly => (IsDeclaredReadOnly || ContainingType?.IsReadOnly == true) && MethodKind != MethodKind.Constructor; /// /// Returns interface methods explicitly implemented by this method. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 8a6174acd8f937142822369d1eb332f85327dd04..50a91a57b5ab402afc7566264dede75ab8aff604 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1262,6 +1262,16 @@ Po #pragma warning safeonly byl očekáván typ s možnou hodnotou null. + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. Název „_“ odkazuje na typ {0}, ne vzor discard. Použijte „@_“ pro tento typ nebo „var _“ pro zahození. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index d31f7ac0226b26296d9a8f04acb8239242d19177..1a703aee249e8dfe94356c7e75fcf788c213e98e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1262,6 +1262,16 @@ Nach "#pragma warning safeonly" Nullable erwartet + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. Der Name "_" verweist auf den Typ "{0}", nicht auf das discard-Muster. Verwenden Sie "@_" für den Typ oder "var _" zum Verwerfen. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index c25089375097db9278118c700844fcf10099cfff..a4b2c7da9b0ea6f4bceb7f3748dd4bca13a0f961 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1264,6 +1264,16 @@ Tipo que acepta valores NULL esperado después de advertencia de #pragma "safeonly" + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. El nombre "_" hace referencia al tipo "{0}", no al patrón de descarte. Use "@_" para el tipo o "var _" para el descarte. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index be69e544854b145a7ca3b52e41cbf1380ca31cda..91f7d5afe3696b39122e0490cbe1947589f2a4d5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1263,6 +1263,16 @@ Nullable attendu après un avertissement #pragma safeonly + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. Le nom '_' fait référence au type '{0}', pas au modèle d'abandon. Utilisez '@_' pour le type, ou 'var _' pour abandonner. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 14067aa00e44ea3bce595e1b5f52c24856411c83..377e721ae731d9dcc2cd42d985715c43f3b21a79 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1262,6 +1262,16 @@ Dopo l'avviso della direttiva #pragma safeonly è previsto nullable + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. Il nome '_' fa riferimento al tipo '{0}' e non al criterio di eliminazione. Usare '@_' per il tipo oppure 'var _' per eliminare. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index f5c7a11d048216038a06cf5f3f0134fef1b50618..476e88ad33c5168eaa57c8d6ed352fe75dfb746e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1262,6 +1262,16 @@ #pragma warning safeonly の後に Null 許容が必要です + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. 名前 '_' は、破棄パターンではなく型 '{0}' を参照しています。型の場合は '@_' を、破棄する場合は 'var _' をご使用ください。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index e662123204999b18bec3fc122abed2e6afa0a7eb..ef3b1bf8425c439e926aa0f50e1f3af9ddbe89c3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1262,6 +1262,16 @@ #pragma warning safeonly 뒤에 nullable이 필요합니다. + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. '_' 이름은 '{0}' 형식을 참조하며, 무시 패턴은 참조하지 않습니다. 형식에 '@_'을 사용하거나, 무시하려면 'var _'을 사용하세요. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index fd0b96420bf61f76155647335863b0729c6b5df5..14610b42aee9257c375fad17cefa268c69aa335d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1262,6 +1262,16 @@ Oczekiwano elementu dopuszczającego wartość null po dyrektywne #pragma warning safeonly + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. Nazwa „_” odwołuje się do typu „{0}”, a nie do wzorca odrzucania. Użyj elementu „@_” aby odwołać się do typu, lub użyj elementu „var _”, aby odrzucić wartość. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index b75f6042790c598d0db6c9a8c71f4c2e0e54cc0f..d3620f1c7ef34492b77c46852d81ec1fbc67ab60 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1262,6 +1262,16 @@ Esperado anulável após #pragma warning safeonly + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. O nome '_' refere-se ao tipo '{0}', não ao padrão de descarte. Use '@_' para o tipo ou 'var _' para descarte. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 0a3732915a9456351a38c4df4eb740f879898473..0f092e34879fb36a8b90e8b8c892e517731ed76f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1262,6 +1262,16 @@ После #pragma warning safeonly ожидается тип, допускающий значения NULL. + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. Имя "_" ссылается на тип "{0}", а не на шаблон отмены. Используйте "@_" в качестве типа или "var _" для отмены. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 6d905ac3f7fa736d235130784847ed6293884225..0677b5eb4d4034daeefc4ab7b5153073f20f609f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1263,6 +1263,16 @@ #pragma uyarısı safeonly sonrasında boş değer atanabilir beklendi + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. '_' adı atma desenine değil '{0}' türüne başvuruyor. Tür için '@_' adını veya atmak için 'var _' adını kullanın. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 28a8ba7489d9933f135fca8c13a0a5e38900d32b..fceff8fe7622086a2041587ff932bb9e39fa15ba 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1263,6 +1263,16 @@ 在 #pragma warning safeonly 之后应是可为 null + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. 名称 "_" 引用类型“{0}”,而不引用放弃模式。对于类型,请使用 "@_";对于弃用,请使用 "var _"。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index e03d85f48ed331cec3e8803f8fa11c17a65b919a..7c813c36cfd215195405741e04254668d7dc409c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1262,6 +1262,16 @@ 在#pragma warning safeonly 後面必須是可為 Null 的項目 + + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + Call to non-readonly member '{0}' from a 'readonly' member results in an implicit copy of '{1}'. + + + + Call to non-readonly member from a 'readonly' member results in an implicit copy. + Call to non-readonly member from a 'readonly' member results in an implicit copy. + + The name '_' refers to the type '{0}', not the discard pattern. Use '@_' for the type, or 'var _' to discard. 名稱 '_' 參考類型 '{0}',而非捨棄模式。請為類型使用 '@_',或使用 'var _' 捨棄。 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyStructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyStructTests.cs index d3aa569e79ef29ee90bc42c3952c9f86a81be317..b04593c8c46402b3d33a5271037f40bb43ed8d17 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyStructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyStructTests.cs @@ -1342,8 +1342,12 @@ static void Main() "; var verifier = CompileAndVerify(csharp, expectedOutput: "123"); - // PROTOTYPE: should warn about copying 'this' when calling M2 - verifier.VerifyDiagnostics(); + + verifier.VerifyDiagnostics( + // (9,9): warning CS8655: Call to non-readonly member 'M2' from a 'readonly' member results in an implicit copy of 'this'. + // M2(); + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "M2").WithArguments("M2", "this").WithLocation(9, 9)); + verifier.VerifyIL("S.M1", @" { // Code size 51 (0x33) @@ -1404,7 +1408,7 @@ static void Main() "; var verifier = CompileAndVerify(csharp, expectedOutput: "123"); - // PROTOTYPE: should warn about copying 's' when calling M2 + // should warn about calling s.M2 in warning wave (see https://github.com/dotnet/roslyn/issues/33968) verifier.VerifyDiagnostics(); verifier.VerifyIL("S.M1", @" { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs index 4dc213dea2dc3bcad68c0e728feb0149bbd3e0cd..2778ffb82d56182015feb0917401fc501c5b59cf 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ReadOnlyStructsTests.cs @@ -57,6 +57,8 @@ public void WriteableInstanceFieldsInRoStructs() void AssignField() { + // error + this = default; // error this.x = 1; @@ -71,9 +73,11 @@ void AssignField() // int x; Diagnostic(ErrorCode.ERR_FieldsInRoStruct, "x").WithLocation(11, 9), // (16,9): error CS1604: Cannot assign to 'this' because it is read-only + // this = default; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "this").WithArguments("this").WithLocation(16, 9), + // (18,9): error CS1604: Cannot assign to 'this' because it is read-only // this.x = 1; - Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "this.x").WithArguments("this").WithLocation(16, 9) - ); + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "this.x").WithArguments("this").WithLocation(18, 9)); } [Fact()] @@ -166,6 +170,8 @@ public struct A readonly void AssignField() { + // error + this = default; // error this.x = 1; @@ -177,8 +183,14 @@ public struct A } } "; - // PROTOTYPE: should give ERR_AssgReadonlyLocal - CreateCompilation(text).VerifyDiagnostics(); + CreateCompilation(text).VerifyDiagnostics( + // (11,9): error CS1604: Cannot assign to 'this' because it is read-only + // this = default; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "this").WithArguments("this").WithLocation(11, 9), + // PROTOTYPE: The error message should state the specific thing being assigned and more specifically why it can't be assigned. + // (13,9): error CS1604: Cannot assign to 'this' because it is read-only + // this.x = 1; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "this.x").WithArguments("this").WithLocation(13, 9)); } [Fact] @@ -236,8 +248,36 @@ public void M3() } "; var comp = CreateCompilation(csharp); - // PROTOTYPE: should give ERR_RefReadonlyLocal - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (15,16): error CS1605: Cannot use 'this' as a ref or out value because it is read-only + // M1(ref this); // error + Diagnostic(ErrorCode.ERR_RefReadonlyLocal, "this").WithArguments("this").WithLocation(15, 16)); + } + + [Fact] + public void ReadOnlyMethod_RefLocal() + { + var csharp = @" +public struct S +{ + public void M1() + { + ref S s1 = ref this; // ok + ref readonly S s2 = ref this; // ok + } + + public readonly void M2() + { + ref S s1 = ref this; // error + ref readonly S s2 = ref this; // ok + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (12,24): error CS1605: Cannot use 'this' as a ref or out value because it is read-only + // ref S s1 = ref this; // error + Diagnostic(ErrorCode.ERR_RefReadonlyLocal, "this").WithArguments("this").WithLocation(12, 24)); } [Fact] @@ -270,8 +310,10 @@ public void M3() } "; var comp = CreateCompilation(csharp); - // PROTOTYPE: should give ERR_RefReadonlyLocal - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (21,16): error CS1605: Cannot use 'this' as a ref or out value because it is read-only + // M1(ref f2); // error + Diagnostic(ErrorCode.ERR_RefReadonlyLocal, "f2").WithArguments("this").WithLocation(21, 16)); } [Fact] @@ -289,6 +331,211 @@ public struct S comp.VerifyDiagnostics(); } + [Fact] + public void ReadOnlyMethod_CallBaseMethod() + { + var csharp = @" +public struct S +{ + readonly void M() + { + // no warnings for calls to base members + GetType(); + ToString(); + GetHashCode(); + Equals(null); + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ReadOnlyMethod_OverrideBaseMethod() + { + var csharp = @" +public struct S1 +{ + // note: GetType can't be overridden + public override string ToString() => throw null; + public override int GetHashCode() => throw null; + public override bool Equals(object o) => throw null; + + readonly void M() + { + // should warn--non-readonly invocation + ToString(); + GetHashCode(); + Equals(null); + + // ok + base.ToString(); + base.GetHashCode(); + base.Equals(null); + } +} + +public struct S2 +{ + public readonly override string ToString() => throw null; + public readonly override int GetHashCode() => throw null; + public readonly override bool Equals(object o) => throw null; + + readonly void M() + { + // no warnings + ToString(); + GetHashCode(); + Equals(null); + + base.ToString(); + base.GetHashCode(); + base.Equals(null); + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + + // (12,9): warning CS8655: Call to non-readonly member 'ToString' from a 'readonly' member results in an implicit copy of 'this'. + // ToString(); + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "ToString").WithArguments("ToString", "this").WithLocation(12, 9), + // (13,9): warning CS8655: Call to non-readonly member 'GetHashCode' from a 'readonly' member results in an implicit copy of 'this'. + // GetHashCode(); + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "GetHashCode").WithArguments("GetHashCode", "this").WithLocation(13, 9), + // (14,9): warning CS8655: Call to non-readonly member 'Equals' from a 'readonly' member results in an implicit copy of 'this'. + // Equals(null); + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "Equals").WithArguments("Equals", "this").WithLocation(14, 9)); + } + + [Fact] + public void ReadOnlyMethod_Override_AssignThis() + { + var csharp = @" +public struct S +{ + public int i; + + // error + public readonly override string ToString() => (i++).ToString(); + public readonly override int GetHashCode() => (i++).GetHashCode(); + public readonly override bool Equals(object o) => (i++).Equals(o); +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (7,52): error CS1604: Cannot assign to 'this' because it is read-only + // public readonly override string ToString() => (i++).ToString(); + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(7, 52), + // (8,52): error CS1604: Cannot assign to 'this' because it is read-only + // public readonly override int GetHashCode() => (i++).GetHashCode(); + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(8, 52), + // (9,56): error CS1604: Cannot assign to 'this' because it is read-only + // public readonly override bool Equals(object o) => (i++).Equals(o); + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(9, 56)); + } + + [Fact] + public void ReadOnlyMethod_GetPinnableReference() + { + var csharp = @" +public unsafe struct S1 +{ + static int i = 0; + ref int GetPinnableReference() => ref i; + void M1() + { + fixed (int *i = this) {} // ok + } + readonly void M2() + { + fixed (int *i = this) {} // warn + } +} + +public unsafe struct S2 +{ + static int i = 0; + readonly ref int GetPinnableReference() => ref i; + void M1() + { + fixed (int *i = this) {} // ok + } + readonly void M2() + { + fixed (int *i = this) {} // ok + } +} +"; + var comp = CreateCompilation(csharp, options: TestOptions.UnsafeReleaseDll); + comp.VerifyDiagnostics( + // (12,25): warning CS8655: Call to non-readonly member 'GetPinnableReference' from a 'readonly' member results in an implicit copy of 'this'. + // fixed (int *i = this) {} // warn + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "this").WithArguments("GetPinnableReference", "this").WithLocation(12, 25)); + } + + [Fact] + public void ReadOnlyMethod_Iterator() + { + var csharp = @" +using System.Collections.Generic; + +public struct S +{ + public int i; + public readonly IEnumerable M1() + { + yield return i; + yield return i+1; + } + + public readonly IEnumerable M2() + { + yield return i; + i++; + yield return i; + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (16,9): error CS1604: Cannot assign to 'this' because it is read-only + // i++; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(16, 9)); + } + + [Fact] + public void ReadOnlyMethod_Async() + { + var csharp = @" +using System.Threading.Tasks; + +public struct S +{ + public int i; + public readonly async Task M1() + { + await Task.Delay(1); + return i; + } + + public readonly async Task M2() + { + await Task.Delay(1); + i++; + await Task.Delay(1); + return i; + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (16,9): error CS1604: Cannot assign to 'this' because it is read-only + // i++; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(16, 9)); + } + [Fact] public void ReadOnlyAccessor_CallNormalMethod() { @@ -328,8 +575,203 @@ static void Main() "; var verifier = CompileAndVerify(csharp, expectedOutput: "123"); - // PROTOTYPE: should warn about copying 'this' when calling M - verifier.VerifyDiagnostics(); + verifier.VerifyDiagnostics( + // (11,13): warning CS8655: Call to non-readonly member 'M' from a 'readonly' member results in an implicit copy of 'this'. + // M(); + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "M").WithArguments("M", "this").WithLocation(11, 13)); + } + + [Fact] + public void ReadOnlyAccessor_CallNormalGetAccessor() + { + var csharp = @" +public struct S +{ + public int i; + + public int P1 + { + readonly get + { + // should create local copy + _ = P2; // warning + System.Console.Write(i); + + // explicit local copy, no warning + var copy = this; + _ = copy.P2; // ok + System.Console.Write(copy.i); + + return i; + } + } + + int P2 => i = 23; + + static void Main() + { + var s = new S { i = 1 }; + _ = s.P1; + } +} +"; + + var verifier = CompileAndVerify(csharp, expectedOutput: "123"); + verifier.VerifyDiagnostics( + // (11,17): warning CS8655: Call to non-readonly member 'get_P2' from a 'readonly' member results in an implicit copy of 'this'. + // _ = P2; + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "P2").WithArguments("get_P2", "this").WithLocation(11, 17)); + } + + [Fact] + public void ReadOnlyMethod_CallSetAccessor() + { + var csharp = @" +public class C +{ + public int P { get; set; } +} + +public struct S +{ + C c; + public S(C c) + { + this.c = c; + P2 = 0; + } + + static int P1 { get; set; } + int P2 { get; set; } + readonly int P3 + { + get => 42; + set {} + } + + public readonly void M() + { + P1 = 1; // ok + P2 = 2; // error + P3 = 2; // ok + c.P = 42; // ok + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (27,9): error CS1604: Cannot assign to 'this' because it is read-only + // P2 = 2; // error + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "P2").WithArguments("this").WithLocation(27, 9)); + } + + [Fact] + public void ReadOnlyMethod_IncrementOperator() + { + var csharp = @" +public struct S +{ + public int i; + public readonly void M() + { + i++; + i--; + ++i; + --i; + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (7,9): error CS1604: Cannot assign to 'this' because it is read-only + // i++; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(7, 9), + // (8,9): error CS1604: Cannot assign to 'this' because it is read-only + // i--; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(8, 9), + // (9,11): error CS1604: Cannot assign to 'this' because it is read-only + // ++i; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(9, 11), + // (10,11): error CS1604: Cannot assign to 'this' because it is read-only + // --i; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(10, 11)); + } + + [Fact] + public void ReadOnlyMethod_CompoundAssignmentOperator() + { + var csharp = @" +public struct S +{ + public int i; + public readonly void M() + { + i += 1; + i -= 1; + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (7,9): error CS1604: Cannot assign to 'this' because it is read-only + // i += 1; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(7, 9), + // (8,9): error CS1604: Cannot assign to 'this' because it is read-only + // i -= 1; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(8, 9)); + } + + [Fact] + public void ReadOnlyMethod_AddRemoveEventHandlers() + { + var csharp = @" +using System; +public struct S +{ + public event Action E; + + public readonly void M() + { + E += handler; + E -= handler; + E = handler; + E(new EventArgs()); + + void handler(EventArgs args) { } + } +} +"; + // should warn about E += handler in warning wave (see https://github.com/dotnet/roslyn/issues/33968) + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (11,9): error CS1604: Cannot assign to 'this' because it is read-only + // E = handler; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "E").WithArguments("this").WithLocation(11, 9)); + } + + [Fact] + public void ReadOnlyEventAccessors() + { + var csharp = @" +using System; +public struct S +{ + public int i; + public readonly event Action E + { + add { i++; } + remove { i--; } + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (8,15): error CS1604: Cannot assign to 'this' because it is read-only + // add { i++; } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(8, 15), + // (9,18): error CS1604: Cannot assign to 'this' because it is read-only + // remove { i--; } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "i").WithArguments("this").WithLocation(9, 18)); } [Fact] @@ -774,16 +1216,16 @@ public struct S [Fact] public void RefReturningReadOnlyMethod() { - // PROTOTYPE: would be good to add some more mutation here - // as well as expected diagnostics once that part of the feature is ready. var csharp = @" public struct S { private static int f1; - public readonly ref int M1() => ref f1; + public readonly ref int M1_1() => ref f1; // ok + public readonly ref readonly int M1_2() => ref f1; // ok private static readonly int f2; - public readonly ref readonly int M2() => ref f2; + public readonly ref int M2_1() => ref f2; // error + public readonly ref readonly int M2_2() => ref f2; // ok private static readonly int f3; public ref readonly int M3() @@ -794,7 +1236,10 @@ public struct S } "; var comp = CreateCompilation(csharp); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (9,43): error CS8161: A static readonly field cannot be returned by writable reference + // public readonly ref int M2_1() => ref f2; // error + Diagnostic(ErrorCode.ERR_RefReturnReadonlyStatic, "f2").WithLocation(9, 43)); } [Fact] @@ -817,6 +1262,255 @@ public struct S Diagnostic(ErrorCode.ERR_BadMemberFlag, "S").WithArguments("readonly").WithLocation(5, 21)); } + [Fact] + public void ReadOnlyStruct_Constructor() + { + var csharp = @" +public readonly struct S +{ + public readonly int i; + public S(int i) + { + this.i = i; // ok + M(ref this); // ok + } + public static void M(ref S s) + { + s.i = 42; // error + s = default; // ok + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (12,9): error CS0191: A readonly field cannot be assigned to (except in a constructor or a variable initializer) + // s.i = 42; // error + Diagnostic(ErrorCode.ERR_AssgReadonly, "s.i").WithLocation(12, 9)); + } + + [Fact] + public void ReadOnlyMethod_GetEnumerator() + { + var csharp = @" +using System.Collections; + +public struct S1 +{ + public IEnumerator GetEnumerator() => throw null; + void M1() + { + foreach (var x in this) {} // ok + } + readonly void M2() + { + foreach (var x in this) {} // warning-- implicit copy + } +} + +public struct S2 +{ + public readonly IEnumerator GetEnumerator() => throw null; + void M1() + { + foreach (var x in this) {} // ok + } + readonly void M2() + { + foreach (var x in this) {} // ok + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (13,27): warning CS8655: Call to non-readonly member 'GetEnumerator' from a 'readonly' member results in an implicit copy of 'this'. + // foreach (var x in this) {} // warning-- implicit copy + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "this").WithArguments("GetEnumerator", "this").WithLocation(13, 27)); + } + + [Fact] + public void ReadOnlyMethod_GetEnumerator_MethodMissing() + { + var csharp = @" +public struct S1 +{ + readonly void M2() + { + foreach (var x in this) {} + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (6,27): error CS1579: foreach statement cannot operate on variables of type 'S1' because 'S1' does not contain a public instance definition for 'GetEnumerator' + // foreach (var x in this) {} + Diagnostic(ErrorCode.ERR_ForEachMissingMember, "this").WithArguments("S1", "GetEnumerator").WithLocation(6, 27)); + } + + [Fact] + public void ReadOnlyMethod_AsyncStreams() + { + var csharp = @" +using System.Threading.Tasks; +using System.Collections.Generic; + +public struct S1 +{ + public IAsyncEnumerator GetAsyncEnumerator() => throw null; + + public async Task M1() + { + await foreach (var x in this) {} + } + + public readonly async Task M2() + { + await foreach (var x in this) {} // warn + } +} + +public struct S2 +{ + public readonly IAsyncEnumerator GetAsyncEnumerator() => throw null; + + public async Task M1() + { + await foreach (var x in this) {} + } + + public readonly async Task M2() + { + await foreach (var x in this) {} // ok + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { csharp, AsyncStreamsTypes }); + comp.VerifyDiagnostics( + // (16,33): warning CS8655: Call to non-readonly member 'GetAsyncEnumerator' from a 'readonly' member results in an implicit copy of 'this'. + // await foreach (var x in this) {} // warn + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "this").WithArguments("GetAsyncEnumerator", "this").WithLocation(16, 33)); + } + + [Fact] + public void ReadOnlyMethod_Using() + { + // 'using' results in a boxing conversion when the struct implements 'IDisposable'. + // Boxing conversions are out of scope of the implicit copy warning. + // 'await using' can't be used with ref structs, so implicity copy warnings can't be produced in that scenario. + var csharp = @" +public ref struct S1 +{ + public void Dispose() {} + + void M1() + { + using (this) { } // ok + } + + readonly void M2() + { + using (this) { } // should warn + } +} + +public ref struct S2 +{ + public readonly void Dispose() {} + + void M1() + { + using (this) { } // ok + } + + readonly void M2() + { + using (this) { } // ok + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (13,16): warning CS8655: Call to non-readonly member 'Dispose' from a 'readonly' member results in an implicit copy of 'this'. + // using (this) { } // should warn + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "this").WithArguments("Dispose", "this").WithLocation(13, 16)); + } + + [Fact] + public void ReadOnlyMethod_Deconstruct() + { + var csharp = @" +public struct S1 +{ + void M1() + { + var (x, y) = this; // ok + } + + readonly void M2() + { + var (x, y) = this; // should warn + } + + public void Deconstruct(out int x, out int y) + { + x = 42; + y = 123; + } +} + +public struct S2 +{ + void M1() + { + var (x, y) = this; // ok + } + + readonly void M2() + { + var (x, y) = this; // ok + } + + public readonly void Deconstruct(out int x, out int y) + { + x = 42; + y = 123; + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (11,22): warning CS8655: Call to non-readonly member 'Deconstruct' from a 'readonly' member results in an implicit copy of 'this'. + // var (x, y) = this; // should warn + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "this").WithArguments("Deconstruct", "this").WithLocation(11, 22)); + } + + [Fact] + public void ReadOnlyMethod_Deconstruct_MethodMissing() + { + var csharp = @" +public struct S2 +{ + readonly void M1() + { + var (x, y) = this; // error + } +} +"; + var comp = CreateCompilation(csharp); + comp.VerifyDiagnostics( + // (6,14): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'x'. + // var (x, y) = this; // error + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "x").WithArguments("x").WithLocation(6, 14), + // (6,17): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'y'. + // var (x, y) = this; // error + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "y").WithArguments("y").WithLocation(6, 17), + // (6,22): error CS1061: 'S2' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'S2' could be found (are you missing a using directive or an assembly reference?) + // var (x, y) = this; // error + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "this").WithArguments("S2", "Deconstruct").WithLocation(6, 22), + // (6,22): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'S2', with 2 out parameters and a void return type. + // var (x, y) = this; // error + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "this").WithArguments("S2", "2").WithLocation(6, 22)); + } + [Fact] public void ReadOnlyDestructor() { diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index f8d2ef83d1becec0e5ce1a93ec9b98f6b9bc3607..e6b079e176a49b90e229323667da683e95673c8d 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -301,6 +301,7 @@ public void WarningLevel_2() case ErrorCode.WRN_AsOperatorMayReturnNull: case ErrorCode.WRN_DefaultExpressionMayIntroduceNullT: case ErrorCode.WRN_NullLiteralMayIntroduceNullT: + case ErrorCode.WRN_ImplicitCopyInReadOnlyMember: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_InvalidVersionFormat: