Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

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

99 files changed

Lines changed: 3071 additions & 158 deletions

File tree

Some content is hidden

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

‎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
<CompileInclude="..\Java.Interop.Tools.TypeNameMappings\Java.Interop.Tools.TypeNameMappings\JavaNativeTypeManager.cs">
2426
<Link>JavaNativeTypeManager.cs</Link>
2527
</Compile>
28+
<CompileInclude="..\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+
<CompileCondition=" '$(TargetFramework)' != 'netstandard2.0' "Remove="NullableAttributes.cs" />
2223
<CompileRemove="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-
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState(IList<T>value,ParameterAttributessynchronize)
161+
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState([MaybeNull]IList<T>value,ParameterAttributessynchronize)
162162
{
163163
returnJavaArray<T>.CreateArgumentState(value,synchronize,(list,copy)=>{
164164
vara=copy
@@ -169,7 +169,7 @@ public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState
169169
});
170170
}
171171

172-
publicoverridevoidDestroyGenericArgumentState(IList<T>value,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
172+
publicoverridevoidDestroyGenericArgumentState([AllowNull]IList<T>value,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
173173
{
174174
JavaArray<T>.DestroyArgumentState<JavaObjectArray<T>>(value,refstate,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
usingSystem;
44
usingSystem.Collections.Generic;
5+
usingSystem.Diagnostics.CodeAnalysis;
56
usingSystem.Linq;
67
usingSystem.Linq.Expressions;
78
usingSystem.Reflection;
@@ -203,7 +204,7 @@ public override JniValueMarshalerState CreateArgumentState (object? value, Param
203204
thrownewNotSupportedException();
204205
}
205206

206-
publicoverrideJniValueMarshalerStateCreateGenericArgumentState(IntPtrvalue,ParameterAttributessynchronize)
207+
publicoverrideJniValueMarshalerStateCreateGenericArgumentState([MaybeNull]IntPtrvalue,ParameterAttributessynchronize)
207208
{
208209
thrownewNotSupportedException();
209210
}
@@ -213,7 +214,7 @@ public override JniValueMarshalerState CreateObjectReferenceArgumentState (objec
213214
thrownewNotSupportedException();
214215
}
215216

216-
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState(IntPtrvalue,ParameterAttributessynchronize)
217+
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState([MaybeNull]IntPtrvalue,ParameterAttributessynchronize)
217218
{
218219
thrownewNotSupportedException();
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-
protectedvirtualstringGetSimpleReference(Typetype)
158+
protectedvirtualstring?GetSimpleReference(Typetype)
159159
{
160160
returnGetSimpleReferences(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-
publicabstractIJavaPeerablePeekPeer(JniObjectReferencereference);
202+
publicabstractIJavaPeerable?PeekPeer(JniObjectReferencereference);
203203

204204
publicobject?PeekValue(JniObjectReferencereference)
205205
{
@@ -261,7 +261,7 @@ static Type GetPeerType (Type type)
261261
returntype;
262262
}
263263

264-
publicvirtualIJavaPeerableCreatePeer(refJniObjectReferencereference,JniObjectReferenceOptionstransfer,Type?targetType)
264+
publicvirtualIJavaPeerable?CreatePeer(refJniObjectReferencereference,JniObjectReferenceOptionstransfer,Type?targetType)
265265
{
266266
if(disposed)
267267
thrownewObjectDisposedException(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(refreference,options,targetType);
401+
#pragma warning restore CS8601// Possible null reference assignment.
400402
}
401403

402404
varmarshaler=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(refreference,options,targetType);
480+
#pragma warning restore CS8601// Possible null reference assignment.
477481
}
478482

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

610-
sealedclassJavaPeerableValueMarshaler:JniValueMarshaler<IJavaPeerable>{
614+
sealedclassJavaPeerableValueMarshaler:JniValueMarshaler<IJavaPeerable?>{
611615

612616
internalstaticJavaPeerableValueMarshalerInstance=newJavaPeerableValueMarshaler();
613617

614618
[return:MaybeNull]
615-
publicoverrideIJavaPeerableCreateGenericValue(refJniObjectReferencereference,JniObjectReferenceOptionsoptions,Type?targetType)
619+
publicoverrideIJavaPeerable?CreateGenericValue(refJniObjectReferencereference,JniObjectReferenceOptionsoptions,Type?targetType)
616620
{
617621
varjvm=JniEnvironment.Runtime;
618622
varmarshaler=jvm.ValueManager.GetValueMarshaler(targetType??typeof(IJavaPeerable));
@@ -621,15 +625,15 @@ public override IJavaPeerable CreateGenericValue (ref JniObjectReference referen
621625
returnjvm.ValueManager.CreatePeer(refreference,options,targetType);
622626
}
623627

624-
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState(IJavaPeerablevalue,ParameterAttributessynchronize)
628+
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState([MaybeNull]IJavaPeerable?value,ParameterAttributessynchronize)
625629
{
626630
if(value==null||!value.PeerReference.IsValid)
627631
returnnewJniValueMarshalerState();
628632
varr=value.PeerReference.NewLocalRef();
629633
returnnewJniValueMarshalerState(r);
630634
}
631635

632-
publicoverridevoidDestroyGenericArgumentState(IJavaPeerablevalue,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
636+
publicoverridevoidDestroyGenericArgumentState([MaybeNull]IJavaPeerable?value,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
633637
{
634638
varr=state.ReferenceValue;
635639
JniObjectReference.Dispose(refr);
@@ -694,12 +698,12 @@ public override T CreateGenericValue (ref JniObjectReference reference, JniObjec
694698
return(T)ValueMarshaler.CreateValue(refreference,options,targetType??typeof(T))!;
695699
}
696700

697-
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState(Tvalue,ParameterAttributessynchronize)
701+
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState([MaybeNull]Tvalue,ParameterAttributessynchronize)
698702
{
699703
returnValueMarshaler.CreateObjectReferenceArgumentState(value,synchronize);
700704
}
701705

702-
publicoverridevoidDestroyGenericArgumentState(Tvalue,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
706+
publicoverridevoidDestroyGenericArgumentState([AllowNull]Tvalue,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
703707
{
704708
ValueMarshaler.DestroyArgumentState(value,refstate,synchronize);
705709
}
@@ -720,12 +724,12 @@ public override Expression CreateReturnValueFromManagedExpression (JniValueMarsh
720724
}
721725
}
722726

723-
sealedclassProxyValueMarshaler:JniValueMarshaler<object>{
727+
sealedclassProxyValueMarshaler:JniValueMarshaler<object?>{
724728

725729
internalstaticProxyValueMarshalerInstance=newProxyValueMarshaler();
726730

727731
[return:MaybeNull]
728-
publicoverrideobjectCreateGenericValue(refJniObjectReferencereference,JniObjectReferenceOptionsoptions,Type?targetType)
732+
publicoverrideobject?CreateGenericValue(refJniObjectReferencereference,JniObjectReferenceOptionsoptions,Type?targetType)
729733
{
730734
varjvm=JniEnvironment.Runtime;
731735

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

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

768-
publicoverridevoidDestroyGenericArgumentState(objectvalue,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
772+
publicoverridevoidDestroyGenericArgumentState(object?value,refJniValueMarshalerStatestate,ParameterAttributessynchronize)
769773
{
770774
varvm=state.ExtraasJniValueMarshaler;
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
usingSystem;
4+
usingSystem.Diagnostics.CodeAnalysis;
45
usingSystem.Linq.Expressions;
56
usingSystem.Reflection;
67
usingSystem.Runtime.CompilerServices;
@@ -18,7 +19,7 @@ sealed class JniStringValueMarshaler : JniValueMarshaler<string?> {
1819
returnJniEnvironment.Strings.ToString(refreference,options,targetType??typeof(string));
1920
}
2021

21-
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState(string?value,ParameterAttributessynchronize)
22+
publicoverrideJniValueMarshalerStateCreateGenericObjectReferenceArgumentState([MaybeNull]string?value,ParameterAttributessynchronize)
2223
{
2324
varr=JniEnvironment.Strings.NewString(value);
2425
returnnewJniValueMarshalerState(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']]"

0 commit comments

Comments
 (0)