Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the list of attributes that support nullable analysis. #1191

Open
wants to merge 14 commits into
base: draft-v8
Choose a base branch
from
273 changes: 272 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 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.

### 22.5.2 The AttributeUsage attribute
Expand Down Expand Up @@ -858,6 +860,275 @@ 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
jskeet marked this conversation as resolved.
Show resolved Hide resolved

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 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*
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be reworded. The compiler doesn't use this attribute for definite assignment nor reachability. However, it will only supress any nullability.


#### §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) { ... }
> ```
>
> 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<string>`, the default return is `null`, but when `Find` searches an `IEnumerable<int>`, the default return is 0. As such, specifying the return type as `T?` isn’t appropriate. 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` attribute (§The-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 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:
>
> <!-- 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 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:
>
> <!-- 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
Loading