Open moljac opened 4 years ago
Background: changing type
is usually considered Bad™, because the type
value is used to compute the JNI signature for member lookups, and if it changes it could prevent the member from being found. Thus, changing managedType
is preferred, as it shouldn't impact the JNI signatures.
The diff
in the generated code between the "working yet wrong" version which changes type
and the "broken yet 'correct' (?)" version which changes managedType
is interesting:
--- ../androidx.leanback.leanback-preference-obj/Debug/monoandroid90/generated/src/AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.cs 2020-02-26 13:10:39.000000000 -0500
+++ androidx.leanback.leanback-preference/obj/Debug/monoandroid90/generated/src/AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.cs 2020-02-26 13:12:37.000000000 -0500
@@ -132,28 +132,28 @@
}
}
- static Delegate cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I;
+ static Delegate cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I;
#pragma warning disable 0169
- static Delegate GetOnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_IHandler ()
+ static Delegate GetOnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_IHandler ()
{
- if (cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I == null)
- cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr, int>) n_OnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I);
- return cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I;
+ if (cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I == null)
+ cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr, int>) n_OnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I);
+ return cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I;
}
- static void n_OnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I (IntPtr jnienv, IntPtr native__this, IntPtr native_holder, int position)
+ static void n_OnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I (IntPtr jnienv, IntPtr native__this, IntPtr native_holder, int position)
{
global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.AdapterMulti __this = global::Java.Lang.Object.GetObject<global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.AdapterMulti> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
- global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder holder = global::Java.Lang.Object.GetObject<global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder> (native_holder, JniHandleOwnership.DoNotTransfer);
+ global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.ViewHolder holder = global::Java.Lang.Object.GetObject<global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.ViewHolder> (native_holder, JniHandleOwnership.DoNotTransfer);
__this.OnBindViewHolder (holder, position);
}
#pragma warning restore 0169
- // Metadata.xml XPath method reference: path="/api/package[@name='androidx.leanback.preference']/class[@name='LeanbackListPreferenceDialogFragment.AdapterMulti']/method[@name='onBindViewHolder' and count(parameter)=2 and parameter[1][@type='AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder'] and parameter[2][@type='int']]"
- [Register ("onBindViewHolder", "(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V", "GetOnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_IHandler")]
- public override unsafe void OnBindViewHolder (global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder holder, int position)
+ // Metadata.xml XPath method reference: path="/api/package[@name='androidx.leanback.preference']/class[@name='LeanbackListPreferenceDialogFragment.AdapterMulti']/method[@name='onBindViewHolder' and count(parameter)=2 and parameter[1][@type='androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder'] and parameter[2][@type='int']]"
+ [Register ("onBindViewHolder", "(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V", "GetOnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_IHandler")]
+ public virtual unsafe void OnBindViewHolder (global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder holder, int position)
{
- const string __id = "onBindViewHolder.(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V";
+ const string __id = "onBindViewHolder.(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [2];
__args [0] = new JniArgumentValue ((holder == null) ? IntPtr.Zero : ((global::Java.Lang.Object) holder).Handle);
@@ -351,28 +351,28 @@
}
}
- static Delegate cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I;
+ static Delegate cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I;
#pragma warning disable 0169
- static Delegate GetOnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_IHandler ()
+ static Delegate GetOnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_IHandler ()
{
- if (cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I == null)
- cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr, int>) n_OnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I);
- return cb_onBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I;
+ if (cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I == null)
+ cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr, int>) n_OnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I);
+ return cb_onBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I;
}
- static void n_OnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I (IntPtr jnienv, IntPtr native__this, IntPtr native_holder, int position)
+ static void n_OnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_I (IntPtr jnienv, IntPtr native__this, IntPtr native_holder, int position)
{
global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.AdapterSingle __this = global::Java.Lang.Object.GetObject<global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.AdapterSingle> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
- global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder holder = global::Java.Lang.Object.GetObject<global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder> (native_holder, JniHandleOwnership.DoNotTransfer);
+ global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.ViewHolder holder = global::Java.Lang.Object.GetObject<global::AndroidX.Leanback.Preference.LeanbackListPreferenceDialogFragment.ViewHolder> (native_holder, JniHandleOwnership.DoNotTransfer);
__this.OnBindViewHolder (holder, position);
}
#pragma warning restore 0169
- // Metadata.xml XPath method reference: path="/api/package[@name='androidx.leanback.preference']/class[@name='LeanbackListPreferenceDialogFragment.AdapterSingle']/method[@name='onBindViewHolder' and count(parameter)=2 and parameter[1][@type='AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder'] and parameter[2][@type='int']]"
- [Register ("onBindViewHolder", "(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V", "GetOnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_IHandler")]
- public override unsafe void OnBindViewHolder (global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder holder, int position)
+ // Metadata.xml XPath method reference: path="/api/package[@name='androidx.leanback.preference']/class[@name='LeanbackListPreferenceDialogFragment.AdapterSingle']/method[@name='onBindViewHolder' and count(parameter)=2 and parameter[1][@type='androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder'] and parameter[2][@type='int']]"
+ [Register ("onBindViewHolder", "(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V", "GetOnBindViewHolder_Landroidx_leanback_preference_LeanbackListPreferenceDialogFragment_ViewHolder_IHandler")]
+ public virtual unsafe void OnBindViewHolder (global::AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder holder, int position)
{
- const string __id = "onBindViewHolder.(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V";
+ const string __id = "onBindViewHolder.(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [2];
__args [0] = new JniArgumentValue ((holder == null) ? IntPtr.Zero : ((global::Java.Lang.Object) holder).Handle);
Of particular note is the OnBindViewHolder()
changed from override
to virtual
.
It appears that virtual
-vs-override
detection should be based on the managed name, and not based on the Java name, so that Metadata can be used to coerce generator
to emit overrides
when necessary.
Also of note is that the JNI signature changed, which is to be expected, e.g.
- const string __id = "onBindViewHolder.(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V";
+ const string __id = "onBindViewHolder.(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V";
(I guess that generator
must lookup the Java type from the managed type somewhere/somehow, which is why AndroidX.RecyclerView.Widget.RecyclerView.ViewHolder
becomes androidx.recyclerview.widget.RecyclerView.ViewHolder
? 'Cause otherwise, that doesn't make sense to me.)
This change is also weird, but not a bug "per-se"; Turns Out™, LeanbackListPreferenceDialogFragment.AdapterMulti
has two onBindViewHolder()
methods, one of which matches the "wrong" JNI signature, meaning it's not "wrong".
Digging further, I'm starting to think that this is in part an "api fixup" bug. Again, there are two onBindViewHolder()
methods:
% javap -classpath externals/androidx.leanback/leanback-preference/classes.jar 'androidx/leanback/preference/LeanbackListPreferenceDialogFragment$AdapterSingle' | grep onBind
public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment$ViewHolder, int);
public void onBindViewHolder(androidx.recyclerview.widget.RecyclerView$ViewHolder, int);
api.xml.class-parse
output contains both:
<method
abstract="false"
deprecated="not deprecated"
final="false"
name="onBindViewHolder"
native="false"
return="void"
jni-return="V"
static="false"
synchronized="false"
visibility="public"
bridge="false"
synthetic="false"
jni-signature="(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V">
<parameter
name="holder"
type="androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder"
jni-type="Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;" />
<parameter
name="position"
type="int"
jni-type="I" />
</method>
<method
abstract="false"
deprecated="not deprecated"
final="false"
name="onBindViewHolder"
native="false"
return="void"
jni-return="V"
static="false"
synchronized="false"
visibility="public"
bridge="true"
synthetic="true"
jni-signature="(Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V">
<parameter
name="p0"
type="androidx.recyclerview.widget.RecyclerView.ViewHolder"
jni-type="Landroidx/recyclerview/widget/RecyclerView$ViewHolder;" />
<parameter
name="p1"
type="int"
jni-type="I" />
</method>
But api.xml
does not!
<method abstract="false" deprecated="not deprecated" final="false" name="onBindViewHolder" jni-signature="(Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;I)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public">
<parameter name="holder" type="androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder" jni-type="Landroidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder;">
</parameter>
<parameter name="position" type="int" jni-type="I">
</parameter>
</method>
Additionally, note that it's the overload which has synthetic="true"
which is removed.
So here's what's going on, I think: Java generics & parameter covariance, oh my!
Something like this is the presumed Java code:
package androidx.leanback.preference;
public /* partial */ class LeanbackListPreferenceDialogFragment {
public /* partial */ class AdapterMulti extends RecyclerView.Adapter<LeanbackListPreferenceDialogFragment.ViewHolder> {
@Override
public void onBindViewHolder(LeanbackListPreferenceDialogFragment.ViewHolder holder, int position) {
}
}
}
However, because RecyclerView.Adapter<VH>
is generic, and RecyclerView.Adapter<VH>.onBindViewHolder(VH, int)
takes that generic type as a parameter, this is where Java generic type erasure enters the picture:
RecylerView.Adapter<VH>.onBindViewHolder()
's Java byte code only contains a declaration that matches the constraint on VH
, which is RecyclerView.ViewHolder
:
% javap -s -classpath 'externals/androidx.recyclerview/recyclerview/classes.jar' 'androidx/recyclerview/widget/RecyclerView$Adapter' | grep -1 onBindV
public abstract void onBindViewHolder(VH, int);
descriptor: (Landroidx/recyclerview/widget/RecyclerView$ViewHolder;I)V
Along comes LeanbackListPreferenceDialogFragment.AdapterMulti
. It declares onBindViewHolder(LeanbackListPreferenceDialogFragment.ViewHolder, int)
, so that must exist, but in the world of Java generics, how does an onBindViewHolder()
invocation on an RecyclerView.Adapter
variable get routed to LeanbackListPreferenceDialogFragment.AdapterMulti
? That's where the "synthetic method" comes in: the Java compiler also emits an override with the "synthetic" attribute set, so the Java byte code is equivalent to if the Author wrote:
package androidx.leanback.preference;
public /* partial */ class LeanbackListPreferenceDialogFragment {
public /* partial */ class AdapterMulti extends RecyclerView.Adapter<LeanbackListPreferenceDialogFragment.ViewHolder> {
@Override
public void onBindViewHolder(LeanbackListPreferenceDialogFragment.ViewHolder holder, int position) {
/* ... */
}
// "synthetic method"!
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
onBindViewHolder ((LeanbackListPreferenceDialogFragment.ViewHolder) holder, position);
}
}
}
It's because this synthetic method exists, and that the "wrong" JNI method uses the synthetic method signature, that the bindings work at runtime.
See also: Issue #587.
When the
<attr/>
element is used to updatemanagedType
, the new value should be used to determine if the method that the parameter is one isoverrides
a base class method or is instead a newvirtual
method.See: https://github.com/xamarin/java.interop/issues/586#issuecomment-591572465
That said, the other problem is that we shouldn't need an
<attr/>
to updatemanagedType
in the first place. This itself is a bug, but I'm less certain how to describe it, other than an issue seemingly related to "contravariant method parameters due to the use of Java generics".-- original report follows --
Problem
Changing
type
(java) with managed FQCVlassName works:https://github.com/moljac/Samples.AndroidX/blob/master/source/bindings/type-vs-managedType/source/androidx.leanback/leanback-preference/transforms/Metadata.xml#L6-L7
while changing
managedType
causes build errors (does not change managed type):https://github.com/moljac/Samples.AndroidX/blob/master/source/bindings/type-vs-managedType/source/androidx.leanback/leanback-preference/transforms/Metadata.xml#L26-L36
Errors:
References Links
Solution (minimal repro) is zipped/archived in:
https://github.com/moljac/Samples.AndroidX/blob/master/source/bindings/type-vs-managedType.zip
api.xml
https://github.com/moljac/Samples.AndroidX/blob/master/source/bindings/type-vs-managedType/api.xml