Skip to content

Commit 59d86de

Browse files
jpobstjonpryor
authored andcommitted
[generator] Add nullable reference types (NRT) support. (#563)
Fixes: #468 Context: dotnet/android#4227 Add [C#8 nullable reference type][0] (NRT) support to `generator` when given `generator -lang-features=nullable-reference-types`. This uses a variety of Java annotations to infer nullable information (18c29b7) via the `//method/@return-not-null` and `//parameter/@not-null` attribute values within `api.xml` to "forward" nullability information to the generated C# binding. ~~ Goals ~~ `generator` should be able to interpret the nullable annotations provided by an input `.jar` file (via `class-parse`). It should use this information to generate bindings that expose similar nullable annotations on the produced public C# API. For example, this Java: // Java public class Foo { public void bar (@NotNull Object baz, String value) { … } } Should generate this C# API: // C# Binding public class Foo : Java.Lang.Object { public void Bar (Java.Lang.Object baz, string? value) { … } } Additionally, the generated binding code should not produce any additional warnings *on its own*. That is, the internal plumbing code itself should not create warnings. ~~ Non-Goals ~~ There exists cases in our generated plumbing code that do not play nicely with the provability of C#8 nullable reference types. For example, we may generate code like this: int Java.Lang.IComparable.CompareTo (Java.Lang.Object o) { return CompareTo (global::Java.Interop.JavaObjectExtensions.JavaCast<Android.Util.Half>(o)); } Technically `.JavaCast<>()` can return `null`, which cannot be passed to `.CompareTo (object o)` because it does not accept `null`. In these cases we liberally use the [null forgiving operator (`!`)][1] to suppress warnings. It may be desirable to change how this code is structured to be better provably `null`-safe, however this PR does not attempt to make those modification. It is assumed that the code is currently working, so `null` is prevented here via other mechanisms. No functional changes are made to generated code. Additionally, there are cases where Java nullable annotations can create scenarios that will produce warnings in C#, particularly around inheritance. For example: // Java public class Base { public void m (@NotNull Object baz) { … } } public class Derived extends Base { @OverRide public void m (Object baz) { … } } This would produce a C# warning such as: CS8610: Nullability of reference types in type of parameter 'M' doesn't match overridden member. `generator` will not attempt to resolve this error, it is an exercise for the user. This can be accomplished by fixing the Java code or using `metadata` to override the `//@not-null` attribute such as: <attr path="/api/package[@name='blah']/class[@name='Foo2']/method[@name='Bar' and count(parameter)=1 and parameter[1][@type='object']]/parameter" name="not-null">true</attr> ~~ Unit Test Changes ~~ Several of the unit test "expected output" files changed their property type from `java.lang.String` to `string`. This occurred due to a related refactoring of parameter & return type generation code. This change shouldn't be "user visible" because the unit tests don't go through a "complete" pipeline which would involve ensuring that get- and set-method pairs have consistent parameter & return types. [0]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references [1]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving
1 parent 64c2719 commit 59d86de

File tree

99 files changed

+3071
-158
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+3071
-158
lines changed

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5+
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
6+
<LangVersion>8.0</LangVersion>
57
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
68
</PropertyGroup>
79

@@ -23,6 +25,9 @@
2325
<Compile Include="..\Java.Interop.Tools.TypeNameMappings\Java.Interop.Tools.TypeNameMappings\JavaNativeTypeManager.cs">
2426
<Link>JavaNativeTypeManager.cs</Link>
2527
</Compile>
28+
<Compile Include="..\Java.Interop\NullableAttributes.cs">
29+
<Link>NullableAttributes.cs</Link>
30+
</Compile>
2631
</ItemGroup>
2732

2833
<ItemGroup>

src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs

Lines changed: 47 additions & 43 deletions
Large diffs are not rendered by default.

src/Java.Interop/Java.Interop.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<DefineConstants>DEBUG;$(DefineConstants)</DefineConstants>
2020
</PropertyGroup>
2121
<ItemGroup>
22+
<Compile Condition=" '$(TargetFramework)' != 'netstandard2.0' " Remove="NullableAttributes.cs" />
2223
<Compile Remove="Java.Interop\JniLocationException.cs" />
2324
</ItemGroup>
2425
<PropertyGroup>

src/Java.Interop/Java.Interop/JavaObjectArray.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public override IList<T> CreateGenericValue (ref JniObjectReference reference, J
158158
});
159159
}
160160

161-
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (IList<T> value, ParameterAttributes synchronize)
161+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]IList<T> value, ParameterAttributes synchronize)
162162
{
163163
return JavaArray<T>.CreateArgumentState (value, synchronize, (list, copy) => {
164164
var a = copy
@@ -169,7 +169,7 @@ public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState
169169
});
170170
}
171171

172-
public override void DestroyGenericArgumentState (IList<T> value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
172+
public override void DestroyGenericArgumentState ([AllowNull]IList<T> value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
173173
{
174174
JavaArray<T>.DestroyArgumentState<JavaObjectArray<T>> (value, ref state, synchronize);
175175
}

src/Java.Interop/Java.Interop/JniRuntime.JniMarshalMemberBuilder.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Linq;
67
using System.Linq.Expressions;
78
using System.Reflection;
@@ -203,7 +204,7 @@ public override JniValueMarshalerState CreateArgumentState (object? value, Param
203204
throw new NotSupportedException ();
204205
}
205206

206-
public override JniValueMarshalerState CreateGenericArgumentState (IntPtr value, ParameterAttributes synchronize)
207+
public override JniValueMarshalerState CreateGenericArgumentState ([MaybeNull]IntPtr value, ParameterAttributes synchronize)
207208
{
208209
throw new NotSupportedException ();
209210
}
@@ -213,7 +214,7 @@ public override JniValueMarshalerState CreateObjectReferenceArgumentState (objec
213214
throw new NotSupportedException ();
214215
}
215216

216-
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (IntPtr value, ParameterAttributes synchronize)
217+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]IntPtr value, ParameterAttributes synchronize)
217218
{
218219
throw new NotSupportedException ();
219220
}

src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ static Type GetUnderlyingType (Type type, out int rank)
155155
}
156156

157157
// `type` will NOT be an array type.
158-
protected virtual string GetSimpleReference (Type type)
158+
protected virtual string? GetSimpleReference (Type type)
159159
{
160160
return GetSimpleReferences (type).FirstOrDefault ();
161161
}

src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public virtual void DisposePeerUnlessReferenced (IJavaPeerable value)
199199
DisposePeer (h, value);
200200
}
201201

202-
public abstract IJavaPeerable PeekPeer (JniObjectReference reference);
202+
public abstract IJavaPeerable? PeekPeer (JniObjectReference reference);
203203

204204
public object? PeekValue (JniObjectReference reference)
205205
{
@@ -261,7 +261,7 @@ static Type GetPeerType (Type type)
261261
return type;
262262
}
263263

264-
public virtual IJavaPeerable CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type? targetType)
264+
public virtual IJavaPeerable? CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type? targetType)
265265
{
266266
if (disposed)
267267
throw new ObjectDisposedException (GetType ().Name);
@@ -396,7 +396,9 @@ public T CreateValue<T> (ref JniObjectReference reference, JniObjectReferenceOpt
396396
targetType = targetType ?? typeof (T);
397397

398398
if (typeof (IJavaPeerable).IsAssignableFrom (targetType)) {
399+
#pragma warning disable CS8601 // Possible null reference assignment.
399400
return (T) JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType);
401+
#pragma warning restore CS8601 // Possible null reference assignment.
400402
}
401403

402404
var marshaler = GetValueMarshaler<T> ();
@@ -473,7 +475,9 @@ public T GetValue<T> (ref JniObjectReference reference, JniObjectReferenceOption
473475
}
474476

475477
if (typeof (IJavaPeerable).IsAssignableFrom (targetType)) {
478+
#pragma warning disable CS8601 // Possible null reference assignment.
476479
return (T) JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType);
480+
#pragma warning restore CS8601 // Possible null reference assignment.
477481
}
478482

479483
var marshaler = GetValueMarshaler<T> ();
@@ -607,12 +611,12 @@ public override void DestroyArgumentState (object? value, ref JniValueMarshalerS
607611
}
608612
}
609613

610-
sealed class JavaPeerableValueMarshaler : JniValueMarshaler<IJavaPeerable> {
614+
sealed class JavaPeerableValueMarshaler : JniValueMarshaler<IJavaPeerable?> {
611615

612616
internal static JavaPeerableValueMarshaler Instance = new JavaPeerableValueMarshaler ();
613617

614618
[return: MaybeNull]
615-
public override IJavaPeerable CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType)
619+
public override IJavaPeerable? CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType)
616620
{
617621
var jvm = JniEnvironment.Runtime;
618622
var marshaler = jvm.ValueManager.GetValueMarshaler (targetType ?? typeof(IJavaPeerable));
@@ -621,15 +625,15 @@ public override IJavaPeerable CreateGenericValue (ref JniObjectReference referen
621625
return jvm.ValueManager.CreatePeer (ref reference, options, targetType);
622626
}
623627

624-
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (IJavaPeerable value, ParameterAttributes synchronize)
628+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]IJavaPeerable? value, ParameterAttributes synchronize)
625629
{
626630
if (value == null || !value.PeerReference.IsValid)
627631
return new JniValueMarshalerState ();
628632
var r = value.PeerReference.NewLocalRef ();
629633
return new JniValueMarshalerState (r);
630634
}
631635

632-
public override void DestroyGenericArgumentState (IJavaPeerable value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
636+
public override void DestroyGenericArgumentState ([MaybeNull]IJavaPeerable? value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
633637
{
634638
var r = state.ReferenceValue;
635639
JniObjectReference.Dispose (ref r);
@@ -694,12 +698,12 @@ public override T CreateGenericValue (ref JniObjectReference reference, JniObjec
694698
return (T) ValueMarshaler.CreateValue (ref reference, options, targetType ?? typeof (T))!;
695699
}
696700

697-
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (T value, ParameterAttributes synchronize)
701+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]T value, ParameterAttributes synchronize)
698702
{
699703
return ValueMarshaler.CreateObjectReferenceArgumentState (value, synchronize);
700704
}
701705

702-
public override void DestroyGenericArgumentState (T value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
706+
public override void DestroyGenericArgumentState ([AllowNull]T value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
703707
{
704708
ValueMarshaler.DestroyArgumentState (value, ref state, synchronize);
705709
}
@@ -720,12 +724,12 @@ public override Expression CreateReturnValueFromManagedExpression (JniValueMarsh
720724
}
721725
}
722726

723-
sealed class ProxyValueMarshaler : JniValueMarshaler<object> {
727+
sealed class ProxyValueMarshaler : JniValueMarshaler<object?> {
724728

725729
internal static ProxyValueMarshaler Instance = new ProxyValueMarshaler ();
726730

727731
[return: MaybeNull]
728-
public override object CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType)
732+
public override object? CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType)
729733
{
730734
var jvm = JniEnvironment.Runtime;
731735

@@ -748,7 +752,7 @@ public override object CreateGenericValue (ref JniObjectReference reference, Jni
748752
return jvm.ValueManager.CreatePeer (ref reference, options, targetType);
749753
}
750754

751-
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (object value, ParameterAttributes synchronize)
755+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]object? value, ParameterAttributes synchronize)
752756
{
753757
if (value == null)
754758
return new JniValueMarshalerState ();
@@ -765,7 +769,7 @@ public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState
765769
return new JniValueMarshalerState (p!.PeerReference.NewLocalRef ());
766770
}
767771

768-
public override void DestroyGenericArgumentState (object value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
772+
public override void DestroyGenericArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
769773
{
770774
var vm = state.Extra as JniValueMarshaler;
771775
if (vm != null) {

src/Java.Interop/Java.Interop/JniStringValueMarshaler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#nullable enable
22

33
using System;
4+
using System.Diagnostics.CodeAnalysis;
45
using System.Linq.Expressions;
56
using System.Reflection;
67
using System.Runtime.CompilerServices;
@@ -18,7 +19,7 @@ sealed class JniStringValueMarshaler : JniValueMarshaler<string?> {
1819
return JniEnvironment.Strings.ToString (ref reference, options, targetType ?? typeof (string));
1920
}
2021

21-
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (string? value, ParameterAttributes synchronize)
22+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]string? value, ParameterAttributes synchronize)
2223
{
2324
var r = JniEnvironment.Strings.NewString (value);
2425
return new JniValueMarshalerState (r);

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceProperties.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ int Count {
55
[Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set;
66
}
77

8-
java.lang.String Key {
8+
string Key {
99
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Key' and count(parameter)=0]"
1010
[Register ("get_Key", "()Ljava/lang/String;", "Getget_KeyHandler:java.code.IMyInterfaceInvoker, ")] get;
1111
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]"

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterface.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public partial interface IMyInterface : IJavaObject, IJavaPeerable {
3939
[Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set;
4040
}
4141

42-
java.lang.String Key {
42+
string Key {
4343
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Key' and count(parameter)=0]"
4444
[Register ("get_Key", "()Ljava/lang/String;", "Getget_KeyHandler:java.code.IMyInterfaceInvoker, ")] get;
4545
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]"

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDeclaration.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public partial interface IMyInterface : IJavaObject, IJavaPeerable {
99
[Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set;
1010
}
1111

12-
java.lang.String Key {
12+
string Key {
1313
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Key' and count(parameter)=0]"
1414
[Register ("get_Key", "()Ljava/lang/String;", "Getget_KeyHandler:java.code.IMyInterfaceInvoker, ")] get;
1515
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
2+
{
3+
return GetEnumerator ();
4+
}
5+
6+
public System.Collections.Generic.IEnumerator<char> GetEnumerator ()
7+
{
8+
for (int i = 0; i < Length(); i++)
9+
yield return CharAt (i);
10+
}
11+

0 commit comments

Comments
 (0)