diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
index a90f0d7dbeb..2476b2eddfe 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
@@ -142,6 +142,16 @@ sealed class JavaPeerProxyData
///
public bool IsGenericDefinition { get; init; }
+ ///
+ /// True if the proxied peer type is a Java interface. Interfaces have no constructors, so
+ /// the proxy must derive from the non-generic JavaPeerProxy base instead of
+ /// JavaPeerProxy<T>: closing the generic (whose T is annotated with
+ /// [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)]) over an
+ /// interface makes ILC fail to load the type (TypeLoadException). Instances are still created
+ /// from in CreateInstance.
+ ///
+ public bool IsInterface { get; init; }
+
///
/// True when the Java stub must not call RegisterNatives from a static initializer because
/// the type can be instantiated before the runtime is fully ready (for example Application
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
index 4671394e936..b6edba82265 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
@@ -290,6 +290,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
},
IsAcw = isAcw,
IsGenericDefinition = peer.IsGenericDefinition,
+ IsInterface = peer.IsInterface,
CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor,
};
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index 8ab18800ea5..a68a256ecf1 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -665,9 +665,16 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary` annotates T with
+ // [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)], and closing it
+ // over an interface (which has no constructors) makes ILC fail to load the type
+ // (TypeLoadException: "Failed to load type 'JavaPeerProxy`1'"). The peer is
+ // still activated from its InvokerType in CreateInstance, so behaviour is unchanged.
+ bool useNonGenericBase = proxy.IsGenericDefinition || proxy.IsInterface;
EntityHandle proxyBaseType;
MemberReferenceHandle baseCtorRef;
- if (proxy.IsGenericDefinition) {
+ if (useNonGenericBase) {
proxyBaseType = _javaPeerProxyNonGenericRef;
baseCtorRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
@@ -709,9 +716,9 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary {
encoder.OpCode (ILOpCode.Ldarg_0);
encoder.LoadString (metadata.GetOrAddUserString (proxy.JniName));
- if (proxy.IsGenericDefinition) {
- // Non-generic base ctor signature: (string, Type, Type?). Push the open-generic
- // target type as the second argument.
+ if (useNonGenericBase) {
+ // Non-generic base ctor signature: (string, Type, Type?). Push the
+ // target type (open-generic definition or interface) as the second argument.
encoder.LoadToken (targetTypeRef);
encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
}
@@ -721,7 +728,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary {
switch (proxyType.BaseType.Kind) {
case HandleKind.TypeSpecification:
- // Non-generic target types derive from the closed `JavaPeerProxy`.
+ // Concrete (constructible) target types derive from the closed `JavaPeerProxy`.
var baseTypeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) proxyType.BaseType);
var baseTypeName = baseTypeSpec.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null);
Assert.StartsWith ("Java.Interop.JavaPeerProxy`1<", baseTypeName, StringComparison.Ordinal);
break;
case HandleKind.TypeReference:
- // Open generic target types derive from the non-generic `JavaPeerProxy`.
+ // Open generic definitions and interfaces derive from the non-generic `JavaPeerProxy`.
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType);
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
@@ -165,6 +165,29 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
objectProxyBaseType.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null));
}
+ [Fact]
+ public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase ()
+ {
+ // JavaPeerProxy annotates T with [DynamicallyAccessedMembers(Constructors)]. Closing it
+ // over an interface (which has no constructors) makes ILC fail to load the closed generic
+ // type ("Failed to load type 'JavaPeerProxy`1'"). Interface proxies must
+ // therefore derive from the non-generic JavaPeerProxy base (a plain TypeReference).
+ var peers = ScanFixtures ();
+ using var stream = GenerateAssembly (peers);
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var interfaceProxy = reader.TypeDefinitions
+ .Select (h => reader.GetTypeDefinition (h))
+ .Where (t => reader.GetString (t.Namespace) == "_TypeMap.Proxies")
+ .First (t => reader.GetString (t.Name) == "Android_Views_IOnClickListener_Proxy");
+
+ Assert.Equal (HandleKind.TypeReference, interfaceProxy.BaseType.Kind);
+ var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) interfaceProxy.BaseType);
+ Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
+ Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
+ }
+
// Regression test: every generated proxy type must carry a custom attribute whose
// constructor points at the proxy's own TypeDefinitionHandle (either as a MemberRef
// parented on the TypeDef, or as a MethodDefinition on the TypeDef). This is how