diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs
new file mode 100644
index 00000000000..d742d120d8c
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs
@@ -0,0 +1,5 @@
+using System.Reflection.PortableExecutable;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader);
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs
index 82b914900a3..2eb6ba5f3c1 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs
@@ -323,7 +323,7 @@ internal void LoadManagedArgument (TrackedInstructionEncoder encoder, TypeRefDat
{
string managedTypeName = managedType.ManagedTypeName;
- ThrowIfUnsupportedManagedType (managedTypeName);
+ ThrowIfUnsupportedManagedType (managedType);
if (TryEmitExportParameterArgument (encoder, exportKind, argumentIndex)) {
return;
@@ -410,8 +410,10 @@ void ConvertManagedReturnValue (TrackedInstructionEncoder encoder, TypeRefData m
encoder.Call (_context.JniEnvToLocalJniHandleRef, parameterCount: 1, returnsValue: true);
}
- void ThrowIfUnsupportedManagedType (string managedTypeName)
+ void ThrowIfUnsupportedManagedType (TypeRefData managedType)
{
+ string managedTypeName = managedType.ManagedTypeName;
+
if (managedTypeName.EndsWith ("&", StringComparison.Ordinal) || managedTypeName.EndsWith ("*", StringComparison.Ordinal)) {
throw new NotSupportedException ($"[Export] methods with by-ref or pointer signature types are not supported: '{managedTypeName}'.");
}
@@ -421,8 +423,8 @@ void ThrowIfUnsupportedManagedType (string managedTypeName)
nonArrayTypeName = nonArrayTypeName.Substring (0, nonArrayTypeName.Length - 2);
}
- if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || nonArrayTypeName.IndexOf ('<') >= 0) {
- throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedTypeName}'.");
+ if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || managedType.GenericArguments.Count > 0 || nonArrayTypeName.IndexOf ('<') >= 0) {
+ throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedType.DisplayName}'.");
}
}
@@ -571,7 +573,7 @@ void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType)
{
string managedTypeName = managedType.ManagedTypeName;
- ThrowIfUnsupportedManagedType (managedTypeName);
+ ThrowIfUnsupportedManagedType (managedType);
if (managedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
EncodeManagedType (encoder.SZArray (), managedType with {
ManagedTypeName = managedTypeName.Substring (0, managedTypeName.Length - 2),
@@ -598,7 +600,7 @@ void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType)
}
var typeHandle = ResolveManagedTypeHandle (managedType);
- encoder.Type (typeHandle, isValueType: managedType.IsEnum);
+ encoder.Type (typeHandle, isValueType: managedType.EncodeAsValueType);
}
void AddUnmanagedCallersOnlyAttribute (MethodDefinitionHandle handle)
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
index 93f8ce5c5fc..c606912621c 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
@@ -38,10 +38,24 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
}
foreach (var proxy in data.ProxyTypes) {
writer.Write (proxy.TypeName);
- writer.Write (proxy.TargetType.ManagedTypeName);
- writer.Write (proxy.TargetType.AssemblyName);
+ writer.WriteTypeRef (proxy.TargetType);
writer.Write ((byte)(proxy.ActivationCtor?.Style ?? 0));
+ if (proxy.ActivationCtor is not null) {
+ writer.WriteTypeRef (proxy.ActivationCtor.DeclaringType);
+ }
writer.Write ((byte)(proxy.InvokerActivationCtorStyle ?? 0));
+ writer.Write (proxy.UcoMethods.Count);
+ foreach (var method in proxy.UcoMethods) {
+ writer.WriteUcoMethod (method);
+ }
+ writer.Write (proxy.UcoConstructors.Count);
+ foreach (var constructor in proxy.UcoConstructors) {
+ writer.WriteUcoConstructor (constructor);
+ }
+ writer.Write (proxy.NativeRegistrations.Count);
+ foreach (var registration in proxy.NativeRegistrations) {
+ writer.WriteNativeRegistration (registration);
+ }
}
foreach (var assoc in data.Associations) {
writer.Write (assoc.SourceTypeReference);
@@ -50,4 +64,68 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
writer.Flush ();
return sha.ComputeHash (stream.ToArray ());
}
+
+ static void WriteTypeRef (this System.IO.BinaryWriter writer, TypeRefData type)
+ {
+ writer.Write (type.ManagedTypeName);
+ writer.Write (type.AssemblyName);
+ writer.Write (type.IsValueType ? (byte) 1 : (byte) 0);
+ writer.Write (type.IsEnum ? (byte) 1 : (byte) 0);
+ writer.Write (type.GenericArguments.Count);
+ foreach (var argument in type.GenericArguments) {
+ writer.WriteTypeRef (argument);
+ }
+ }
+
+ static void WriteUcoMethod (this System.IO.BinaryWriter writer, UcoMethodData method)
+ {
+ writer.Write (method.WrapperName);
+ writer.Write (method.CallbackMethodName);
+ writer.WriteTypeRef (method.CallbackType);
+ writer.Write (method.JniSignature);
+ writer.WriteExportMethodDispatch (method.ExportMethodDispatch);
+ }
+
+ static void WriteExportMethodDispatch (this System.IO.BinaryWriter writer, ExportMethodDispatchData? dispatch)
+ {
+ writer.Write (dispatch is not null);
+ if (dispatch is null) {
+ return;
+ }
+
+ writer.Write (dispatch.ManagedMethodName);
+ writer.Write (dispatch.ParameterTypes.Count);
+ foreach (var parameterType in dispatch.ParameterTypes) {
+ writer.WriteTypeRef (parameterType);
+ }
+ writer.Write (dispatch.ParameterKinds.Count);
+ foreach (var parameterKind in dispatch.ParameterKinds) {
+ writer.Write ((int) parameterKind);
+ }
+ writer.WriteTypeRef (dispatch.ReturnType);
+ writer.Write ((int) dispatch.ReturnKind);
+ writer.Write (dispatch.IsStatic);
+ }
+
+ static void WriteUcoConstructor (this System.IO.BinaryWriter writer, UcoConstructorData constructor)
+ {
+ writer.Write (constructor.WrapperName);
+ writer.WriteTypeRef (constructor.TargetType);
+ writer.Write (constructor.JniSignature);
+ writer.Write (constructor.HasMatchingManagedCtor);
+ writer.Write (constructor.ManagedParameterTypes.Count);
+ foreach (var parameterType in constructor.ManagedParameterTypes) {
+ writer.WriteTypeRef (parameterType);
+ }
+ }
+
+ static void WriteNativeRegistration (this System.IO.BinaryWriter writer, NativeRegistrationData registration)
+ {
+ writer.Write (registration.JniMethodName);
+ writer.Write (registration.JniSignature);
+ writer.Write (registration.WrapperMethodName);
+ writer.Write (registration.WrapperTarget.TypeNamespace);
+ writer.Write (registration.WrapperTarget.TypeName);
+ writer.Write (registration.WrapperTarget.MethodName);
+ }
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
index a90f0d7dbeb..f7d2403f6e3 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
@@ -186,11 +187,75 @@ sealed record TypeRefData
public required string AssemblyName { get; init; }
///
- /// True if this type — or, for array types, the element type — is an enum.
+ /// Generic arguments for a constructed generic type. Empty for non-generic
+ /// types and open generic definitions.
+ ///
+ public IReadOnlyList GenericArguments { get; init; } = [];
+
+ ///
+ /// True if this type — or, for array types, the element type — is a value type.
/// Used by the IL emitter to encode the type as ELEMENT_TYPE_VALUETYPE
/// rather than ELEMENT_TYPE_CLASS in member references and signatures.
///
+ public bool IsValueType { get; init; }
+
+ ///
+ /// True if this type — or, for array types, the element type — is an enum.
+ /// Used by JNI signature generation to map enum values to their underlying
+ /// primitive ABI type.
+ ///
public bool IsEnum { get; init; }
+
+ public bool EncodeAsValueType => IsValueType || IsEnum;
+
+ public string DisplayName {
+ get {
+ if (ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
+ return $"{(this with { ManagedTypeName = ManagedTypeName.Substring (0, ManagedTypeName.Length - 2) }).DisplayName}[]";
+ }
+ return GenericArguments.Count == 0
+ ? ManagedTypeName
+ : $"{ManagedTypeName}<{string.Join (",", GenericArguments.Select (t => t.DisplayName))}>";
+ }
+ }
+
+ public bool Equals (TypeRefData? other)
+ {
+ if (ReferenceEquals (this, other)) {
+ return true;
+ }
+ if (other is null) {
+ return false;
+ }
+ if (!string.Equals (ManagedTypeName, other.ManagedTypeName, StringComparison.Ordinal) ||
+ !string.Equals (AssemblyName, other.AssemblyName, StringComparison.Ordinal) ||
+ IsValueType != other.IsValueType ||
+ IsEnum != other.IsEnum ||
+ GenericArguments.Count != other.GenericArguments.Count) {
+ return false;
+ }
+ for (int i = 0; i < GenericArguments.Count; i++) {
+ if (!GenericArguments [i].Equals (other.GenericArguments [i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public override int GetHashCode ()
+ {
+ unchecked {
+ int hash = 17;
+ hash = hash * 31 + StringComparer.Ordinal.GetHashCode (ManagedTypeName);
+ hash = hash * 31 + StringComparer.Ordinal.GetHashCode (AssemblyName);
+ hash = hash * 31 + IsValueType.GetHashCode ();
+ hash = hash * 31 + IsEnum.GetHashCode ();
+ foreach (var argument in GenericArguments) {
+ hash = hash * 31 + argument.GetHashCode ();
+ }
+ return hash;
+ }
+ }
}
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
index 4671394e936..ceba650aaff 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
@@ -304,10 +304,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
if (peer.ActivationCtor != null) {
bool isOnLeaf = string.Equals (peer.ActivationCtor.DeclaringTypeName, peer.ManagedTypeName, StringComparison.Ordinal);
proxy.ActivationCtor = new ActivationCtorData {
- DeclaringType = new TypeRefData {
- ManagedTypeName = peer.ActivationCtor.DeclaringTypeName,
- AssemblyName = peer.ActivationCtor.DeclaringAssemblyName,
- },
+ DeclaringType = peer.ActivationCtor.DeclaringType,
IsOnLeafType = isOnLeaf,
Style = peer.ActivationCtor.Style,
};
@@ -333,7 +330,7 @@ static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy)
proxy.UcoMethods.Add (new UcoMethodData {
WrapperName = $"n_{mm.JniName}_uco_{ucoIndex}",
CallbackMethodName = mm.NativeCallbackName,
- CallbackType = new TypeRefData {
+ CallbackType = mm.DeclaringType ?? new TypeRefData {
ManagedTypeName = !mm.DeclaringTypeName.IsNullOrEmpty () ? mm.DeclaringTypeName : peer.ManagedTypeName,
AssemblyName = !mm.DeclaringAssemblyName.IsNullOrEmpty () ? mm.DeclaringAssemblyName : peer.AssemblyName,
},
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
index 57e57b7a835..cd5ff6b2b84 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
@@ -23,7 +24,7 @@ sealed class PEAssemblyBuilder
static readonly byte [] MonoAndroidPublicKeyToken = { 0x84, 0xe0, 0x4f, 0xf9, 0xcf, 0xb7, 0x90, 0x65 };
readonly Dictionary _asmRefCache = new (StringComparer.OrdinalIgnoreCase);
- readonly Dictionary<(string Assembly, string Type), EntityHandle> _typeRefCache = new ();
+ readonly Dictionary _typeRefCache = new ();
// Reusable scratch BlobBuilders — avoids allocating a new one per method body / attribute / member ref.
// Each is Clear()'d before use. Safe because all emission is single-threaded and non-reentrant.
@@ -148,16 +149,95 @@ public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, Act
///
public EntityHandle ResolveTypeRef (TypeRefData typeRef)
{
- var cacheKey = (typeRef.AssemblyName, typeRef.ManagedTypeName);
+ var cacheKey = GetTypeRefCacheKey (typeRef);
if (_typeRefCache.TryGetValue (cacheKey, out var cached)) {
return cached;
}
var asmRef = FindOrAddAssemblyRef (typeRef.AssemblyName);
- var result = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
+ EntityHandle result;
+ if (typeRef.GenericArguments.Count > 0) {
+ var openType = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
+ var blob = new BlobBuilder (64);
+ WriteGenericInstantiationSignature (blob, openType, typeRef);
+ result = Metadata.AddTypeSpecification (Metadata.GetOrAddBlob (blob));
+ } else {
+ result = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
+ }
_typeRefCache [cacheKey] = result;
return result;
}
+ static string GetTypeRefCacheKey (TypeRefData typeRef)
+ {
+ var typeKind = typeRef.EncodeAsValueType ? "valuetype" : "class";
+ if (typeRef.GenericArguments.Count == 0) {
+ return $"{typeKind}:{typeRef.AssemblyName}:{typeRef.ManagedTypeName}";
+ }
+ return $"{typeKind}:{typeRef.AssemblyName}:{typeRef.ManagedTypeName}<{string.Join (",", typeRef.GenericArguments.Select (GetTypeRefCacheKey))}>";
+ }
+
+ void WriteGenericInstantiationSignature (BlobBuilder blob, EntityHandle openType, TypeRefData typeRef)
+ {
+ blob.WriteByte (0x15); // ELEMENT_TYPE_GENERICINST
+ blob.WriteByte (typeRef.EncodeAsValueType ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (openType));
+ blob.WriteCompressedInteger (typeRef.GenericArguments.Count);
+ foreach (var argument in typeRef.GenericArguments) {
+ WriteTypeSignature (blob, argument);
+ }
+ }
+
+ public void WriteTypeSignature (BlobBuilder blob, TypeRefData typeRef)
+ {
+ if (typeRef.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
+ blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
+ WriteTypeSignature (blob, typeRef with {
+ ManagedTypeName = typeRef.ManagedTypeName.Substring (0, typeRef.ManagedTypeName.Length - 2),
+ });
+ return;
+ }
+
+ switch (typeRef.ManagedTypeName) {
+ case "System.Boolean": blob.WriteByte (0x02); return;
+ case "System.Char": blob.WriteByte (0x03); return;
+ case "System.SByte": blob.WriteByte (0x04); return;
+ case "System.Byte": blob.WriteByte (0x05); return;
+ case "System.Int16": blob.WriteByte (0x06); return;
+ case "System.UInt16": blob.WriteByte (0x07); return;
+ case "System.Int32": blob.WriteByte (0x08); return;
+ case "System.UInt32": blob.WriteByte (0x09); return;
+ case "System.Int64": blob.WriteByte (0x0A); return;
+ case "System.UInt64": blob.WriteByte (0x0B); return;
+ case "System.Single": blob.WriteByte (0x0C); return;
+ case "System.Double": blob.WriteByte (0x0D); return;
+ case "System.String": blob.WriteByte (0x0E); return;
+ case "System.Object": blob.WriteByte (0x1C); return;
+ case "System.IntPtr": blob.WriteByte (0x18); return;
+ }
+
+ if (typeRef.GenericArguments.Count > 0) {
+ var asmRef = FindOrAddAssemblyRef (typeRef.AssemblyName);
+ var openType = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
+ WriteGenericInstantiationSignature (blob, openType, typeRef);
+ return;
+ }
+
+ if (typeRef.ManagedTypeName.StartsWith ("!!", StringComparison.Ordinal)) {
+ blob.WriteByte (0x1E); // ELEMENT_TYPE_MVAR
+ blob.WriteCompressedInteger (int.Parse (typeRef.ManagedTypeName.Substring (2), System.Globalization.CultureInfo.InvariantCulture));
+ return;
+ }
+ if (typeRef.ManagedTypeName.StartsWith ("!", StringComparison.Ordinal)) {
+ blob.WriteByte (0x13); // ELEMENT_TYPE_VAR
+ blob.WriteCompressedInteger (int.Parse (typeRef.ManagedTypeName.Substring (1), System.Globalization.CultureInfo.InvariantCulture));
+ return;
+ }
+
+ var typeHandle = ResolveTypeRef (typeRef);
+ blob.WriteByte (typeRef.EncodeAsValueType ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle));
+ }
+
TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string managedTypeName)
{
int plusIndex = managedTypeName.IndexOf ('+');
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index 8ab18800ea5..cc507f2ee3e 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -1059,7 +1059,7 @@ MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnl
blob.WriteCompressedInteger (parameterTypes.Count);
blob.WriteByte (0x01); // ELEMENT_TYPE_VOID
foreach (var parameterType in parameterTypes) {
- WriteManagedTypeSignature (blob, parameterType.ManagedTypeName, parameterType.AssemblyName);
+ _pe.WriteTypeSignature (blob, parameterType);
}
return _pe.Metadata.AddMemberReference (declaringTypeRef, _pe.Metadata.GetOrAddString (".ctor"), _pe.Metadata.GetOrAddBlob (blob));
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
index 64ca498c814..52ff5b3a9f8 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
@@ -17,6 +17,7 @@ sealed class AssemblyIndex : IDisposable
public MetadataReader Reader { get; }
public string AssemblyName { get; }
+ public string AssemblyPath { get; }
///
/// Maps full managed type name (e.g., "Android.App.Activity") to its TypeDefinitionHandle.
@@ -48,18 +49,19 @@ sealed class AssemblyIndex : IDisposable
///
public bool MayUseJniAddNativeMethodRegistrationAttribute { get; private set; }
- AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName)
+ AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName, string assemblyPath)
{
this.peReader = peReader;
this.customAttributeTypeProvider = new CustomAttributeTypeProvider (reader);
Reader = reader;
AssemblyName = assemblyName;
+ AssemblyPath = assemblyPath;
}
- public static AssemblyIndex Create (PEReader peReader, string assemblyName)
+ public static AssemblyIndex Create (PEReader peReader, string assemblyName, string assemblyPath = "")
{
var reader = peReader.GetMetadataReader ();
- var index = new AssemblyIndex (peReader, reader, assemblyName);
+ var index = new AssemblyIndex (peReader, reader, assemblyName, assemblyPath);
index.Build ();
return index;
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
index 7382a4ed60a..0307ac79638 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
@@ -200,6 +200,12 @@ public sealed record MarshalMethodInfo
///
public string DeclaringAssemblyName { get; init; } = "";
+ ///
+ /// Exact type that declares the managed method. This can be a constructed
+ /// generic base type, e.g. BaseAdapter<object>.
+ ///
+ internal TypeRefData? DeclaringType { get; init; }
+
///
/// The native callback method name, e.g., "n_onCreate".
/// This is the Java/JNI-visible native method name that the generated JCW calls.
@@ -376,6 +382,15 @@ public sealed record ActivationCtorInfo
///
public required string DeclaringAssemblyName { get; init; }
+ ///
+ /// Exact type that declares the activation constructor. This can be a
+ /// constructed generic base type.
+ ///
+ internal TypeRefData DeclaringType { get; init; } = new () {
+ ManagedTypeName = "",
+ AssemblyName = "",
+ };
+
///
/// The style of activation constructor found.
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index 08316ff1787..b4a17a52f3a 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
@@ -50,35 +51,6 @@ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHan
return false;
}
- ///
- /// Resolves a TypeReferenceHandle to (fullName, assemblyName), correctly handling
- /// nested types whose ResolutionScope is another TypeReference.
- ///
- static (string fullName, string assemblyName) ResolveTypeReference (TypeReferenceHandle handle, AssemblyIndex index)
- {
- var typeRef = index.Reader.GetTypeReference (handle);
- var name = index.Reader.GetString (typeRef.Name);
- var ns = index.Reader.GetString (typeRef.Namespace);
-
- var scope = typeRef.ResolutionScope;
- switch (scope.Kind) {
- case HandleKind.AssemblyReference: {
- var asmRef = index.Reader.GetAssemblyReference ((AssemblyReferenceHandle)scope);
- var fullName = MetadataTypeNameResolver.JoinNamespaceAndName (ns, name);
- return (fullName, index.Reader.GetString (asmRef.Name));
- }
- case HandleKind.TypeReference: {
- // Nested type: recurse to get the declaring type's full name and assembly
- var (parentFullName, assemblyName) = ResolveTypeReference ((TypeReferenceHandle)scope, index);
- return (MetadataTypeNameResolver.JoinNestedTypeName (parentFullName, name), assemblyName);
- }
- default: {
- var fullName = MetadataTypeNameResolver.JoinNamespaceAndName (ns, name);
- return (fullName, index.AssemblyName);
- }
- }
- }
-
///
/// Looks up the [Register] JNI name for a type identified by name + assembly.
///
@@ -96,9 +68,12 @@ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHan
/// Phase 2: Scan all types and produce JavaPeerInfo records.
///
public List Scan (IReadOnlyList<(string Name, PEReader Reader)> assemblies)
+ => Scan (assemblies.Select (a => new AssemblyInput (a.Name, "", a.Reader)));
+
+ public List Scan (IEnumerable assemblies)
{
- foreach (var (name, reader) in assemblies) {
- var index = AssemblyIndex.Create (reader, name);
+ foreach (var assembly in assemblies) {
+ var index = AssemblyIndex.Create (assembly.Reader, assembly.Name, assembly.Path);
assemblyCache [index.AssemblyName] = index;
}
@@ -525,7 +500,8 @@ void CollectInterfaceMethodImplementations (TypeDefinition typeDef, AssemblyInde
continue;
}
- var (ifaceTypeName, ifaceAssemblyName) = resolved.Value;
+ var ifaceTypeName = resolved.ManagedTypeName;
+ var ifaceAssemblyName = resolved.AssemblyName;
if (!TryResolveType (ifaceTypeName, ifaceAssemblyName, out var ifaceHandle, out var ifaceIndex)) {
continue;
}
@@ -669,7 +645,7 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
// Check if this ctor's params are already covered by a base registered ctor
bool alreadyCovered = false;
foreach (var baseCtor in baseRegisteredCtors) {
- if (HaveIdenticalParameterTypes (methodDef, baseCtor.Method)) {
+ if (HaveIdenticalParameterTypes (methodDef, index, baseCtor.Method, baseCtor.Index, baseCtor.DeclaringType)) {
alreadyCovered = true;
break;
}
@@ -859,7 +835,7 @@ TypeRefData EnrichTypeRefWithEnumInfo (TypeRefData type)
return type;
}
- return IsEnumOrEnumArray (type.ManagedTypeName, type.AssemblyName) ? type with { IsEnum = true } : type;
+ return IsEnumOrEnumArray (type.ManagedTypeName, type.AssemblyName) ? type with { IsEnum = true, IsValueType = true } : type;
}
static bool IsEnumType (TypeDefinition typeDef, AssemblyIndex index)
@@ -904,7 +880,8 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI
var currentTypeDef = typeDef;
var currentIndex = index;
- while (TryResolveBaseType (currentTypeDef, currentIndex, out var baseTypeDef, out var baseHandle, out var baseIndex, out _, out _)) {
+ TypeRefData? currentTypeRef = null;
+ while (TryResolveBaseType (currentTypeDef, currentIndex, currentTypeRef, out var baseTypeDef, out var baseHandle, out var baseIndex, out _, out _, out var baseTypeRef)) {
foreach (var methodHandle in baseTypeDef.GetMethods ()) {
var methodDef = baseIndex.Reader.GetMethodDefinition (methodHandle);
var name = baseIndex.Reader.GetString (methodDef.Name);
@@ -914,7 +891,7 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI
if (TryGetMethodRegisterInfo (methodDef, baseIndex, out var registerInfo, out _) &&
registerInfo is not null && registerInfo.Signature is not null) {
- result.Add (new BaseCtorInfo (methodDef, baseIndex, registerInfo));
+ result.Add (new BaseCtorInfo (methodDef, baseIndex, registerInfo, baseTypeRef));
}
}
@@ -926,6 +903,7 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI
currentTypeDef = baseTypeDef;
currentIndex = baseIndex;
+ currentTypeRef = baseTypeRef;
}
return result;
@@ -937,20 +915,32 @@ List CollectBaseRegisteredCtors (TypeDefinition typeDef, AssemblyI
///
bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
out TypeDefinition baseTypeDef, out TypeDefinitionHandle baseHandle, [NotNullWhen (true)] out AssemblyIndex? baseIndex,
- out string baseTypeName, out string baseAssemblyName)
+ out string baseTypeName, out string baseAssemblyName, out TypeRefData baseTypeRef)
+ => TryResolveBaseType (typeDef, index, currentTypeRef: null, out baseTypeDef, out baseHandle, out baseIndex, out baseTypeName, out baseAssemblyName, out baseTypeRef);
+
+ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, TypeRefData? currentTypeRef,
+ out TypeDefinition baseTypeDef, out TypeDefinitionHandle baseHandle, [NotNullWhen (true)] out AssemblyIndex? baseIndex,
+ out string baseTypeName, out string baseAssemblyName, out TypeRefData baseTypeRef)
{
baseTypeDef = default;
baseHandle = default;
baseIndex = null;
baseTypeName = "";
baseAssemblyName = "";
+ baseTypeRef = new TypeRefData {
+ ManagedTypeName = "",
+ AssemblyName = "",
+ };
var baseInfo = GetBaseTypeInfo (typeDef, index);
if (baseInfo is null) {
return false;
}
- (baseTypeName, baseAssemblyName) = baseInfo.Value;
+ baseTypeRef = currentTypeRef is null ? baseInfo : SubstituteGenericArguments (baseInfo, currentTypeRef);
+ baseTypeName = baseTypeRef.ManagedTypeName;
+ baseAssemblyName = baseTypeRef.AssemblyName;
+
if (!TryResolveType (baseTypeName, baseAssemblyName, out baseHandle, out baseIndex)) {
return false;
}
@@ -959,7 +949,38 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
return true;
}
- readonly record struct BaseCtorInfo (MethodDefinition Method, AssemblyIndex Index, RegisterInfo RegisterInfo);
+ static TypeRefData SubstituteGenericArguments (TypeRefData type, TypeRefData context)
+ {
+ if (type.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
+ var elementType = SubstituteGenericArguments (type with {
+ ManagedTypeName = type.ManagedTypeName.Substring (0, type.ManagedTypeName.Length - 2),
+ }, context);
+ return elementType with {
+ ManagedTypeName = $"{elementType.ManagedTypeName}[]",
+ };
+ }
+
+ if (type.ManagedTypeName.StartsWith ("!", StringComparison.Ordinal) &&
+ !type.ManagedTypeName.StartsWith ("!!", StringComparison.Ordinal) &&
+ int.TryParse (type.ManagedTypeName.Substring (1), out int parameterIndex) &&
+ (uint) parameterIndex < (uint) context.GenericArguments.Count) {
+ return context.GenericArguments [parameterIndex];
+ }
+
+ if (type.GenericArguments.Count == 0) {
+ return type;
+ }
+
+ var arguments = new TypeRefData [type.GenericArguments.Count];
+ for (int i = 0; i < arguments.Length; i++) {
+ arguments [i] = SubstituteGenericArguments (type.GenericArguments [i], context);
+ }
+ return type with {
+ GenericArguments = arguments,
+ };
+ }
+
+ readonly record struct BaseCtorInfo (MethodDefinition Method, AssemblyIndex Index, RegisterInfo RegisterInfo, TypeRefData DeclaringType);
///
/// Walks the base type hierarchy looking for a method with [Register] that matches
@@ -967,10 +988,10 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
/// info along with the declaring type's full name and assembly name (needed so
/// UCO wrappers call n_* on the correct base type).
///
- (RegisterInfo Info, string DeclaringTypeName, string DeclaringAssemblyName)? FindBaseRegisteredMethodInfo (
- TypeDefinition typeDef, AssemblyIndex index, string methodName, MethodDefinition derivedMethod)
+ (RegisterInfo Info, TypeRefData DeclaringType)? FindBaseRegisteredMethodInfo (
+ TypeDefinition typeDef, AssemblyIndex index, string methodName, MethodDefinition derivedMethod, AssemblyIndex derivedIndex, TypeRefData? currentTypeRef = null)
{
- if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) {
+ if (!TryResolveBaseType (typeDef, index, currentTypeRef, out var baseTypeDef, out _, out var baseIndex, out _, out _, out var baseTypeRef)) {
return null;
}
@@ -988,7 +1009,7 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
continue;
}
- if (!HaveIdenticalParameterTypes (derivedMethod, baseMethodDef)) {
+ if (!HaveIdenticalParameterTypes (derivedMethod, derivedIndex, baseMethodDef, baseIndex, baseTypeRef)) {
continue;
}
@@ -997,19 +1018,19 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
// derived override must NOT inherit a base [Export] registration —
// only [Register]-driven entries propagate through inheritance.
if (TryGetMethodRegisterInfo (baseMethodDef, baseIndex, out var registerInfo, out var exportInfo) && registerInfo is not null && exportInfo is null) {
- return (registerInfo, baseTypeName, baseAssemblyName);
+ return (registerInfo, baseTypeRef);
}
}
// Keep walking the full base hierarchy so overrides can inherit [Register]
// metadata declared above an intermediate MCW base type.
- return FindBaseRegisteredMethodInfo (baseTypeDef, baseIndex, methodName, derivedMethod);
+ return FindBaseRegisteredMethodInfo (baseTypeDef, baseIndex, methodName, derivedMethod, derivedIndex, baseTypeRef);
}
MarshalMethodInfo? FindBaseRegisteredMethod (TypeDefinition typeDef, AssemblyIndex index,
string methodName, MethodDefinition derivedMethod)
{
- var result = FindBaseRegisteredMethodInfo (typeDef, index, methodName, derivedMethod);
+ var result = FindBaseRegisteredMethodInfo (typeDef, index, methodName, derivedMethod, index);
if (result is null || result.Value.Info.Signature is null) {
return null;
}
@@ -1023,8 +1044,9 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
ManagedMethodName = methodName,
NativeCallbackName = GetNativeCallbackName (registerInfo.Connector, methodName, isConstructor),
IsConstructor = isConstructor,
- DeclaringTypeName = result.Value.DeclaringTypeName,
- DeclaringAssemblyName = result.Value.DeclaringAssemblyName,
+ DeclaringTypeName = result.Value.DeclaringType.ManagedTypeName,
+ DeclaringAssemblyName = result.Value.DeclaringType.AssemblyName,
+ DeclaringType = result.Value.DeclaringType,
};
}
@@ -1033,9 +1055,9 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
/// matches the given getter name and has a compatible signature.
///
MarshalMethodInfo? FindBaseRegisteredProperty (TypeDefinition typeDef, AssemblyIndex index,
- string getterName, MethodDefinition derivedGetter)
+ string getterName, MethodDefinition derivedGetter, TypeRefData? currentTypeRef = null)
{
- if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) {
+ if (!TryResolveBaseType (typeDef, index, currentTypeRef, out var baseTypeDef, out _, out var baseIndex, out _, out _, out var baseTypeRef)) {
return null;
}
@@ -1068,31 +1090,33 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
ManagedMethodName = getterName,
NativeCallbackName = GetNativeCallbackName (propRegister.Connector, getterName, false),
IsConstructor = false,
- DeclaringTypeName = baseTypeName,
- DeclaringAssemblyName = baseAssemblyName,
+ DeclaringTypeName = baseTypeRef.ManagedTypeName,
+ DeclaringAssemblyName = baseTypeRef.AssemblyName,
+ DeclaringType = baseTypeRef,
};
}
}
// Keep walking the full base hierarchy so property overrides can inherit
// [Register] metadata declared above an intermediate MCW base type.
- return FindBaseRegisteredProperty (baseTypeDef, baseIndex, getterName, derivedGetter);
+ return FindBaseRegisteredProperty (baseTypeDef, baseIndex, getterName, derivedGetter, baseTypeRef);
}
///
/// Checks if two methods have identical parameter types by comparing their decoded signatures.
///
- static bool HaveIdenticalParameterTypes (MethodDefinition method1, MethodDefinition method2)
+ static bool HaveIdenticalParameterTypes (MethodDefinition derivedMethod, AssemblyIndex derivedIndex, MethodDefinition baseMethod, AssemblyIndex baseIndex, TypeRefData baseTypeRef)
{
- var sig1 = method1.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
- var sig2 = method2.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default);
+ var derivedSig = derivedMethod.DecodeSignature (TypeRefSignatureTypeProvider.Instance, genericContext: derivedIndex);
+ var baseSig = baseMethod.DecodeSignature (TypeRefSignatureTypeProvider.Instance, genericContext: baseIndex);
- if (sig1.ParameterTypes.Length != sig2.ParameterTypes.Length) {
+ if (derivedSig.ParameterTypes.Length != baseSig.ParameterTypes.Length) {
return false;
}
- for (int i = 0; i < sig1.ParameterTypes.Length; i++) {
- if (!string.Equals (sig1.ParameterTypes [i], sig2.ParameterTypes [i], StringComparison.Ordinal)) {
+ for (int i = 0; i < derivedSig.ParameterTypes.Length; i++) {
+ var baseParameterType = SubstituteGenericArguments (baseSig.ParameterTypes [i], baseTypeRef);
+ if (!derivedSig.ParameterTypes [i].Equals (baseParameterType)) {
return false;
}
}
@@ -1195,6 +1219,10 @@ static bool SupportsDirectManagedMethodCall (MethodSignature manage
static bool SupportsDirectManagedMethodCall (TypeRefData type)
{
+ if (type.GenericArguments.Count > 0) {
+ return false;
+ }
+
var typeName = type.ManagedTypeName;
if (typeName.EndsWith ("&", StringComparison.Ordinal) || typeName.EndsWith ("*", StringComparison.Ordinal)) {
return false;
@@ -1204,7 +1232,7 @@ static bool SupportsDirectManagedMethodCall (TypeRefData type)
typeName = typeName.Substring (0, typeName.Length - 2);
}
- return !typeName.StartsWith ("!", StringComparison.Ordinal) && typeName.IndexOf ('<') < 0;
+ return !typeName.StartsWith ("!", StringComparison.Ordinal);
}
static string GetJavaAccess (MethodAttributes access)
@@ -1219,7 +1247,7 @@ static string GetJavaAccess (MethodAttributes access)
string? ResolveBaseJavaName (TypeDefinition typeDef, AssemblyIndex index, Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results)
{
- if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out _)) {
+ if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out _, out _)) {
return null;
}
@@ -1264,7 +1292,7 @@ List ResolveImplementedInterfaceJavaNames (TypeDefinition typeDef, Assem
string? ResolveInterfaceJniName (EntityHandle interfaceHandle, AssemblyIndex index)
{
var resolved = ResolveEntityHandle (interfaceHandle, index);
- return resolved is not null ? ResolveRegisterJniName (resolved.Value.typeName, resolved.Value.assemblyName) : null;
+ return resolved is not null ? ResolveRegisterJniName (resolved.ManagedTypeName, resolved.AssemblyName) : null;
}
bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, out RegisterInfo? registerInfo, out ExportInfo? exportInfo)
@@ -1564,9 +1592,9 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
};
}
- ActivationCtorInfo? ResolveActivationCtor (string typeName, TypeDefinition typeDef, AssemblyIndex index)
+ ActivationCtorInfo? ResolveActivationCtor (string typeName, TypeDefinition typeDef, AssemblyIndex index, TypeRefData? currentTypeRef = null)
{
- var cacheKey = (typeName, index.AssemblyName);
+ var cacheKey = (currentTypeRef?.DisplayName ?? typeName, index.AssemblyName);
if (activationCtorCache.TryGetValue (cacheKey, out var cached)) {
return cached;
}
@@ -1574,7 +1602,20 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
// Check this type's constructors
var ownCtor = FindActivationCtorOnType (typeDef, index);
if (ownCtor is not null) {
- var info = new ActivationCtorInfo { DeclaringTypeName = typeName, DeclaringAssemblyName = index.AssemblyName, Style = ownCtor.Value };
+ var info = new ActivationCtorInfo {
+ DeclaringTypeName = typeName,
+ DeclaringAssemblyName = index.AssemblyName,
+ DeclaringType = new TypeRefData {
+ ManagedTypeName = typeName,
+ AssemblyName = index.AssemblyName,
+ },
+ Style = ownCtor.Value,
+ };
+ if (currentTypeRef is not null) {
+ info = info with {
+ DeclaringType = currentTypeRef,
+ };
+ }
activationCtorCache [cacheKey] = info;
return info;
}
@@ -1582,10 +1623,12 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
// Walk base type hierarchy
var baseInfo = GetBaseTypeInfo (typeDef, index);
if (baseInfo is not null) {
- var (baseTypeName, baseAssemblyName) = baseInfo.Value;
+ baseInfo = currentTypeRef is null ? baseInfo : SubstituteGenericArguments (baseInfo, currentTypeRef);
+ var baseTypeName = baseInfo.ManagedTypeName;
+ var baseAssemblyName = baseInfo.AssemblyName;
if (TryResolveType (baseTypeName, baseAssemblyName, out var baseHandle, out var baseIndex)) {
var baseTypeDef = baseIndex.Reader.GetTypeDefinition (baseHandle);
- var result = ResolveActivationCtor (baseTypeName, baseTypeDef, baseIndex);
+ var result = ResolveActivationCtor (baseTypeName, baseTypeDef, baseIndex, baseInfo);
if (result is not null) {
activationCtorCache [cacheKey] = result;
}
@@ -1630,53 +1673,28 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
/// Resolves a TypeSpecificationHandle (generic instantiation) to the underlying
/// type's (fullName, assemblyName) by reading the raw signature blob.
///
- static (string fullName, string assemblyName)? ResolveTypeSpecification (TypeSpecificationHandle specHandle, AssemblyIndex index)
+ TypeRefData? ResolveTypeSpecification (TypeSpecificationHandle specHandle, AssemblyIndex index)
{
var typeSpec = index.Reader.GetTypeSpecification (specHandle);
- var blobReader = index.Reader.GetBlobReader (typeSpec.Signature);
-
- // Generic instantiation blob: GENERICINST (CLASS|VALUETYPE) coded-token count args...
- var elementType = blobReader.ReadByte ();
- if (elementType != 0x15) { // ELEMENT_TYPE_GENERICINST
- return null;
- }
-
- var classOrValueType = blobReader.ReadByte ();
- if (classOrValueType != 0x12 && classOrValueType != 0x11) { // CLASS or VALUETYPE
- return null;
- }
-
- // TypeDefOrRefOrSpec coded index: 2 tag bits (0=TypeDef, 1=TypeRef, 2=TypeSpec)
- var codedToken = blobReader.ReadCompressedInteger ();
- var tag = codedToken & 0x3;
- var row = codedToken >> 2;
-
- switch (tag) {
- case 0: { // TypeDef
- var handle = MetadataTokens.TypeDefinitionHandle (row);
- var baseDef = index.Reader.GetTypeDefinition (handle);
- return (MetadataTypeNameResolver.GetFullName (baseDef, index.Reader), index.AssemblyName);
- }
- case 1: // TypeRef
- return ResolveTypeReference (MetadataTokens.TypeReferenceHandle (row), index);
- default:
- return null;
- }
+ return typeSpec.DecodeSignature (TypeRefSignatureTypeProvider.Instance, index);
}
///
/// Resolves an EntityHandle (TypeDef, TypeRef, or TypeSpec) to (typeName, assemblyName).
/// Shared by base type resolution, interface resolution, and any handle-to-name lookup.
///
- (string typeName, string assemblyName)? ResolveEntityHandle (EntityHandle handle, AssemblyIndex index)
+ TypeRefData? ResolveEntityHandle (EntityHandle handle, AssemblyIndex index)
{
switch (handle.Kind) {
case HandleKind.TypeDefinition: {
var td = index.Reader.GetTypeDefinition ((TypeDefinitionHandle)handle);
- return (MetadataTypeNameResolver.GetFullName (td, index.Reader), index.AssemblyName);
+ return new TypeRefData {
+ ManagedTypeName = MetadataTypeNameResolver.GetFullName (td, index.Reader),
+ AssemblyName = index.AssemblyName,
+ };
}
case HandleKind.TypeReference:
- return ResolveTypeReference ((TypeReferenceHandle)handle, index);
+ return MetadataTypeNameResolver.GetTypeRefFromReference (index.Reader, (TypeReferenceHandle)handle, index.AssemblyName, rawTypeKind: 0);
case HandleKind.TypeSpecification:
return ResolveTypeSpecification ((TypeSpecificationHandle)handle, index);
default:
@@ -1684,7 +1702,7 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
}
}
- (string typeName, string assemblyName)? GetBaseTypeInfo (TypeDefinition typeDef, AssemblyIndex index)
+ TypeRefData? GetBaseTypeInfo (TypeDefinition typeDef, AssemblyIndex index)
{
return typeDef.BaseType.IsNil ? null : ResolveEntityHandle (typeDef.BaseType, index);
}
@@ -1769,7 +1787,8 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)
return false;
}
- var (baseTypeName, baseAssemblyName) = baseInfo.Value;
+ var baseTypeName = baseInfo.ManagedTypeName;
+ var baseAssemblyName = baseInfo.AssemblyName;
if (!TryResolveType (baseTypeName, baseAssemblyName, out var baseHandle, out var baseIndex)) {
return false;
@@ -2031,7 +2050,7 @@ List BuildJavaConstructors (List marshal
bool unsupportedParam = false;
foreach (var p in sig.ParameterTypes) {
var paramTypeName = p.ManagedTypeName;
- if (paramTypeName.IndexOf ('<') >= 0 || paramTypeName.EndsWith ("&", StringComparison.Ordinal) || paramTypeName.EndsWith ("*", StringComparison.Ordinal)) {
+ if (p.GenericArguments.Count > 0 || paramTypeName.EndsWith ("&", StringComparison.Ordinal) || paramTypeName.EndsWith ("*", StringComparison.Ordinal)) {
unsupportedParam = true;
break;
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs
index 468fef15f25..fa13b8c5094 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetadataTypeNameResolver.cs
@@ -8,6 +8,8 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
static class MetadataTypeNameResolver
{
+ const byte ElementTypeValueType = 0x11;
+
internal static string JoinNamespaceAndName (string ns, string name)
{
return ns.Length > 0 ? $"{ns}.{name}" : name;
@@ -40,6 +42,7 @@ public static TypeRefData GetTypeRefFromDefinition (MetadataReader reader, TypeD
return new TypeRefData {
ManagedTypeName = GetTypeFromDefinition (reader, handle, rawTypeKind),
AssemblyName = assemblyName,
+ IsValueType = rawTypeKind == ElementTypeValueType,
};
}
@@ -64,6 +67,7 @@ public static TypeRefData GetTypeRefFromReference (MetadataReader reader, TypeRe
return new TypeRefData {
ManagedTypeName = managedTypeName,
AssemblyName = assemblyName,
+ IsValueType = rawTypeKind == ElementTypeValueType,
};
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs
index 779e4f76f70..1639d582573 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs
@@ -108,9 +108,7 @@ public TypeRefData GetPointerType (TypeRefData elementType) => elementType with
public TypeRefData GetGenericInstantiation (TypeRefData genericType, ImmutableArray typeArguments)
{
- return genericType with {
- ManagedTypeName = $"{genericType.ManagedTypeName}<{string.Join (",", typeArguments.Select (t => t.ManagedTypeName))}>",
- };
+ return genericType with { GenericArguments = typeArguments.ToArray () };
}
public TypeRefData GetGenericTypeParameter (AssemblyIndex genericContext, int index) => new () {
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
index 95d0446b205..416c461fe28 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
@@ -27,7 +27,7 @@ public TrimmableTypeMapGenerator (ITrimmableTypeMapLogger logger)
/// No file IO is performed — all results are returned in memory.
///
public TrimmableTypeMapResult Execute (
- IReadOnlyList<(string Name, PEReader Reader)> assemblies,
+ IReadOnlyList assemblies,
Version systemRuntimeVersion,
HashSet frameworkAssemblyNames,
bool useSharedTypemapUniverse = false,
@@ -155,7 +155,7 @@ GeneratedManifest GenerateManifest (List allPeers, AssemblyManifes
return new GeneratedManifest (doc, providerNames.Count > 0 ? providerNames.ToArray () : []);
}
- (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies, string? packageNamingPolicy, HashSet frameworkAssemblyNames)
+ (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList assemblies, string? packageNamingPolicy, HashSet frameworkAssemblyNames)
{
using var scanner = new JavaPeerScanner (packageNamingPolicy, logger, frameworkAssemblyNames);
var peers = scanner.Scan (assemblies);
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
index 9a03ef4ff21..96a4a9f104c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
@@ -135,7 +135,7 @@ public override bool RunTask ()
Directory.CreateDirectory (JavaSourceOutputDirectory);
var peReaders = new List ();
- var assemblies = new List<(string Name, PEReader Reader)> ();
+ var assemblies = new List ();
TrimmableTypeMapResult? result = null;
try {
foreach (var (path, isFrameworkAssembly) in assemblyInputs) {
@@ -143,7 +143,7 @@ public override bool RunTask ()
peReaders.Add (peReader);
var mdReader = peReader.GetMetadataReader ();
var assemblyName = mdReader.GetString (mdReader.GetAssemblyDefinition ().Name);
- assemblies.Add ((assemblyName, peReader));
+ assemblies.Add (new AssemblyInput (assemblyName, path, peReader));
if (isFrameworkAssembly) {
frameworkAssemblyNames.Add (assemblyName);
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
index b3eba523be1..d793f87bc3b 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
@@ -55,7 +55,7 @@ public void Execute_AssemblyWithNoPeers_ReturnsEmpty ()
var testAssemblyPath = typeof (TrimmableTypeMapGeneratorTests).Assembly.Location;
using var peReader = new PEReader (File.OpenRead (testAssemblyPath));
var result = CreateGenerator ().Execute (
- new List<(string, PEReader)> { ("TestAssembly", peReader) },
+ [Input ("TestAssembly", peReader)],
new Version (11, 0),
new HashSet ());
Assert.Empty (result.GeneratedAssemblies);
@@ -67,7 +67,7 @@ public void Execute_AssemblyWithNoPeers_ReturnsEmpty ()
public void Execute_WithTestFixtures_ProducesOutputs ()
{
using var peReader = CreateTestFixturePEReader ();
- var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ());
+ var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ());
Assert.NotEmpty (result.GeneratedAssemblies);
Assert.NotEmpty (result.GeneratedJavaSources);
Assert.Contains (result.GeneratedAssemblies, a => a.Name == "_Microsoft.Android.TypeMaps");
@@ -78,7 +78,7 @@ public void Execute_WithTestFixtures_ProducesOutputs ()
public void Execute_CollectsDeferredRegistrationTypes_ForAllApplicationAndInstrumentationSubtypes ()
{
using var peReader = CreateTestFixturePEReader ();
- var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ());
+ var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ());
// Abstract Instrumentation/Application subtypes are included too: their native
// methods (e.g. n_OnCreate, n_OnStart) are declared on the abstract base class
@@ -139,7 +139,7 @@ public void CollectApplicationRegistrationTypes_ExcludesLegacyFrameworkDescendan
[Fact]
public void Execute_NullAssemblyList_Throws ()
{
- IReadOnlyList<(string Name, PEReader Reader)>? n = null;
+ IReadOnlyList? n = null;
#pragma warning disable CS8604
Assert.Throws (() => CreateGenerator ().Execute (n, new Version (11, 0), new HashSet ()));
#pragma warning restore CS8604
@@ -149,7 +149,7 @@ public void Execute_NullAssemblyList_Throws ()
public void Execute_GeneratedAssembliesAreValidPE ()
{
using var peReader = CreateTestFixturePEReader ();
- var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ());
+ var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ());
foreach (var assembly in result.GeneratedAssemblies) {
assembly.Content.Position = 0;
using var vr = new PEReader (assembly.Content, PEStreamOptions.LeaveOpen);
@@ -162,7 +162,7 @@ public void Execute_GeneratedAssembliesAreValidPE ()
public void Execute_JavaSourcesHaveCorrectStructure ()
{
using var peReader = CreateTestFixturePEReader ();
- var result = CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ());
+ var result = CreateGenerator ().Execute ([Input ("TestFixtures", peReader)], new Version (11, 0), new HashSet ());
foreach (var source in result.GeneratedJavaSources)
Assert.Contains ("class ", source.Content);
}
@@ -172,7 +172,7 @@ public void Execute_FrameworkAssembly_GeneratesFrameworkJcwTypes ()
{
using var peReader = CreateTestFixturePEReader ();
var result = CreateGenerator ().Execute (
- new List<(string, PEReader)> { ("Mono.Android", peReader) },
+ [Input ("Mono.Android", peReader)],
new Version (11, 0),
new HashSet (StringComparer.OrdinalIgnoreCase) { "Mono.Android" });
@@ -196,7 +196,7 @@ public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting ()
""");
var result = CreateGenerator ().Execute (
- new List<(string, PEReader)> { ("TestFixtures", peReader) },
+ [Input ("TestFixtures", peReader)],
new Version (11, 0),
new HashSet (),
useSharedTypemapUniverse: false,
@@ -217,6 +217,8 @@ public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting ()
TrimmableTypeMapGenerator CreateGenerator (List warnings) =>
new (new TestTrimmableTypeMapLogger (logMessages, warnings));
+ static AssemblyInput Input (string name, PEReader reader) => new (name, "", reader);
+
[Theory]
[InlineData ("com/example/MyActivity", "com.example.MyActivity", "com.example", "activity", "com.example.MyActivity")]
[InlineData ("com/example/MyActivity", "com.example.MyActivity", "com.example", "activity", ".MyActivity")]
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
index 28b2ad29b2a..cbeddcc7cb3 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs
@@ -45,6 +45,17 @@ static List FindCtorMemberRefs (MetadataReader reader, st
static MemberReferenceHandle FindCtorMemberRef (MetadataReader reader, string parentNamespace, string parentName, params string [] parameterTypes) =>
FindCtorMemberRefs (reader, parentNamespace, parentName, parameterTypes).First ();
+ static string GetTypeDefOrRefName (MetadataReader reader, int codedToken)
+ {
+ int tag = codedToken & 0x3;
+ int row = codedToken >> 2;
+ return tag switch {
+ 0 => reader.GetString (reader.GetTypeDefinition (MetadataTokens.TypeDefinitionHandle (row)).Name),
+ 1 => reader.GetString (reader.GetTypeReference (MetadataTokens.TypeReferenceHandle (row)).Name),
+ _ => throw new InvalidOperationException ($"Unexpected TypeDefOrRefOrSpec tag {tag}."),
+ };
+ }
+
static TypeRefData TypeRef (string managedTypeName) => new () {
ManagedTypeName = managedTypeName,
AssemblyName = GetAssemblyNameForManagedType (managedTypeName),
@@ -106,6 +117,55 @@ public void Generate_CreatesProxyTypes ()
Assert.Contains (proxyTypes, t => reader.GetString (t.Name) == "Java_Lang_Object_Proxy");
}
+ [Theory]
+ [InlineData ("my/app/GenericSelectableList")]
+ [InlineData ("my/app/GenericForwardingSelectableList")]
+ public void Generate_InheritedGenericBaseCallback_UsesConstructedBaseMemberRef (string javaName)
+ {
+ var peer = ScanFixtures ().Single (p => p.JavaName == javaName);
+ using var stream = GenerateAssembly (new [] { peer });
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var member = reader.MemberReferences
+ .Select (h => reader.GetMemberReference (h))
+ .Single (m => reader.GetString (m.Name) == "n_SetSelection_I");
+
+ Assert.Equal (HandleKind.TypeSpecification, member.Parent.Kind);
+ var typeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) member.Parent);
+ var blob = reader.GetBlobReader (typeSpec.Signature);
+
+ Assert.Equal (0x15, blob.ReadByte ()); // ELEMENT_TYPE_GENERICINST
+ Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS
+ Assert.Equal ("GenericSelectionHost`1", GetTypeDefOrRefName (reader, blob.ReadCompressedInteger ()));
+ Assert.Equal (1, blob.ReadCompressedInteger ());
+ Assert.Equal (0x0E, blob.ReadByte ()); // ELEMENT_TYPE_STRING
+ }
+
+ [Fact]
+ public void Generate_InheritedGenericBaseCallback_UsesValueTypeGenericArgument ()
+ {
+ var peer = ScanFixtures ().Single (p => p.JavaName == "my/app/EnumSelectableList");
+ using var stream = GenerateAssembly (new [] { peer });
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var member = reader.MemberReferences
+ .Select (h => reader.GetMemberReference (h))
+ .Single (m => reader.GetString (m.Name) == "n_SetSelection_I");
+
+ Assert.Equal (HandleKind.TypeSpecification, member.Parent.Kind);
+ var typeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) member.Parent);
+ var blob = reader.GetBlobReader (typeSpec.Signature);
+
+ Assert.Equal (0x15, blob.ReadByte ()); // ELEMENT_TYPE_GENERICINST
+ Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS
+ Assert.Equal ("GenericValueTypeSelectionHost`1", GetTypeDefOrRefName (reader, blob.ReadCompressedInteger ()));
+ Assert.Equal (1, blob.ReadCompressedInteger ());
+ Assert.Equal (0x11, blob.ReadByte ()); // ELEMENT_TYPE_VALUETYPE
+ Assert.Equal ("SelectionMode", GetTypeDefOrRefName (reader, blob.ReadCompressedInteger ()));
+ }
+
[Fact]
public void Generate_ProxyType_HasCtorAndCreateInstance ()
{
@@ -708,6 +768,76 @@ public void Generate_IdenticalContent_ProducesIdenticalMVIDs ()
Assert.Equal (mvid1, mvid2);
}
+ [Fact]
+ public void TypeRefData_Equality_ComparesGenericArgumentsByValue ()
+ {
+ var left = new TypeRefData {
+ ManagedTypeName = "Test.GenericBase`1",
+ AssemblyName = "TestAsm",
+ GenericArguments = [
+ TypeRef ("System.String"),
+ ],
+ };
+ var right = new TypeRefData {
+ ManagedTypeName = "Test.GenericBase`1",
+ AssemblyName = "TestAsm",
+ GenericArguments = [
+ TypeRef ("System.String"),
+ ],
+ };
+ var different = right with {
+ GenericArguments = [
+ TypeRef ("System.Object"),
+ ],
+ };
+
+ Assert.Equal (left, right);
+ Assert.Equal (left.GetHashCode (), right.GetHashCode ());
+ Assert.NotEqual (left, different);
+ }
+
+ [Fact]
+ public void Generate_DifferentConstructedCallbackTypes_ProducesDifferentMVIDs ()
+ {
+ var peer = MakePeerWithActivation ("test/TypeA", "Test.TypeA", "TestAsm");
+ var stringPeer = peer with {
+ MarshalMethods = [
+ CreateInheritedGenericCallback (TypeRef ("System.String")),
+ ],
+ };
+ var objectPeer = peer with {
+ MarshalMethods = [
+ CreateInheritedGenericCallback (TypeRef ("System.Object")),
+ ],
+ };
+
+ using var stream1 = GenerateAssembly (new [] { stringPeer }, "SameName");
+ using var stream2 = GenerateAssembly (new [] { objectPeer }, "SameName");
+
+ using var pe1 = new PEReader (stream1);
+ using var pe2 = new PEReader (stream2);
+ var mvid1 = pe1.GetMetadataReader ().GetGuid (pe1.GetMetadataReader ().GetModuleDefinition ().Mvid);
+ var mvid2 = pe2.GetMetadataReader ().GetGuid (pe2.GetMetadataReader ().GetModuleDefinition ().Mvid);
+
+ Assert.NotEqual (mvid1, mvid2);
+
+ static MarshalMethodInfo CreateInheritedGenericCallback (TypeRefData genericArgument) => new () {
+ JniName = "setValue",
+ NativeCallbackName = "n_SetValue_Ljava_lang_Object",
+ JniSignature = "(Ljava/lang/Object;)V",
+ ManagedMethodName = "SetValue",
+ DeclaringTypeName = "Test.GenericBase`1",
+ DeclaringAssemblyName = "TestAsm",
+ DeclaringType = new TypeRefData {
+ ManagedTypeName = "Test.GenericBase`1",
+ AssemblyName = "TestAsm",
+ GenericArguments = [
+ genericArgument,
+ ],
+ },
+ };
+ }
+
[Fact]
public void Generate_AcwProxy_HasRegisterNativesAndUcoMethods ()
{
@@ -1542,6 +1672,44 @@ public void Generate_ExportProxy_UnsupportedManagedShapesThrow (string parameter
Assert.Contains (expectedMessage, ex.Message);
}
+ [Fact]
+ public void Generate_ExportProxy_StructuredGenericArgumentThrows ()
+ {
+ var peer = MakePeerWithActivation ("my/app/UnsupportedExport", "MyApp.UnsupportedExport", "App") with {
+ DoNotGenerateAcw = false,
+ MarshalMethods = new List {
+ new () {
+ JniName = "badExport",
+ NativeCallbackName = "n_badExport",
+ JniSignature = "(Ljava/lang/Object;)V",
+ ManagedMethodName = "BadExport",
+ ManagedParameterTypes = new [] {
+ new TypeRefData {
+ ManagedTypeName = "System.Collections.Generic.List`1",
+ AssemblyName = "System.Collections",
+ GenericArguments = new [] {
+ new TypeRefData {
+ ManagedTypeName = "System.String",
+ AssemblyName = "System.Runtime",
+ },
+ },
+ },
+ },
+ ManagedReturnType = new TypeRefData {
+ ManagedTypeName = "System.Void",
+ AssemblyName = "System.Runtime",
+ },
+ IsExport = true,
+ },
+ },
+ };
+
+ var ex = Assert.Throws (() => {
+ using var stream = GenerateAssembly (new [] { peer }, "UnsupportedStructuredGenericExport");
+ });
+ Assert.Contains ("generic", ex.Message);
+ }
+
[Fact]
public void Generate_MultipleAcwProxies_DeduplicatesUtf8Strings ()
{
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs
index f1e2cbd0ab8..81e33f417f5 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs
@@ -1,3 +1,4 @@
+using System;
using System.Linq;
using Xunit;
@@ -125,6 +126,66 @@ public void OverrideAcrossGenericIntermediateMcwBase_Detected ()
var setSelection = Assert.Single (peer.MarshalMethods, m => m.JniName == "setSelection");
Assert.Equal ("(I)V", setSelection.JniSignature);
Assert.Equal ("GetSetSelection_IHandler", setSelection.Connector);
+ var declaringType = setSelection.DeclaringType;
+ Assert.NotNull (declaringType);
+ if (declaringType is null) {
+ throw new InvalidOperationException ("Expected override declaring type.");
+ }
+ Assert.Equal ("MyApp.GenericSelectionHost`1", declaringType.ManagedTypeName);
+ var argument = Assert.Single (declaringType.GenericArguments);
+ Assert.Equal ("System.String", argument.ManagedTypeName);
+ }
+
+ [Fact]
+ public void OverrideAcrossGenericForwardingIntermediateMcwBase_Detected ()
+ {
+ var peer = FindFixtureByJavaName ("my/app/GenericForwardingSelectableList");
+ var setSelection = Assert.Single (peer.MarshalMethods, m => m.JniName == "setSelection");
+ Assert.Equal ("(I)V", setSelection.JniSignature);
+ Assert.Equal ("GetSetSelection_IHandler", setSelection.Connector);
+ var declaringType = setSelection.DeclaringType;
+ Assert.NotNull (declaringType);
+ if (declaringType is null) {
+ throw new InvalidOperationException ("Expected override declaring type.");
+ }
+ Assert.Equal ("MyApp.GenericSelectionHost`1", declaringType.ManagedTypeName);
+ var argument = Assert.Single (declaringType.GenericArguments);
+ Assert.Equal ("System.String", argument.ManagedTypeName);
+ }
+
+ [Fact]
+ public void OverrideAcrossGenericBaseWithSubstitutedParameter_Detected ()
+ {
+ var peer = FindFixtureByJavaName ("my/app/StringValueList");
+ var applyValue = Assert.Single (peer.MarshalMethods, m => m.JniName == "applyValue");
+ Assert.Equal ("(Ljava/lang/Object;)V", applyValue.JniSignature);
+ Assert.Equal ("GetApplyValue_Ljava_lang_Object_Handler", applyValue.Connector);
+ var declaringType = applyValue.DeclaringType;
+ Assert.NotNull (declaringType);
+ if (declaringType is null) {
+ throw new InvalidOperationException ("Expected override declaring type.");
+ }
+ Assert.Equal ("MyApp.GenericValueHost`1", declaringType.ManagedTypeName);
+ var argument = Assert.Single (declaringType.GenericArguments);
+ Assert.Equal ("System.String", argument.ManagedTypeName);
+ }
+
+ [Fact]
+ public void OverrideAcrossGenericValueTypeArgument_PreservesValueTypeArgument ()
+ {
+ var peer = FindFixtureByJavaName ("my/app/EnumSelectableList");
+ var setSelection = Assert.Single (peer.MarshalMethods, m => m.JniName == "setSelection");
+ Assert.Equal ("(I)V", setSelection.JniSignature);
+ Assert.Equal ("GetSetSelection_IHandler", setSelection.Connector);
+ var declaringType = setSelection.DeclaringType;
+ Assert.NotNull (declaringType);
+ if (declaringType is null) {
+ throw new InvalidOperationException ("Expected override declaring type.");
+ }
+ Assert.Equal ("MyApp.GenericValueTypeSelectionHost`1", declaringType.ManagedTypeName);
+ var argument = Assert.Single (declaringType.GenericArguments);
+ Assert.Equal ("MyApp.SelectionMode", argument.ManagedTypeName);
+ Assert.True (argument.IsValueType);
}
[Fact]
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs
index ca0b8f4adf8..76d2cd61e2f 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs
@@ -1013,6 +1013,26 @@ public abstract class GenericSelectionContainer : GenericSelectionHost
protected GenericSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
}
+ ///
+ /// Generic intermediate MCW base that forwards its generic parameter to the
+ /// registered generic base. This mirrors Xamarin.Forms renderer hierarchies
+ /// such as VisualElementRenderer<TElement>.
+ ///
+ [Register ("my/app/GenericForwardingSelectionContainer", DoNotGenerateAcw = true)]
+ public abstract class GenericForwardingSelectionContainer : GenericSelectionHost where T : class
+ {
+ protected GenericForwardingSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+ }
+
+ ///
+ /// Non-generic MCW base that closes the generic forwarding base.
+ ///
+ [Register ("my/app/StringForwardingSelectionContainer", DoNotGenerateAcw = true)]
+ public abstract class StringForwardingSelectionContainer : GenericForwardingSelectionContainer
+ {
+ protected StringForwardingSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+ }
+
///
/// Overrides a registered method declared above the first MCW base in the hierarchy.
///
@@ -1035,6 +1055,91 @@ protected GenericSelectableList (IntPtr handle, JniHandleOwnership transfer) : b
public override void SetSelection (int position) { }
}
+ ///
+ /// Overrides a registered method declared above a generic base that forwards
+ /// type parameters through another generic base.
+ ///
+ [Register ("my/app/GenericForwardingSelectableList")]
+ public class GenericForwardingSelectableList : StringForwardingSelectionContainer
+ {
+ protected GenericForwardingSelectableList (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+
+ public override void SetSelection (int position) { }
+ }
+
+ ///
+ /// Generic base whose registered method uses its type parameter in the managed
+ /// signature. Override detection must compare this as string when the
+ /// base is closed over string.
+ ///
+ [Register ("my/app/GenericValueHost", DoNotGenerateAcw = true)]
+ public abstract class GenericValueHost : Java.Lang.Object where T : class
+ {
+ protected GenericValueHost (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+
+ [Register ("applyValue", "(Ljava/lang/Object;)V", "GetApplyValue_Ljava_lang_Object_Handler")]
+ public abstract void ApplyValue (T value);
+ }
+
+ ///
+ /// Intermediate MCW base that closes a registered generic method parameter.
+ ///
+ [Register ("my/app/StringValueContainer", DoNotGenerateAcw = true)]
+ public abstract class StringValueContainer : GenericValueHost
+ {
+ protected StringValueContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+ }
+
+ ///
+ /// Overrides a registered method whose base managed signature contains a
+ /// substituted generic type parameter.
+ ///
+ [Register ("my/app/StringValueList")]
+ public class StringValueList : StringValueContainer
+ {
+ protected StringValueList (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+
+ public override void ApplyValue (string value) { }
+ }
+
+ public enum SelectionMode {
+ Single,
+ Multiple,
+ }
+
+ ///
+ /// Generic base closed over an enum argument so TypeSpec emission must encode
+ /// the argument as ELEMENT_TYPE_VALUETYPE.
+ ///
+ [Register ("my/app/GenericValueTypeSelectionHost", DoNotGenerateAcw = true)]
+ public abstract class GenericValueTypeSelectionHost : Java.Lang.Object
+ {
+ protected GenericValueTypeSelectionHost (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+
+ [Register ("setSelection", "(I)V", "GetSetSelection_IHandler")]
+ public abstract void SetSelection (int position);
+ }
+
+ ///
+ /// Intermediate MCW base that closes a registered generic base over an enum.
+ ///
+ [Register ("my/app/EnumSelectionContainer", DoNotGenerateAcw = true)]
+ public abstract class EnumSelectionContainer : GenericValueTypeSelectionHost
+ {
+ protected EnumSelectionContainer (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+ }
+
+ ///
+ /// Overrides a registered method declared on a constructed generic enum base.
+ ///
+ [Register ("my/app/EnumSelectableList")]
+ public class EnumSelectableList : EnumSelectionContainer
+ {
+ protected EnumSelectableList (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
+
+ public override void SetSelection (int position) { }
+ }
+
///
/// Has a ctor with unsigned primitive params to test JNI mapping.
///