Skip to content

Commit f4ebeb4

Browse files
BillWagnerNigel-Ecmajskeetgafterjnm2
authored
Add the list of attributes that support nullable analysis. (#1191)
* 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]>
1 parent 000309e commit f4ebeb4

File tree

5 files changed

+357
-2
lines changed

5 files changed

+357
-2
lines changed

standard/attributes.md

Lines changed: 274 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,14 +519,16 @@ Using the terms defined in [§22.4.2](attributes.md#2242-compilation-of-an-attri
519519
520520
### 22.5.1 General
521521
522-
A small number of attributes affect the language in some way. These attributes include:
522+
A number of attributes affect the language in some way. These attributes include:
523523
524524
- `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.
525525
- `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.
526526
- `System.ObsoleteAttribute` ([§22.5.4](attributes.md#2254-the-obsolete-attribute)), which is used to mark a member as obsolete.
527527
- `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.
528528
- `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.
529529
530+
The Nullable static analysis attributesNullable-Analysis-Attributes) can improve the correctness of warnings generated for nullabilities and null states (§8.9.5).
531+
530532
An execution environment may provide additional implementation-defined attributes that affect the execution of a Cprogram.
531533
532534
### 22.5.2 The AttributeUsage attribute
@@ -858,6 +860,277 @@ For invocations that occur within field or event initializers, the member name u
858860
859861
For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent.
860862
863+
### §Nullable-Analysis-Attributes Code analysis attributes
864+
865+
#### §Nullable-Analysis-Attributes-General General
866+
867+
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.
868+
869+
The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`.
870+
871+
**Attribute** | **Meaning**
872+
------------------ | ------------------
873+
`AllowNull` (§The-AllowNull-Attribute) | A non-nullable argument may be null.
874+
`DisallowNull` (§The-DisallowNull-Attribute) | A nullable argument should never be null.
875+
`MaybeNull` (§The-MaybeNull-Attribute) | A non-nullable return value may be null.
876+
`NotNull` (§The-NotNull-Attribute) | A nullable return value will never be null.
877+
`MaybeNullWhen` (§The-MaybeNullWhen-Attribute) | A non-nullable argument may be null when the method returns the specified `bool` value.
878+
`NotNullWhen` (§The-NotNullWhen-Attribute) | A nullable argument won't be null when the method returns the specified `bool` value.
879+
`NotNullIfNotNull` (§The-NotNullIfNotNull-Attribute) | A return value isn't null if the argument for the specified parameter isn't null.
880+
`MemberNotNull` (§The-MemberNotNull-Attribute) | The listed member won't be null when the method returns.
881+
`MemberNotNullWhen` (§The-MemberNotNullWhen-Attribute) | The listed member won't be null when the method returns the specified `bool` value.
882+
`DoesNotReturn` (§The-DoesNotReturn-Attribute) | This method never returns.
883+
`DoesNotReturnIf` (§The-DoesNotReturnIf-Attribute) | This method never returns if the associated `bool` parameter has the specified value.
884+
885+
The following sections in §Nullable-Analysis-Attributes-General are conditionally normative.
886+
887+
#### §The-AllowNull-Attribute The AllowNull attribute
888+
889+
Specifies that a null value is allowed as an input even if the corresponding type disallows it.
890+
891+
> *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.
892+
>
893+
> <!-- Example: {template:"standalone-lib", name:"AllowNullAttribute", replaceEllipsis:true, customEllipsisReplacements:["\"XYZ\""]} -->
894+
> ```csharp
895+
> #nullable enable
896+
> public class X
897+
> {
898+
> [AllowNull]
899+
> public string ScreenName
900+
> {
901+
> get => _screenName;
902+
> set => _screenName = value ?? GenerateRandomScreenName();
903+
> }
904+
> private string _screenName = GenerateRandomScreenName();
905+
> private static string GenerateRandomScreenName() => ...;
906+
> }
907+
> ```
908+
>
909+
> Given the following use of that property’s set accessor
910+
>
911+
> ```csharp
912+
> var v = new X();
913+
> v.ScreenName = null; // may warn without attribute AllowNull
914+
> ```
915+
>
916+
> 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*
917+
918+
#### §The-DisallowNull-Attribute The DisallowNull attribute
919+
920+
Specifies that a null value is disallowed as an input even if the corresponding type allows it.
921+
922+
> *Example*: Consider the following property in which null is the default value, but clients can only set it to a non-null value.
923+
>
924+
> <!-- Example: {template:"standalone-lib", name:"DisallowNullAttribute"} -->
925+
> ```csharp
926+
> #nullable enable
927+
> public class X
928+
> {
929+
> [DisallowNull]
930+
> public string? ReviewComment
931+
> {
932+
> get => _comment;
933+
> set => _comment = value ?? throw new ArgumentNullException(nameof(value),
934+
> "Cannot set to null");
935+
> }
936+
> private string? _comment = default;
937+
> }
938+
> ```
939+
>
940+
> 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*
941+
942+
#### §The-DoesNotReturn-Attribute The DoesNotReturn attribute
943+
944+
Specifies that a given method never returns.
945+
946+
> *Example*: Consider the following:
947+
>
948+
> <!-- Example: {template:"standalone-lib", name:"DoesNotReturnAttribute"} -->
949+
> ```csharp
950+
> public class X
951+
> {
952+
> [DoesNotReturn]
953+
> private void FailFast() =>
954+
> throw new InvalidOperationException();
955+
>
956+
> public void SetState(object? containedField)
957+
> {
958+
> if ((!isInitialized) || (containedField == null))
959+
> {
960+
> FailFast();
961+
> }
962+
> // null check not needed.
963+
> _field = containedField;
964+
> }
965+
>
966+
> private bool isInitialized = false;
967+
> private object _field;
968+
> }
969+
> ```
970+
>
971+
> 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.
972+
>
973+
> 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*
974+
975+
#### §The-DoesNotReturnIf-Attribute The DoesNotReturnIf attribute
976+
977+
Specifies that a given method never returns if the associated `bool` parameter has the specified value.
978+
979+
> *Example*: Consider the following:
980+
>
981+
> <!-- Example: {template:"standalone-lib", name:"DoesNotReturnIfAttribute", expectedWarnings:["CS0414"]} -->
982+
> ```csharp
983+
> #nullable enable
984+
> public class X
985+
> {
986+
> private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName)
987+
> {
988+
> if (!isNull)
989+
> {
990+
> throw new ArgumentException(argumentName, $"argument {argumentName} can't be null");
991+
> }
992+
> }
993+
>
994+
> public void SetFieldState(object containedField)
995+
> {
996+
> ThrowIfNull(containedField == null, nameof(containedField));
997+
> // unreachable code when "isInitialized" is false:
998+
> _field = containedField;
999+
> }
1000+
>
1001+
> private bool isInitialized = false;
1002+
> private object _field = default!;
1003+
> }
1004+
> ```
1005+
>
1006+
> *end example*
1007+
1008+
#### §The-MaybeNull-Attribute The MaybeNull attribute
1009+
1010+
Specifies that a non-nullable return value may be null.
1011+
1012+
> *Example*: Consider the following generic method:
1013+
>
1014+
> <!-- Example: {template:"code-in-class-lib-without-using", name:"MaybeNull1Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
1015+
> ```csharp
1016+
> #nullable enable
1017+
> public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
1018+
> ```
1019+
>
1020+
> 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:
1021+
>
1022+
> <!-- Example: {template:"code-in-class-lib", name:"MaybeNull2Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
1023+
> ```csharp
1024+
> #nullable enable
1025+
> [return: MaybeNull]
1026+
> public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
1027+
> ```
1028+
>
1029+
> The attribute informs callers that the contract implies a non-nullable type, but the return value may actually be `null`. *end example*
1030+
1031+
#### §The-MaybeNullWhen-Attribute The MaybeNullWhen attribute
1032+
1033+
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.
1034+
1035+
#### §The-MemberNotNull-Attribute The MemberNotNull attribute
1036+
1037+
Specifies that the given member won't be `null` when the method returns.
1038+
1039+
> *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:
1040+
>
1041+
> <!-- Example: {template:"standalone-lib", name:"MemberNotNullAttribute"} -->
1042+
> ```csharp
1043+
> #nullable enable
1044+
> public class Container
1045+
> {
1046+
> private string _uniqueIdentifier; // must be initialized.
1047+
> private string? _optionalMessage;
1048+
>
1049+
> public Container()
1050+
> {
1051+
> Helper();
1052+
> }
1053+
>
1054+
> public Container(string message)
1055+
> {
1056+
> Helper();
1057+
> _optionalMessage = message;
1058+
> }
1059+
>
1060+
> [MemberNotNull(nameof(_uniqueIdentifier))]
1061+
> private void Helper()
1062+
> {
1063+
> _uniqueIdentifier = DateTime.Now.Ticks.ToString();
1064+
> }
1065+
> }
1066+
> ```
1067+
>
1068+
> Multiple field names may be given as arguments to the attributes constructor. *end example*
1069+
1070+
#### §The-MemberNotNullWhen-Attribute The MemberNotNullWhen attribute
1071+
1072+
Specifies that the listed member won't be `null` when the method returns the specified `bool` value.
1073+
1074+
> *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*
1075+
1076+
#### §The-NotNull-Attribute The NotNull attribute
1077+
1078+
Specifies that a nullable value will never be `null` if the method returns (rather than throwing).
1079+
1080+
> *Example*: Consider the following:
1081+
>
1082+
> <!-- Example: {template:"code-in-class-lib", name:"NotNullAttribute"} -->
1083+
> ```csharp
1084+
> #nullable enable
1085+
> public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") =>
1086+
> _ = value ?? throw new ArgumentNullException(valueExpression);
1087+
>
1088+
> public static void LogMessage(string? message)
1089+
> {
1090+
> ThrowWhenNull(message, nameof(message));
1091+
> Console.WriteLine(message.Length);
1092+
> }
1093+
> ```
1094+
>
1095+
> 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*
1096+
1097+
#### §The-NotNullIfNotNull-Attribute The NotNullIfNotNull attribute
1098+
1099+
Specifies that a return value isn't `null` if the argument for the specified parameter isn't `null`.
1100+
1101+
> *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:
1102+
>
1103+
> <!-- Example: {template:"code-in-class-lib", name:"NotNullIfNotNull1Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return \"\";"]} -->
1104+
> ```csharp
1105+
> #nullable enable
1106+
> string GetTopLevelDomainFromFullUrl(string url) { ... }
1107+
> ```
1108+
>
1109+
> 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:
1110+
>
1111+
> <!-- Example: {template:"code-in-class-lib", name:"NotNullIfNotNull2Attribute", replaceEllipsis:true, customEllipsisReplacements: ["return \"\";"]} -->
1112+
> ```csharp
1113+
> #nullable enable
1114+
> [return: NotNullIfNotNull("url")]
1115+
> string? GetTopLevelDomainFromFullUrl(string? url) { ... }
1116+
> ```
1117+
>
1118+
> *end example*
1119+
1120+
#### §The-NotNullWhen-Attribute The NotNullWhen attribute
1121+
1122+
Specifies that a nullable argument won't be `null` when the method returns the specified `bool` value.
1123+
1124+
> *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:
1125+
>
1126+
> <!-- Example: {template:"code-in-class-lib", name:"NotNullWhenAttribute", replaceEllipsis:true, customEllipsisReplacements: ["return default;"]} -->
1127+
> ```csharp
1128+
> #nullable enable
1129+
> bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
1130+
> ```
1131+
>
1132+
> *end example*
1133+
8611134
## 22.6 Attributes for interoperation
8621135
8631136
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.

standard/standard-library.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,86 @@ namespace System.Runtime.CompilerServices
571571
public bool IsCompleted { get; }
572572
public TResult GetResult();
573573
}
574+
575+
[System.AttributeUsage(System.AttributeTargets.Field |
576+
System.AttributeTargets.Parameter | System.AttributeTargets.Property,
577+
Inherited=false)]
578+
public sealed class AllowNullAttribute : Attribute
579+
{
580+
public AllowNullAttribute() { }
581+
}
582+
583+
[System.AttributeUsage(System.AttributeTargets.Field |
584+
System.AttributeTargets.Parameter | System.AttributeTargets.Property,
585+
Inherited=false)]
586+
public sealed class DisallowNullAttribute : Attribute
587+
{
588+
public DisallowNullAttribute() {}
589+
}
590+
591+
[System.AttributeUsage(System.AttributeTargets.Method, Inherited=false)]
592+
public sealed class DoesNotReturnAttribute : Attribute
593+
{
594+
public DoesNotReturnAttribute() {}
595+
}
596+
597+
[System.AttributeUsage(System.AttributeTargets.Parameter, Inherited=false)]
598+
public sealed class DoesNotReturnIfAttribute : Attribute
599+
{
600+
public DoesNotReturnIfAttribute(bool parameterValue) {}
601+
}
602+
603+
[System.AttributeUsage(System.AttributeTargets.Field |
604+
System.AttributeTargets.Parameter | System.AttributeTargets.Property |
605+
System.AttributeTargets.ReturnValue, Inherited=false)]
606+
public sealed class MaybeNullAttribute : Attribute
607+
{
608+
public MaybeNullAttribute() {}
609+
}
610+
611+
[System.AttributeUsage(System.AttributeTargets.Parameter, Inherited=false)]
612+
public sealed class MaybeNullWhenAttribute : Attribute
613+
{
614+
public MaybeNullWhenAttribute(bool returnValue) {}
615+
}
616+
617+
[System.AttributeUsage(System.AttributeTargets.Method |
618+
System.AttributeTargets.Property, AllowMultiple=true, Inherited=false)]
619+
public sealed class MemberNotNullAttribute : Attribute
620+
{
621+
public MemberNotNullAttribute(string member) {}
622+
public MemberNotNullAttribute(params string[] members) {}
623+
}
624+
625+
[System.AttributeUsage(System.AttributeTargets.Method |
626+
System.AttributeTargets.Property, AllowMultiple=true, Inherited=false)]
627+
public sealed class MemberNotNullWhenAttribute : Attribute
628+
{
629+
public MemberNotNullWhenAttribute(bool returnValue, string member) {}
630+
public MemberNotNullWhenAttribute(bool returnValue, params string[] members) {}
631+
}
632+
633+
[System.AttributeUsage(System.AttributeTargets.Field |
634+
System.AttributeTargets.Parameter | System.AttributeTargets.Property |
635+
System.AttributeTargets.ReturnValue, Inherited=false)]
636+
public sealed class NotNullAttribute : Attribute
637+
{
638+
public NotNullAttribute() {}
639+
}
640+
641+
[System.AttributeUsage(System.AttributeTargets.Parameter |
642+
System.AttributeTargets.Property | System.AttributeTargets.ReturnValue,
643+
AllowMultiple=true, Inherited=false)]
644+
public sealed class NotNullIfNotNullAttribute : Attribute
645+
{
646+
public NotNullIfNotNullAttribute(string parameterName) {}
647+
}
648+
649+
[System.AttributeUsage(System.AttributeTargets.Parameter, Inherited=false)]
650+
public sealed class NotNullWhenAttribute : Attribute
651+
{
652+
public NotNullWhenAttribute(bool returnValue) {}
653+
}
574654
}
575655

576656
namespace System.Threading.Tasks

tools/ExampleExtractor/ExtractorAndTesterUsersGuide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ template_name
108108
;
109109
```
110110

111-
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.
111+
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.
112112

113113
#### standalone-console
114114

tools/example-templates/code-in-class-lib/Library.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Security.Permissions;
1111
using System.Text;
1212
using System.Threading;
13+
using System.Diagnostics.CodeAnalysis;
1314

1415
partial class Class1
1516
{

0 commit comments

Comments
 (0)