Adjust the way nullable annotations are represented in metadata#31212
Adjust the way nullable annotations are represented in metadata#31212AlekseyTs merged 7 commits intodotnet:masterfrom
Conversation
Closes dotnet#30075. Closes dotnet#30065. Closes dotnet#29594.
| @@ -22,54 +22,43 @@ namespace System.Runtime.CompilerServices | |||
| AllowMultiple = false)] | |||
| public sealed class NullableAttribute : Attribute | |||
There was a problem hiding this comment.
NullableAttribute [](start = 24, length = 17)
Not blocking this PR: we'll need to think about how the values of the parameters will be stored in the fields of NullableAttribute. Jared mentioned we need to store those values so that they are accessible from reflection.
Tracked by #30143 #Resolved
|
|
||
| NullableAttribute(1) should be placed on a type parameter definition that has a `class!` constraint. | ||
| NullableAttribute(2) should be placed on a type parameter definition that has a `class?` constraint. | ||
| Other forms of NullableAttribute ar not emitted on type parameter definitions and are not specially recognized on them. |
There was a problem hiding this comment.
ar [](start = 33, length = 2)
typo: are #Resolved
| string OptString; // string? | ||
| [Nullable(new[] { true, false, true })] | ||
| [Nullable(new[] { 2, 1, 2 })] | ||
| Dictionary<string, object> OptDictionaryOptValues; // Dictionary<string!, object?>? |
There was a problem hiding this comment.
object> [](start = 19, length = 7)
Should this be Never mind. The type here is from PE. #ClosedDictionary<..., object?>? in source as well?
| AttributeInfo info = FindTargetAttribute(token, AttributeDescription.NullableAttribute); | ||
| Debug.Assert(!info.HasValue || info.SignatureIndex == 0 || info.SignatureIndex == 1); | ||
|
|
||
| defaultTransform = 0; |
There was a problem hiding this comment.
0; [](start = 31, length = 2)
Consider using another value to represent "not a value" and only set to 0 at line 2437 (from TryExtractValueFromAttribute). #Resolved
There was a problem hiding this comment.
Consider using another value to represent "not a value" and only set to 0 at line 2437 (from TryExtractValueFromAttribute).
I see no benefit in inventing new "defaults" when this value just works, this is a well established pattern for functions that return information via out parameters and return boolean value indicating success of the operation. Consumers that really depend on the fact whether the attribute was found and cracked successfully should simply check return value of this method.
In reply to: 234368109 [](ancestors = 234368109)
| Dictionary<string, object?>? OptDictionaryOptValues; // dictionary may be null, values may be null | ||
| ``` | ||
| A warning is reported when annotating a reference type or unconstrained generic type with `?` outside a `NonNullTypes(true)` context. | ||
| A warning is reported when annotating a reference type or unconstrained generic type with `?` outside a `#nullable` context. |
There was a problem hiding this comment.
I thought it was an error to annotate an unconstrained generic type. #Resolved
| break; | ||
|
|
||
| case NullableAnnotation.Unknown: | ||
| Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown); |
There was a problem hiding this comment.
Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown); [](start = 20, length = 78)
The assert looks redundant. #Closed
|
|
||
| case NullableAnnotation.Unknown: | ||
| Debug.Assert((NullableAnnotation)transformFlag == NullableAnnotation.Unknown); | ||
| if (result.NullableAnnotation != NullableAnnotation.Unknown && (!result.NullableAnnotation.IsAnyNullable() || !oldTypeSymbol.IsNullableType())) |
There was a problem hiding this comment.
(!result.NullableAnnotation.IsAnyNullable() || !oldTypeSymbol.IsNullableType()) [](start = 83, length = 79)
nit: took me a while to understand this check. Inverting this last block helps: .. && !(result.NullableAnnotation.IsAnyNullable() && oldTypeSymbol.IsNullableType())
Maybe add a comment too: "// keep nullable annotation on Nullable<T> types" #Resolved
| var type = comp.GetWellKnownType(WellKnownType.Microsoft_CodeAnalysis_EmbeddedAttribute); | ||
| Assert.True(type.IsErrorType()); | ||
|
|
||
| // https://github.com/dotnet/roslyn/issues/29683 CSharpCompilation.AbstractSymbolSearcher needs to inject namespaces and types too |
This test requires both constructors (because we have simple and mixed case of nullability). I assume we produce the same error even if we only need one constructor and it exists. #Resolved Refers to: src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs:74 in 60ce669. [](commit_id = 60ce669, deletion_comment = False) |
Yes, this change didn't change the number of constructors and didn't change the way we complain about one of them is missing. In reply to: 440002524 [](ancestors = 440002524) Refers to: src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Nullable.cs:74 in 60ce669. [](commit_id = 60ce669, deletion_comment = False) |
|
|
||
| if (format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) && | ||
| !typeOpt.IsNullableType() && | ||
| !typeOpt.IsNullableType() && !typeOpt.IsValueType && |
There was a problem hiding this comment.
!typeOpt.IsValueType && [](start = 45, length = 23)
I didn't understand what's a scenario were we put a nullable annotation on a value type, but it's not Nullable<T>.
Is the when loading bad metadata? #Closed
There was a problem hiding this comment.
I didn't understand what's a scenario were we put a nullable annotation on a value type, but it's not Nullable.
Is the when loading bad metadata?
On the contrary, binding from source does that, always did.
In reply to: 234741474 [](ancestors = 234741474)
There was a problem hiding this comment.
From our discussion, some of the weird combinations (int marked as nullable) only come from PE.
You mentioned that one reason for letting such combinations into the compiler is to avoid cycles. Do you remember what's a scenario?
In particular, I wonder why is that problem unique to nullability, but was ok for decoding dynamic or tuple names?
If it were possible, I still think it would be better for metadata import to ignore, rather than to propagate, bad metadata combinations. I'd expect all value types (except Nullable<T>) should be imported as oblivious. I believe that is what existing metadata import tends to do.
Along those lines, why encode a nullable annotation for value types at all? We don't encode a dynamic flag for types that are not object, and we don't encode tuple names for types that are not tuples.
In reply to: 234742698 [](ancestors = 234742698,234741474)
There was a problem hiding this comment.
You mentioned that one reason for letting such combinations into the compiler is to avoid cycles. Do you remember what's a scenario?
For example, when we are loading type parameter constraints, we cannot ask questions whether the type parameter is a value type or a reference type
In particular, I wonder why is that problem unique to nullability, but was ok for decoding dynamic or tuple names?
In order to decode dynamic or tuple names we do not check whether targets are value or reference types either.
If it were possible, I still think it would be better for metadata import to ignore, rather than to propagate, bad metadata combinations. I'd expect all value types (except Nullable) should be imported as oblivious. I believe that is what existing metadata import tends to do.
I do not think this is something we should pursue. In any case, I think this PR doesn't change the way we import metadata in that specific regard, and it wasn't one of the goals to change this.
Along those lines, why encode a nullable annotation for value types at all? We don't encode a dynamic flag for types that are not object, and we don't encode tuple names for types that are not tuples.
Compiler always marks value types as oblivious in metadata, but we should be prepared to deal with malformed cases. This particular line of code makes sure that display doesn't confuse the reader that it deals with Nullable<T> case.
In reply to: 234772025 [](ancestors = 234772025,234742698,234741474)
Not related to a file in this PR: since we're undoing the injection of NNT attribute, two EnC tests can be re-enabled. #29662 Refers to: src/Workspaces/CoreTest/SymbolKeyTests.cs:68 in 60ce669. [](commit_id = 60ce669, deletion_comment = False) |
| } | ||
| } | ||
|
|
||
| // https://github.com/dotnet/roslyn/issues/30075: Test different [NonNullTypes] on method and containing type. |
There was a problem hiding this comment.
#30075 [](start = 27, length = 45)
Can be resolved? #Resolved
| The `NullableAttribute` type declaration is synthesized by the compiler if it is not included in the compilation, but is needed to produce the output. | ||
|
|
||
| ```c# | ||
| // C# representation of metadata |
There was a problem hiding this comment.
It would be good to expand this section with an example involving value types (int and Nullable<int>). #Resolved
Closes #30075.
Closes #30065.
Closes #29594.