Closed terrajobst closed 3 years ago
The linker will warn if there's a
typeof(NullableAttribute)
in the code needed by the app (I assume this code here would do that). It's basically to guard against the cases where it wants to remove an attribute which the app might need.
No, this code checks by name because the framework doesn't define NullableAttribute
nor NullableContextAttribute
, they are emitted by the compiler and embedded in each assembly.
I was told that by default we don't trim user code, so presumably that would mean that the types as well as the attribute applications in user code would remain intact; that should address the 90% case for ASP.NET/EF scenarios.
When a user opts into trimming of their own assemblies, presumably there is a way to configure what to root?
It's true that for now we default to not trim app code, only our frameworks. So it could still be an issue if the code ever needs this information on a framework type.
But we need to solve this going forward since we want to be able to fully trim applications - including the app's code.
A reminder: last I checked, the mono linker removes nullability annotations when trimming assemblies. Since the functionality proposed here operates over runtime assemblies rather than reference assemblies, we'll need to figure out how to reconcile this. I don't know if there are any tracking issues for this on the mono side.
Also we set the flag for the compiler to not emit nullable metadata for non visible outside the assembly APIs; I don't know how interesting it would be for apps to figure out nullability for internal/private APIs of the framework, I guess that is a non-goal for this API but something to add on the docs?
I don't know how interesting it would be for apps to figure out nullability for internal/private APIs of the framework
From what I saw, even nullability annotations on public APIs are being trimmed from System.Private.CoreLib and other assemblies.
All nullable annotations are removed - because so far almost nothing needed it - and it's a notable size improvement doing this (there's SO MANY nullable annotations everywhere). Maybe we could only remove them on non-public items, but that would probably still not work correctly for the app's code itself.
Maybe we could only remove them on non-public items
Right, but that wouldn't be the linker's job. The compiler has a switch to not emit metadata for non-public items, which we currently use for our assemblies.
@safern
I don't know how interesting it would be for apps to figure out nullability for internal/private APIs of the framework, I guess that is a non-goal for this API but something to add on the docs?
Franky I don't care how important that is for app authors -- my opinion is that we should do nothing to help support this :-)
I could understand the desire for public annotations, but even that feels fringe TBH.
@vitek-karas
All nullable annotations are removed - because so far almost nothing needed it - and it's a notable size improvement doing this (there's SO MANY nullable annotations everywhere). Maybe we could only remove them on non-public items, but that would probably still not work correctly for the app's code itself.
When you say "all" you mean including user code? That would be problematic, but I guess similar to serialization the areas where this matters could be isolated.
my opinion is that we should do nothing to help support this :-)
Completely agreed. Just something worth including in the docs?
my opinion is that we should do nothing to help support this :-)
Completely agreed. Just something worth including in the docs?
We support the APIs we're documenting and we don't document private APIs, so I don't think we need to call this out explicitly. However, the linker docs should clarify what information is stripped from public APIs, such as custom attributes and nullable annotation because it's at least counter intuitive that these annotations are sometimes there and sometimes missing.
A reminder: last I checked, the mono linker removes nullability annotations when trimming assemblies.
More specifically, it only removes the attributes if the runtime is Mono, because the instruction to remove the attributes are embedded in Mono's CoreLib. We really need to address #48217 and have consistency between runtimes in this respect.
Related issue: Nullable annotations metadata on non-public members #52879
I have updated the proposal to address the feedback.
Also we set the flag for the compiler to not emit nullable metadata for non visible outside the assembly APIs; I don't know how interesting it would be for apps to figure out nullability for internal/private APIs of the framework, I guess that is a non-goal for this API but something to add on the docs?
If the member is private or internal we could check if the module has the NullablePublicOnlyAttribute set and return NullableState.Unknown
if the attribute is set
I have updated the proposal to address the feedback.
Thanks @terrajobst i would like to propose few updates to the proposal:
public sealed class NullabilityInfoContext
{
public NullabilityInfo Create(ParameterInfo parameterInfo); // existing APIs
...
public NullabilityInfo Create(MethodBase methodBase); // add this overload for parsing nullability of a method return value
}
public enum NullableState
{
Undefined, // for me sounds better than Unknown :)
NonNullable, // NotNull is confusing with the `System.Diagnostics.CodeAnalysis.NotNull` attribute
Nullable, // MaybeNull is confusing with the `System.Diagnostics.CodeAnalysis.MaybeNull ` attribute
// probably add below states for the nullability states depending on other attributes
MaybeNullWhen, // result depend on MaybeNullWhenAttribute within CustomAttributes
NotNullWhen, // result depend on NotNullWhenAttribute in CustomAttributes
NotNullIfNotNull // this one probably redundant, result depend on NotNullIfNotNullAttribute in CustomAttributes
}
The latest proposal Immo edited at the top looks good to me.
[MaybeNullWhen]
should count as a "maybe null" return answer.NullabilityInfo.GenericTypeArguments
to a non-nullable array to match standard reflection API practices.
NullabilityInfo
objects are expected to be freshly returned each time there's no parallel-caller mutation concern.MaybeNull
member to Nullable
NullabilityState
.namespace System.Reflection
{
public sealed class NullabilityInfoContext
{
public NullabilityInfo Create(ParameterInfo parameterInfo);
public NullabilityInfo Create(PropertyInfo propertyInfo);
public NullabilityInfo Create(EventInfo eventInfo);
public NullabilityInfo Create(FieldInfo parameterInfo);
}
public sealed class NullabilityInfo
{
public Type Type { get; }
public NullabilityState ReadState { get; }
public NullabilityState WriteState { get; }
public NullabilityInfo? ElementType { get; }
public NullabilityInfo[] GenericTypeArguments { get; }
}
public enum NullabilityState
{
Unknown,
NotNull,
Nullable
}
}
Nit, it still says NullableState above.
I'm listening to the recording and I feel compelled to mention that if you want to handle this in combination with Nullable<T>
, then the scenario [NotNull] int? Prop { get; }
should give "not null" for its ReadState and [DisallowNull] int? Prop { get; set; }
should give "not null" for its WriteState.
Nit, it still says NullableState above.
Fixed, thanks!
Is this happening in .NET 6? We'd like to take a dependency on it.
Yes, #54985 is intended to be included in Preview 7.
With C# 8, developers will be able to express whether a given reference type can be null:
(Please note that existing code that wasn't compiled using C# 8 and nullable turned on is considered to be unknown.)
This information isn't only useful for the compiler but also attractive for reflection-based tools to provide a better experience. For example:
MVC
[Required]
, or resort to additional null-checksstring?
but not nested, such asIEnumerable<string?>
EF
The nullable information is persisted in metadata using custom attributes. In principle, any interested party can already read the custom attributes without additional work from the BCL. However, this is not ideal because the encoding is somewhat non-trivial:
It's tempting to think of nullable information as additional information on
System.Type
. However, we can't just expose an additional property onType
because at runtime there is no difference betweenstring
(unknown),string?
(nullable), andstring
(non-null). So we'd have to expose some sort of API that allows consumers to walk the type structure and getting information.Unifying nullable-value types and nullable-reference types
It was suggested that these APIs also return
NullableState.MaybeNull
for nullable value types, which seems desirable indeed. Boxing a nullable value type causes the non-nullable representation to be boxed. Which also means you can always cast a boxed non-nullable value type to its nullable representation. Since the reflection API surface is exclusively aroundobject
it seems logical to unify these two models. For customers that want to differentiate the two, they can trivially check the top-level type to see whether it's a reference type or not.API proposal
Sample usage
Getting top-level nullability information
Getting nested nullability information
Custom Attributes
The following custom attributes in
System.Diagnostics.CodeAnalysis
are processed and combined with type information:[AllowNull]
[DisallowNull]
[MaybeNull]
[NotNull]
The following attributes aren't processed because they don't annotate static state but information related to dataflow:
[DoesNotReturn]
[DoesNotReturnIf]
[MaybeNullWhen]
[MemberNotNull]
[MemberNotNullWhen]
[NotNullIfNotNull]
[NotNullWhen]
@dotnet/nullablefc @dotnet/ldm @dotnet/fxdc @rynowak @divega @ajcvickers @roji @steveharter