-
Notifications
You must be signed in to change notification settings - Fork 87
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
C# 8 Nullable reference types: Define different reference types: oblivious, nullable, and non-nullable #1105
Conversation
Worth labeling for discussion next week, or is it too early for that? |
I'll add the label after making edits. I hope we can discuss it next week. |
As I start on this, I wonder if a separate PR that updates all test examples so they generate the existing output in a nullable context is a good idea. It would separate out that work into one PR to review. If you both agree, I'll add that PR for Wednesday as well. |
89e8279
to
01b1bd7
Compare
I'm not entirely sure whether that's a good idea. It might end up making a lot of examples that have nothing to do with nullability very null-syntax heavy. But I could be wrong. Maybe make a start but see how you feel without doing it for all examples? |
standard/types.md
Outdated
|
||
> *Note*: Here, “should” is used rather than “shall,” as this a declaration of intent. The presence of a `?` suffix is not enough to make it a non-nullable type; for that, the annotation context has to be enabled. If it is not, the `?` is ignored, and the type is nullable. *end note* | ||
|
||
Unlike with nullable value types, where value types `V` and `V?` denote different types, given a reference type `R`, the notations `R` and `R?` denote the exact same type; the difference in their notations indicates, at compile time, only the intent of their usage, and allows for static flow analysis. Unlike a nullable value type, a nullable reference type has no relationship to the type `System.Nullable<T>`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "exact same type" wording feels troublesome. If the ?
nullable-reference-type annotation is used in an alias
#nullable enable
using ListOfNullableString = System.Collections.Generic.List<string?>;
class C
{
// warning CS8619
System.Collections.Generic.List<string> sl = new ListOfNullableString();
}
then the standard needs some wording to specify that the initializer of the field sl
gets both its type and its nullability annotations from the alias ListOfNullableString
. Currently, §14.5.2 (Using alias directives) just says "the identifier introduced by the using_alias_directive can be used to reference the given namespace or type" and does not mention nullability.
The alternative would be to specify R
and R?
as different types that have two-way identity conversions between them, like there are between object
and dynamic
, or between similar tuple types. But perhaps there's something about flow analysis that makes this not possible.
#nullable enable
class TypeArgumentDeductionWithIdentityConversion
{
static void OneNrt(string? a, string b)
{
N(ref a, ref b); // warning CS8601
}
static void TwoNrt(string? a, string? b)
{
N(ref a, ref b);
}
static void ObjectVsDynamic(object[] a, dynamic[] b)
{
N(ref a, ref b);
}
static void TupleElementNames((int X, int Y) a, (int Z, int W) b)
{
N(ref a, ref b);
}
static void N<T>(ref T a, ref T b)
{
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're thinking that the identity conversion path is the correct one. (We haven't speced out the warning section yet, but there are other identity conversions that can produce warnings).
7d15798
to
dcb3731
Compare
@BillWagner As we discussed privately recently, I'm OK with spinning off the examples into a separate PR. I recall there were many, and I applied them in several large commits. |
I'm leaning that way now too! I updated one sample in this PR because it made sense to show the nullable annotations as part of this PR. That didn't compile in the sample extractor, because nullable was off. Then, when I turned it on, several things failed. The best way to handle this may be starting with #1106. In that PR, I set the Then, later, we can change the setting to |
2cf37c0
to
f84f365
Compare
bbff3bc
to
a9a124e
Compare
4dab247
to
d9a83b7
Compare
|
||
Every line of source code has a ***nullable annotation context*** and a ***nullable warning context***. The former controls whether nullable annotations (§Nullable-Annotation-Context) have effect; the latter controls whether nullable warnings (§Nullable-Warning-Context) are issued by the compiler. The state of each of the annotation and warning contexts of a given line is disabled or enabled. | ||
|
||
Both nullable contexts may be specified within source code via nullable directives (§Nullable-Directives) and/or via some implementation-specific mechanism external to the source code. If both approaches are used, nullable directives supersede the settings made via an external mechanism. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is causing the section renumber failure. That PR should merge first, then this can be updated as needed.
|
||
Both nullable contexts may be specified within source code via nullable directives (§Nullable-Directives) and/or via some implementation-specific mechanism external to the source code. If both approaches are used, nullable directives supersede the settings made via an external mechanism. | ||
|
||
The default state for the nullable annotation context and the nullable warning context is implementation defined. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rex had originally written this to say the default state is "disabled". I prefer implementation defined, so in the future, if the implementation changes, it won't require changing the standard. (Yes, it's already documented for roslyn).
Adding the meeting discuss label. I don't think this is ready to merge. But, we should discuss terms, definitions, and concepts so that the edits can be refined after this next meeting. |
- *nullable*: A *nullable-reference-type* can be assigned `null`. It's default null state is *maybe-null*. | ||
- *non-nullable*" A *non-nullable reference* should not be assigned a `null` value. Its default null state is *not-null*. | ||
|
||
Unlike with nullable value types, where value types `V` and `V?` denote different types, given a reference type `R`, the notations `R` and `R?` denote the same underlying type; the difference in their notations indicates, at compile time, only the intent of their usage, and allows for static flow analysis. An identity conversion exists among a nullable reference type, its corresponding non-nullable reference type, and its corresponding null-oblivious reference type (§10.2.2). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've said they're different types - I think we should reword "same underlying type", but it will require careful wordsmithing. We should see what we say about dynamic
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jskeet – Does the above say they are different types? We have:
The terms "nullable reference type" and "non-nullable reference type" refer to annotations that declare whether an expression is intended to permit null values or not.
This language suggests there is one type which can have different annotations, not that there are multiple distinct types.
Maybe the term “underlying type” should not be overloaded to deal with (non-)nullable reference types? Given the text is leaning on annotations maybe a better term to use here is “non annotated type” (is it non annotated, nonannotated or unannotated?). This would give:
[…] given a reference type
R
, the notationsR
andR?
denote the same non annotated type […]
@@ -27,10 +27,15 @@ For convenience, throughout this specification, some library type names are writ | |||
|
|||
### 8.2.1 General | |||
|
|||
A reference type is a class type, an interface type, an array type, a delegate type, or the `dynamic` type. | |||
A reference type is a class type, an interface type, an array type, a delegate type, or the `dynamic` type. For each non-nullable reference type, there is a corresponding nullable reference type noted by appending the `?` to the type name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And a corresponding null-oblivious type? Later on we seem to be treating them as three different types. Let's discuss how we want to describe this, and then try really hard to be consistent.
The first commit ports in the text from dotnet#700 substantially without change. The only editorial change is to move the clause on "Nullable directives" in lexical-structure before the clause on Pragma directives.
This will likely cause new failures, as I turned on nullable annotations and warnings in one of the templates. Update code samples for the template in use to remove nullable warnings. I did this as we plan to have nullable annotations on by default in this version of the spec.
This reverts commit dcb3731.
If the samples compile cleanly in this mode, I can continue in this PR without breaking all the samples.
First draft at language for variations of nullability and reference types.
ff073bd
to
0efff98
Compare
Co-authored-by: Jon Skeet <skeet@pobox.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, as my comments undoubtedly show, I’m not sure it is yet clear what the boundary is between what an implementation (and we probably shouldn't say “compiler” here) must do and may do to be conformant.
- *nullable*: A *nullable-reference-type* can be assigned `null`. It's default null state is *maybe-null*. | ||
- *non-nullable*" A *non-nullable reference* should not be assigned a `null` value. Its default null state is *not-null*. | ||
|
||
Unlike with nullable value types, where value types `V` and `V?` denote different types, given a reference type `R`, the notations `R` and `R?` denote the same underlying type; the difference in their notations indicates, at compile time, only the intent of their usage, and allows for static flow analysis. An identity conversion exists among a nullable reference type, its corresponding non-nullable reference type, and its corresponding null-oblivious reference type (§10.2.2). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jskeet – Does the above say they are different types? We have:
The terms "nullable reference type" and "non-nullable reference type" refer to annotations that declare whether an expression is intended to permit null values or not.
This language suggests there is one type which can have different annotations, not that there are multiple distinct types.
Maybe the term “underlying type” should not be overloaded to deal with (non-)nullable reference types? Given the text is leaning on annotations maybe a better term to use here is “non annotated type” (is it non annotated, nonannotated or unannotated?). This would give:
[…] given a reference type
R
, the notationsR
andR?
denote the same non annotated type […]
Closing in favor of #1124 |
Related to #700
Fixes #1089
Add text to define the different "flavors" of reference types: "Nullable oblivious" (class reference types), "nullable reference types", and "non-nullable reference types".
Commit history is less clean than I'd like. I'd start thinking that one PR for the contexts
#nullable
changes and the types would be fine. That was getting larger. So I split the additional changes for#nullable
into #1108