Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b93d9c4
[NativeAOT] Make the trimmable typemap the default
simonrozsival Jun 30, 2026
9ede12f
[NativeAOT] Keep _PreTrimmingFixLegacyDesignerUpdateItems off the tri…
simonrozsival Jun 30, 2026
607f07b
[Tests] Skip NativeAOT cases that inspect illink's linked/ output
simonrozsival Jun 30, 2026
a05f746
[Tests] NativeAOT BuildHasNoWarnings is now warning-clean
simonrozsival Jun 30, 2026
38a2c58
[Tests] More NativeAOT test adjustments for the trimmable typemap def…
simonrozsival Jun 30, 2026
a4b042c
[Tests] XA4310 NativeAOT no longer emits the IL3053 aggregate warning
simonrozsival Jun 30, 2026
8c246c1
[Tests] DotNetBuild expects mapping.txt for NativeAOT release
simonrozsival Jun 30, 2026
77dc81c
[NativeAOT] Only change the default typemap; keep managed/llvm-ir rea…
simonrozsival Jun 30, 2026
a5abc6a
[Tests] Delete the BuildTest2.NativeAOT type-map test
simonrozsival Jun 30, 2026
3b29bfb
[Tests] Skip SkiaSharpCanvasBasedAppRuns on NativeAOT
simonrozsival Jun 30, 2026
3dac437
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
simonrozsival Jul 1, 2026
adb00e4
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
simonrozsival Jul 1, 2026
94c84db
[NativeAOT] Pass generated ACW keep rules to R8 on trimmable path
simonrozsival Jul 3, 2026
8d424c0
[TrimmableTypeMap] Exclude Java.Interop.ManagedPeer from the type map
simonrozsival Jul 3, 2026
1512449
[TrimmableTypeMap][NativeAOT] Fix XA4321: collect ILC DGML from per-R…
simonrozsival Jul 3, 2026
ef3e6e6
[Tests] Add NativeAOT regression test for R8-kept runtime ACWs
simonrozsival Jul 3, 2026
6ea9395
[TrimmableTypeMap][NativeAOT] Fix single-RID DGML path (both output-p…
simonrozsival Jul 3, 2026
e58cb04
[TrimmableTypeMap][NativeAOT] Fall back to codegen DGML when scan DGM…
simonrozsival Jul 3, 2026
8af5b62
[TrimmableTypeMap] Fix ManifestPlaceholders on the trimmable NativeAO…
simonrozsival Jul 3, 2026
f157df9
[TrimmableTypeMap][NativeAOT] Add _AndroidTrimmableTypemapTrimJavaCod…
simonrozsival Jul 3, 2026
968ac2c
[TrimmableTypeMap] Emit <layout> element for the [Layout] attribute
simonrozsival Jul 3, 2026
518d9b4
[TrimmableTypeMap] Emit XA1010 for invalid $(AndroidManifestPlacehold…
simonrozsival Jul 3, 2026
b1cc978
[Tests] BuildProguardEnabledProject: check references.cfg on NativeAOT
simonrozsival Jul 3, 2026
934cd3b
[TrimmableTypeMap][NativeAOT] Generate per-process runtime provider J…
simonrozsival Jul 4, 2026
91204df
[TrimmableTypeMap] Make generated typemap assemblies byte-deterministic
simonrozsival Jul 4, 2026
8cfd0fd
[TrimmableTypeMap][NativeAOT] Keep user AndroidJavaSource under R8
simonrozsival Jul 4, 2026
12963c7
[TrimmableTypeMap][NativeAOT] Scan runtime host ACWs via a ref assembly
simonrozsival Jul 4, 2026
c74360a
[TrimmableTypeMap][NativeAOT] Auto-detect runtime-only ACW assemblies
simonrozsival Jul 4, 2026
cee639b
[typemap] Fix trimmable primitive and alias arrays
simonrozsival Jul 4, 2026
92a8fe4
[Tests] Skip JniNativeMethodRegistration tests on trimmable/NativeAOT
simonrozsival Jul 4, 2026
87f6975
[TrimmableTypeMap] Resolve java/lang/Object to Java.Lang.Object, not …
simonrozsival Jul 5, 2026
53d54a5
[trimmable-typemap] Order java->managed aliases Mono.Android-first
simonrozsival Jul 5, 2026
449e2a1
[NativeAOT] Keep Resource.Designer assembly on the trimmable type map…
simonrozsival Jul 5, 2026
e0ea458
[NativeAOT] Reference the designer assembly before rooting it for ILC
simonrozsival Jul 5, 2026
6fe4dd4
[TrimmableTypeMap] Inline resource designer constants (experimental, …
simonrozsival Jul 6, 2026
47ffdb7
[TrimmableTypeMap] Enable inline designer constants for CI validation…
simonrozsival Jul 6, 2026
79c0efd
[TrimmableTypeMap] Inline designer constants in the main assembly too
simonrozsival Jul 6, 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 @@ -219,6 +219,8 @@ public unsafe void RegisterNativeMethods_JniNativeMethod ()
static int NativeAdd (IntPtr jnienv, IntPtr self, int a, int b) => a + b;

[Test]
[Category ("NativeAOTIgnore")]
[Category ("TrimmableTypeMapUnsupported")]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniNativeMethodRegistration[] registration path.")]
public unsafe void RegisterNativeMethods_JniNativeMethodRegistration ()
{
Expand Down Expand Up @@ -253,6 +255,8 @@ public unsafe void RegisterNativeMethods_JniNativeMethodRegistration ()
static int ManagedAdd (IntPtr jnienv, IntPtr self, int a, int b) => a + b;

[Test]
[Category ("NativeAOTIgnore")]
[Category ("TrimmableTypeMapUnsupported")]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniNativeMethodRegistration[] registration path with many methods.")]
public unsafe void RegisterNativeMethods_JniNativeMethodRegistration_ManyMethods ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,11 @@ internal static void ApplyPlaceholders (XDocument doc, string? placeholders, str
var eqIndex = entry.IndexOf ('=');
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
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ sealed record ArrayProxyData
/// </summary>
sealed record PrimitiveArrayProxyData
{
public required TypeRefData ConcreteArrayType { get; init; }
public IReadOnlyList<TypeRefData> ConcreteArrayTypes { get; init; } = [];
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ static class ModelBuilder
const string ProxyTypeSuffix = "_Proxy";

static readonly PrimitiveArrayProxyInfo [] PrimitiveArrayProxies = [
new ("Z", "Boolean", "System.Boolean", "Java.Interop.JavaBooleanArray"),
new ("B", "SByte", "System.SByte", "Java.Interop.JavaSByteArray"),
new ("C", "Char", "System.Char", "Java.Interop.JavaCharArray"),
new ("S", "Int16", "System.Int16", "Java.Interop.JavaInt16Array"),
new ("I", "Int32", "System.Int32", "Java.Interop.JavaInt32Array"),
new ("J", "Int64", "System.Int64", "Java.Interop.JavaInt64Array"),
new ("F", "Single", "System.Single", "Java.Interop.JavaSingleArray"),
new ("D", "Double", "System.Double", "Java.Interop.JavaDoubleArray"),
new ("Z", "Boolean", "System.Boolean", ["Java.Interop.JavaBooleanArray"]),
new ("B", "SByte", "System.SByte", ["Java.Interop.JavaSByteArray"]),
new ("B", "Byte", "System.Byte", []),
new ("C", "Char", "System.Char", ["Java.Interop.JavaCharArray"]),
new ("S", "Int16", "System.Int16", ["Java.Interop.JavaInt16Array"]),
new ("S", "UInt16", "System.UInt16", []),
new ("I", "Int32", "System.Int32", ["Java.Interop.JavaInt32Array"]),
new ("I", "UInt32", "System.UInt32", []),
new ("J", "Int64", "System.Int64", ["Java.Interop.JavaInt64Array"]),
new ("J", "UInt64", "System.UInt64", []),
new ("F", "Single", "System.Single", ["Java.Interop.JavaSingleArray"]),
new ("D", "Double", "System.Double", ["Java.Interop.JavaDoubleArray"]),
];

static readonly HashSet<string> EssentialRuntimeTypes = new (StringComparer.Ordinal) {
Expand Down Expand Up @@ -98,9 +102,22 @@ 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
// Order aliases to match the java→managed selection the native runtime performs (see
// clr_typemap_java_to_managed / monovm_typemap_java_to_managed and NativeTypeMappingData):
// the runtime builds its java→managed map by processing the Mono.Android module first and
// keeping the first managed type that claims a Java name (first-writer-wins). GetTypeForSimpleReference
// returns the first (index [0]) alias, so put the Mono.Android peer first — e.g. java/lang/Object
// must resolve to Java.Lang.Object (Mono.Android), not Java.Interop.JavaObject. Remaining peers are
// ordered by ordinal managed type name for deterministic proxy naming.
if (peersForName.Count > 1) {
peersForName.Sort ((a, b) => StringComparer.Ordinal.Compare (a.ManagedTypeName, b.ManagedTypeName));
peersForName.Sort ((a, b) => {
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;
}
return StringComparer.Ordinal.Compare (a.ManagedTypeName, b.ManagedTypeName);
});
}

EmitPeers (model, jniName, peersForName, assemblyName, usedProxyNames);
Expand Down Expand Up @@ -575,12 +592,14 @@ static IReadOnlyList<string> GetArrayTypeReferences (ArrayProxyData proxy)
return ExpandRankOneTypes (rankOneTypes, proxy.Rank);
}

var rankOnePrimitiveTypes = new [] {
List<string> rankOnePrimitiveTypes = [
AddArrayRank (elementType, 1),
MakeGenericTypeReference ("Java.Interop.JavaArray`1", "Java.Interop", elementType),
MakeGenericTypeReference ("Java.Interop.JavaPrimitiveArray`1", "Java.Interop", elementType),
AssemblyQualify (proxy.Primitive.ConcreteArrayType.ManagedTypeName, proxy.Primitive.ConcreteArrayType.AssemblyName),
};
];
foreach (var concreteArrayType in proxy.Primitive.ConcreteArrayTypes) {
rankOnePrimitiveTypes.Add (AssemblyQualify (concreteArrayType.ManagedTypeName, concreteArrayType.AssemblyName));
}
return ExpandRankOneTypes (rankOnePrimitiveTypes, proxy.Rank);
}

Expand Down Expand Up @@ -615,24 +634,27 @@ static string GetArrayProxyMapKey (TypeRefData elementType)
/// <summary>
/// Emits per-rank array TypeMap entries for one peer, anchored to the per-assembly
/// <c>__ArrayMapRank{N}</c> sentinels. Keys are managed element type names (rank is encoded
/// by the sentinel anchor, not by JNI array prefixes). Skips open generics and alias groups.
/// by the sentinel anchor, not by JNI array prefixes). Skips open generics.
/// </summary>
static void EmitArrayEntries (TypeMapAssemblyData model, string jniName, List<JavaPeerInfo> peersForName, int maxArrayRank)
{
if (peersForName.Count != 1) {
if (jniName.Length == 1 && IsJniPrimitiveKeyword (jniName [0])) {
return;
}

var peer = peersForName [0];
foreach (var peer in peersForName) {
EmitArrayEntriesForPeer (model, peer, maxArrayRank);
}
}

static void EmitArrayEntriesForPeer (TypeMapAssemblyData model, JavaPeerInfo peer, int maxArrayRank)
{
if (!peer.GenerateArrayEntries) {
return;
}
if (peer.IsGenericDefinition) {
return;
}
if (jniName.Length == 1 && IsJniPrimitiveKeyword (jniName [0])) {
return;
}

for (int rank = 1; rank <= maxArrayRank; rank++) {
var proxy = new ArrayProxyData {
Expand Down Expand Up @@ -668,10 +690,10 @@ static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRa
},
Rank = rank,
Primitive = new PrimitiveArrayProxyData {
ConcreteArrayType = new TypeRefData {
ManagedTypeName = primitive.ConcreteArrayTypeName,
ConcreteArrayTypes = primitive.ConcreteArrayTypeNames.Select (name => new TypeRefData {
ManagedTypeName = name,
AssemblyName = "Java.Interop",
},
}).ToList (),
},
};
model.ArrayProxyTypes.Add (proxy);
Expand Down Expand Up @@ -711,5 +733,5 @@ readonly record struct PrimitiveArrayProxyInfo (
string JniName,
string Name,
string ManagedTypeName,
string ConcreteArrayTypeName);
IReadOnlyList<string> ConcreteArrayTypeNames);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

Expand Down Expand Up @@ -107,12 +108,28 @@ public void WritePE (Stream stream)
new PEHeaderBuilder (imageCharacteristics: Characteristics.Dll),
new MetadataRootBuilder (Metadata),
ILBuilder,
mappedFieldData: _mappedFieldData.Count > 0 ? _mappedFieldData : null);
mappedFieldData: _mappedFieldData.Count > 0 ? _mappedFieldData : null,
// Derive the PE content id (and thus the header TimeDateStamp/debug id) from a hash of the
// image so the bytes are fully deterministic. Without this, ManagedPEBuilder falls back to a
// time-based id, so every regeneration produces different bytes even for identical input; that
// churns the generated typemap assemblies and breaks incremental packaging (a no-op rebuild
// re-touches the .dll, forcing repackage + re-sign). The MVID is already deterministic.
deterministicIdProvider: DeterministicContentId);
var peBlob = new BlobBuilder ();
peBuilder.Serialize (peBlob);
peBlob.WriteContentTo (stream);
}

static BlobContentId DeterministicContentId (IEnumerable<Blob> content)
{
using var hash = IncrementalHash.CreateHash (HashAlgorithmName.SHA256);
foreach (var blob in content) {
var segment = blob.GetBytes ();
hash.AppendData (segment.Array!, segment.Offset, segment.Count);
}
return BlobContentId.FromHash (hash.GetHashAndReset ());
}

/// <summary>
/// Adds (or retrieves from cache) an assembly reference.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1636,12 +1636,14 @@ IReadOnlyList<RuntimeTypeSpec> GetArrayProxyTypes (ArrayProxyData proxy)
return ExpandRankOneTypes (rankOneObjectTypes, proxy.Rank);
}

var rankOneTypes = new RuntimeTypeSpec [] {
List<RuntimeTypeSpec> rankOneTypes = [
AddSzArrayRank (elementType, 1),
new GenericRuntimeTypeSpec (_javaArrayOpenRef, elementType),
new GenericRuntimeTypeSpec (_javaPrimitiveArrayOpenRef, elementType),
new NamedRuntimeTypeSpec (proxy.Primitive.ConcreteArrayType),
};
];
foreach (var concreteArrayType in proxy.Primitive.ConcreteArrayTypes) {
rankOneTypes.Add (new NamedRuntimeTypeSpec (concreteArrayType));
}

return ExpandRankOneTypes (rankOneTypes, proxy.Rank);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public interface ITrimmableTypeMapLogger
void LogGeneratedJcwFilesInfo (int sourceCount);
void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName);
void LogManifestReferencedTypeNotFoundWarning (string javaTypeName);
void LogInvalidManifestPlaceholderWarning (string placeholders);
void LogUnresolvableJavaPeerSkippedWarning (
string managedTypeName,
string assemblyName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen
// with the wrong AttributeName.
List<IntentFilterInfo>? intentFilters = null;
List<MetaDataInfo>? metaData = null;
Dictionary<string, object?>? layoutProperties = null;

foreach (var caHandle in typeDef.GetCustomAttributes ()) {
var ca = Reader.GetCustomAttribute (caHandle);
Expand Down Expand Up @@ -214,6 +215,8 @@ bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen
metaData ??= new List<MetaDataInfo> ();
var (mdName, mdProps) = ParseNameAndProperties (ca);
metaData.Add (CreateMetaDataInfo (mdName, mdProps));
} else if (attrName == "LayoutAttribute") {
layoutProperties = ParseLayoutAttribute (ca);
} else if (attrInfo is null && ImplementsJniNameProviderAttribute (ca)) {
// Custom attribute implementing IJniNameProviderAttribute (e.g., user-defined [CustomJniName])
var name = TryGetNameProperty (ca);
Expand All @@ -232,6 +235,9 @@ bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen
if (metaData is not null) {
attrInfo.MetaData.AddRange (metaData);
}
if (layoutProperties is not null) {
attrInfo.LayoutProperties = layoutProperties;
}
}

return (registerInfo, attrInfo);
Expand Down Expand Up @@ -425,6 +431,18 @@ RegisterInfo ParseRegisterInfo (CustomAttributeValue<string> value)
return null;
}

Dictionary<string, object?> ParseLayoutAttribute (CustomAttribute ca)
{
var value = DecodeAttribute (ca);
var properties = new Dictionary<string, object?> (StringComparer.Ordinal);
foreach (var named in value.NamedArguments) {
if (named.Name is not null) {
properties [named.Name] = named.Value;
}
}
return properties;
}

IntentFilterInfo ParseIntentFilterAttribute (CustomAttribute ca)
{
var value = DecodeAttribute (ca);
Expand Down Expand Up @@ -712,6 +730,12 @@ class TypeAttributeInfo (string attributeName)
/// Metadata entries declared on this type via [MetaData] attributes.
/// </summary>
public List<MetaDataInfo> MetaData { get; } = [];

/// <summary>
/// Named property values from a [Layout] attribute on this type, or null if none.
/// Maps to the &lt;layout&gt; child element of the component in the manifest.
/// </summary>
public Dictionary<string, object?>? LayoutProperties { get; set; }
}

sealed class ApplicationAttributeInfo () : TypeAttributeInfo ("ApplicationAttribute")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ static void ForceUnconditionalIfPresent (Dictionary<(string ManagedName, string
}
}

// Managed types that must never be emitted into the trimmable type map, keyed by
// (managed full name, assembly simple name). These are reflection-based ([RequiresUnreferencedCode])
// helpers that the trimmable runtime never activates via the type map; emitting proxies for them
// only produces IL2026 trim warnings.
static bool IsUnsupportedByTrimmableTypeMap (string managedFullName, string assemblyName) =>
managedFullName == "Java.Interop.ManagedPeer" && assemblyName == "Java.Interop";

void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results)
{
foreach (var typeHandle in index.Reader.TypeDefinitions) {
Expand All @@ -286,6 +293,16 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A

var fullName = MetadataTypeNameResolver.GetFullName (typeDef, index.Reader);

// Java.Interop.ManagedPeer is a reflection-based helper (marked
// [RequiresUnreferencedCode]) that is not supported by the trimmable type map: on the
// trimmable path native registration goes through IAndroidCallableWrapper.RegisterNatives
// and ManagedPeerNativeRegistration is disabled, so ManagedPeer is never activated via the
// type map. Emitting a proxy for it only produces IL2026 trim warnings (its constructors
// use reflection), so exclude it here.
if (IsUnsupportedByTrimmableTypeMap (fullName, index.AssemblyName)) {
continue;
}

// Temporarily allow [JniAddNativeMethodRegistrationAttribute] while we investigate
// which scenarios fail later in the trimmable typemap pipeline.
// if (index.MayUseJniAddNativeMethodRegistrationAttribute &&
Expand Down Expand Up @@ -2528,6 +2545,7 @@ void CollectExportField (MethodDefinition methodDef, AssemblyIndex index, List<J
Properties = attrInfo.Properties,
IntentFilters = attrInfo.IntentFilters,
MetaData = attrInfo.MetaData,
LayoutProperties = attrInfo.LayoutProperties,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifes
ForceExtractNativeLibs = forceDebuggable,
ManifestPlaceholders = config.ManifestPlaceholders,
ApplicationJavaClass = config.ApplicationJavaClass,
WarnInvalidPlaceholder = placeholders => logger.LogInvalidManifestPlaceholderWarning (placeholders),
};

var (doc, providerNames) = generator.Generate (manifestTemplate, allPeers, assemblyManifestInfo);
Expand Down
9 changes: 6 additions & 3 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ static Array ArrayCreateInstance (Type elementType, int length)
}
}

int arrayRank = GetArrayRank (elementType, out var leafElementType);
throw new NotSupportedException (
$"No TrimmableTypeMap array proxy entry for element type '{elementType}'. " +
$"Array lookups use the element type within the per-rank __ArrayMapRank{GetArrayRank (elementType)} typemap group; " +
$"No TrimmableTypeMap array proxy entry for element type '{elementType}' " +
$"(leaf element type '{leafElementType}', rank {arrayRank}). " +
$"Array lookups use the leaf element type within the per-rank __ArrayMapRank{arrayRank} typemap group; " +
$"ensure the mapping is emitted for that rank (for example by increasing _AndroidTrimmableTypeMapMaxArrayRank) or report an issue.");
}

Expand All @@ -52,7 +54,7 @@ static Array ArrayCreateInstance (Type elementType, int length)
#pragma warning restore IL3050
}

static int GetArrayRank (Type elementType)
static int GetArrayRank (Type elementType, out Type leafElementType)
{
int rank = 1;
while (elementType.IsSZArray) {
Expand All @@ -63,6 +65,7 @@ static int GetArrayRank (Type elementType)
}
elementType = nestedElementType;
}
leafElementType = elementType;
return rank;
}

Expand Down
Loading
Loading