-
Notifications
You must be signed in to change notification settings - Fork 54
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
[Java.Interop] Add JniTypeSignatureAttribute.InvokerType #1284
Conversation
Context: #1263 Context: #1263 (comment) We want the default trimmer infrastructure to be able to automatically preserve the `*Invoker` types which are required for interacting with `abstract` classes and interfaces. The most straightforward way to do this is to add a new `InvokerType` property to `JniTypeSignatureAttribute` (and eventually `RegisterAttribute`): partial class JniTypeSignatureAttribute { [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] public Type? InvokerType {get; set;} } Update `generator` so that `generator --codegen-target=JavaInterop1` output sets this new property on abstract classes and interfaces: namespace Java.Lang { [Java.Interop.JniTypeSignatureAttribute("java/lang/Runnable", GenerateJavaPeer=false, InvokerType=typeof(Java.Lang.IRunnableInvoker))] public partial interface IRunnable { } internal partial class IRunnableInvoker { } [Java.Interop.JniTypeSignatureAttribute("java/lang/Number", GenerateJavaPeer=false, InvokerType=typeof(Java.Lang.NumberInvoker))] public abstract partial class Number { } internal partial class NumberInvoker { } } This allows the default trimmer to automatically preserve the `*Invoker` type and constructors. Update `Java.Interop.JniRuntime.JniValueManager` to no longer look for `*Invoker` types "by string", and instead require use of the `JniTypeSignatureAttribute.InvokerType` property. Update unit tests and expected output so that everything works. Note: XAJavaInterop1 output is impacted, but it shouldn't matter, as it changes the `RegisterAttribute` `connector` on interfaces, which isn't *used* by anything: // old and busted [Register ("xamarin/test/NotificationCompatBase$Action$Factory", "", "Xamarin.Test.NotificationCompatBase/Action/IFactoryInvoker")] // new hawtness [Register ("xamarin/test/NotificationCompatBase$Action$Factory", "", "Xamarin.Test.NotificationCompatBase.Action.IFactoryInvoker")] Further note that the old value is non-sensical.
I am concerned that this might actually be a problem. The non-sensical format is the format used by At the very least we should run a |
Context: dotnet/java-interop#1284 Does It Build™?
And dotnet/android#9596 says that it doesn't even build, because it can't get past the src/Mono.Android API compatibility check:
(among many many others) The change from |
Context: dotnet/java-interop#1284 Does It Build™?
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = assemblyGetTypeMessage)] | ||
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = assemblyGetTypeMessage)] | ||
[return: DynamicallyAccessedMembers (Constructors)] | ||
static Type? AssemblyGetType (Assembly assembly, string typeName) => | ||
assembly.GetType (typeName); |
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.
It's good to remove this, it's probably not called on Android anyway, and this is used instead:
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] | ||
public Type? InvokerType {get; set;} |
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.
The trimmer warnings might not require this, but should this also preserve interfaces? Just thinking generally, if it needs to.
In theory, that would make every interface preserve its interface implementations?
- Trimmer decides
IFoo
is used, and wants to keep the type InvokerType=typeof(FooInvoker)
preserves the invokerInvokerType=typeof(FooInvoker)
then could also preserve all interface implementations ofIFoo
?
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.
I think the TL;DR is that "as-is", the NativeAOT linker cannot be made to do what we want it to do, and I'm not sure if it'll "ever" be supported in some "reasonable" timeframe.
I would also consider not using
All
unless we absolutely have to.All
is kind of weird in that it will keep really everything about the type, so for example if the type implements an interface it will force all the methods on that interface to be kept (for everybody) and so on.
Part of the issue is that this InvokerType
property is used for both abstract classes and interfaces.
The semantic we "need" is "preserve all method overrides/interface implementations." We can't express that semantic right now (afaik).
Trying to "fake" the above semantic with "okay, just preserve everything on the type" means that we'll preserve everything on the type. This is "fine" for interfaces (but see below), but is not fine for abstract classes. Consider LauncherActivity
: as it's an Context+Activity subclass, it contains dozens of members through the inheritance chain. We almost certainly do not need most of that infrastructure; we only need the infrastructure used by the method override!
Since, afaik, we can't actually get our desired semantic right now, and "preserve everything" looks like it would have terrible implications on app size when applied to abstract classes, I see only two solutions here:
InterfaceInvokerType
vs.AbstractClassInvokerType
properties! Which is possible, but ugh.- Go with the least restrictive yet useful constraint (preserve constructors), and rely on "stuff elsewhere" to ensure things work and we get closer to our ideal semantic.
Additionally, "preserve everything" isn't even a useful constraint on interfaces! It's mostly correct, for non-default instance methods. Pull in a default method, or static members, and we certainly should be able to trim those away!
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.
Yeah, I was just suggesting interfaces, not All
, but yeah that doesn't make sense for abstract classes.
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.
I will defer to others on what the correct linker semantics are, but how we are generating the change looks good to me. 😉
Context: #1263
Context: #1263 (comment)
We want the default trimmer infrastructure to be able to automatically preserve the
*Invoker
types which are required for interacting withabstract
classes and interfaces.The most straightforward way to do this is to add a new
InvokerType
property toJniTypeSignatureAttribute
(and eventuallyRegisterAttribute
):Update
generator
so thatgenerator --codegen-target=JavaInterop1
output sets this new property on abstract classes and interfaces:This allows the default trimmer to automatically preserve the
*Invoker
type and constructors.Update
Java.Interop.JniRuntime.JniValueManager
to no longer look for*Invoker
types "by string", and instead require use of theJniTypeSignatureAttribute.InvokerType
property.Update unit tests and expected output so that everything works.