From 98c9d917c0bb38d313215fe61c497f1853f1cd2a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 22 Oct 2024 12:23:14 -0400 Subject: [PATCH 01/14] Include attribute related text from #700 Include all the nullable analysis attributes in the spec. This will be edited heavily and shrunk down to a smaller set of normative text. --- standard/attributes.md | 273 ++++++++++++++++++++++++++++++++++- standard/standard-library.md | 80 ++++++++++ 2 files changed, 352 insertions(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 57c600064..01ff16438 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -519,13 +519,14 @@ Using the terms defined in [§22.4.2](attributes.md#2242-compilation-of-an-attri ### 22.5.1 General -A small number of attributes affect the language in some way. These attributes include: +A number of attributes affect the language in some way. These attributes include: - `System.AttributeUsageAttribute` ([§22.5.2](attributes.md#2252-the-attributeusage-attribute)), which is used to describe the ways in which an attribute class can be used. - `System.Diagnostics.ConditionalAttribute` ([§22.5.3](attributes.md#2253-the-conditional-attribute)), is a multi-use attribute class which is used to define conditional methods and conditional attribute classes. This attribute indicates a condition by testing a conditional compilation symbol. - `System.ObsoleteAttribute` ([§22.5.4](attributes.md#2254-the-obsolete-attribute)), which is used to mark a member as obsolete. - `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§22.5.5](attributes.md#2255-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method. - `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§22.5.6.2](attributes.md#22562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§22.5.6.3](attributes.md#22563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§22.5.6.4](attributes.md#22564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters. +- Nullable static analysis attributes (§Nullable-Analysis-Attributes). An execution environment may provide additional implementation-defined attributes that affect the execution of a C# program. @@ -858,6 +859,276 @@ For invocations that occur within field or event initializers, the member name u For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent. +### §Nullable-Analysis-Attributes Code analysis-attributes + +#### §Nullable-Analysis-Attributes-General General + +Code compiled with both nullable contexts (§Nullable-Contexts) disabled is null oblivious (§Nullabilities-And-Null-States). That means any reference type variable may be null, but null checks aren't required. Once such code is made nullable-aware, those rules change. Reference type variables should never have the null value, and such variables must be checked against null before being dereferenced. + +Some APIs have more complex rules for when variables can or can't be null. In these cases, one or more of the nullable-related attributes described below can be used to express those rules. When user code is compiled in a nullable-enabled context, the compiler is required to warn when that code violates those rules. That is, these attributes help define the nullability contract for an API. + +The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`. + +**Attribute** | **Meaning** +------------------ | ------------------ +`AllowNull` (§The-AllowNull-Attribute) | A non-nullable argument may be null. +`DisallowNull` (§The-DisallowNull-Attribute) | A nullable argument should never be null. +`MaybeNull` (§The-MaybeNull-Attribute) | A non-nullable return value may be null. +`NotNull` (§The-NotNull-Attribute) | A nullable return value will never be null. +`MaybeNullWhen` (§The-MaybeNullWhen-Attribute) | A non-nullable argument may be null when the method returns the specified `bool` value. +`NotNullWhen` (§The-NotNullWhen-Attribute) | A nullable argument won't be null when the method returns the specified `bool` value. +`NotNullIfNotNull` (§The-NotNullIfNotNull-Attribute) | A return value isn't null if the argument for the specified parameter isn't null. +`MemberNotNull` (§The-MemberNotNull-Attribute) | The listed member won't be null when the method returns. +`MemberNotNullWhen` (§The-MemberNotNullWhen-Attribute) | The listed member won't be null when the method returns the specified `bool` value. +`DoesNotReturn` (§The-DoesNotReturn-Attribute) | This method never returns. +`DoesNotReturnIf` (§The-DoesNotReturnIf-Attribute) | This method never returns if the associated `bool` parameter has the specified value. + +#### §The-AllowNull-Attribute The AllowNull attribute + +Specifies that a null value is allowed as an input even if the corresponding type disallows it. + +> *Example*: Consider the following read/write property that never returns `null` because it has a reasonable default value. However, a user can give null to the set accessor to set the property to that default value. +> +> +> ```csharp +> #nullable enable +> public class X +> { +> [AllowNull] +> public string ScreenName +> { +> get => _screenName; +> set => _screenName = value ?? GenerateRandomScreenName(); +> } +> private string _screenName = GenerateRandomScreenName(); +> private static string GenerateRandomScreenName() => ...; +> } +> ``` +> +> Given the following use of that property’s set accessor +> +> ```csharp +> var v = new X(); +> v.ScreenName = null; // without attribute AllowNull, get a warning +> ``` +> +> without the attribute, the compiler is required to generate a warning because the non-nullable-typed property appears to be set to a null value. The presence of the attribute suppresses that warning. *end example* + +#### §The-DisallowNull-Attribute The DisallowNull attribute + +Specifies that a null value is disallowed as an input even if the corresponding type allows it. + +> *Example*: Consider the following property in which null is the default value, but clients can only set it to a non-null value. +> +> +> ```csharp +> #nullable enable +> public class X +> { +> [DisallowNull] +> public string ReviewComment +> { +> get => _comment; +> set => _comment = value ?? throw new ArgumentNullException(nameof(value), +> "Cannot set to null"); +> } +> private string _comment = ""; +> } +> ``` +> +> The get accessor could return the default value of `null`, so the compiler warns that it must be checked before access. Furthermore, it warns callers that, even though it could be null, callers shouldn't explicitly set it to null. *end example* + +#### §The-DoesNotReturn-Attribute The DoesNotReturn attribute + +Specifies that a given method never returns. + +> *Example*: Consider the following: +> +> +> ```csharp +> public class X +> { +> [DoesNotReturn] +> private void FailFast() +> { +> throw new InvalidOperationException(); +> } +> +> public void SetState(object containedField) +> { +> if (!isInitialized) +> { +> FailFast(); +> } +> // unreachable code: +> _field = containedField; +> } +> +> private bool isInitialized = false; +> private object _field; +> } +> ``` +> +> The presence of the attribute helps the compiler in a number of ways. First, the compiler issues a warning if there's a path where the method can exit without throwing an exception. Second, the compiler marks any code after a call to that method as unreachable, until an appropriate catch clause is found. Third, the unreachable code won't affect any null states. *end example* + +#### §The-DoesNotReturnIf-Attribute The DoesNotReturnIf attribute + +Specifies that a given method never returns if the associated `bool` parameter has the specified value. + +> *Example*: Consider the following: +> +> +> ```csharp +> public class X +> { +> private void FailFastIf([DoesNotReturnIf(false)] bool isValid) +> { +> if (!isValid) +> { +> throw new InvalidOperationException(); +> } +> } +> +> public void SetFieldState(object containedField) +> { +> FailFastIf(isInitialized); +> // unreachable code when "isInitialized" is false: +> _field = containedField; +> } +> +> private bool isInitialized = false; +> private object _field; +> } +> ``` +> +> *end example* + +#### §The-MaybeNull-Attribute The MaybeNull attribute + +Specifies that a non-nullable return value may be null. + +> *Example*: Consider the following generic method: +> +> +> ```csharp +> #nullable enable +> public T? Find(IEnumerable sequence, Func predicate) { ... } +> ``` +> +> If `T` is replaced by `string`, `T?` becomes a nullable annotation. If `T` is replaced by `int`, `T?` becomes an `int?`. When `Find` searches an `IEnumerable`, the default return is `null`, but when `Find` searches an `IEnumerable`, the default return is 0. As such, specifying the return type as `T?` isn’t appropriate. However, adding this attribute solves the problem: +> +> +> ```csharp +> #nullable enable +> [return: MaybeNull] +> public T Find(IEnumerable sequence, Func predicate) { ... } +> ``` +> +> The attribute informs callers that the contract implies a non-nullable type, but the return value may actually be `null`. *end example* + +#### §The-MaybeNullWhen-Attribute The MaybeNullWhen attribute + +Specifies that a non-nullable argument may be `null` when the method returns the specified `bool` value. + +#### §The-MemberNotNull-Attribute The MemberNotNull attribute + +Specifies that the given member won't be `null` when the method returns. + +> *Example*: The compiler analyzes constructors and field initializers to make sure that all non-nullable reference fields have been initialized before each constructor returns. However, the compiler doesn't track field assignments through all helper methods. The compiler issues a warning when fields aren't initialized directly in the constructor, but rather in a helper method. This warning is suppressed by applying the `MemberNotNull` attribute to a method declaration and specifying the fields that are initialized to a non-null value in the method. For example, consider the following example: +> +> +> ```csharp +> #nullable enable +> public class Container +> { +> private string _uniqueIdentifier; // must be initialized. +> private string? _optionalMessage; +> +> public Container() +> { +> Helper(); +> } +> +> public Container(string message) +> { +> Helper(); +> _optionalMessage = message; +> } +> +> [MemberNotNull(nameof(_uniqueIdentifier))] +> private void Helper() +> { +> _uniqueIdentifier = DateTime.Now.Ticks.ToString(); +> } +> } +> ``` +> +> Multiple field names may be given as arguments to the attribute’s constructor. *end example* + +#### §The-MemberNotNullWhen-Attribute The MemberNotNullWhen attribute + +Specifies that the listed member won't be `null` when the method returns the specified `bool` value. + +> *Example*: This attribute is like `MemberNotNull` (§The-MemberNotNull-Attribute) except that `MemberNotNullWhen` takes a `bool` argument. `MemberNotNullWhen` is intended for use in situations in which a helper method returns a `bool` indicating whether it initialized fields. *end example* + +#### §The-NotNull-Attribute The NotNull attribute + +Specifies that a nullable return value will never be `null`. + +> *Example*: Consider the following: +> +> +> ```csharp +> #nullable enable +> public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => +> _ = value ?? throw new ArgumentNullException(valueExpression); +> +> public static void LogMessage(string? message) +> { +> ThrowWhenNull(message, nameof(message)); +> Console.WriteLine(message.Length); +> } +> ``` +> +> When null reference types are enabled, method `ThrowWhenNull` compiles without warnings. When that method returns, the `value` argument is guaranteed to be not `null`. However, it's acceptable to call `ThrowWhenNull` with a null reference. *end example* + +#### §The-NotNullIfNotNull-Attribute The NotNullIfNotNull attribute + +Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`. + +> *Example*: Sometimes the null state of a return value depends on the null state of one or more arguments. Such a method will return a non-null value whenever certain arguments aren't `null`. To correctly annotate these methods, add the `NotNullIfNotNull` attribute. Consider the following method: +> +> +> ```csharp +> #nullable enable +> string GetTopLevelDomainFromFullUrl(string url) { ... } +> ``` +> +> If the `url` argument isn't `null`, `null` isn’t returned. When nullable references are enabled, that signature works correctly, provided the API never accepts a null argument. However, if the argument could be null, then return value could also be null. To express that contract correctly, annotate this method as follows: +> +> +> ```csharp +> #nullable enable +> [return: NotNullIfNotNull("url")] +> string? GetTopLevelDomainFromFullUrl(string? url) { ... } +> ``` +> +> *end example* + +#### §The-NotNullWhen-Attribute The NotNullWhen attribute + +Specifies that a nullable argument won't be `null` when the method returns the specified `bool` value. + +> *Example*: The library method `String.IsNullOrEmpty(String)` returns `true` when the argument is `null` or an empty string. It's a form of null-check: Callers don't need to null-check the argument if the method returns `false`. To make a method like this nullable aware, make the parameter type a nullable reference type, and add the NotNullWhen attribute: +> +> +> ```csharp +> #nullable enable +> bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... } +> ``` +> +> *end example* + ## 22.6 Attributes for interoperation For interoperation with other languages, an indexer may be implemented using indexed properties. If no `IndexerName` attribute is present for an indexer, then the name `Item` is used by default. The `IndexerName` attribute enables a developer to override this default and specify a different name. diff --git a/standard/standard-library.md b/standard/standard-library.md index 0810dff78..fbc808b37 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -571,6 +571,86 @@ namespace System.Runtime.CompilerServices public bool IsCompleted { get; } public TResult GetResult(); } + + [System.AttributeUsage(System.AttributeTargets.Field | + System.AttributeTargets.Parameter | System.AttributeTargets.Property, + Inherited=false)] + public sealed class AllowNullAttribute : Attribute + { + public AllowNullAttribute() { } + } + + [System.AttributeUsage(System.AttributeTargets.Field | + System.AttributeTargets.Parameter | System.AttributeTargets.Property, + Inherited=false)] + public sealed class DisallowNullAttribute : Attribute + { + public DisallowNullAttribute() {} + } + + [System.AttributeUsage(System.AttributeTargets.Method, Inherited=false)] + public sealed class DoesNotReturnAttribute : Attribute + { + public DoesNotReturnAttribute() {} + } + + [System.AttributeUsage(System.AttributeTargets.Parameter, Inherited=false)] + public sealed class DoesNotReturnIfAttribute : Attribute + { + public DoesNotReturnIfAttribute(bool parameterValue) {} + } + + [System.AttributeUsage(System.AttributeTargets.Field | + System.AttributeTargets.Parameter | System.AttributeTargets.Property | + System.AttributeTargets.ReturnValue, Inherited=false)] + public sealed class MaybeNullAttribute : Attribute + { + public MaybeNullAttribute() {} + } + + [System.AttributeUsage(System.AttributeTargets.Parameter, Inherited=false)] + public sealed class MaybeNullWhenAttribute : Attribute + { + public MaybeNullWhenAttribute(bool returnValue) {} + } + + [System.AttributeUsage(System.AttributeTargets.Method | + System.AttributeTargets.Property, AllowMultiple=true, Inherited=false)] + public sealed class MemberNotNullAttribute : Attribute + { + public MemberNotNullAttribute(string member) {} + public MemberNotNullAttribute(params string[] members) {} + } + + [System.AttributeUsage(System.AttributeTargets.Method | + System.AttributeTargets.Property, AllowMultiple=true, Inherited=false)] + public sealed class MemberNotNullWhenAttribute : Attribute + { + public MemberNotNullWhenAttribute(bool returnValue, string member) {} + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) {} + } + + [System.AttributeUsage(System.AttributeTargets.Field | + System.AttributeTargets.Parameter | System.AttributeTargets.Property | + System.AttributeTargets.ReturnValue, Inherited=false)] + public sealed class NotNullAttribute : Attribute + { + public NotNullAttribute() {} + } + + [System.AttributeUsage(System.AttributeTargets.Parameter | + System.AttributeTargets.Property | System.AttributeTargets.ReturnValue, + AllowMultiple=true, Inherited=false)] + public sealed class NotNullIfNotNullAttribute : Attribute + { + public NotNullIfNotNullAttribute(string parameterName) {} + } + + [System.AttributeUsage(System.AttributeTargets.Parameter, Inherited=false)] + public sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) {} + } } namespace System.Threading.Tasks From f5cbd75eb16116b7c50976dcc4496e219ec7598a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 22 Oct 2024 14:07:52 -0400 Subject: [PATCH 02/14] update the section links --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 01ff16438..2bdf203fb 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -863,7 +863,7 @@ For invocations that occur within declarations of instance constructors, static #### §Nullable-Analysis-Attributes-General General -Code compiled with both nullable contexts (§Nullable-Contexts) disabled is null oblivious (§Nullabilities-And-Null-States). That means any reference type variable may be null, but null checks aren't required. Once such code is made nullable-aware, those rules change. Reference type variables should never have the null value, and such variables must be checked against null before being dereferenced. +Code compiled with both nullable contexts (§8.9.4) disabled is null oblivious (§8.9.5). That means any reference type variable may be null, but null checks aren't required. Once such code is made nullable-aware, those rules change. Reference type variables should never have the null value, and such variables must be checked against null before being dereferenced. Some APIs have more complex rules for when variables can or can't be null. In these cases, one or more of the nullable-related attributes described below can be used to express those rules. When user code is compiled in a nullable-enabled context, the compiler is required to warn when that code violates those rules. That is, these attributes help define the nullability contract for an API. From bdfcf1c8e7aef3b7755acb260d5cd2d8e80e69e9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 24 Oct 2024 11:53:04 -0400 Subject: [PATCH 03/14] First set of text updates --- standard/attributes.md | 9 +++++---- tools/ExampleTester/Program.cs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 2bdf203fb..660ddeaea 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -526,7 +526,8 @@ A number of attributes affect the language in some way. These attributes include - `System.ObsoleteAttribute` ([§22.5.4](attributes.md#2254-the-obsolete-attribute)), which is used to mark a member as obsolete. - `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§22.5.5](attributes.md#2255-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method. - `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§22.5.6.2](attributes.md#22562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§22.5.6.3](attributes.md#22563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§22.5.6.4](attributes.md#22564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters. -- Nullable static analysis attributes (§Nullable-Analysis-Attributes). + +The Nullable static analysis attributes (§Nullable-Analysis-Attributes) can improve the correctness of warnings generated for nullabilities and null states (§8.9.5). An execution environment may provide additional implementation-defined attributes that affect the execution of a C# program. @@ -863,9 +864,7 @@ For invocations that occur within declarations of instance constructors, static #### §Nullable-Analysis-Attributes-General General -Code compiled with both nullable contexts (§8.9.4) disabled is null oblivious (§8.9.5). That means any reference type variable may be null, but null checks aren't required. Once such code is made nullable-aware, those rules change. Reference type variables should never have the null value, and such variables must be checked against null before being dereferenced. - -Some APIs have more complex rules for when variables can or can't be null. In these cases, one or more of the nullable-related attributes described below can be used to express those rules. When user code is compiled in a nullable-enabled context, the compiler is required to warn when that code violates those rules. That is, these attributes help define the nullability contract for an API. +The attributes in this section are used to provide additional information to support a compiler that provides nullability and null-state diagnostics (§8.9.5). A compiler isn't required to perform any null-state diagnostics. The presence or absence of these attributes do not affect the language nor the behavior of a program. A compiler that doesn't provide null-state diagnostics shall read and ignore the presence of these attributes. A compiler that provides null-state diagnostics shall use the meaning meaning of the attributes defined in this section. The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`. @@ -883,6 +882,8 @@ The code-analysis attributes are declared in namespace `System.Diagnostics.CodeA `DoesNotReturn` (§The-DoesNotReturn-Attribute) | This method never returns. `DoesNotReturnIf` (§The-DoesNotReturnIf-Attribute) | This method never returns if the associated `bool` parameter has the specified value. +The following sections in §Nullable-Analysis-Attributes-General are conditionally normative. + #### §The-AllowNull-Attribute The AllowNull attribute Specifies that a null value is allowed as an input even if the corresponding type disallows it. diff --git a/tools/ExampleTester/Program.cs b/tools/ExampleTester/Program.cs index 937a79e3d..2d68c4aa7 100644 --- a/tools/ExampleTester/Program.cs +++ b/tools/ExampleTester/Program.cs @@ -12,7 +12,8 @@ if ((token is not null) && (headSha is not null)) { - await logger.BuildCheckRunResult(token, "dotnet", "csharpstandard", headSha); + // This is a hack, but I want to test something. + await logger.BuildCheckRunResult(token, "BillWagner", "csharpstandard", headSha); } return exitCode; From faf106df32acffc40203045f009adae9092e3c6e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 24 Oct 2024 14:49:09 -0400 Subject: [PATCH 04/14] fix first test run --- standard/attributes.md | 2 +- tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md | 2 +- tools/ExampleTester/Program.cs | 3 +-- tools/example-templates/standalone-console/Program.cs | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 660ddeaea..38144650a 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -913,7 +913,7 @@ Specifies that a null value is allowed as an input even if the corresponding typ > v.ScreenName = null; // without attribute AllowNull, get a warning > ``` > -> without the attribute, the compiler is required to generate a warning because the non-nullable-typed property appears to be set to a null value. The presence of the attribute suppresses that warning. *end example* +> without the attribute, the compiler may generate a warning because the non-nullable-typed property appears to be set to a null value. The presence of the attribute suppresses that warning. *end example* #### §The-DisallowNull-Attribute The DisallowNull attribute diff --git a/tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md b/tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md index 0c684d447..4c3b5471c 100644 --- a/tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md +++ b/tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md @@ -108,7 +108,7 @@ template_name ; ``` -The unsuffixed and suffixed versions are identical, *except* that the unsuffixed ones have using directioves for all namespaces used by examples, while the suffixed ones do not. The unsuffixed versions are used by those few examples that begin with `#undef` or `#define`, which *must* precede using directives, and which might then have explicit using directives. +The unsuffixed and suffixed versions are identical, *except* that the unsuffixed ones have using directives for all namespaces used by examples, while the suffixed ones do not. The unsuffixed versions are used by those few examples that begin with `#undef` or `#define`, which *must* precede using directives, and which might then have explicit using directives. #### standalone-console diff --git a/tools/ExampleTester/Program.cs b/tools/ExampleTester/Program.cs index 2d68c4aa7..937a79e3d 100644 --- a/tools/ExampleTester/Program.cs +++ b/tools/ExampleTester/Program.cs @@ -12,8 +12,7 @@ if ((token is not null) && (headSha is not null)) { - // This is a hack, but I want to test something. - await logger.BuildCheckRunResult(token, "BillWagner", "csharpstandard", headSha); + await logger.BuildCheckRunResult(token, "dotnet", "csharpstandard", headSha); } return exitCode; diff --git a/tools/example-templates/standalone-console/Program.cs b/tools/example-templates/standalone-console/Program.cs index 01ee290e0..9574295b6 100644 --- a/tools/example-templates/standalone-console/Program.cs +++ b/tools/example-templates/standalone-console/Program.cs @@ -10,5 +10,6 @@ using System.Security.Permissions; using System.Text; using System.Threading; +using System.Diagnostics.CodeAnalysis; $example-code From 045490059e5c16da7864fbcc533cfc6a76f1a4a7 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 24 Oct 2024 15:47:20 -0400 Subject: [PATCH 05/14] Update samples and descriptions --- standard/attributes.md | 25 +++++++++---------- .../code-in-class-lib/Library.cs | 1 + .../standalone-console/Program.cs | 1 - .../standalone-lib/Library.cs | 1 + 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 38144650a..545bc3090 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -910,7 +910,7 @@ Specifies that a null value is allowed as an input even if the corresponding typ > > ```csharp > var v = new X(); -> v.ScreenName = null; // without attribute AllowNull, get a warning +> v.ScreenName = null; // may warn without attribute AllowNull > ``` > > without the attribute, the compiler may generate a warning because the non-nullable-typed property appears to be set to a null value. The presence of the attribute suppresses that warning. *end example* @@ -927,17 +927,17 @@ Specifies that a null value is disallowed as an input even if the corresponding > public class X > { > [DisallowNull] -> public string ReviewComment +> public string? ReviewComment > { > get => _comment; > set => _comment = value ?? throw new ArgumentNullException(nameof(value), > "Cannot set to null"); > } -> private string _comment = ""; +> private string? _comment = default; > } > ``` > -> The get accessor could return the default value of `null`, so the compiler warns that it must be checked before access. Furthermore, it warns callers that, even though it could be null, callers shouldn't explicitly set it to null. *end example* +> The get accessor could return the default value of `null`, so the compiler may warn that it must be checked before access. Furthermore, it warns callers that, even though it could be null, callers shouldn't explicitly set it to null. *end example* #### §The-DoesNotReturn-Attribute The DoesNotReturn attribute @@ -950,18 +950,16 @@ Specifies that a given method never returns. > public class X > { > [DoesNotReturn] -> private void FailFast() -> { +> private void FailFast() => > throw new InvalidOperationException(); -> } > > public void SetState(object containedField) > { > if (!isInitialized) > { > FailFast(); +> // unreachable code when not initialized: > } -> // unreachable code: > _field = containedField; > } > @@ -970,7 +968,7 @@ Specifies that a given method never returns. > } > ``` > -> The presence of the attribute helps the compiler in a number of ways. First, the compiler issues a warning if there's a path where the method can exit without throwing an exception. Second, the compiler marks any code after a call to that method as unreachable, until an appropriate catch clause is found. Third, the unreachable code won't affect any null states. *end example* +> The presence of the attribute helps the compiler in a number of ways. First, the compiler can issue a warning if there's a path where the method can exit without throwing an exception. Second, the compiler can consider any code after a call to that method as unreachable, until an appropriate catch clause is found. Third, the unreachable code won't affect any null states. *end example* #### §The-DoesNotReturnIf-Attribute The DoesNotReturnIf attribute @@ -980,6 +978,7 @@ Specifies that a given method never returns if the associated `bool` parameter h > > > ```csharp +> #nullable enable > public class X > { > private void FailFastIf([DoesNotReturnIf(false)] bool isValid) @@ -1029,13 +1028,13 @@ Specifies that a non-nullable return value may be null. #### §The-MaybeNullWhen-Attribute The MaybeNullWhen attribute -Specifies that a non-nullable argument may be `null` when the method returns the specified `bool` value. +Specifies that a non-nullable argument may be `null` when the method returns the specified `bool` value. This is similar to the `MaybeNull` attribute (§The-MaybeNull-Attribute), but include a parameter for the specified return value. #### §The-MemberNotNull-Attribute The MemberNotNull attribute Specifies that the given member won't be `null` when the method returns. -> *Example*: The compiler analyzes constructors and field initializers to make sure that all non-nullable reference fields have been initialized before each constructor returns. However, the compiler doesn't track field assignments through all helper methods. The compiler issues a warning when fields aren't initialized directly in the constructor, but rather in a helper method. This warning is suppressed by applying the `MemberNotNull` attribute to a method declaration and specifying the fields that are initialized to a non-null value in the method. For example, consider the following example: +> *Example*: The compiler may analyze constructors and field initializers to make sure that all non-nullable reference fields have been initialized before each constructor returns. However, the compiler doesn't track field assignments through called helper methods. The `MemberNotNull` attribute specifies the fields that are initialized to a non-null value in that method. For example, consider the following example: > > > ```csharp @@ -1097,9 +1096,9 @@ Specifies that a nullable return value will never be `null`. Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`. -> *Example*: Sometimes the null state of a return value depends on the null state of one or more arguments. Such a method will return a non-null value whenever certain arguments aren't `null`. To correctly annotate these methods, add the `NotNullIfNotNull` attribute. Consider the following method: +> *Example*: The null state of a return value may depend on the null state of one or more arguments. Such a method will return a non-null value when certain arguments aren't `null`. To correctly annotate these methods, add the `NotNullIfNotNull` attribute. Consider the following method: > -> +> > ```csharp > #nullable enable > string GetTopLevelDomainFromFullUrl(string url) { ... } diff --git a/tools/example-templates/code-in-class-lib/Library.cs b/tools/example-templates/code-in-class-lib/Library.cs index a657e9fe5..3bd998186 100644 --- a/tools/example-templates/code-in-class-lib/Library.cs +++ b/tools/example-templates/code-in-class-lib/Library.cs @@ -10,6 +10,7 @@ using System.Security.Permissions; using System.Text; using System.Threading; +using System.Diagnostics.CodeAnalysis; partial class Class1 { diff --git a/tools/example-templates/standalone-console/Program.cs b/tools/example-templates/standalone-console/Program.cs index 9574295b6..01ee290e0 100644 --- a/tools/example-templates/standalone-console/Program.cs +++ b/tools/example-templates/standalone-console/Program.cs @@ -10,6 +10,5 @@ using System.Security.Permissions; using System.Text; using System.Threading; -using System.Diagnostics.CodeAnalysis; $example-code diff --git a/tools/example-templates/standalone-lib/Library.cs b/tools/example-templates/standalone-lib/Library.cs index 01ee290e0..9574295b6 100644 --- a/tools/example-templates/standalone-lib/Library.cs +++ b/tools/example-templates/standalone-lib/Library.cs @@ -10,5 +10,6 @@ using System.Security.Permissions; using System.Text; using System.Threading; +using System.Diagnostics.CodeAnalysis; $example-code From 6a6361801b64e6ce1f71dd028afc719f05e7801f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 24 Oct 2024 15:58:29 -0400 Subject: [PATCH 06/14] fix final build warning --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 545bc3090..cc3f57214 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -997,7 +997,7 @@ Specifies that a given method never returns if the associated `bool` parameter h > } > > private bool isInitialized = false; -> private object _field; +> private object _field = default!; > } > ``` > From cb57b888826d46d088ca9ce5eb6133a0b88123f7 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 25 Oct 2024 10:56:33 -0400 Subject: [PATCH 07/14] Apply suggestions from code review Co-authored-by: Nigel-Ecma Co-authored-by: Jon Skeet --- standard/attributes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index cc3f57214..45f048adf 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1028,7 +1028,7 @@ Specifies that a non-nullable return value may be null. #### §The-MaybeNullWhen-Attribute The MaybeNullWhen attribute -Specifies that a non-nullable argument may be `null` when the method returns the specified `bool` value. This is similar to the `MaybeNull` attribute (§The-MaybeNull-Attribute), but include a parameter for the specified return value. +Specifies that a non-nullable argument may be `null` when the method returns the specified `bool` value. This is similar to the `MaybeNull` attribute (§The-MaybeNull-Attribute), but includes a parameter for the specified return value. #### §The-MemberNotNull-Attribute The MemberNotNull attribute @@ -1096,7 +1096,7 @@ Specifies that a nullable return value will never be `null`. Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`. -> *Example*: The null state of a return value may depend on the null state of one or more arguments. Such a method will return a non-null value when certain arguments aren't `null`. To correctly annotate these methods, add the `NotNullIfNotNull` attribute. Consider the following method: +> *Example*: The null state of a return value could depend on the null state of one or more arguments. Such a method may return a non-null value when certain arguments aren't `null`. To assist the compiler with analyzing such methods the `NotNullIfNotNull` attribute may be used. Consider the following method: > > > ```csharp @@ -1104,7 +1104,7 @@ Specifies that a return value isn't `null` if the argument for the specified par > string GetTopLevelDomainFromFullUrl(string url) { ... } > ``` > -> If the `url` argument isn't `null`, `null` isn’t returned. When nullable references are enabled, that signature works correctly, provided the API never accepts a null argument. However, if the argument could be null, then return value could also be null. To express that contract correctly, annotate this method as follows: +> If the `url` argument isn't `null`, `null` isn’t returned. When nullable references are enabled, that signature works correctly, provided the API never accepts a null argument. However, if the argument could be null, then the return value could also be null. To express that contract correctly, annotate this method as follows: > > > ```csharp From 58b14fd56746597761667ec8406d2b99c2c64516 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 25 Oct 2024 11:43:25 -0400 Subject: [PATCH 08/14] Apply suggestions from code review Co-authored-by: Jon Skeet --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 45f048adf..2843212bd 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -864,7 +864,7 @@ For invocations that occur within declarations of instance constructors, static #### §Nullable-Analysis-Attributes-General General -The attributes in this section are used to provide additional information to support a compiler that provides nullability and null-state diagnostics (§8.9.5). A compiler isn't required to perform any null-state diagnostics. The presence or absence of these attributes do not affect the language nor the behavior of a program. A compiler that doesn't provide null-state diagnostics shall read and ignore the presence of these attributes. A compiler that provides null-state diagnostics shall use the meaning meaning of the attributes defined in this section. +The attributes in this section are used to provide additional information to support a compiler that provides nullability and null-state diagnostics (§8.9.5). A compiler isn't required to perform any null-state diagnostics. The presence or absence of these attributes do not affect the language nor the behavior of a program. A compiler that doesn't provide null-state diagnostics shall read and ignore the presence of these attributes. A compiler that provides null-state diagnostics shall use the meaning of the attributes defined in this section. The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`. From e4a575c08b58df7530cc004bcb6a9813f41fd663 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 25 Oct 2024 11:55:59 -0400 Subject: [PATCH 09/14] respond to feedback. --- standard/attributes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 2843212bd..2d2029c3c 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -860,7 +860,7 @@ For invocations that occur within field or event initializers, the member name u For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent. -### §Nullable-Analysis-Attributes Code analysis-attributes +### §Nullable-Analysis-Attributes Code analysis attributes #### §Nullable-Analysis-Attributes-General General @@ -1034,9 +1034,9 @@ Specifies that a non-nullable argument may be `null` when the method returns the Specifies that the given member won't be `null` when the method returns. -> *Example*: The compiler may analyze constructors and field initializers to make sure that all non-nullable reference fields have been initialized before each constructor returns. However, the compiler doesn't track field assignments through called helper methods. The `MemberNotNull` attribute specifies the fields that are initialized to a non-null value in that method. For example, consider the following example: +> *Example*: A helper method adds the `MemberNotNull` attribute to specify any fields that are assigned to a non-null value in that method. A compiler that analyzes constructors to make sure that all non-nullable reference fields have been initialized uses this attribute to determine that fields have been set in those helper methods. For example, consider the following example: > -> +> > ```csharp > #nullable enable > public class Container @@ -1096,7 +1096,7 @@ Specifies that a nullable return value will never be `null`. Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`. -> *Example*: The null state of a return value could depend on the null state of one or more arguments. Such a method may return a non-null value when certain arguments aren't `null`. To assist the compiler with analyzing such methods the `NotNullIfNotNull` attribute may be used. Consider the following method: +> *Example*: The null state of a return value could depend on the null state of one or more arguments. Such a method always returns a non-null value when certain arguments aren't `null`. To assist the compiler with analyzing such methods the `NotNullIfNotNull` attribute may be used. Consider the following method: > > > ```csharp From 5ba80eaec42daa7155a2f3c9d435d1ecfe46639f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 25 Oct 2024 15:25:03 -0400 Subject: [PATCH 10/14] Respond to feedback. --- standard/attributes.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 2d2029c3c..48a5148c6 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -864,7 +864,7 @@ For invocations that occur within declarations of instance constructors, static #### §Nullable-Analysis-Attributes-General General -The attributes in this section are used to provide additional information to support a compiler that provides nullability and null-state diagnostics (§8.9.5). A compiler isn't required to perform any null-state diagnostics. The presence or absence of these attributes do not affect the language nor the behavior of a program. A compiler that doesn't provide null-state diagnostics shall read and ignore the presence of these attributes. A compiler that provides null-state diagnostics shall use the meaning of the attributes defined in this section. +The attributes in this section are used to provide additional information to support a compiler that provides nullability and null-state diagnostics (§8.9.5). A compiler isn't required to perform any null-state diagnostics. The presence or absence of these attributes do not affect the language nor the behavior of a program. A compiler that doesn't provide null-state diagnostics shall read and ignore the presence of these attributes. A compiler that provides null-state diagnostics shall use the meaning defined in this section for any of these attributes which it uses to inform its diagnostics. The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`. @@ -953,13 +953,13 @@ Specifies that a given method never returns. > private void FailFast() => > throw new InvalidOperationException(); > -> public void SetState(object containedField) +> public void SetState(object? containedField) > { -> if (!isInitialized) +> if ((!isInitialized) || (containedField == null)) > { > FailFast(); -> // unreachable code when not initialized: > } +> // null check not needed. > _field = containedField; > } > @@ -981,17 +981,17 @@ Specifies that a given method never returns if the associated `bool` parameter h > #nullable enable > public class X > { -> private void FailFastIf([DoesNotReturnIf(false)] bool isValid) +> private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) > { -> if (!isValid) +> if (!isNull) > { -> throw new InvalidOperationException(); +> throw new InvalidArgumentException(name, $"argument {argumentName} can't be null"); > } > } > > public void SetFieldState(object containedField) > { -> FailFastIf(isInitialized); +> ThrowIfNull(containedField == null, nameof(containedField)); > // unreachable code when "isInitialized" is false: > _field = containedField; > } From ce61e7c0465ed291a09ecd6e44c8e6256885cf04 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 28 Oct 2024 13:43:59 -0400 Subject: [PATCH 11/14] fix tests --- standard/attributes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 48a5148c6..3fbf66680 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -976,7 +976,7 @@ Specifies that a given method never returns if the associated `bool` parameter h > *Example*: Consider the following: > -> +> > ```csharp > #nullable enable > public class X @@ -985,7 +985,7 @@ Specifies that a given method never returns if the associated `bool` parameter h > { > if (!isNull) > { -> throw new InvalidArgumentException(name, $"argument {argumentName} can't be null"); +> throw new ArgumentException(argumentName, $"argument {argumentName} can't be null"); > } > } > @@ -1034,7 +1034,7 @@ Specifies that a non-nullable argument may be `null` when the method returns the Specifies that the given member won't be `null` when the method returns. -> *Example*: A helper method adds the `MemberNotNull` attribute to specify any fields that are assigned to a non-null value in that method. A compiler that analyzes constructors to make sure that all non-nullable reference fields have been initialized uses this attribute to determine that fields have been set in those helper methods. For example, consider the following example: +> *Example*: A helper method adds the `MemberNotNull` attribute to specify any fields that are assigned to a non-null value in that method. A compiler that analyzes constructors to make sure that all non-nullable reference fields have been initialized uses this attribute to determine that fields have been set in those helper methods. Consider the following example: > > > ```csharp From d88331dfd202aa90eca5ae08c2b9518b97358cf4 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 28 Oct 2024 13:55:44 -0400 Subject: [PATCH 12/14] Update attributes.md --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 3fbf66680..437331144 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -976,7 +976,7 @@ Specifies that a given method never returns if the associated `bool` parameter h > *Example*: Consider the following: > -> +> > ```csharp > #nullable enable > public class X From 964ad68960938bc4649cc27ecfa65d576767bb9e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 29 Oct 2024 10:21:53 -0400 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: Nigel-Ecma --- standard/attributes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 437331144..7aad6e4a8 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1034,7 +1034,7 @@ Specifies that a non-nullable argument may be `null` when the method returns the Specifies that the given member won't be `null` when the method returns. -> *Example*: A helper method adds the `MemberNotNull` attribute to specify any fields that are assigned to a non-null value in that method. A compiler that analyzes constructors to make sure that all non-nullable reference fields have been initialized uses this attribute to determine that fields have been set in those helper methods. Consider the following example: +> *Example*: A helper method may include the `MemberNotNull` attribute to list any fields that are assigned to a non-null value in that method. A compiler that analyzes constructors to determine whether all non-nullable reference fields have been initialized may then use this attribute to discover which fields have been set by those helper methods. Consider the following example: > > > ```csharp @@ -1096,7 +1096,7 @@ Specifies that a nullable return value will never be `null`. Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`. -> *Example*: The null state of a return value could depend on the null state of one or more arguments. Such a method always returns a non-null value when certain arguments aren't `null`. To assist the compiler with analyzing such methods the `NotNullIfNotNull` attribute may be used. Consider the following method: +> *Example*: The null state of a return value could depend on the null state of one or more arguments. To assist the compiler’s analysis when a method always returns a non-null value when certain arguments are not `null` the `NotNullIfNotNull` attribute may be used. Consider the following method: > > > ```csharp From 7e0719d91581bdba2e90169afd336ddcb2745c99 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 Nov 2024 16:27:54 -0500 Subject: [PATCH 14/14] Don't imply unreachable code When the `DoesNotReturn` attribute is parsed by a compiler that provides nullable diagnostics, that attribute can't impact reachable code analysis. --- standard/attributes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 7aad6e4a8..3d124b74f 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -968,7 +968,9 @@ Specifies that a given method never returns. > } > ``` > -> The presence of the attribute helps the compiler in a number of ways. First, the compiler can issue a warning if there's a path where the method can exit without throwing an exception. Second, the compiler can consider any code after a call to that method as unreachable, until an appropriate catch clause is found. Third, the unreachable code won't affect any null states. *end example* +> The presence of the attribute helps the compiler in a number of ways. First, the compiler can issue a warning if there's a path where the method can exit without throwing an exception. Second, the compiler can suppress nullable warnings in any code after a call to that method, until an appropriate catch clause is found. Third, the unreachable code won't affect any null states. +> +> The compiler can't change reachability (§13.2) or definite assignment (§9.4) analysis based on the presence of this attribute. A compiler can use this attribute only to impact nullability warnings. *end example* #### §The-DoesNotReturnIf-Attribute The DoesNotReturnIf attribute