diff --git a/Documentation/docs-mobile/TOC.yml b/Documentation/docs-mobile/TOC.yml
index 1244c066b05..03bfd8e5f0e 100644
--- a/Documentation/docs-mobile/TOC.yml
+++ b/Documentation/docs-mobile/TOC.yml
@@ -332,6 +332,8 @@
href: messages/xa4249.md
- name: XA4258
href: messages/xa4258.md
+ - name: XA4263
+ href: messages/xa4263.md
- name: XA4301
href: messages/xa4301.md
- name: XA4302
diff --git a/Documentation/docs-mobile/messages/index.md b/Documentation/docs-mobile/messages/index.md
index dabb47571de..4c17d558c1c 100644
--- a/Documentation/docs-mobile/messages/index.md
+++ b/Documentation/docs-mobile/messages/index.md
@@ -229,6 +229,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4255](xa4255.md): Generated trimmable type map Java source '{path}' was not found.
+ [XA4256](xa4256.md): Skipping Java peer type '{type}' from assembly '{assembly}' because referenced type '{referencedType}' from assembly '{referencedAssembly}' could not be resolved in '{path}'. This type will not be included in the trimmable type map.
+ [XA4258](xa4258.md): Java name '{name}' contains reserved Java identifier '{identifier}'. Change the package or type name.
++ [XA4263](xa4263.md): The exported member '{member}' has unsupported signature type '{type}'. Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter].
+ XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI.
+ [XA4301](xa4301.md): Apk already contains the item `xxx`.
+ [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex}
diff --git a/Documentation/docs-mobile/messages/xa4263.md b/Documentation/docs-mobile/messages/xa4263.md
new file mode 100644
index 00000000000..fa454a2957a
--- /dev/null
+++ b/Documentation/docs-mobile/messages/xa4263.md
@@ -0,0 +1,28 @@
+---
+title: .NET for Android error XA4263
+description: XA4263 error code
+ms.date: 08/31/2026
+f1_keywords:
+ - "XA4263"
+---
+
+# .NET for Android error XA4263
+
+## Example message
+
+```text
+error XA4263: The exported member 'Peer.Get' has unsupported signature type 'ManagedValue'. Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter].
+```
+
+## Issue
+
+An `[Export]` method or `[ExportField]` initializer uses a managed parameter or return type that has no Java type mapping.
+The type cannot be represented safely in the generated Java declaration.
+This error also occurs when `[ExportParameter]` specifies a Java mapping that is incompatible with the managed type.
+
+## Solution
+
+Use a primitive, string, array, Java peer, or another supported managed-to-Java type.
+Apply input/output stream mappings only to `Stream`, and XML parser mappings only to `XmlReader`.
+Do not apply these scalar mappings to arrays or other wrappers around those types.
+The types must be the framework `System.IO.Stream` and `System.Xml.XmlReader` types, not user-defined types with the same full name.
diff --git a/Xamarin.Android.slnx b/Xamarin.Android.slnx
index 9ec9f9fcdb6..4c1a64bd0c8 100644
--- a/Xamarin.Android.slnx
+++ b/Xamarin.Android.slnx
@@ -60,8 +60,10 @@
+
+
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
index 185c2637dcf..fdaca8c7f46 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs
@@ -118,6 +118,10 @@ static void WriteUcoConstructor (this BinaryWriter writer, UcoConstructorData co
foreach (var parameterType in constructor.ManagedParameterTypes) {
writer.WriteTypeRef (parameterType);
}
+ writer.Write (constructor.ParameterKinds.Count);
+ foreach (var parameterKind in constructor.ParameterKinds) {
+ writer.Write ((int) parameterKind);
+ }
}
static void WriteNativeRegistration (this BinaryWriter writer, NativeRegistrationData registration)
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
index 054a44f1795..03100fa3cae 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs
@@ -407,6 +407,11 @@ sealed record UcoConstructorData
/// to the corresponding managed type before calling the user ctor.
///
public IReadOnlyList ManagedParameterTypes { get; init; } = [];
+
+ ///
+ /// Per-parameter [ExportParameter] kinds for constructor argument marshalling.
+ ///
+ public IReadOnlyList ParameterKinds { get; init; } = [];
}
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
index f77ee24e0b8..65d6a024180 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
@@ -380,6 +380,7 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy)
AssemblyName = peer.AssemblyName,
},
ManagedParameterTypes = ctor.ManagedParameterTypes,
+ ParameterKinds = ctor.ManagedParameterExportKinds,
HasMatchingManagedCtor = ctor.HasMatchingManagedCtor,
});
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index 56a160a7914..4633e1df0b5 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -1182,7 +1182,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
if (uco.HasMatchingManagedCtor) {
var ctorRef = AddManagedCtorRef (targetTypeRef, uco.ManagedParameterTypes);
var managedCtorHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig,
- enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, jniParams),
+ enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, uco.ParameterKinds, jniParams),
blob => EncodeUcoConstructorLocals_DefaultConstructor (blob, targetTypeRef));
AddUnmanagedCallersOnlyAttribute (managedCtorHandle);
return managedCtorHandle;
@@ -1278,6 +1278,7 @@ void EmitManagedConstructorActivation (
EntityHandle targetTypeRef,
MemberReferenceHandle ctorRef,
IReadOnlyList managedParameterTypes,
+ IReadOnlyList parameterKinds,
IReadOnlyList jniParams)
{
var havePeer = enc.DefineLabel ();
@@ -1303,7 +1304,8 @@ void EmitManagedConstructorActivation (
enc.MarkLabel (havePeer);
enc.LoadLocal (4);
for (int i = 0; i < managedParameterTypes.Count; i++) {
- EmitManagedConstructorArgument (enc, managedParameterTypes [i], jniParams [i], i + 2);
+ var parameterKind = i < parameterKinds.Count ? parameterKinds [i] : ExportParameterKindInfo.Unspecified;
+ EmitManagedConstructorArgument (enc, managedParameterTypes [i], parameterKind, jniParams [i], i + 2);
}
enc.Call (ctorRef, managedParameterTypes.Count, isInstance: true);
@@ -1383,53 +1385,14 @@ void EmitUcoConstructorBodyWithMarshal (TrackedInstructionEncoder encoder, Contr
cfb.AddFinallyRegion (tryStart, finallyStart, finallyStart, afterAll);
}
- void EmitManagedConstructorArgument (TrackedInstructionEncoder encoder, TypeRefData managedType, JniParamKind jniKind, int argumentIndex)
+ void EmitManagedConstructorArgument (
+ TrackedInstructionEncoder encoder,
+ TypeRefData managedType,
+ ExportParameterKindInfo exportKind,
+ JniParamKind jniKind,
+ int argumentIndex)
{
- if (managedType.ManagedTypeName == "System.Boolean") {
- encoder.LoadArgument (argumentIndex);
- encoder.LoadConstantI4 (0);
- encoder.OpCode (ILOpCode.Cgt_un);
- return;
- }
-
- if (jniKind != JniParamKind.Object) {
- encoder.LoadArgument (argumentIndex);
- return;
- }
-
- if (managedType.ManagedTypeName == "System.String") {
- encoder.LoadArgument (argumentIndex);
- encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
- encoder.Call (_jniEnvGetStringRef, parameterCount: 2, returnsValue: true);
- return;
- }
-
- if (TryGetSzArrayElementType (managedType.ManagedTypeName, out var elementType)) {
- var arrayType = ResolveManagedTypeHandle (managedType.ManagedTypeName, managedType.AssemblyName);
- var elementTypeHandle = ResolveManagedTypeHandle (elementType, managedType.AssemblyName);
-
- encoder.LoadArgument (argumentIndex);
- encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
- encoder.LoadToken (elementTypeHandle);
- encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
- encoder.Call (_jniEnvGetArrayRef, parameterCount: 3, returnsValue: true);
- encoder.CastClass (arrayType);
- return;
- }
-
- encoder.LoadArgument (argumentIndex);
- encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
- if (managedType.ManagedTypeName == "System.Object") {
- encoder.OpCode (ILOpCode.Ldnull);
- encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true);
- return;
- }
-
- var managedTypeHandle = ResolveManagedTypeHandle (managedType.ManagedTypeName, managedType.AssemblyName);
- encoder.LoadToken (managedTypeHandle);
- encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
- encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true);
- encoder.CastClass (managedTypeHandle);
+ GetExportMethodDispatchEmitter ().LoadManagedArgument (encoder, managedType, exportKind, jniKind, argumentIndex);
}
EntityHandle ResolveRuntimeTypeSpec (RuntimeTypeSpec type)
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs
index 879bf42aafc..edd36592f83 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs
@@ -23,8 +23,10 @@ void LogUnresolvableJavaPeerSkippedWarning (
void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName);
void LogInvalidJavaNameError (string javaName, string invalidIdentifier);
void LogExportFieldWithParametersError ();
+ void LogExportOnGenericTypeError ();
void LogExportFieldOnGenericTypeError ();
void LogExportFieldReturnsVoidError ();
+ void LogUnsupportedExportSignatureError (string memberName, string managedTypeName);
void LogCustomJavaObjectError (string managedTypeName);
void LogCustomJavaObjectWarning (string managedTypeName);
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
index 30ca13c1b06..30e340ef367 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs
@@ -30,6 +30,7 @@ sealed class AssemblyIndex : IDisposable
public MetadataReader Reader { get; }
public string AssemblyName { get; }
+ public string MetadataAssemblyName { get; }
public string AssemblyPath { get; }
internal TypeRefSignatureTypeProvider TypeRefSignatureProvider { get; }
@@ -53,6 +54,11 @@ sealed class AssemblyIndex : IDisposable
///
public HashSet ExportedTypeNames { get; } = new (StringComparer.Ordinal);
+ ///
+ /// Maps forwarded managed type names to the target assembly simple name.
+ ///
+ public Dictionary ForwardedTypeAssemblies { get; } = new (StringComparer.Ordinal);
+
///
/// True iff the assembly's metadata mentions
/// Java.Interop.JniAddNativeMethodRegistrationAttribute (as a
@@ -77,6 +83,7 @@ sealed class AssemblyIndex : IDisposable
valueTypeReferences = new TypeRefData? [reader.TypeReferences.Count + 1];
Reader = reader;
AssemblyName = assemblyName;
+ MetadataAssemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name);
AssemblyPath = assemblyPath;
TypeRefSignatureProvider = new TypeRefSignatureTypeProvider (this);
}
@@ -107,7 +114,12 @@ void Build ()
foreach (var exportedTypeHandle in Reader.ExportedTypes) {
var exportedType = Reader.GetExportedType (exportedTypeHandle);
- ExportedTypeNames.Add (GetExportedTypeFullName (exportedType));
+ var fullName = GetExportedTypeFullName (exportedType);
+ ExportedTypeNames.Add (fullName);
+ var forwardedAssemblyName = GetForwardedAssemblyName (exportedType);
+ if (forwardedAssemblyName is not null) {
+ ForwardedTypeAssemblies [fullName] = forwardedAssemblyName;
+ }
}
foreach (var typeHandle in Reader.TypeDefinitions) {
@@ -146,6 +158,18 @@ string GetExportedTypeFullName (ExportedType exportedType)
var ns = Reader.GetString (exportedType.Namespace);
return MetadataTypeNameResolver.JoinNamespaceAndName (ns, name);
}
+
+ string? GetForwardedAssemblyName (ExportedType exportedType)
+ {
+ if (exportedType.Implementation.Kind == HandleKind.AssemblyReference) {
+ var assemblyReference = Reader.GetAssemblyReference ((AssemblyReferenceHandle) exportedType.Implementation);
+ return Reader.GetString (assemblyReference.Name);
+ }
+ if (exportedType.Implementation.Kind == HandleKind.ExportedType) {
+ return GetForwardedAssemblyName (Reader.GetExportedType ((ExportedTypeHandle) exportedType.Implementation));
+ }
+ return null;
+ }
}
(RegisterInfo? register, TypeAttributeInfo? attrs) ParseAttributes (TypeDefinition typeDef)
@@ -173,8 +197,6 @@ string GetExportedTypeFullName (ExportedType exportedType)
registerInfo = registerInfo with { JniName = registerInfo.JniName.Replace ('.', '/') };
} else if (attrName == "JniTypeSignatureAttribute") {
registerInfo = ParseJniTypeSignatureAttribute (ca);
- } else if (attrName == "ExportAttribute") {
- // [Export] is a method-level attribute; it is parsed at scan time by JavaPeerScanner
} else if (IsKnownComponentAttribute (attrName)) {
attrInfo ??= CreateTypeAttributeInfo (attrName);
var value = DecodeAttribute (ca);
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs
index 15497a95bf7..1a102926209 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs
@@ -6,6 +6,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
enum ExportParameterKindInfo
{
+ Invalid = -1,
Unspecified = 0,
InputStream = 1,
OutputStream = 2,
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
index 86915bff704..114856936f9 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
@@ -368,6 +368,11 @@ public sealed record JavaConstructorInfo
///
internal IReadOnlyList ManagedParameterTypes { get; init; } = [];
+ ///
+ /// Per-parameter [ExportParameter] kinds for constructor argument marshalling.
+ ///
+ internal IReadOnlyList ManagedParameterExportKinds { get; init; } = [];
+
///
/// True when this Java constructor has a matching public managed constructor on the target type.
///
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index 6cf34b2693e..a401094dd30 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -61,15 +61,36 @@ internal JavaPeerScanner (string? packageNamingPolicy, ITrimmableTypeMapLogger?
///
bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHandle handle, [NotNullWhen (true)] out AssemblyIndex? resolvedIndex)
{
- if (assemblyCache.TryGetValue (assemblyName, out resolvedIndex) &&
- resolvedIndex.TypesByFullName.TryGetValue (typeName, out handle)) {
- return true;
+ var visitedAssemblies = new HashSet (StringComparer.Ordinal);
+ while (visitedAssemblies.Add (assemblyName) &&
+ TryGetAssemblyIndex (assemblyName, out resolvedIndex)) {
+ if (resolvedIndex.TypesByFullName.TryGetValue (typeName, out handle)) {
+ return true;
+ }
+ if (!resolvedIndex.ForwardedTypeAssemblies.TryGetValue (typeName, out assemblyName)) {
+ break;
+ }
}
handle = default;
resolvedIndex = null;
return false;
}
+ bool TryGetAssemblyIndex (string assemblyName, [NotNullWhen (true)] out AssemblyIndex? index)
+ {
+ if (assemblyCache.TryGetValue (assemblyName, out index)) {
+ return true;
+ }
+ foreach (var candidate in assemblyCache.Values) {
+ if (string.Equals (candidate.MetadataAssemblyName, assemblyName, StringComparison.Ordinal)) {
+ index = candidate;
+ return true;
+ }
+ }
+ index = null;
+ return false;
+ }
+
///
/// Resolves the type that declares the native n_* callback. When the [Register] connector
/// names a declaring type (e.g. an *Invoker in another assembly) that type is used;
@@ -677,10 +698,17 @@ static void SkipArrayShape (ref BlobReader reader)
// Pass 1: collect methods with [Register], [Export], or [ExportField] directly on them
foreach (var methodHandle in typeDef.GetMethods ()) {
var methodDef = index.Reader.GetMethodDefinition (methodHandle);
+ var methodName = index.Reader.GetString (methodDef.Name);
+ if (methodName == ".cctor" && (methodDef.Attributes & MethodAttributes.Static) != 0) {
+ continue;
+ }
if (!ValidateExportField (methodDef, index, isGenericType)) {
continue;
}
+ if (!ValidateExportSignature (methodDef, index, isGenericType)) {
+ continue;
+ }
// Check for [ExportField] — produces both a marshal method AND a field
CollectExportField (methodDef, index, fields);
@@ -750,6 +778,16 @@ static bool IsExportFieldAttribute (CustomAttribute attribute, AssemblyIndex ind
return AssemblyIndex.IsCustomAttributeMatch (attribute, index.Reader, "Java.Interop", "ExportFieldAttribute");
}
+ static bool IsExportAttribute (CustomAttribute attribute, AssemblyIndex index)
+ {
+ return AssemblyIndex.IsCustomAttributeMatch (attribute, index.Reader, "Java.Interop", "ExportAttribute");
+ }
+
+ static bool IsExportParameterAttribute (CustomAttribute attribute, AssemblyIndex index)
+ {
+ return AssemblyIndex.IsCustomAttributeMatch (attribute, index.Reader, "Java.Interop", "ExportParameterAttribute");
+ }
+
bool ValidateExportField (MethodDefinition methodDef, AssemblyIndex index, bool isGenericType)
{
foreach (var caHandle in methodDef.GetCustomAttributes ()) {
@@ -778,6 +816,85 @@ bool ValidateExportField (MethodDefinition methodDef, AssemblyIndex index, bool
return true;
}
+ bool ValidateExportSignature (MethodDefinition methodDef, AssemblyIndex index, bool isGenericType)
+ {
+ bool isExport = false;
+ bool isExportField = false;
+ foreach (var caHandle in methodDef.GetCustomAttributes ()) {
+ var ca = index.Reader.GetCustomAttribute (caHandle);
+ if (IsExportAttribute (ca, index)) {
+ isExport = true;
+ } else if (IsExportFieldAttribute (ca, index)) {
+ isExportField = true;
+ }
+ }
+ if (!isExport && !isExportField) {
+ return true;
+ }
+
+ var methodName = index.Reader.GetString (methodDef.Name);
+ bool isConstructor = methodName == ".ctor";
+ if (isConstructor && !isExport) {
+ return true;
+ }
+ if (!isConstructor && isExport && isGenericType) {
+ logger?.LogExportOnGenericTypeError ();
+ return false;
+ }
+
+ var sig = methodDef.DecodeSignature (index.TypeRefSignatureProvider, index);
+ var (parameterKinds, returnKind) = GetExportParameterKinds (methodDef, index, sig.ParameterTypes.Length);
+ var declaringType = index.Reader.GetTypeDefinition (methodDef.GetDeclaringType ());
+ string declaringTypeName = MetadataTypeNameResolver.GetFullName (declaringType, index.Reader);
+ string memberName = methodName [0] == '.'
+ ? declaringTypeName + methodName
+ : $"{declaringTypeName}.{methodName}";
+
+ for (int i = 0; i < sig.ParameterTypes.Length; i++) {
+ if (isConstructor && IsOwnedByConstructorDiagnostics (sig.ParameterTypes [i])) {
+ continue;
+ }
+ if (!HasExportSignatureMapping (sig.ParameterTypes [i], parameterKinds [i])) {
+ logger?.LogUnsupportedExportSignatureError (memberName, sig.ParameterTypes [i].DisplayName);
+ return false;
+ }
+ }
+ if (isConstructor) {
+ return true;
+ }
+ if (!HasExportSignatureMapping (sig.ReturnType, returnKind)) {
+ logger?.LogUnsupportedExportSignatureError (memberName, sig.ReturnType.DisplayName);
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// PR #12567 owns XA4260 for signature shapes that no Java constructor can represent.
+ /// This layer only adds XA4263 for unresolved types and invalid [ExportParameter] pairs.
+ ///
+ internal static bool IsOwnedByConstructorDiagnostics (TypeRefData parameterType)
+ {
+ string managedTypeName = parameterType.ManagedTypeName;
+ if (managedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
+ return IsOwnedByConstructorDiagnostics (parameterType with {
+ ManagedTypeName = managedTypeName.Substring (0, managedTypeName.Length - 2),
+ });
+ }
+ return parameterType.GenericArguments.Count > 0 ||
+ managedTypeName.StartsWith ("!", StringComparison.Ordinal) ||
+ managedTypeName == "delegate*" ||
+ managedTypeName.EndsWith ("&", StringComparison.Ordinal) ||
+ managedTypeName.EndsWith ("*", StringComparison.Ordinal) ||
+ (managedTypeName.EndsWith ("]", StringComparison.Ordinal) &&
+ !managedTypeName.EndsWith ("[]", StringComparison.Ordinal));
+ }
+
+ internal bool HasExportSignatureMapping (TypeRefData managedType, ExportParameterKindInfo exportKind)
+ {
+ return TryManagedTypeToJniDescriptor (managedType, exportKind, out _);
+ }
+
static bool HasJniAddNativeMethodRegistrationAttribute (TypeDefinition typeDef, AssemblyIndex index)
{
const string JniAddNativeMethodRegistrationAttribute = "JniAddNativeMethodRegistrationAttribute";
@@ -1126,32 +1243,47 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index,
}
///
- /// Looks up a managed type name across loaded assemblies. If the type has
+ /// Looks up a managed type in its referenced assembly. If the type has
/// [Register], returns "L<jniName>;". Otherwise returns null.
///
+ string? TryResolveJniObjectDescriptor (TypeRefData managedType)
+ {
+ if (!TryResolveType (managedType.ManagedTypeName, managedType.AssemblyName, out var handle, out var index)) {
+ return null;
+ }
+ return GetJniObjectDescriptor (handle, index);
+ }
+
+ ///
+ /// Looks up an unqualified managed type name across loaded assemblies.
+ /// Used only by legacy-compatible constructor signature discovery.
+ ///
string? TryResolveJniObjectDescriptor (string managedType)
{
foreach (var index in assemblyCache.Values) {
if (index.TypesByFullName.TryGetValue (managedType, out var handle)) {
- if (index.RegisterInfoByType.TryGetValue (handle, out var registerInfo)) {
- return $"L{registerInfo.JniName};";
- }
-
- // User peer types (extend a Java peer but lack [Register])
- // get a CRC64-based JNI name in ScanAssembly. Mirror that here
- // so [Export]/[ExportField] signatures referring to such types
- // emit the correct peer descriptor instead of falling back to
- // java/lang/Object.
- var typeDef = index.Reader.GetTypeDefinition (handle);
- if (ExtendsJavaPeer (handle, typeDef, index)) {
- var (jniName, _) = ComputeAutoJniNames (typeDef, index);
- return $"L{jniName};";
- }
+ return GetJniObjectDescriptor (handle, index);
}
}
return null;
}
+ string? GetJniObjectDescriptor (TypeDefinitionHandle handle, AssemblyIndex index)
+ {
+ if (index.RegisterInfoByType.TryGetValue (handle, out var registerInfo)) {
+ return $"L{registerInfo.JniName};";
+ }
+
+ // User peer types (extend a Java peer but lack [Register]) get a CRC64-based
+ // JNI name in ScanAssembly. Mirror that for exported signatures.
+ var typeDef = index.Reader.GetTypeDefinition (handle);
+ if (ExtendsJavaPeer (handle, typeDef, index)) {
+ var (jniName, _) = ComputeAutoJniNames (typeDef, index);
+ return $"L{jniName};";
+ }
+ return null;
+ }
+
///
/// Resolves a `typeof(X)` argument captured as an assembly-qualified name
/// (e.g. "Java.IO.IOException, Mono.Android, ...") to its JNI internal
@@ -1202,17 +1334,12 @@ bool IsEnumOrEnumArray (string managedType, string? assemblyName = null)
(TypeDefinition typeDef, AssemblyIndex index)? TryFindEnumTypeDefinition (string managedType, string? assemblyName = null)
{
- // Prefer the typed assembly hint so two assemblies with same-named types
- // (one enum, one not) resolve deterministically — assemblyCache
- // enumeration order is non-deterministic.
- if (assemblyName is { Length: > 0 } &&
- assemblyCache.TryGetValue (assemblyName, out var hintedIndex) &&
- hintedIndex.TypesByFullName.TryGetValue (managedType, out var hintedHandle)) {
- var hintedDef = hintedIndex.Reader.GetTypeDefinition (hintedHandle);
- if (IsEnumType (hintedDef, hintedIndex)) {
- return (hintedDef, hintedIndex);
+ if (assemblyName is { Length: > 0 }) {
+ if (!TryResolveType (managedType, assemblyName, out var resolvedHandle, out var resolvedIndex)) {
+ return null;
}
- // Hinted assembly had a same-named non-enum; keep scanning.
+ var resolvedType = resolvedIndex.Reader.GetTypeDefinition (resolvedHandle);
+ return IsEnumType (resolvedType, resolvedIndex) ? (resolvedType, resolvedIndex) : null;
}
foreach (var index in assemblyCache.Values) {
@@ -1745,7 +1872,7 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index,
return true;
}
- if (attrName == "ExportAttribute") {
+ if (IsExportAttribute (ca, index)) {
(registerInfo, exportInfo) = ParseExportAttribute (ca, methodDef, index);
return true;
}
@@ -1786,6 +1913,7 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index,
(RegisterInfo registerInfo, ExportInfo exportInfo) ParseExportAttribute (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index)
{
var value = index.DecodeAttribute (ca);
+ var managedName = index.Reader.GetString (methodDef.Name);
// [Export("name")] or [Export] (uses method name)
string? exportName = null;
@@ -1828,7 +1956,10 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index,
}
if (string.IsNullOrEmpty (exportName)) {
- exportName = index.Reader.GetString (methodDef.Name);
+ exportName = managedName;
+ }
+ if (managedName == ".ctor") {
+ exportName = ".ctor";
}
string resolvedExportName = exportName ?? throw new InvalidOperationException ("Export name should not be null at this point.");
@@ -1889,36 +2020,29 @@ static ExportParameterKindInfo GetExportParameterKind (Parameter parameter, Asse
{
foreach (var caHandle in parameter.GetCustomAttributes ()) {
var ca = index.Reader.GetCustomAttribute (caHandle);
- var attrName = index.GetCustomAttributeName (ca);
- if (attrName != "ExportParameterAttribute") {
+ if (!IsExportParameterAttribute (ca, index)) {
continue;
}
var value = index.DecodeAttribute (ca);
- if (value.FixedArguments.Length > 0 && TryConvertExportParameterKind (value.FixedArguments [0].Value, out var ctorKind)) {
- return ctorKind;
+ if (value.FixedArguments.Length > 0) {
+ return ConvertExportParameterKind (value.FixedArguments [0].Value);
}
foreach (var named in value.NamedArguments) {
- if (named.Name == "Kind" && TryConvertExportParameterKind (named.Value, out var namedKind)) {
- return namedKind;
+ if (named.Name == "Kind") {
+ return ConvertExportParameterKind (named.Value);
}
}
+
+ return ExportParameterKindInfo.Invalid;
}
return ExportParameterKindInfo.Unspecified;
}
- static bool TryConvertExportParameterKind (object? value, out ExportParameterKindInfo kind)
- {
- if (value is int i && Enum.IsDefined (typeof (ExportParameterKindInfo), i)) {
- kind = (ExportParameterKindInfo) i;
- return true;
- }
-
- kind = ExportParameterKindInfo.Unspecified;
- return false;
- }
+ static ExportParameterKindInfo ConvertExportParameterKind (object? value) =>
+ value is int i ? (ExportParameterKindInfo) i : ExportParameterKindInfo.Invalid;
string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnlyList parameterKinds, ExportParameterKindInfo returnKind)
{
@@ -1942,11 +2066,18 @@ string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnly
{
var managedName = index.Reader.GetString (methodDef.Name);
var sig = methodDef.DecodeSignature (index.TypeRefSignatureProvider, index);
- var jniSig = BuildJniSignatureFromManaged (sig, GetDefaultExportKinds (sig.ParameterTypes.Length), ExportParameterKindInfo.Unspecified);
+ var (parameterKinds, returnKind) = GetExportParameterKinds (methodDef, index, sig.ParameterTypes.Length);
+ var jniSig = BuildJniSignatureFromManaged (sig, parameterKinds, returnKind);
return (
new RegisterInfo { JniName = managedName, Signature = jniSig, Connector = "__export__", DoNotGenerateAcw = false },
- new ExportInfo { ThrownNames = null, SuperArgumentsString = null, IsField = true }
+ new ExportInfo {
+ ThrownNames = null,
+ SuperArgumentsString = null,
+ IsField = true,
+ ParameterKinds = parameterKinds,
+ ReturnKind = returnKind,
+ }
);
}
@@ -1956,30 +2087,50 @@ string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnly
/// for types that cannot be resolved (used by [Export] signature computation).
///
string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindInfo exportKind = ExportParameterKindInfo.Unspecified)
+ {
+ return TryManagedTypeToJniDescriptor (managedType, exportKind, out var descriptor)
+ ? descriptor
+ : "Ljava/lang/Object;";
+ }
+
+ bool TryManagedTypeToJniDescriptor (
+ TypeRefData managedType,
+ ExportParameterKindInfo exportKind,
+ out string descriptor)
{
if (exportKind != ExportParameterKindInfo.Unspecified) {
- return exportKind switch {
- ExportParameterKindInfo.InputStream => "Ljava/io/InputStream;",
- ExportParameterKindInfo.OutputStream => "Ljava/io/OutputStream;",
- ExportParameterKindInfo.XmlPullParser => "Lorg/xmlpull/v1/XmlPullParser;",
- ExportParameterKindInfo.XmlResourceParser => "Landroid/content/res/XmlResourceParser;",
- _ => "Ljava/lang/Object;",
- };
+ return TryGetExportParameterDescriptor (managedType, exportKind, out descriptor);
}
- var primitive = TryGetPrimitiveJniDescriptor (managedType.ManagedTypeName);
- if (primitive is not null) {
- return primitive;
+ if (managedType.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
+ var elementType = managedType with {
+ ManagedTypeName = managedType.ManagedTypeName.Substring (0, managedType.ManagedTypeName.Length - 2),
+ };
+ if (TryManagedTypeToJniDescriptor (elementType, ExportParameterKindInfo.Unspecified, out var elementDescriptor)) {
+ descriptor = $"[{elementDescriptor}";
+ return true;
+ }
+ descriptor = "";
+ return false;
}
- if (managedType.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
- return $"[{ManagedTypeToJniDescriptor (managedType with { ManagedTypeName = managedType.ManagedTypeName.Substring (0, managedType.ManagedTypeName.Length - 2) })}";
+ var primitive = TryGetPrimitiveJniDescriptor (managedType.ManagedTypeName);
+ if (primitive is not null &&
+ IsSpecialManagedType (
+ managedType,
+ managedType.ManagedTypeName,
+ "System.Runtime",
+ "System.Private.CoreLib",
+ "mscorlib")) {
+ descriptor = primitive;
+ return true;
}
// Try to resolve as a Java peer type with [Register]
- var resolved = TryResolveJniObjectDescriptor (managedType.ManagedTypeName);
+ var resolved = TryResolveJniObjectDescriptor (managedType);
if (resolved is not null) {
- return resolved;
+ descriptor = resolved;
+ return true;
}
// Well-known interface types that legacy CallbackCode mapped explicitly
@@ -1988,24 +2139,91 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
// live in System.Collections (no Java peer at all) and are wrapped at
// runtime by JavaList/JavaDictionary/JavaCollection.
var wellKnown = managedType.ManagedTypeName switch {
- "Java.Lang.ICharSequence" => "Ljava/lang/CharSequence;",
- "System.Collections.IList" => "Ljava/util/List;",
- "System.Collections.IDictionary" => "Ljava/util/Map;",
- "System.Collections.ICollection" => "Ljava/util/Collection;",
+ "Java.Lang.ICharSequence" when IsSpecialManagedType (
+ managedType,
+ "Java.Lang.ICharSequence",
+ "Mono.Android") =>
+ "Ljava/lang/CharSequence;",
+ "System.Collections.IList" when IsSpecialManagedType (
+ managedType,
+ "System.Collections.IList",
+ "System.Runtime",
+ "System.Collections.NonGeneric",
+ "System.Private.CoreLib",
+ "mscorlib") =>
+ "Ljava/util/List;",
+ "System.Collections.IDictionary" when IsSpecialManagedType (
+ managedType,
+ "System.Collections.IDictionary",
+ "System.Runtime",
+ "System.Collections.NonGeneric",
+ "System.Private.CoreLib",
+ "mscorlib") =>
+ "Ljava/util/Map;",
+ "System.Collections.ICollection" when IsSpecialManagedType (
+ managedType,
+ "System.Collections.ICollection",
+ "System.Runtime",
+ "System.Collections.NonGeneric",
+ "System.Private.CoreLib",
+ "mscorlib") =>
+ "Ljava/util/Collection;",
_ => null,
};
if (wellKnown is not null) {
- return wellKnown;
+ descriptor = wellKnown;
+ return true;
}
// Enum parameters use their underlying primitive JNI ABI (matches legacy
// CallbackCode behavior).
var enumDescriptor = TryResolveEnumUnderlyingDescriptor (managedType.ManagedTypeName, managedType.AssemblyName);
if (enumDescriptor is not null) {
- return enumDescriptor;
+ descriptor = enumDescriptor;
+ return true;
}
- return "Ljava/lang/Object;";
+ descriptor = "";
+ return false;
+ }
+
+ bool TryGetExportParameterDescriptor (
+ TypeRefData managedType,
+ ExportParameterKindInfo exportKind,
+ out string descriptor)
+ {
+ descriptor = exportKind switch {
+ ExportParameterKindInfo.InputStream when IsSpecialManagedType (managedType, "System.IO.Stream", "System.Runtime", "System.Private.CoreLib") =>
+ "Ljava/io/InputStream;",
+ ExportParameterKindInfo.OutputStream when IsSpecialManagedType (managedType, "System.IO.Stream", "System.Runtime", "System.Private.CoreLib") =>
+ "Ljava/io/OutputStream;",
+ ExportParameterKindInfo.XmlPullParser when IsSpecialManagedType (
+ managedType,
+ "System.Xml.XmlReader",
+ "System.Xml.ReaderWriter",
+ "System.Private.Xml") =>
+ "Lorg/xmlpull/v1/XmlPullParser;",
+ ExportParameterKindInfo.XmlResourceParser when IsSpecialManagedType (
+ managedType,
+ "System.Xml.XmlReader",
+ "System.Xml.ReaderWriter",
+ "System.Private.Xml") =>
+ "Landroid/content/res/XmlResourceParser;",
+ _ => "",
+ };
+ return descriptor.Length > 0;
+ }
+
+ bool IsSpecialManagedType (TypeRefData managedType, string managedTypeName, params string [] assemblyNames)
+ {
+ if (!string.Equals (managedType.ManagedTypeName, managedTypeName, StringComparison.Ordinal)) {
+ return false;
+ }
+ if (assemblyNames.Contains (managedType.AssemblyName, StringComparer.Ordinal)) {
+ return true;
+ }
+ return TryResolveType (managedTypeName, managedType.AssemblyName, out _, out var resolvedIndex) &&
+ assemblyNames.Contains (resolvedIndex.MetadataAssemblyName, StringComparer.Ordinal);
}
///
@@ -2569,7 +2787,10 @@ List BuildJavaConstructors (List marshal
// Unsupported managed parameter shapes fail in model building for [Export]
// constructors; non-[Export] registrations keep the legacy activation fallback.
publicConstructors ??= GetSupportedPublicConstructors (typeDef, index);
- var managedParams = TryGetMatchingPublicConstructorParameterTypes (publicConstructors, mm.JniSignature);
+ var managedParams = TryGetMatchingPublicConstructorParameterTypes (
+ publicConstructors,
+ mm.JniSignature,
+ mm.ManagedParameterExportKinds);
ctors.Add (new JavaConstructorInfo {
JniSignature = mm.JniSignature,
ConstructorIndex = ctorIndex,
@@ -2578,6 +2799,7 @@ List BuildJavaConstructors (List marshal
HasMatchingManagedCtor = managedParams != null,
ManagedParameterTypes = managedParams ?? [],
Annotations = mm.Annotations,
+ ManagedParameterExportKinds = mm.ManagedParameterExportKinds,
});
ctorIndex++;
}
@@ -2598,10 +2820,7 @@ List GetSupportedPublicConstructors (TypeDefinition typeD
var sig = methodDef.DecodeSignature (index.TypeRefSignatureProvider, genericContext: index);
bool unsupported = false;
foreach (var parameter in sig.ParameterTypes) {
- var typeName = parameter.ManagedTypeName;
- if (parameter.GenericArguments.Count > 0 ||
- typeName.EndsWith ("&", StringComparison.Ordinal) ||
- typeName.EndsWith ("*", StringComparison.Ordinal)) {
+ if (IsOwnedByConstructorDiagnostics (parameter)) {
unsupported = true;
break;
}
@@ -2625,27 +2844,54 @@ List GetSupportedPublicConstructors (TypeDefinition typeD
/// parameter types. Returns when no compatible
/// constructor exists.
///
- static IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes (
+ IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes (
List publicConstructors,
- string jniSignature)
+ string jniSignature,
+ IReadOnlyList parameterKinds)
{
int closeParen = jniSignature.IndexOf (')');
if (closeParen < 0) {
throw new ArgumentException ($"Malformed JNI signature '{jniSignature}': missing ')'");
}
int parameterSignatureLength = closeParen + 1;
+ var jniParams = parameterKinds.Any (kind => kind != ExportParameterKindInfo.Unspecified)
+ ? JniSignatureHelper.ParseParameters (jniSignature)
+ : null;
foreach (var constructor in publicConstructors) {
- if (constructor.JniParameterSignature.Length != parameterSignatureLength ||
- string.CompareOrdinal (jniSignature, 0, constructor.JniParameterSignature, 0, parameterSignatureLength) != 0) {
+ if (jniParams is null) {
+ if (constructor.JniParameterSignature.Length != parameterSignatureLength ||
+ string.CompareOrdinal (jniSignature, 0, constructor.JniParameterSignature, 0, parameterSignatureLength) != 0) {
+ continue;
+ }
+ } else if (!ManagedConstructorParametersMatchJniSignature (constructor.ParameterTypes, parameterKinds, jniParams)) {
continue;
}
// If multiple overloads with the same JNI-compatible signature exist, match
// the first public constructor in metadata order, like TypeManager.Activate.
- return [.. constructor.ParameterTypes];
+ return constructor.ParameterTypes;
}
return null;
}
+ bool ManagedConstructorParametersMatchJniSignature (
+ IReadOnlyList managedParams,
+ IReadOnlyList parameterKinds,
+ IReadOnlyList jniParams)
+ {
+ if (managedParams.Count != jniParams.Count) {
+ return false;
+ }
+
+ for (int i = 0; i < managedParams.Count; i++) {
+ var parameterKind = i < parameterKinds.Count ? parameterKinds [i] : ExportParameterKindInfo.Unspecified;
+ var managedDescriptor = ManagedTypeToJniDescriptor (managedParams [i], parameterKind);
+ if (!string.Equals (managedDescriptor, jniParams [i].JniType, StringComparison.Ordinal)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
///
/// Checks a single method for [ExportField] and adds a JavaFieldInfo if found.
/// Called inline during Pass 1 to avoid a separate iteration.
@@ -2671,7 +2917,8 @@ void CollectExportField (MethodDefinition methodDef, AssemblyIndex index, List
+ /// Looks up a localized string similar to The exported member '{0}' has unsupported signature type '{1}'.
+ /// Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter]..
+ ///
+ public static string XA4263 {
+ get {
+ return ResourceManager.GetString("XA4263", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Native library '{0}' will not be bundled because it has an unsupported ABI. Move this file to a directory with a valid Android ABI name such as 'libs/armeabi-v7a/'..
///
diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
index aef2dbc527a..37d338c5f2b 100644
--- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
+++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
@@ -1242,6 +1242,12 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS
The following are literal names and should not be translated: Java.
{0} - Java package or type name
{1} - Java reserved keyword or restricted identifier
+
+
+ The exported member '{0}' has unsupported signature type '{1}'. Use a Java peer type, a supported managed-to-Java mapping, or [ExportParameter].
+ The following are literal names and should not be translated: Java, [ExportParameter].
+{0} - Fully-qualified managed member name
+{1} - Managed parameter or return type
Command '{0}' failed.\n{1}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
index 1c2b02aa583..f9121daccb3 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
@@ -61,10 +61,14 @@ public void LogInvalidJavaNameError (string javaName, string invalidIdentifier)
log.LogCodedError ("XA4258", Properties.Resources.XA4258, javaName, invalidIdentifier);
public void LogExportFieldWithParametersError () =>
log.LogCodedError ("XA4205", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4205);
+ public void LogExportOnGenericTypeError () =>
+ log.LogCodedError ("XA4206", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4206);
public void LogExportFieldOnGenericTypeError () =>
log.LogCodedError ("XA4207", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4207);
public void LogExportFieldReturnsVoidError () =>
log.LogCodedError ("XA4208", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4208);
+ public void LogUnsupportedExportSignatureError (string memberName, string managedTypeName) =>
+ log.LogCodedError ("XA4263", Properties.Resources.XA4263, memberName, managedTypeName);
public void LogCustomJavaObjectError (string managedTypeName) =>
log.LogError ("{0}", $"XA4212: {string.Format (Properties.Resources.XA4212, managedTypeName)}");
public void LogCustomJavaObjectWarning (string managedTypeName) =>
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs
index a426a5b7f0d..9c1df5990e5 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs
@@ -95,6 +95,438 @@ public void Build_InvalidExportField_ReportsLegacyDiagnostic (
}
}
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "arbitrary-parameter", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "arbitrary-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "arbitrary-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "arbitrary-return", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "arbitrary-return", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "arbitrary-return", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "arbitrary-field", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "arbitrary-field", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "arbitrary-field", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-parameter", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-instantiation", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-instantiation", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-instantiation", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "function-pointer", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "function-pointer", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "function-pointer", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "by-ref-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "by-ref-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "pointer-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "pointer-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "rectangular-array-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "rectangular-array-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-declaring-type", "XA4206")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-declaring-type", "XA4206")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-declaring-type", "XA4206")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "mismatched-export-parameter", "XALNS7004")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "mismatched-export-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "mismatched-export-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "generic-export-parameter", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "generic-export-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "generic-export-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "function-pointer-export-parameter", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "function-pointer-export-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "function-pointer-export-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "mismatched-field-export-parameter", "XALNS7004")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "mismatched-field-export-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "mismatched-field-export-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-array-parameter", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-array-parameter", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-array-parameter", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-array-return", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-array-return", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-array-return", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-array-field", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-array-field", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-array-field", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "special-xml-array-return", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "special-xml-array-return", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "special-xml-array-return", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-static-constructor", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-static-constructor", "success")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-static-constructor", "success")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-constructor-arbitrary", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-constructor-arbitrary", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-constructor-arbitrary", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-constructor-invalid-kind", "XALNS7004")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-constructor-invalid-kind", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-constructor-invalid-kind", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-constructor-valid-kind", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-constructor-valid-kind", "success")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-constructor-valid-kind", "success")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-named-constructor-arbitrary", "XALNS7003")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-named-constructor-arbitrary", "XA4263")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-named-constructor-arbitrary", "XA4263")]
+ [TestCase ("llvm-ir", AndroidRuntime.CoreCLR, "export-named-constructor-valid-kind", "success")]
+ [TestCase ("trimmable", AndroidRuntime.CoreCLR, "export-named-constructor-valid-kind", "success")]
+ [TestCase ("trimmable", AndroidRuntime.NativeAOT, "export-named-constructor-valid-kind", "success")]
+ public void Build_ExportSignature_MatchesRuntimeClassification (
+ string typeMapImplementation,
+ AndroidRuntime runtime,
+ string invalidShape,
+ string expectedCode)
+ {
+ bool isRelease = runtime == AndroidRuntime.NativeAOT;
+ if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) {
+ return;
+ }
+
+ var (additionalTypes, member, marker, typeParameters) = invalidShape switch {
+ "arbitrary-parameter" => (
+ "public sealed class ManagedOnly { }",
+ """[Export ("unsupported")] public void UnsupportedMember (ManagedOnly value) { }""",
+ "unsupported",
+ ""),
+ "arbitrary-return" => (
+ "public sealed class ManagedOnly { }",
+ """[Export ("unsupported")] public ManagedOnly UnsupportedMember () => new ();""",
+ "unsupported",
+ ""),
+ "arbitrary-field" => (
+ "public sealed class ManagedOnly { }",
+ """[ExportField ("UNSUPPORTED_FIELD")] public ManagedOnly UnsupportedMember () => new ();""",
+ "UNSUPPORTED_FIELD",
+ ""),
+ "generic-parameter" => (
+ "",
+ """[Export ("unsupported")] public T UnsupportedMember (T value) => value;""",
+ "unsupported",
+ ""),
+ "generic-instantiation" => (
+ "",
+ """[Export ("unsupported")] public List UnsupportedMember (List value) => value;""",
+ "unsupported",
+ ""),
+ "function-pointer" => (
+ "",
+ """[Export ("unsupported")] public unsafe delegate* unmanaged UnsupportedMember (delegate* unmanaged value) => value;""",
+ "unsupported",
+ ""),
+ "by-ref-parameter" => (
+ "",
+ """[Export ("unsupported")] public void UnsupportedMember (ref int value) { }""",
+ "unsupported",
+ ""),
+ "pointer-parameter" => (
+ "",
+ """[Export ("unsupported")] public unsafe void UnsupportedMember (int* value) { }""",
+ "unsupported",
+ ""),
+ "rectangular-array-parameter" => (
+ "",
+ """[Export ("unsupported")] public void UnsupportedMember (string [,] value) { }""",
+ "unsupported",
+ ""),
+ "generic-declaring-type" => (
+ "",
+ """[Export ("unsupported")] public int UnsupportedMember () => 0;""",
+ "unsupported",
+ ""),
+ "mismatched-export-parameter" => (
+ "public sealed class ManagedOnly { }",
+ """
+ [Export ("unsupported")]
+ public ManagedOnly UnsupportedMember (
+ [ExportParameter (ExportParameterKind.InputStream)] ManagedOnly value)
+ => value;
+ """,
+ "unsupported",
+ ""),
+ "generic-export-parameter" => (
+ "",
+ """
+ [Export ("unsupported")]
+ public T UnsupportedMember (
+ [ExportParameter (ExportParameterKind.InputStream)] T value)
+ => value;
+ """,
+ "unsupported",
+ ""),
+ "function-pointer-export-parameter" => (
+ "",
+ """
+ [Export ("unsupported")]
+ public unsafe delegate* unmanaged UnsupportedMember (
+ [ExportParameter (ExportParameterKind.InputStream)] delegate* unmanaged value)
+ => value;
+ """,
+ "unsupported",
+ ""),
+ "mismatched-field-export-parameter" => (
+ "public sealed class ManagedOnly { }",
+ """
+ [return: ExportParameter (ExportParameterKind.OutputStream)]
+ [ExportField ("UNSUPPORTED_FIELD")]
+ public ManagedOnly UnsupportedMember () => new ();
+ """,
+ "UNSUPPORTED_FIELD",
+ ""),
+ "special-array-parameter" => (
+ "",
+ """
+ [Export ("unsupported")]
+ public void UnsupportedMember (
+ [ExportParameter (ExportParameterKind.InputStream)] Stream [] value)
+ {
+ }
+ """,
+ "unsupported",
+ ""),
+ "special-array-return" => (
+ "",
+ """
+ [return: ExportParameter (ExportParameterKind.OutputStream)]
+ [Export ("unsupported")]
+ public Stream [] UnsupportedMember () => [];
+ """,
+ "unsupported",
+ ""),
+ "special-array-field" => (
+ "",
+ """
+ [return: ExportParameter (ExportParameterKind.OutputStream)]
+ [ExportField ("UNSUPPORTED_FIELD")]
+ public Stream [] UnsupportedMember () => [];
+ """,
+ "UNSUPPORTED_FIELD",
+ ""),
+ "special-xml-array-return" => (
+ "",
+ """
+ [return: ExportParameter (ExportParameterKind.XmlPullParser)]
+ [Export ("unsupported")]
+ public XmlReader [] UnsupportedMember () => [];
+ """,
+ "unsupported",
+ ""),
+ "export-static-constructor" => (
+ "",
+ """
+ [Export]
+ static SignaturePeer ()
+ {
+ }
+ """,
+ ".cctor",
+ ""),
+ "export-constructor-arbitrary" => (
+ "public sealed class ManagedOnly { }",
+ """
+ [Export (".ctor", SuperArgumentsString = "")]
+ public SignaturePeer (ManagedOnly value)
+ {
+ }
+ """,
+ "SignaturePeer",
+ ""),
+ "export-constructor-invalid-kind" => (
+ "public sealed class ManagedOnly { }",
+ """
+ [Export (".ctor", SuperArgumentsString = "")]
+ public SignaturePeer (
+ [ExportParameter (ExportParameterKind.InputStream)] ManagedOnly value)
+ {
+ }
+ """,
+ "SignaturePeer",
+ ""),
+ "export-constructor-valid-kind" => (
+ "",
+ """
+ [Export (".ctor", SuperArgumentsString = "")]
+ public SignaturePeer (
+ [ExportParameter (ExportParameterKind.InputStream)] Stream value)
+ {
+ }
+ """,
+ "java.io.InputStream",
+ ""),
+ "export-named-constructor-arbitrary" => (
+ "public sealed class ManagedOnly { }",
+ """
+ [Export ("notAConstructor", SuperArgumentsString = "")]
+ public SignaturePeer (ManagedOnly value)
+ {
+ }
+ """,
+ "SignaturePeer",
+ ""),
+ "export-named-constructor-valid-kind" => (
+ "",
+ """
+ [Export ("notAConstructor", SuperArgumentsString = "")]
+ public SignaturePeer (
+ [ExportParameter (ExportParameterKind.InputStream)] Stream value)
+ {
+ }
+ """,
+ "java.io.InputStream",
+ ""),
+ _ => throw new InvalidOperationException ($"Unknown unsupported [Export] shape '{invalidShape}'."),
+ };
+ var proj = new XamarinAndroidApplicationProject {
+ IsRelease = isRelease,
+ References = {
+ new BuildItem.Reference ("Mono.Android.Export"),
+ },
+ };
+ proj.SetRuntime (runtime);
+ proj.SetProperty ("AndroidTypeMapImplementation", typeMapImplementation);
+ proj.SetProperty ("AllowUnsafeBlocks", "true");
+ proj.Sources.Add (new BuildItem.Source ("ExportSignatureValidation.cs") {
+ TextContent = () => $$"""
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Xml;
+ using Android.Runtime;
+ using Java.Interop;
+
+ namespace ExportSignatureValidation {
+ {{additionalTypes}}
+
+ [Register ("com/example/exports/SignaturePeer")]
+ public class SignaturePeer{{typeParameters}} : Java.Lang.Object {
+ public SignaturePeer () {
+ }
+
+ {{member}}
+ }
+ }
+ """,
+ });
+
+ using var builder = CreateApkBuilder ();
+ builder.ThrowOnBuildFailure = false;
+ var succeeded = builder.Build (proj);
+ if (expectedCode == "success") {
+ Assert.IsTrue (succeeded, $"{runtime}/{typeMapImplementation} should retain legacy build support for {invalidShape}.");
+ return;
+ }
+
+ Assert.IsFalse (succeeded, $"{runtime}/{typeMapImplementation} should reject {invalidShape}.");
+ StringAssertEx.Contains ($"error {expectedCode}", builder.LastBuildOutput, $"The build should report {expectedCode}.");
+ if (expectedCode == "XA4263") {
+ var expectedMemberName = invalidShape.Contains ("constructor", StringComparison.Ordinal)
+ ? "ExportSignatureValidation.SignaturePeer.ctor"
+ : "ExportSignatureValidation.SignaturePeer.UnsupportedMember";
+ StringAssertEx.Contains (
+ expectedMemberName,
+ builder.LastBuildOutput,
+ "The diagnostic should identify the unsupported managed member."
+ );
+ }
+ AssertNoExportOutputs (builder, marker);
+ }
+
+ [TestCase (AndroidRuntime.CoreCLR)]
+ [TestCase (AndroidRuntime.NativeAOT)]
+ public void Build_SpecialMappingLookalikeTypes_ReportXA4263WithoutPartialOutputs (AndroidRuntime runtime)
+ {
+ bool isRelease = runtime == AndroidRuntime.NativeAOT;
+ if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject {
+ IsRelease = isRelease,
+ References = {
+ new BuildItem.Reference ("Mono.Android.Export"),
+ },
+ };
+ proj.SetRuntime (runtime);
+ proj.SetProperty ("AndroidTypeMapImplementation", "trimmable");
+ proj.Sources.Add (new BuildItem.Source ("SpecialMappingLookalikes.cs") {
+ TextContent = () => """
+ using Android.Runtime;
+ using Java.Interop;
+
+ namespace System.IO {
+ public class Stream {
+ }
+ }
+
+ namespace System.Xml {
+ public class XmlReader {
+ }
+ }
+
+ namespace Java.Lang {
+ public interface ICharSequence {
+ }
+ }
+
+ namespace System.Collections {
+ public interface IList {
+ }
+
+ public interface IDictionary {
+ }
+
+ public interface ICollection {
+ }
+ }
+
+ namespace SpecialMappingLookalikes {
+ [Register ("com/example/exports/SpecialMappingLookalikePeer")]
+ public class SpecialMappingLookalikePeer : Java.Lang.Object {
+ [return: ExportParameter (ExportParameterKind.OutputStream)]
+ [Export ("invalidStream")]
+ public System.IO.Stream InvalidStream (
+ [ExportParameter (ExportParameterKind.InputStream)] System.IO.Stream value)
+ => value;
+
+ [return: ExportParameter (ExportParameterKind.XmlPullParser)]
+ [ExportField ("INVALID_XML_FIELD")]
+ public System.Xml.XmlReader InvalidXmlField () => new ();
+
+ [Export ("invalidCharSequence")]
+ public Java.Lang.ICharSequence InvalidCharSequence (Java.Lang.ICharSequence value) => value;
+
+ [Export ("invalidList")]
+ public System.Collections.IList InvalidList (System.Collections.IList value) => value;
+
+ [Export ("invalidDictionary")]
+ public System.Collections.IDictionary InvalidDictionary (System.Collections.IDictionary value) => value;
+
+ [Export ("invalidCollection")]
+ public System.Collections.ICollection InvalidCollection (System.Collections.ICollection value) => value;
+
+ [Export ("notAConstructor", SuperArgumentsString = "")]
+ public SpecialMappingLookalikePeer (
+ [ExportParameter (ExportParameterKind.InputStream)] System.IO.Stream value)
+ {
+ }
+ }
+ }
+ """,
+ });
+
+ using var builder = CreateApkBuilder ();
+ builder.ThrowOnBuildFailure = false;
+ Assert.IsFalse (builder.Build (proj), $"{runtime}/trimmable should reject special-mapping lookalike types.");
+ foreach (var memberName in new [] {
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidStream",
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidXmlField",
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidCharSequence",
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidList",
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidDictionary",
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.InvalidCollection",
+ "SpecialMappingLookalikes.SpecialMappingLookalikePeer.ctor",
+ }) {
+ Assert.IsTrue (
+ builder.LastBuildOutput.Any (line =>
+ line.Contains ("error XA4263", StringComparison.Ordinal) &&
+ line.Contains (memberName, StringComparison.Ordinal)),
+ $"The build should report XA4263 for '{memberName}'."
+ );
+ }
+ AssertNoExportOutputs (builder, "invalidStream");
+ }
+
static XamarinAndroidApplicationProject CreateExportFieldValidationProject (
AndroidRuntime runtime,
string typeMapImplementation,
@@ -129,6 +561,73 @@ public ValidationPeer () {
return proj;
}
+ [Test]
+ public void FindExportOutputs_FindsEveryArtifactKind ()
+ {
+ var root = Path.Combine (Root, "temp", TestName);
+ var typemapDirectory = Path.Combine (root, "typemap");
+ var acwMapFile = Path.Combine (root, "acw-map.txt");
+ var trimmableJavaDirectory = Path.Combine (typemapDirectory, "java", "com", "example");
+ var llvmIrJavaDirectory = Path.Combine (root, "android", "src", "com", "example");
+ Directory.CreateDirectory (trimmableJavaDirectory);
+ Directory.CreateDirectory (llvmIrJavaDirectory);
+
+ var expected = new [] {
+ Path.Combine (typemapDirectory, "_Example.TypeMap.dll"),
+ Path.Combine (typemapDirectory, "_Microsoft.Android.TypeMaps.dll"),
+ acwMapFile,
+ Path.Combine (trimmableJavaDirectory, "TrimmablePeer.java"),
+ Path.Combine (llvmIrJavaDirectory, "LlvmIrPeer.java"),
+ };
+ File.WriteAllBytes (expected [0], []);
+ File.WriteAllBytes (expected [1], []);
+ File.WriteAllText (expected [2], "Managed, Assembly;com/example/Peer");
+ File.WriteAllText (expected [3], "public int VALUE = InitialValue ();");
+ File.WriteAllText (expected [4], "public int VALUE = InitialValue ();");
+
+ CollectionAssert.AreEquivalent (
+ expected,
+ FindExportOutputs (typemapDirectory, acwMapFile, Path.Combine (root, "android", "src"), "VALUE")
+ );
+ }
+
+ static void AssertNoExportOutputs (ProjectBuilder builder, string memberName)
+ {
+ var typemapDirectory = builder.Output.GetIntermediaryPath ("typemap");
+ var acwMapFile = builder.Output.GetIntermediaryPath ("acw-map.txt");
+ var androidSourceDirectory = builder.Output.GetIntermediaryPath (Path.Combine ("android", "src"));
+ var outputs = FindExportOutputs (typemapDirectory, acwMapFile, androidSourceDirectory, memberName);
+ Assert.IsEmpty (
+ outputs,
+ "Invalid exported metadata should not produce typemap assemblies, ACW maps, or partial Java output:" +
+ Environment.NewLine + string.Join (Environment.NewLine, outputs)
+ );
+ }
+
+ static string [] FindExportOutputs (string typemapDirectory, string acwMapFile, string androidSourceDirectory, string memberName)
+ {
+ var outputs = new List ();
+ if (Directory.Exists (typemapDirectory)) {
+ outputs.AddRange (Directory.GetFiles (typemapDirectory, "*.TypeMap.dll", SearchOption.AllDirectories));
+ outputs.AddRange (Directory.GetFiles (typemapDirectory, "_Microsoft.Android.TypeMaps.dll", SearchOption.AllDirectories));
+ }
+ if (File.Exists (acwMapFile)) {
+ outputs.Add (acwMapFile);
+ }
+
+ foreach (var javaDirectory in new [] { Path.Combine (typemapDirectory, "java"), androidSourceDirectory }) {
+ if (!Directory.Exists (javaDirectory)) {
+ continue;
+ }
+ foreach (var javaFile in Directory.GetFiles (javaDirectory, "*.java", SearchOption.AllDirectories)) {
+ if (File.ReadAllText (javaFile).Contains (memberName, StringComparison.Ordinal)) {
+ outputs.Add (javaFile);
+ }
+ }
+ }
+ return outputs.ToArray ();
+ }
+
[Test]
public void Build_PublishAotProject_UsesTrimmableTypeMapForCoreClrDebug ()
{
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj
index 96df7a6693b..7e5b7d0bd14 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj
@@ -22,6 +22,7 @@
+
@@ -36,6 +37,8 @@
ReferenceOutputAssembly=false because we don't compile against it — we just need the DLL on disk. -->
+
+
+
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/ICharSequence.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/ICharSequence.cs
new file mode 100644
index 00000000000..8fbba093837
--- /dev/null
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/ICharSequence.cs
@@ -0,0 +1,7 @@
+namespace Java.Lang
+{
+ // Mirrors Mono.Android's interface without a [Register] attribute.
+ public interface ICharSequence
+ {
+ }
+}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj
new file mode 100644
index 00000000000..92393ba196a
--- /dev/null
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj
@@ -0,0 +1,12 @@
+
+
+
+ $(DotNetStableTargetFramework)
+ latest
+ enable
+ Mono.Android
+ Mono.Android
+ false
+
+
+
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs
index d6b10c033ce..9033bca8e81 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs
@@ -148,6 +148,9 @@ public void Scan_ExportMethod_CapturesPreciseManagedTypeMetadata ()
var xmlMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes")
.MarshalMethods.First (m => m.JniName == "readXml");
+ var streamMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes")
+ .MarshalMethods.First (m => m.JniName == "wrapStream");
+ Assert.Equal ("System.Runtime", streamMethod.ManagedReturnType.AssemblyName);
Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlMethod.ManagedParameterExportKinds [0]);
Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlMethod.ManagedReturnExportKind);
Assert.Equal ("System.Xml.ReaderWriter", xmlMethod.ManagedReturnType.AssemblyName);
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs
index 88ca5b2f78b..2baf133e290 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs
@@ -10,6 +10,27 @@ public enum JniHandleOwnership
}
}
+namespace Lookalike
+{
+ [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)]
+ public sealed class ExportAttribute : Attribute
+ {
+ public ExportAttribute (string name) { }
+ }
+
+ [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false)]
+ public sealed class ExportParameterAttribute : Attribute
+ {
+ public ExportParameterAttribute (Java.Interop.ExportParameterKind kind) { }
+ }
+
+ [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)]
+ public sealed class ExportFieldAttribute : Attribute
+ {
+ public ExportFieldAttribute (string name) { }
+ }
+}
+
namespace Java.Interop
{
public struct JniObjectReference
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj
index c5f61f8238c..fb6e87d28d5 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj
@@ -11,6 +11,7 @@
+
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs
index 5d0b6000196..51375b8a434 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs
@@ -29,12 +29,6 @@ public class Exception : Throwable
{
protected Exception (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { }
}
-
- // Mirrors Mono.Android's Java.Lang.ICharSequence: an interface without a
- // [Register] attribute. The trimmable typemap scanner / emitter must
- // special-case it to map onto java/lang/CharSequence and dispatch via
- // Android.Runtime.CharSequence.ToLocalJniHandle.
- public interface ICharSequence { }
}
namespace Android.App
@@ -580,6 +574,137 @@ public class ExportCollectionShapes : Java.Lang.Object
public System.Collections.ICollection? EchoCollection (System.Collections.ICollection? value) => value;
}
+ public class UnsupportedExportValue
+ {
+ }
+
+ [Register ("my/app/ExportWithUnsupportedManagedParameter")]
+ public class ExportWithUnsupportedManagedParameter : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public void UnsupportedMember (UnsupportedExportValue value) { }
+ }
+
+ [Register ("my/app/ExportWithUnsupportedManagedReturn")]
+ public class ExportWithUnsupportedManagedReturn : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public UnsupportedExportValue UnsupportedMember () => new ();
+ }
+
+ [Register ("my/app/ExportFieldWithUnsupportedManagedReturn")]
+ public class ExportFieldWithUnsupportedManagedReturn : Java.Lang.Object
+ {
+ [Java.Interop.ExportField ("UNSUPPORTED_FIELD")]
+ public UnsupportedExportValue UnsupportedMember () => new ();
+ }
+
+ [Register ("my/app/ExportWithGenericMethodParameter")]
+ public class ExportWithGenericMethodParameter : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public T UnsupportedMember (T value) => value;
+ }
+
+ [Register ("my/app/ExportWithGenericInstantiation")]
+ public class ExportWithGenericInstantiation : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public System.Collections.Generic.List UnsupportedMember (System.Collections.Generic.List value) => value;
+ }
+
+ [Register ("my/app/ExportWithInvalidExportParameterType")]
+ public class ExportWithInvalidExportParameterType : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public UnsupportedExportValue UnsupportedMember (
+ [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] UnsupportedExportValue value)
+ => value;
+ }
+
+ [Register ("my/app/ExportWithInvalidExportParameterKind")]
+ public class ExportWithInvalidExportParameterKind : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public void UnsupportedMember (
+ [Java.Interop.ExportParameter ((Java.Interop.ExportParameterKind) 999)] int value)
+ {
+ }
+ }
+
+ [Register ("my/app/ExportWithGenericExportParameter")]
+ public class ExportWithGenericExportParameter : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public T UnsupportedMember (
+ [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] T value)
+ => value;
+ }
+
+ [Register ("my/app/ExportFieldWithInvalidExportParameterType")]
+ public class ExportFieldWithInvalidExportParameterType : Java.Lang.Object
+ {
+ [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.OutputStream)]
+ [Java.Interop.ExportField ("UNSUPPORTED_FIELD")]
+ public UnsupportedExportValue UnsupportedMember () => new ();
+ }
+
+ [Register ("my/app/GenericExportType")]
+ public class GenericExportType : Java.Lang.Object
+ {
+ [Java.Interop.Export ("unsupported")]
+ public int UnsupportedMember () => 0;
+ }
+
+ [Register ("my/app/ExportAttributeLookalikes")]
+ public class ExportAttributeLookalikes : Java.Lang.Object
+ {
+ [Lookalike.Export ("NOT_AN_EXPORT")]
+ public void LookalikeExport (
+ [Lookalike.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] UnsupportedExportValue value)
+ {
+ }
+
+ [Java.Interop.Export ("realExport")]
+ public string RealExport (
+ [Lookalike.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] string value)
+ => value;
+ }
+
+ [Register ("my/app/ExportConstructorUnsupportedManagedParameter")]
+ public class ExportConstructorUnsupportedManagedParameter : Android.App.Activity
+ {
+ [Java.Interop.Export ("notAConstructor", SuperArgumentsString = "")]
+ public ExportConstructorUnsupportedManagedParameter (UnsupportedExportValue value) { }
+ }
+
+ [Register ("my/app/ExportConstructorInvalidExportParameter")]
+ public class ExportConstructorInvalidExportParameter : Android.App.Activity
+ {
+ [Java.Interop.Export (".ctor", SuperArgumentsString = "")]
+ public ExportConstructorInvalidExportParameter (
+ [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] UnsupportedExportValue value)
+ {
+ }
+ }
+
+ [Register ("my/app/ExportConstructorMappedParameter")]
+ public class ExportConstructorMappedParameter : Android.App.Activity
+ {
+ [Java.Interop.Export ("notAConstructor", SuperArgumentsString = "")]
+ public ExportConstructorMappedParameter (
+ [Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] Stream value)
+ {
+ }
+ }
+
+ [Register ("my/app/ExportStaticConstructor")]
+ public class ExportStaticConstructor : Java.Lang.Object
+ {
+ [Java.Interop.Export]
+ static ExportStaticConstructor () { }
+ }
+
///
/// Has [Export] methods with different access modifiers.
/// The JCW should respect the C# visibility for [Export] methods.
@@ -616,6 +741,14 @@ protected ExportFieldExample (IntPtr handle, JniHandleOwnership transfer) : base
[Java.Interop.ExportField ("VALUE")]
[JavaAnnotation]
public string GetValue () => "";
+
+ [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.OutputStream)]
+ [Java.Interop.ExportField ("OUTPUT_STREAM")]
+ public Stream? GetOutputStream () => null;
+
+ [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.XmlPullParser)]
+ [Java.Interop.ExportField ("XML_PARSER")]
+ public XmlReader? GetXmlParser () => null;
}
[Register ("my/app/BaseInstrumentation")]