-
Notifications
You must be signed in to change notification settings - Fork 63
[generator] Import NRT data from managed assemblies. #912
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| using System; | ||
| using System.Linq; | ||
| using Mono.Cecil; | ||
| using Mono.Collections.Generic; | ||
|
|
||
| namespace Java.Interop.Tools.Cecil | ||
| { | ||
| // Partial support for determining NRT status of method and field types. | ||
| // Reference: https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md | ||
| // The basics are supported, but advanced annotations like array elements, | ||
| // type parameters, and tuples are not supported. Our use case doesn't really need them. | ||
| public static class NullableReferenceTypesRocks | ||
| { | ||
| public static Nullability GetTypeNullability (this FieldDefinition field) | ||
| { | ||
| if (field.FieldType.FullName == "System.Void") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A "concern" here is that this doesn't check for assembly name, so if any yahoo adds I'm not sure of a good way to say "this is a core assembly", so this concern is likely ignorable, but it is something to be aware of.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can live with this. I'm not comfortable trying to enumerate every "core" assembly that they might have put |
||
| return Nullability.NotNull; | ||
|
|
||
| // Look for explicit annotation on field | ||
| var metadata = NullableMetadata.FromAttributeCollection (field.CustomAttributes); | ||
|
|
||
| if (metadata != null) | ||
| return (Nullability) metadata.Data [0]; | ||
|
|
||
| // Default nullability status for type | ||
| return GetNullableContext (field.DeclaringType.CustomAttributes); | ||
| } | ||
|
|
||
| public static Nullability GetReturnTypeNullability (this MethodDefinition method) | ||
| { | ||
| if (method.MethodReturnType.ReturnType.FullName == "System.Void") | ||
| return Nullability.NotNull; | ||
|
|
||
| // Look for explicit annotation on return type | ||
| var metadata = NullableMetadata.FromAttributeCollection (method.MethodReturnType.CustomAttributes); | ||
|
|
||
| if (metadata != null) | ||
| return (Nullability) metadata.Data [0]; | ||
|
|
||
| // Default nullability status for method | ||
| var nullable = GetNullableContext (method.CustomAttributes); | ||
|
|
||
| if (nullable != Nullability.Oblivous) | ||
| return nullable; | ||
|
|
||
| // Default nullability status for type | ||
| return GetNullableContext (method.DeclaringType.CustomAttributes); | ||
| } | ||
|
|
||
| public static Nullability GetTypeNullability (this ParameterDefinition parameter, MethodDefinition method) | ||
| { | ||
| if (parameter.ParameterType.FullName == "System.Void") | ||
| return Nullability.NotNull; | ||
|
|
||
| // Look for explicit annotation on parameter | ||
| var metadata = NullableMetadata.FromAttributeCollection (parameter.CustomAttributes); | ||
|
|
||
| if (metadata != null) | ||
| return (Nullability) metadata.Data [0]; | ||
|
|
||
| // Default nullability status for method | ||
| var nullable = GetNullableContext (method.CustomAttributes); | ||
|
|
||
| if (nullable != Nullability.Oblivous) | ||
| return nullable; | ||
|
|
||
| // Default nullability status for type | ||
| return GetNullableContext (method.DeclaringType.CustomAttributes); | ||
| } | ||
|
|
||
| static Nullability GetNullableContext (Collection<CustomAttribute> attrs) | ||
| { | ||
| var attribute = attrs.FirstOrDefault (t => t.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); | ||
|
|
||
| if (attribute != null) | ||
| return (Nullability) (byte) attribute.ConstructorArguments.First ().Value; | ||
|
|
||
| return Nullability.Oblivous; | ||
| } | ||
| } | ||
|
|
||
| public enum Nullability | ||
| { | ||
| Oblivous, | ||
| NotNull, | ||
| Nullable | ||
| } | ||
|
|
||
| class NullableMetadata | ||
| { | ||
| public byte [] Data { get; private set; } | ||
|
|
||
| NullableMetadata (byte [] data) => Data = data; | ||
|
|
||
| NullableMetadata (byte data) => Data = new [] { data }; | ||
|
|
||
| public static NullableMetadata? FromAttributeCollection (Collection<CustomAttribute> attrs) | ||
| { | ||
| if (attrs is null) | ||
| return null; | ||
|
|
||
| var attribute = attrs.FirstOrDefault (t => t.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); | ||
|
|
||
| if (attribute is null) | ||
| return null; | ||
|
|
||
| var ctor_arg = attribute.ConstructorArguments.First (); | ||
|
|
||
| if (ctor_arg.Value is CustomAttributeArgument [] caa) | ||
| ctor_arg = caa [0]; | ||
|
|
||
| if (ctor_arg.Value is byte b) | ||
| return new NullableMetadata (b); | ||
|
|
||
| if (ctor_arg.Value is byte [] b2) | ||
| return new NullableMetadata (b2); | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
| } | ||
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.
At least for commit message purposes, an elaboration on "our use case doesn't really need them" would be good. Is it not possible for Java code to express the difference between
String?[]?vs.String[]?(possibly nullable array containing possibly null items vs. possibly null array which won't contain any null items)? That's my assumption.At the same time, I now wonder if we're binding arrays & collections wrong; should all arrays permit null items?
As a "spot-check", we're binding
DatabaseUtils.longForQuery(SQLiteDatabase, String, String[])as:And in this case, that looks correct; null items aren't allowed:
Compare to
Arrays.hashCode(Object[]), which we similarly bind as:i.e. array can be null, but elements can't be. Compare to the source code, which does allow for null elements:
implying that we should have bound this as
Object?[]?, notObject[]?.I'm not sure there's a "good" answer here, particularly since the few Java annotations I've seen around nullability work at an "outermost type" level.
However, Kotlin appears to support it:
…but that said, our
//@not-nullattribute (01d0684) only works on the "outermost type" level as well, meaning we can't differentiateArray<String?>fromArray<String?>?anyway…Uh oh!
There was an error while loading. Please reload this page.
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.
Technically some of the annotations we look for do support
string?[]?, likeLorg/jetbrains/annotations/NotNull;:(source)
(My understanding is it's the
ElementType.TYPE_USEthat allows specifyingstring?[].)However the Android ones we primarily use only support
string[]?:(source)
As you pointed out, our tools do not support specifying the array element nullability.
While we probably wouldn't gain much by adding support to our tools since the primary Android annotations don't support it, we probably should update
generatorto always emit nullable array elements, since the default should be nullable unless specified@NotNull.So we should always emit
string?[]orstring?[]?.