Skip to content
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

[generator] Add nullable reference types (NRT) support. #563

Merged
merged 14 commits into from
Apr 22, 2020
Merged

Conversation

jpobst
Copy link
Contributor

@jpobst jpobst commented Feb 4, 2020

Context: #468
XA Companion PR: dotnet/android#4227

Adds nullable reference types (NRT) support in generator, when passed the flag -lang-features=nullable-reference-types, using Java's @NotNull annotations to annotate the C# public API.

Goals

Surface Java Provided Annotations

generator should be able to interpret the nullable annotations provided by an input jar (technically the info provided by class-parse). It should use this information to generate bindings that expose the same nullable annotations on the produced public C# API.

For example, this Java:

class Foo {
  void Bar (@NotNull object baz, string value) { ... }
}

Should generate this C# API:

class Foo {
  void Bar (object baz, string? value) { ... }
}

No Additional Infrastructure Warnings

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

Generated Binding Code Audit

There exists cases in our generated plumbing code that do not play nicely with the provability of NRT. 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 forgiveness operator (!) 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.

Fixing "Wrong" Java Annotations

There are cases where Java nullable annotations can create scenarios that will produce warnings in C#, particularly around inheritance. For example:

class Foo {
  void Bar (@NotNull object baz) { ... }
}

class Foo2 : Foo {
  @override void Bar (object baz) { ... }
}

This would produce a C# warning such as:

CS8610: Nullability of reference types in type of parameter 'baz' 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 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>

Release Notes

I don't think this change needs to be specifically called out, this is just a reminder to call it out in the 2 places it will be visible to users:

@jpobst jpobst force-pushed the generator-nrt branch 3 times, most recently from e22c29a to 0bfc0e9 Compare April 2, 2020 18:53
@dotnet dotnet deleted a comment from azure-pipelines bot Apr 2, 2020
@jpobst jpobst changed the title [WIP] [generator] Enable NRT support. [generator] Add NRT support. Apr 10, 2020
@jpobst jpobst changed the title [generator] Add NRT support. [generator] Add nullable reference types (NRT) support. Apr 10, 2020
@jpobst jpobst marked this pull request as ready for review April 10, 2020 17:01
@jpobst jpobst requested a review from jonpryor April 10, 2020 17:01
@@ -233,12 +234,12 @@ public override Boolean CreateGenericValue (ref JniObjectReference reference, Jn
return JniBoolean.GetValueFromJni (ref reference, options, targetType);
}

public override JniValueMarshalerState CreateGenericArgumentState (Boolean value, ParameterAttributes synchronize = ParameterAttributes.In)
public override JniValueMarshalerState CreateGenericArgumentState ([MaybeNull]Boolean value, ParameterAttributes synchronize = ParameterAttributes.In)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have commit message info for why the introduction of [MaybeNull]? Why wasn't this needed before?

Additionally, there should be a corresponding change to src/Java.Interop/Java.Interop/JniBuiltinMarshalers.tt. Visual Studio (for Mac?) should update JniBuiltinMarshalers.cs based on JniBuiltinMarshalers.tt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably "too late", but did we need the Java.Interop.dll changes intermixed with the generator changes?

Copy link
Contributor Author

@jpobst jpobst Apr 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method it is overriding specifies [MaybeNull], so you get a "nullability mismatch warning":

https://github.com/xamarin/java.interop/blob/master/src/Java.Interop/Java.Interop/JniValueMarshaler.cs#L221

I can't find an instance of it right now, from what I can tell this warning was still being tweaked by the Roslyn team, and it only appears on newer versions of the C# compiler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed these changes for now, as they'll need to be done in the .tt files. There's about 60 warnings that I am seeing now:

https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=3665517&view=logs&j=f31c9f97-4411-58e7-49ac-fc73f645e6b6&t=cbeb8522-7b64-5cc0-7bee-eff0e164a409

I don't know how I missed these before. I suggest we go ahead and continue getting this PR merged and then we can go back and clean up those warnings.

@@ -246,7 +247,7 @@ public override IList<Boolean> CreateGenericValue (ref JniObjectReference refere
(ref JniObjectReference h, JniObjectReferenceOptions o) => new JavaBooleanArray (ref h, o));
}

public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (IList<Boolean> value, ParameterAttributes synchronize)
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]IList<Boolean> value, ParameterAttributes synchronize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes should likewise prompt a change to src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt.

@@ -5,7 +5,7 @@ int Count {
[Register ("set_Count", "(I)V", "Getset_Count_IHandler:java.code.IMyInterfaceInvoker, ")] set;
}

java.lang.String Key {
string Key {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change concerns me: why did it occur? For that matter, how was it java.lang.String in the first place?

Fortunately this doesn't trigger any API breakage in Mono.Android.dll, as per https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=3648625&view=results

I just find this really weird. What change to generator is triggering this? Was it deliberate? What's the rationale?

I don't see this mentioned in the commit message.

Copy link
Contributor Author

@jpobst jpobst Apr 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is that previously we were writing this type from property.Type, which is Setter != null ? Setter.Parameters [0].Type : Getter.ReturnType.

https://github.com/xamarin/java.interop/blob/master/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs#L1690

This was refactored and combined with other places that were writing property.Getter.ReturnType, to a new method which always prefers Getter.ReturnType:

https://github.com/xamarin/java.interop/pull/563/files#diff-18357c6193fe262963d21bb1d0bab9feR99-R105

The test is a unit test and does not go through the full pipeline of resolving all the types, thus its Setter.Parameters [0].Type isn't changing from java.lang.String to string, but its Getter.ReturnType is getting resolved to string.

That is, the unit test code is incorrectly this:

  • Setter.Parameters [0].Type = java.lang.String
  • Getter.ReturnType = string

In the real world, Setter.Parameters [0].Type and Getter.ReturnType should always refer to the same symbol, so this should not affect real code.

@jonpryor jonpryor merged commit 01d0684 into master Apr 22, 2020
@jonpryor jonpryor deleted the generator-nrt branch April 22, 2020 20:01
jonpryor pushed a commit that referenced this pull request Apr 22, 2020
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
@jpobst jpobst added this to the 10.4 (16.7 / 8.7) milestone Apr 23, 2020
@github-actions github-actions bot locked and limited conversation to collaborators Apr 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants