Commit 8a3e162
authored
[Java.Interop] Generic Type Definitions are allowed (#1336)
Context: dotnet/android#9913
Context: dotnet/android@de49d96
Fixes: #1324
dotnet/android#9913 updated `JNIEnv.GetJniName(Type)` to use
`JniRuntime.JniTypeManager.GetTypeSignature(Type)`.
It promptly failed on CI:
E NUnit : : System.NotSupportedException : 'Java.InteropTests.GenericHolder`1[T]' contains a generic type definition. This is not supported.
E NUnit : at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type )
E NUnit : at Android.Runtime.JNIEnv.GetJniName(Type )
E NUnit : at Java.Interop.TypeManager.RegisterType(String , Type )
E NUnit : at Android.Runtime.JNIEnvInit.RegisterJniNatives(IntPtr , Int32 , IntPtr , IntPtr , Int32 )
E NUnit : at Java.Interop.JniEnvironment.Object.AllocObject(JniObjectReference )
E NUnit : at Java.Interop.JniType.AllocObject()
E NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods.StartCreateInstance(String , Type , JniArgumentValue* )
E NUnit : at Java.Lang.Object..ctor()
E NUnit : at Java.InteropTests.GenericHolder`1[[System.Int32, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]..ctor()
E NUnit : at Java.InteropTests.JnienvTest.NewClosedGenericTypeWorks()
E NUnit : at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
E NUnit : at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
because of this check within
`JniRuntime.JniTypeManager.GetTypeSignature()`:
if (type.ContainsGenericParameters)
throw new NotSupportedException ($"'{type}' contains a generic type definition. This is not supported.");
Why is `type.ContainsGenericParameters` true?
`type.ContainsGenericParameters` is true because XAJavaInterop1-style
Java Callable Wrappers contain an Assembly Qualified Type Name, and
if the underlying type is generic, then the assembly qualified type
name mentions the type definition. Consider:
// C#
class GenericHolder<T> : Java.Lang.Object {
}
results in this Java Callable Wrapper snippet:
// Java
public /* partial */ class GenericHolder_1
{
public static final String __md_methods;
static {
__md_methods = "";
mono.android.Runtime.register ("Java.InteropTests.GenericHolder`1, Mono.Android-Tests", GenericHolder_1.class, __md_methods);
}
}
Note ``Java.InteropTests.GenericHolder`1, Mono.Android-Tests``:
``Type.GetType("Java.InteropTests.GenericHolder`1, Mono.Android-Tests")``
results in a type definition, in which `type.ContainsGenericParameters`
is true.
*Is this a problem*? No, it is not a problem *in type registration*,
as this has worked in dotnet/android for many years at this point.
Is this a problem *elsewhere*? Yes:
1. Instances of `GenericHolder_1` cannot be created *from Java*,
because Java doesn't know what type parameters to provide,
because of type erasure.
2. `[JavaCallable]` methods on generic types also cannot work,
as only *one* method can be registered, while semantically a
generic method is *N* methods (per type).
Update `JniRuntime.JniTypeManager.GetTypeSignature()` to no longer
check for `type.ContainsGenericParameters`.
Update `JniTypeManagerTests` to assert that constructing instances
from Java results in an exception.
A "funny" thing happened when adding that test: it didn't assert!
There are two reasons for this. The first is that the Java Callable
Wrapper for `Java.InteropTests.GenericHolder<T>` *did not* have a
Java constructor! Without a constructor, it had the default
constructor, which has no such verification check.
The cause of the missing constructor is that
`Java.Interop.Tools.JavaCallableWrappers` needs to know the JNI
signature of the constructor to generate, and the `JavaObject`
default constructor didn't have `[JniConstructorSignature]`.
Update the `JavaObject` default constructor appropriately:
partial class JavaObject {
[JniConstructorSignature ("()V")]
public JavaObject () …
}
This causes the Java Callable Wrapper for `GenericHolder<T>` to have
the desired constructor which calls `ManagedPeer.construct()`,
which in turn is a prerequisite for throwing `NotSupportedException`.
The second issue is that the `CannotCreateGenericHolderFromJava()`
test *must* use `JNIEnv::AllocObject()` +
`JNIEnv::CallNonvirtualVoidMethod()` to construct the instance,
***not*** `JNIEnv::NewObject()`, because we special-case
`JNIEnv::NewObject()`; see also commit 1c99956.1 parent d909947 commit 8a3e162
File tree
3 files changed
+25
-5
lines changed- src/Java.Interop/Java.Interop
- tests/Java.Interop-Tests/Java.Interop
3 files changed
+25
-5
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| 67 | + | |
67 | 68 | | |
68 | 69 | | |
69 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
149 | | - | |
| 148 | + | |
150 | 149 | | |
151 | 150 | | |
152 | 151 | | |
| |||
183 | 182 | | |
184 | 183 | | |
185 | 184 | | |
186 | | - | |
187 | | - | |
188 | 185 | | |
189 | 186 | | |
190 | 187 | | |
| |||
Lines changed: 23 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
22 | 21 | | |
23 | 22 | | |
24 | 23 | | |
| |||
91 | 90 | | |
92 | 91 | | |
93 | 92 | | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
94 | 98 | | |
95 | 99 | | |
96 | 100 | | |
| |||
215 | 219 | | |
216 | 220 | | |
217 | 221 | | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
218 | 240 | | |
219 | 241 | | |
220 | 242 | | |
| |||
0 commit comments