Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ca78fa2
[TrimmableTypeMap] Add AOT-safe array and collection factories
simonrozsival Jul 9, 2026
db04690
[TrimmableTypeMap] Address safe collection factory review
simonrozsival Jul 9, 2026
9e0ce62
[TrimmableTypeMap] Document NativeAOT factory assumptions
simonrozsival Jul 9, 2026
0c62664
[TrimmableTypeMap] Share primitive value type factories
simonrozsival Jul 9, 2026
53d50fb
[TrimmableTypeMap] Clarify array factory AOT justification
simonrozsival Jul 9, 2026
b959f4a
[TrimmableTypeMap] Fix NativeAOT warning leak and update apk size bas…
simonrozsival Jul 9, 2026
1143044
[TrimmableTypeMap] Address review: file-scoped namespaces, drop unuse…
simonrozsival Jul 9, 2026
d41d005
[TrimmableTypeMap] Fix nullable warnings in SafeJavaCollectionFactory
simonrozsival Jul 9, 2026
187d9fa
[TrimmableTypeMap] Address review: value/value dict test, drop unused…
simonrozsival Jul 9, 2026
c0ca782
Merge origin/main into android-trimmable-revisit-arrays-generic-colle…
simonrozsival Jul 13, 2026
f663705
Merge remote-tracking branch 'origin/main' into dev/simonrozsival/and…
simonrozsival Jul 13, 2026
cb99cd2
[TrimmableTypeMap] Address review: nameof and redundant MakeGenericType
simonrozsival Jul 13, 2026
e01bc8c
[TrimmableTypeMap] Fix NativeAOT array-types fallback to return full set
simonrozsival Jul 13, 2026
6eabdc9
[TrimmableTypeMap] Restore byte collection support on trimmable path
simonrozsival Jul 13, 2026
2a9cba7
[TrimmableTypeMap] Address collection factory review
simonrozsival Jul 14, 2026
0ce5dd1
[TrimmableTypeMap] Strengthen factory regression coverage
simonrozsival Jul 14, 2026
da5f681
Merge origin/main into trimmable type map work
simonrozsival Jul 14, 2026
949eef2
[TrimmableTypeMap] Complete runtime array proxy replacement
simonrozsival Jul 14, 2026
8d6578f
[TrimmableTypeMap] Update CoreCLR APK size baseline
simonrozsival Jul 14, 2026
409b05b
[TrimmableTypeMap] Fix byte collection test build
simonrozsival Jul 14, 2026
1fc0450
[tests] Scope trimmable factory coverage
simonrozsival Jul 15, 2026
f87ade5
[tests] Restore JavaConvert factory coverage
simonrozsival Jul 15, 2026
cf8cd39
[Java.Interop] Map nullable byte to java.lang.Byte
simonrozsival Jul 15, 2026
8fc311d
[tests] Cover nullable byte vectors
simonrozsival Jul 15, 2026
a1c4381
Merge origin/main and resolve conflicts
Copilot Aug 19, 2026
dab9835
Merge origin/main and resolve submodule conflict
Copilot Aug 19, 2026
c7ca410
Address nullable byte array review feedback
simonrozsival Aug 21, 2026
dce6c49
Add nullable byte value marshaler
simonrozsival Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,15 @@ stages:
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-NativeAOT

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
testName: Mono.Android.NET_Tests-NativeAOTTrimmable
project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
extraBuildArgs: -p:TestsFlavor=NativeAOTTrimmable -p:PublishAot=true -p:AndroidTypeMapImplementation=trimmable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these settings already the default for NativeAOT? If so, this is a duplicate test run?

artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-NativeAOTTrimmable

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ static bool GetBuiltInTypeSignature (Type type, ref JniTypeSignature signature)
signature = GetCachedTypeSignature (ref __BooleanNullableTypeSignature, "java/lang/Boolean");
return true;
}
if (type == typeof (Byte?)) {
Comment thread
simonrozsival marked this conversation as resolved.
signature = GetCachedTypeSignature (ref __SByteNullableTypeSignature, "java/lang/Byte");
return true;
}

if (type == typeof (SByte?)) {
signature = GetCachedTypeSignature (ref __SByteNullableTypeSignature, "java/lang/Byte");
return true;
Expand Down Expand Up @@ -162,6 +167,7 @@ static KeyValuePair<Type, JniValueMarshaler>[] InitJniBuiltinMarshalers ()
return new []{
new KeyValuePair<Type, JniValueMarshaler>(typeof (string), JniStringValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (JavaProxyObject), ProxyValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (Byte?), JniNullableByteValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (Boolean), JniBooleanValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (Boolean?), JniNullableBooleanValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (SByte), JniSByteValueMarshaler.Instance),
Expand Down Expand Up @@ -465,6 +471,37 @@ public override void DestroyGenericArgumentState (SByte? value, ref JniValueMars
}
}

sealed class JniNullableByteValueMarshaler : JniValueMarshaler<Byte?> {

internal static readonly JniNullableByteValueMarshaler Instance = new JniNullableByteValueMarshaler ();

public override Byte? CreateGenericValue (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
Type? targetType)
{
if (!reference.IsValid)
return null;

return unchecked ((Byte) JniByte.GetValueFromJni (ref reference, options, targetType: null));
}

public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull] Byte? value, ParameterAttributes synchronize)
{
if (!value.HasValue)
return new JniValueMarshalerState ();
var r = JniByte.CreateLocalRef (unchecked ((SByte) value.Value));
return new JniValueMarshalerState (r);
}

public override void DestroyGenericArgumentState (Byte? value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
{
var r = state.ReferenceValue;
JniObjectReference.Dispose (ref r);
state = new JniValueMarshalerState ();
}
}

static class JniCharacter {
internal const string JniTypeName = "java/lang/Character";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ namespace Java.Interop {
if (!type.IsValueType)
return false;

if (type == typeof (Byte?)) {
signature = GetCachedTypeSignature (ref __SByteNullableTypeSignature, "java/lang/Byte");
return true;
}

<#
foreach (var type in types) {
#>
Expand Down Expand Up @@ -127,6 +132,7 @@ namespace Java.Interop {
return new []{
new KeyValuePair<Type, JniValueMarshaler>(typeof (string), JniStringValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (JavaProxyObject), ProxyValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (Byte?), JniNullableByteValueMarshaler.Instance),
<#
foreach (var type in types) {
#>
Expand Down Expand Up @@ -284,6 +290,41 @@ namespace Java.Interop {
}
}
<#
if (type.UnsignedType == "Byte") {
#>

sealed class JniNullableByteValueMarshaler : JniValueMarshaler<Byte?> {

internal static readonly JniNullableByteValueMarshaler Instance = new JniNullableByteValueMarshaler ();

public override Byte? CreateGenericValue (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
Type? targetType)
{
if (!reference.IsValid)
return null;

return unchecked ((Byte) JniByte.GetValueFromJni (ref reference, options, targetType: null));
}

public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull] Byte? value, ParameterAttributes synchronize)
{
if (!value.HasValue)
return new JniValueMarshalerState ();
var r = JniByte.CreateLocalRef (unchecked ((SByte) value.Value));
return new JniValueMarshalerState (r);
}

public override void DestroyGenericArgumentState (Byte? value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
{
var r = state.ReferenceValue;
JniObjectReference.Dispose (ref r);
state = new JniValueMarshalerState ();
}
}
<#
}
}
#>
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ public void ObjectArrayType ()
}
}

[TestFixture]
public class JavaObjectArray_NullableByte_ContractTest : JavaObjectArrayContractTest<byte?> {
protected override byte? CreateValueA () {return 128;}
protected override byte? CreateValueB () {return 255;}

[Test]
public void NullableByteValues ()
{
var values = new byte? [] { 1, null, 127, 128, 200, 255 };
using (var array = new JavaObjectArray<byte?> (values)) {
Assert.AreEqual ("[Ljava/lang/Byte;", array.GetJniTypeName ());
CollectionAssert.AreEqual (values, array.ToArray ());
}
}
}

[TestFixture]
public class JavaObjectArray_Int32Array_ContractTest : JavaObjectArrayContractTest<int[]> {
protected override int[] CreateValueA () {return new[]{1};}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public void GetTypeSignature_Type ()
AssertGetJniTypeInfoForType (typeof (bool), "Z", true, 0);
AssertGetJniTypeInfoForType (typeof (void), "V", true, 0);

AssertGetJniTypeInfoForType (typeof (float?), "java/lang/Float", true, 0);
AssertGetJniTypeInfoForType (typeof (float?), "java/lang/Float", true, 0);
AssertGetJniTypeInfoForType (typeof (byte?), "java/lang/Byte", true, 0);
AssertGetJniTypeInfoForType (typeof (byte?[]), "[Ljava/lang/Byte;", true, 1);
AssertGetJniTypeInfoForType (typeof (byte?[][]), "[[Ljava/lang/Byte;", true, 2);
AssertGetJniTypeInfoForType (typeof (byte?[][][]), "[[[Ljava/lang/Byte;", true, 3);

AssertGetJniTypeInfoForType (typeof (JavaObject), "java/lang/Object", false, 0);

Expand Down Expand Up @@ -136,6 +140,7 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo
Assert.AreEqual (typeof (float), GetType ("F"));
Assert.AreEqual (typeof (double), GetType ("D"));
Assert.AreEqual (typeof (string), GetType ("java/lang/String"));
Assert.AreEqual (typeof (sbyte?), GetType ("java/lang/Byte"));
Assert.AreEqual (typeof (float?), GetType ("java/lang/Float"));
Assert.AreEqual (null, GetType ("com/example/does/not/exist"));
Assert.AreEqual (null, GetType ("Lcom/example/does/not/exist;"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ protected static string GetTypeName (Type type)
switch (type.Name) {
case "Boolean":
return "bool";
case "Byte":
return "byte";
case "Char":
return "char";
case "Double":
Expand Down Expand Up @@ -422,6 +424,12 @@ public class JniValueMarshaler_NullableSByte_ContractTests : JniValueMarshalerCo
protected override sbyte? Value {get {return (sbyte) 2;}}
}

[TestFixture]
[Category("LLVMIgnore")] //FIXME: https://github.com/dotnet/runtime/issues/89190
public class JniValueMarshaler_NullableByte_ContractTests : JniValueMarshalerContractTests<byte?> {
protected override byte? Value {get {return (byte) 200;}}
}

[TestFixture]
[Category("LLVMIgnore")] //FIXME: https://github.com/dotnet/runtime/issues/89190
public class JniValueMarshaler_NullableChar_ContractTests : JniValueMarshalerContractTests<char?> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,8 @@ public void GetArray_NullableByte ()
}

[Test]
[Category ("NativeAOTTrimmable")]
public void GetArray_NullableByteArrayArray ()
{
if (!Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) {
Assert.Ignore ("Test only relevant for the trimmable typemap path.");
}

var values = new [] {
new byte? [] { 1, null, 200 },
new byte? [] { 255, 128 },
Expand All @@ -265,6 +260,32 @@ public void GetArray_NullableByteArrayArray ()
}
}

[Test]
public void GetArray_NullableByteArrayArrayArray ()
{
var values = new [] {
new [] {
new byte? [] { 1, null, 200 },
new byte? [] { 255, 128 },
},
new [] {
new byte? [] { null, 42 },
},
};
using (var array = new Java.Lang.Object (JNIEnv.NewArray (values), JniHandleOwnership.TransferLocalRef)) {
Assert.AreEqual ("[[[Ljava/lang/Byte;", JNIEnv.GetClassNameFromInstance (array.Handle));

var copy = JNIEnv.GetArray<byte?[][]> (array.Handle);
Assert.AreEqual (values.Length, copy.Length);
for (int i = 0; i < values.Length; i++) {
Assert.AreEqual (values [i].Length, copy [i].Length);
for (int j = 0; j < values [i].Length; j++) {
AssertArrays ($"GetArray<byte?[][]>[{i}][{j}]", copy [i][j], values [i][j]);
}
}
}
}

[Test]
public void GetArray_JavaLangStringArrayToJavaLangObjectArray ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ public void GetStaticMethodFallbackTypes_ReturnsDesugarFallbacks (string jniSimp
Assert.AreEqual (expectedFallback, fallbacks [1]);
}

[Test]
[TestCase (typeof (byte?[][]), "[[Ljava/lang/Byte;")]
[TestCase (typeof (byte?[][][]), "[[[Ljava/lang/Byte;")]
[Category ("NativeAOTTrimmable")]
public void GetTypeSignature_NullableByteArrayArray_ReturnsJavaLangByte ()
public void GetTypeSignature_NullableByteJaggedArray_ReturnsJavaLangByte (Type type, string expected)
{
using var manager = new TestableTrimmableTypeMapTypeManager ();
var signature = manager.GetTypeSignature (typeof (byte?[][]));
var signature = manager.GetTypeSignature (type);

Assert.AreEqual ("[[Ljava/lang/Byte;", signature.Name);
Assert.AreEqual (expected, signature.Name);
}

// Verifies the generic-type-definition fallback in GetProxyForManagedType:
Expand Down
Loading