Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
98f3f32
[NativeAOT] Make the trimmable typemap the default
simonrozsival Jun 30, 2026
c5cdadf
[NativeAOT] Keep _PreTrimmingFixLegacyDesignerUpdateItems off the tri…
simonrozsival Jun 30, 2026
63102ad
[Tests] Skip NativeAOT cases that inspect illink's linked/ output
simonrozsival Jun 30, 2026
3b06042
[Tests] NativeAOT BuildHasNoWarnings is now warning-clean
simonrozsival Jun 30, 2026
0f2f615
[Tests] More NativeAOT test adjustments for the trimmable typemap def…
simonrozsival Jun 30, 2026
fe44c64
[Tests] XA4310 NativeAOT no longer emits the IL3053 aggregate warning
simonrozsival Jun 30, 2026
99fe7a4
[Tests] DotNetBuild expects mapping.txt for NativeAOT release
simonrozsival Jun 30, 2026
cc1fbfa
[NativeAOT] Only change the default typemap; keep managed/llvm-ir rea…
simonrozsival Jun 30, 2026
9318a5e
[Tests] Delete the BuildTest2.NativeAOT type-map test
simonrozsival Jun 30, 2026
e3363c2
[Tests] Skip SkiaSharpCanvasBasedAppRuns on NativeAOT
simonrozsival Jun 30, 2026
8e7ed12
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
simonrozsival Jul 1, 2026
ab15510
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
simonrozsival Jul 1, 2026
93cc872
[NativeAOT] Pass generated ACW keep rules to R8 on trimmable path
simonrozsival Jul 3, 2026
3be151c
[TrimmableTypeMap] Exclude Java.Interop.ManagedPeer from the type map
simonrozsival Jul 3, 2026
47c94aa
[TrimmableTypeMap][NativeAOT] Fix XA4321: collect ILC DGML from per-R…
simonrozsival Jul 3, 2026
bab3929
[Tests] Add NativeAOT regression test for R8-kept runtime ACWs
simonrozsival Jul 3, 2026
e681a74
[TrimmableTypeMap][NativeAOT] Fix single-RID DGML path (both output-p…
simonrozsival Jul 3, 2026
25f202b
[TrimmableTypeMap][NativeAOT] Fall back to codegen DGML when scan DGM…
simonrozsival Jul 3, 2026
74e4d8e
[TrimmableTypeMap] Fix ManifestPlaceholders on the trimmable NativeAO…
simonrozsival Jul 3, 2026
5045ea6
[TrimmableTypeMap][NativeAOT] Add _AndroidTrimmableTypemapTrimJavaCod…
simonrozsival Jul 3, 2026
fec53db
[TrimmableTypeMap] Emit <layout> element for the [Layout] attribute
simonrozsival Jul 3, 2026
936874c
[TrimmableTypeMap] Emit XA1010 for invalid $(AndroidManifestPlacehold…
simonrozsival Jul 3, 2026
17cd01b
[Tests] BuildProguardEnabledProject: check references.cfg on NativeAOT
simonrozsival Jul 3, 2026
da9cfde
[TrimmableTypeMap][NativeAOT] Generate per-process runtime provider J…
simonrozsival Jul 4, 2026
4c72cbd
[TrimmableTypeMap] Make generated typemap assemblies byte-deterministic
simonrozsival Jul 4, 2026
9d70d6a
[TrimmableTypeMap][NativeAOT] Keep user AndroidJavaSource under R8
simonrozsival Jul 4, 2026
fc1a9f6
[TrimmableTypeMap][NativeAOT] Scan runtime host ACWs via a ref assembly
simonrozsival Jul 4, 2026
4aada84
[TrimmableTypeMap][NativeAOT] Auto-detect runtime-only ACW assemblies
simonrozsival Jul 4, 2026
f35a921
[Tests] Skip JniNativeMethodRegistration tests on trimmable/NativeAOT
simonrozsival Jul 4, 2026
c810758
[TrimmableTypeMap] Resolve java/lang/Object to Java.Lang.Object, not …
simonrozsival Jul 5, 2026
1901193
[trimmable-typemap] Order java->managed aliases Mono.Android-first
simonrozsival Jul 5, 2026
a134731
[NativeAOT] Keep Resource.Designer assembly on the trimmable type map…
simonrozsival Jul 5, 2026
9cfc9f5
[NativeAOT] Reference the designer assembly before rooting it for ILC
simonrozsival Jul 5, 2026
a3158f8
[TrimmableTypeMap] Inline resource designer constants (experimental, …
simonrozsival Jul 6, 2026
95e5d3f
[TrimmableTypeMap] Inline designer constants in the main assembly too
simonrozsival Jul 6, 2026
800538e
[TrimmableTypeMap] Inline resource designer constants by default (tri…
simonrozsival Jul 6, 2026
7d858b7
Revert inline-resource-designer-constants experiment
simonrozsival Jul 7, 2026
cb8c1fa
[TrimmableTypeMap] Merge library .aar manifests on the legacy merger …
simonrozsival Jul 7, 2026
abe459e
[TrimmableTypeMap] Emit array proxy for the System.String built-in ma…
simonrozsival Jul 7, 2026
b286a0a
[Java.Interop] Add GetType array coverage for boxed nullable value types
simonrozsival Jul 7, 2026
4fe4a8f
[TrimmableTypeMap] Emit array proxies for boxed nullable value types
simonrozsival Jul 7, 2026
e029f28
[TrimmableTypeMap] Split array-proxy rank: reference-1, primitive-3
simonrozsival Jul 7, 2026
897a9e4
[TrimmableTypeMap] Treat System.String as a rank-3 built-in array ele…
simonrozsival Jul 7, 2026
7ac794c
[TrimmableTypeMap] Treat boxed Nullable<T> as rank-3 built-in array e…
simonrozsival Jul 7, 2026
9306605
Fix nullable value type codegen
simonrozsival Jul 7, 2026
2095221
[TrimmableTypeMap] Make JavaPeerProxy ctor accessible to typemaps
simonrozsival Jul 7, 2026
6553752
[tests] Use blittable RegisterNatives in JNI callback tests
simonrozsival Jul 8, 2026
cde0f09
[Tests] Keep rank-1 object array proxy coverage
simonrozsival Jul 8, 2026
c3f555b
[TrimmableTypeMap] Address array proxy review feedback
simonrozsival Jul 8, 2026
c0db8fa
[TrimmableTypeMap] Track reference array rank incrementally
simonrozsival Jul 8, 2026
a8a9ff5
Address trimmable typemap review feedback
simonrozsival Jul 7, 2026
ff84e55
Simplify deterministic PE blob assertion
simonrozsival Jul 7, 2026
ea6d41f
[TrimmableTypeMap][NativeAOT] Address review feedback
simonrozsival Jul 7, 2026
7962df1
[Tests] Check exact NativeAOT proguard config path
simonrozsival Jul 8, 2026
a9ebf40
[TrimmableTypeMap][NativeAOT] Auto-detect runtime-only ACW assemblies
simonrozsival Jul 4, 2026
345ea2e
[Tests] Check NativeAOT proguard config per RID
simonrozsival Jul 8, 2026
a539ea3
[Tests] Expect one NativeAOT proguard references config
simonrozsival Jul 8, 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 @@ -161,6 +161,20 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo
AssertArrayTypesFromSignature<double[]> (manager, "[[D", typeof (JavaObjectArray<JavaPrimitiveArray<double>>));
AssertArrayTypesFromSignature<string[]> (manager, "[[Ljava/lang/String;", typeof (JavaObjectArray<JavaObjectArray<string>>));

// Nullable counterparts of the primitive value types: the boxed reference signatures
// java/lang/<Boxed> map to Nullable<T> (see JniBuiltinSimpleReferenceToType /
// GetBuiltInTypeForSimpleReference). Unlike the primitive keyword arrays above, these are
// non-keyword reference arrays, so the element type is the boxed Nullable<T> and the array
// family is T?[] and JavaObjectArray<T?> (no JavaArray<T>/JavaPrimitiveArray<T>).
AssertArrayTypesFromSignature<bool?> (manager, "[Ljava/lang/Boolean;", typeof (JavaObjectArray<bool?>));
AssertArrayTypesFromSignature<sbyte?> (manager, "[Ljava/lang/Byte;", typeof (JavaObjectArray<sbyte?>));
AssertArrayTypesFromSignature<char?> (manager, "[Ljava/lang/Character;", typeof (JavaObjectArray<char?>));
AssertArrayTypesFromSignature<short?> (manager, "[Ljava/lang/Short;", typeof (JavaObjectArray<short?>));
AssertArrayTypesFromSignature<int?> (manager, "[Ljava/lang/Integer;", typeof (JavaObjectArray<int?>));
AssertArrayTypesFromSignature<long?> (manager, "[Ljava/lang/Long;", typeof (JavaObjectArray<long?>));
AssertArrayTypesFromSignature<float?> (manager, "[Ljava/lang/Float;", typeof (JavaObjectArray<float?>));
AssertArrayTypesFromSignature<double?> (manager, "[Ljava/lang/Double;", typeof (JavaObjectArray<double?>));

// Yes, these look weird...
// Assume: class II {}
Assert.AreEqual (null, GetType ("II"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,18 +219,16 @@ public unsafe void RegisterNativeMethods_JniNativeMethod ()
static int NativeAdd (IntPtr jnienv, IntPtr self, int a, int b) => a + b;

[Test]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniNativeMethodRegistration[] registration path.")]
public unsafe void RegisterNativeMethods_JniNativeMethodRegistration ()
public unsafe void RegisterNativeMethods_JniNativeMethod_UnmanagedCallersOnly ()
{
using (var nativeClass = new JniType ("net/dot/jni/test/RegisterNativesTestType")) {
// Test the JniNativeMethodRegistration[] registration path (the one that marshals to blittable)
var methods = new JniNativeMethodRegistration [1];
methods [0] = new JniNativeMethodRegistration (
"add",
"(II)I",
new AddDelegate (ManagedAdd));

JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, methods, methods.Length);
Span<JniNativeMethod> methods = stackalloc JniNativeMethod [1];
fixed (byte* namePtr = "add"u8)
fixed (byte* sigPtr = "(II)I"u8) {
methods [0] = new JniNativeMethod (namePtr, sigPtr,
(IntPtr) (delegate* unmanaged<IntPtr, IntPtr, int, int, int>) &ManagedAdd);
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, methods);
}

// Call the native method from Java to verify registration worked
var ctor = JniEnvironment.InstanceMethods.GetMethodID (nativeClass.PeerReference, "<init>", "()V");
Expand All @@ -248,26 +246,26 @@ public unsafe void RegisterNativeMethods_JniNativeMethodRegistration ()
}
}

delegate int AddDelegate (IntPtr jnienv, IntPtr self, int a, int b);

[UnmanagedCallersOnly]
static int ManagedAdd (IntPtr jnienv, IntPtr self, int a, int b) => a + b;

[Test]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniNativeMethodRegistration[] registration path with many methods.")]
public unsafe void RegisterNativeMethods_JniNativeMethodRegistration_ManyMethods ()
public unsafe void RegisterNativeMethods_JniNativeMethod_UnmanagedCallersOnly_ManyMethods ()
{
using (var nativeClass = new JniType ("net/dot/jni/test/RegisterNativesTestType")) {
// Test the heap allocation path (> 32 methods) to ensure the marshalling loop handles it
var methods = new JniNativeMethodRegistration [40];
for (int i = 0; i < methods.Length; ++i) {
methods [i] = new JniNativeMethodRegistration (
"add",
"(II)I",
new AddDelegate (ManagedAdd));
}
// Keep coverage for a large registration set while using the NativeAOT-compatible
// blittable overload instead of the delegate-marshaling overload.
var methods = new JniNativeMethod [40];
fixed (byte* namePtr = "add"u8)
fixed (byte* sigPtr = "(II)I"u8) {
for (int i = 0; i < methods.Length; ++i) {
methods [i] = new JniNativeMethod (namePtr, sigPtr,
(IntPtr) (delegate* unmanaged<IntPtr, IntPtr, int, int, int>) &ManagedAdd);
}

// This should not crash even with > 32 methods
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, methods, methods.Length);
// This should not crash even with > 32 methods.
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, methods);
}

// Verify the registration worked by calling the method
var ctor = JniEnvironment.InstanceMethods.GetMethodID (nativeClass.PeerReference, "<init>", "()V");
Expand All @@ -286,4 +284,3 @@ public unsafe void RegisterNativeMethods_JniNativeMethodRegistration_ManyMethods
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ static class ComponentElementBuilder
element.Add (CreateIntentFilterElement (intentFilter));
}

// Add <layout> element from a [Layout] attribute, if present
if (component.LayoutProperties is not null) {
if (component.Kind == ComponentKind.Activity && component.LayoutProperties is not null) {
var layout = CreateLayoutElement (component.LayoutProperties);
if (layout is not null) {
element.Add (layout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,9 +516,13 @@ internal static void ApplyPlaceholders (XDocument doc, string? placeholders, str
if (!placeholders.IsNullOrEmpty ()) {
foreach (var entry in placeholders.Split (PlaceholderSeparators, StringSplitOptions.RemoveEmptyEntries)) {
var eqIndex = entry.IndexOf ('=');
if (eqIndex > 0) {
if (eqIndex >= 0) {
var key = entry.Substring (0, eqIndex).Trim ();
var value = entry.Substring (eqIndex + 1).Trim ();
// Normalize '\' to the platform directory separator to match the legacy pipeline,
// where the substituted manifest is re-encoded by aapt2 (which rewrites backslashes
// to '/' on Unix). The trimmable generator writes the merged manifest directly, so we
// apply the same normalization here to keep placeholder values byte-for-byte identical.
var value = entry.Substring (eqIndex + 1).Trim ().Replace ('\\', Path.DirectorySeparatorChar);
replacements ["${" + key + "}"] = value;
} else if (eqIndex < 0) {
// An entry without '=' is not a valid key=value pair. Mirror the legacy
Expand Down
150 changes: 140 additions & 10 deletions src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ static class ModelBuilder
new ("D", "Double", "System.Double", ["Java.Interop.JavaDoubleArray"]),
];

// Nullable value types map to the boxed java/lang/<Boxed> references (java/lang/Boolean etc.) via
// TrimmableTypeMapTypeManager.GetBuiltInTypeForSimpleReference. Like System.String they are neither
// scanned Java peers nor primitives, so their array proxies are emitted explicitly. Only the eight
// types with a boxed java/lang mapping are included (java/lang/Byte -> sbyte?, not byte?, etc.).
static readonly (string Name, string ManagedTypeName) [] NullableArrayProxies = [
("Boolean", "System.Boolean"),
("SByte", "System.SByte"),
("Char", "System.Char"),
("Int16", "System.Int16"),
("Int32", "System.Int32"),
("Int64", "System.Int64"),
("Single", "System.Single"),
("Double", "System.Double"),
];

static readonly HashSet<string> EssentialRuntimeTypes = new (StringComparer.Ordinal) {
"java/lang/Object",
"java/lang/Class",
Expand All @@ -55,7 +70,7 @@ static class ModelBuilder
/// Emit per-rank array <c>TypeMap</c> entries + <c>__ArrayMapRank{N}</c> sentinels
/// for ranks 1..<paramref name="maxArrayRank"/>. 0 disables array entry emission.
/// </param>
public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, string outputPath, string? assemblyName = null, int maxArrayRank = 0)
public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, string outputPath, string? assemblyName = null, int maxArrayRank = 0, int maxReferenceArrayRank = 0)
{
if (peers is null) {
throw new ArgumentNullException (nameof (peers));
Expand All @@ -66,13 +81,20 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
if (maxArrayRank < 0) {
throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0.");
}
if (maxReferenceArrayRank < 0) {
throw new ArgumentOutOfRangeException (nameof (maxReferenceArrayRank), maxReferenceArrayRank, "Must be >= 0.");
}

assemblyName ??= Path.GetFileNameWithoutExtension (outputPath);

var model = new TypeMapAssemblyData {
AssemblyName = assemblyName,
ModuleName = Path.GetFileName (outputPath),
MaxArrayRank = maxArrayRank,
// The per-assembly __ArrayMapRank{N} anchor count must be uniform across every typemap
// assembly (the root generator builds a rectangular [assembly][rank] matrix), so the model
// always carries the overall maximum rank. Primitive and reference element types may
// populate different rank ranges within that shared anchor set.
MaxArrayRank = Math.Max (maxArrayRank, maxReferenceArrayRank),
};

// Invoker types are NOT emitted as separate proxies or TypeMap entries.
Expand Down Expand Up @@ -102,19 +124,19 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
string jniName = kvp.Key;
var peersForName = kvp.Value;

// Sort aliases by managed type name for deterministic proxy naming
if (peersForName.Count > 1) {
peersForName.Sort ((a, b) => StringComparer.Ordinal.Compare (a.ManagedTypeName, b.ManagedTypeName));
peersForName.Sort (CompareAliasesForRuntimeResolution);
}

EmitPeers (model, jniName, peersForName, assemblyName, usedProxyNames);

if (maxArrayRank > 0) {
EmitArrayEntries (model, jniName, peersForName, maxArrayRank);
// Java peer types are reference types: emit array proxies only up to the reference rank.
if (maxReferenceArrayRank > 0) {
EmitArrayEntries (model, jniName, peersForName, maxReferenceArrayRank);
}
}

if (maxArrayRank > 0 && string.Equals (assemblyName, "_Java.Interop.TypeMap", StringComparison.Ordinal)) {
if (string.Equals (assemblyName, "_Java.Interop.TypeMap", StringComparison.Ordinal)) {
EmitPrimitiveArrayEntries (model, maxArrayRank);
}

Expand All @@ -141,6 +163,19 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
return model;
}

static int CompareAliasesForRuntimeResolution (JavaPeerInfo a, JavaPeerInfo b)
{
// Keep alias [0] aligned with the native java→managed map, which processes Mono.Android first.
bool aMonoAndroid = string.Equals (a.AssemblyName, "Mono.Android", StringComparison.Ordinal);
bool bMonoAndroid = string.Equals (b.AssemblyName, "Mono.Android", StringComparison.Ordinal);
if (aMonoAndroid != bMonoAndroid) {
return aMonoAndroid ? -1 : 1;
}

int result = StringComparer.Ordinal.Compare (a.ManagedTypeName, b.ManagedTypeName);
return result != 0 ? result : StringComparer.Ordinal.Compare (a.AssemblyName, b.AssemblyName);
}

static void EmitPeers (TypeMapAssemblyData model, string jniName,
List<JavaPeerInfo> peersForName, string assemblyName, HashSet<string> usedProxyNames)
{
Expand Down Expand Up @@ -541,6 +576,26 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr
static string AssemblyQualify (string typeName, string assemblyName)
=> $"{typeName}, {assemblyName}";

static string AssemblyQualify (TypeRefData type)
{
if (type.GenericArguments.Count == 0) {
return AssemblyQualify (type.ManagedTypeName, type.AssemblyName);
}

var builder = new StringBuilder ();
builder.Append (type.ManagedTypeName);
builder.Append ("[[");
for (int i = 0; i < type.GenericArguments.Count; i++) {
if (i > 0) {
builder.Append ("],[");
}
builder.Append (AssemblyQualify (type.GenericArguments [i]));
}
builder.Append ("]], ");
builder.Append (type.AssemblyName);
return builder.ToString ();
}

static string AddArrayRank (string typeReference, int rank)
{
if (rank == 0) {
Expand Down Expand Up @@ -569,7 +624,7 @@ static string MakeNestedJavaObjectArrayTypeReference (string elementTypeReferenc

static IReadOnlyList<string> GetArrayTypeReferences (ArrayProxyData proxy)
{
var elementType = AssemblyQualify (proxy.ElementType.ManagedTypeName, proxy.ElementType.AssemblyName);
var elementType = AssemblyQualify (proxy.ElementType);
if (proxy.Primitive is null) {
var rankOneTypes = new [] {
MakeGenericTypeReference ("Java.Interop.JavaObjectArray`1", "Java.Interop", elementType),
Expand All @@ -585,7 +640,7 @@ static IReadOnlyList<string> GetArrayTypeReferences (ArrayProxyData proxy)
MakeGenericTypeReference ("Java.Interop.JavaPrimitiveArray`1", "Java.Interop", elementType),
];
foreach (var concreteArrayType in proxy.Primitive.ConcreteArrayTypes) {
rankOnePrimitiveTypes.Add (AssemblyQualify (concreteArrayType.ManagedTypeName, concreteArrayType.AssemblyName));
rankOnePrimitiveTypes.Add (AssemblyQualify (concreteArrayType));
}
return ExpandRankOneTypes (rankOnePrimitiveTypes, proxy.Rank);
}
Expand Down Expand Up @@ -616,7 +671,7 @@ static void AddArrayProxyAssociations (TypeMapAssemblyData model, ArrayProxyData
}

static string GetArrayProxyMapKey (TypeRefData elementType)
=> AssemblyQualify (elementType.ManagedTypeName, elementType.AssemblyName);
=> AssemblyQualify (elementType);

/// <summary>
/// Emits per-rank array TypeMap entries for one peer, anchored to the per-assembly
Expand Down Expand Up @@ -665,6 +720,13 @@ static void EmitArrayEntriesForPeer (TypeMapAssemblyData model, JavaPeerInfo pee
}
}

// Emits array proxies for the built-in element types that are not scanned Java peers:
// * the keyword primitives (int/bool/...) up to maxArrayRank (jagged/multidim primitive arrays
// like int[][][] are cheap — a small fixed set of element types),
// * System.String up to maxArrayRank (also a built-in element type, so multidimensional string
// arrays like String[][] stay resolvable),
// * the boxed Nullable<T> value types up to maxArrayRank (also built-in element types mapping to
// java/lang/<Boxed>; a small fixed set, so multidim boxed arrays stay resolvable too).
static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRank)
{
foreach (var primitive in PrimitiveArrayProxies) {
Expand Down Expand Up @@ -694,6 +756,74 @@ static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRa
AddArrayProxyAssociations (model, proxy, proxyReference);
}
}

// System.String maps to java/lang/String but is a built-in element type injected at runtime by
// TrimmableTypeMapTypeManager.GetBuiltInTypeForSimpleReference, so it is neither a scanned Java
// peer (EmitArrayEntriesForPeer) nor a keyword primitive. Emit its array proxies here (Primitive
// is null, so it gets the reference-array family) so GetTypes ("[Ljava/lang/String;") yields
// System.String[] / JavaObjectArray<string> / JavaArray<string> on NativeAOT, matching CoreCLR.
// String is a single, fixed built-in element type (not part of the scanned-peer explosion), so —
// like the keyword primitives — it goes up to maxArrayRank. This keeps multidimensional string
// arrays (String[][], "[[Ljava/lang/String;") resolvable, matching the other type-map backends.
for (int rank = 1; rank <= maxArrayRank; rank++) {
var proxy = new ArrayProxyData {
TypeName = ManagedTypeNameToArrayProxyTypeName ("System.String", rank),
ElementType = new TypeRefData {
ManagedTypeName = "System.String",
AssemblyName = "System.Runtime",
},
Rank = rank,
};
model.ArrayProxyTypes.Add (proxy);
var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
MapKey = GetArrayProxyMapKey (proxy.ElementType),
ProxyTypeReference = proxyReference,
TargetTypeReference = proxyReference,
AnchorRank = rank,
});
AddArrayProxyAssociations (model, proxy, proxyReference);
}

// Nullable counterparts of the primitive value types map to the boxed java/lang/<Boxed>
// references and, like System.String, are built-in element mappings (no scanned peer, no
// primitive proxy). Emit reference-array proxies (Primitive is null) so GetTypes
// ("[Ljava/lang/Boolean;") yields bool?[] / JavaObjectArray<bool?> on NativeAOT. The element
// key uses the normalized generic form (simple assembly names) that
// TrimmableTypeMap.BuildManagedTypeKey produces at runtime for Nullable<T>, while the model
// keeps the generic instantiation structured so the emitter writes a real Nullable<T> type
// signature. Like the primitives and System.String, these are a small fixed set of built-in
// element types (not the scanned-peer explosion), so they go up to maxArrayRank and keep
// multidim boxed arrays resolvable.
foreach (var nullablePrimitive in NullableArrayProxies) {
for (int rank = 1; rank <= maxArrayRank; rank++) {
var proxy = new ArrayProxyData {
TypeName = $"Nullable_{nullablePrimitive.Name}_ArrayProxy{rank}",
ElementType = new TypeRefData {
ManagedTypeName = "System.Nullable`1",
AssemblyName = "System.Runtime",
GenericArguments = [
new TypeRefData {
ManagedTypeName = nullablePrimitive.ManagedTypeName,
AssemblyName = "System.Runtime",
IsValueType = true,
},
],
IsValueType = true,
},
Rank = rank,
};
model.ArrayProxyTypes.Add (proxy);
var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
MapKey = GetArrayProxyMapKey (proxy.ElementType),
ProxyTypeReference = proxyReference,
TargetTypeReference = proxyReference,
AnchorRank = rank,
});
AddArrayProxyAssociations (model, proxy, proxyReference);
}
}
}

static string Brackets (int rank) => rank switch {
Expand Down
Loading
Loading