Skip to content

Commit

Permalink
Add the list of attributes that support nullable analysis. (#1191)
Browse files Browse the repository at this point in the history
* 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.

* update the section links

* First set of text updates

* fix first test run

* Update samples and descriptions

* fix final build warning

* Apply suggestions from code review

Co-authored-by: Nigel-Ecma <[email protected]>
Co-authored-by: Jon Skeet <[email protected]>

* Apply suggestions from code review

Co-authored-by: Jon Skeet <[email protected]>

* respond to feedback.

* Respond to feedback.

* fix tests

* Update attributes.md

* Apply suggestions from code review

Co-authored-by: Nigel-Ecma <[email protected]>

* 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.

* Apply suggestions from code review

Co-authored-by: Neal Gafter <[email protected]>
Co-authored-by: Joseph Musser <[email protected]>

---------

Co-authored-by: Nigel-Ecma <[email protected]>
Co-authored-by: Jon Skeet <[email protected]>
Co-authored-by: Neal Gafter <[email protected]>
Co-authored-by: Joseph Musser <[email protected]>
  • Loading branch information
5 people authored Nov 20, 2024
1 parent 000309e commit f4ebeb4
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 2 deletions.
275 changes: 274 additions & 1 deletion standard/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,14 +519,16 @@ 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.
The Nullable static analysis attributesNullable-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 Cprogram.
### 22.5.2 The AttributeUsage attribute
Expand Down Expand Up @@ -858,6 +860,277 @@ 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
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`.
**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 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.
> *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.
>
> <!-- Example: {template:"standalone-lib", name:"AllowNullAttribute", replaceEllipsis:true, customEllipsisReplacements:["\"XYZ\""]} -->
> ```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; // 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*
#### §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.
>
> <!-- Example: {template:"standalone-lib", name:"DisallowNullAttribute"} -->
> ```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 = default;
> }
> ```
>
> 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
Specifies that a given method never returns.
> *Example*: Consider the following:
>
> <!-- Example: {template:"standalone-lib", name:"DoesNotReturnAttribute"} -->
> ```csharp
> public class X
> {
> [DoesNotReturn]
> private void FailFast() =>
> throw new InvalidOperationException();
>
> public void SetState(object? containedField)
> {
> if ((!isInitialized) || (containedField == null))
> {
> FailFast();
> }
> // null check not needed.
> _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 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 attribute does not change reachability13.2) or definite assignment9.4) analysis based on the presence of this attribute. It is used only to impact nullability warnings. *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:
>
> <!-- Example: {template:"standalone-lib", name:"DoesNotReturnIfAttribute", expectedWarnings:["CS0414"]} -->
> ```csharp
> #nullable enable
> public class X
> {
> private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName)
> {
> if (!isNull)
> {
> throw new ArgumentException(argumentName, $"argument {argumentName} can't be null");
> }
> }
>
> public void SetFieldState(object containedField)
> {
> ThrowIfNull(containedField == null, nameof(containedField));
> // unreachable code when "isInitialized" is false:
> _field = containedField;
> }
>
> private bool isInitialized = false;
> private object _field = default!;
> }
> ```
>
> *end example*
#### §The-MaybeNull-Attribute The MaybeNull attribute
Specifies that a non-nullable return value may be null.
> *Example*: Consider the following generic method:
>
> <!-- Example: {template:"code-in-class-lib-without-using", name:"MaybeNull1Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
> ```csharp
> #nullable enable
> public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
> ```
>
> The idea of this code is that if `T` is replaced by `string`, `T?` becomes a nullable annotation. However, this code is not legal because `T` is not constrained to be a reference type. However, adding this attribute solves the problem:
>
> <!-- Example: {template:"code-in-class-lib", name:"MaybeNull2Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
> ```csharp
> #nullable enable
> [return: MaybeNull]
> public T Find<T>(IEnumerable<T> sequence, Func<T, bool> 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. This is similar to the `MaybeNull` attributeThe-MaybeNull-Attribute), but includes 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*: 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:
>
> <!-- Example: {template:"standalone-lib", name:"MemberNotNullAttribute"} -->
> ```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 attributes 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 value will never be `null` if the method returns (rather than throwing).
> *Example*: Consider the following:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullAttribute"} -->
> ```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*: The null state of a return value could depend on the null state of one or more arguments. To assist the compilers 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:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullIfNotNull1Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return \"\";"]} -->
> ```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 the return value could also be null. To express that contract correctly, annotate this method as follows:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullIfNotNull2Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return \"\";"]} -->
> ```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:
>
> <!-- Example: {template:"code-in-class-lib", name:"NotNullWhenAttribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
> ```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.
Expand Down
80 changes: 80 additions & 0 deletions standard/standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tools/example-templates/code-in-class-lib/Library.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Diagnostics.CodeAnalysis;

partial class Class1
{
Expand Down
Loading

0 comments on commit f4ebeb4

Please sign in to comment.