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