Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ sealed class TypeMapAssemblyData
/// </summary>
public List<AliasHolderData> AliasHolders { get; } = new ();

/// <summary>
/// Maximum array rank for which the generator emits per-rank <c>__ArrayMapRank{N}</c>
/// sentinel TypeDefs and <c>TypeMap</c> entries. 0 disables.
/// </summary>
public int MaxArrayRank { get; set; }

/// <summary>
/// Assembly names that need [IgnoresAccessChecksTo] for cross-assembly n_* calls.
/// </summary>
Expand Down Expand Up @@ -77,6 +83,12 @@ sealed record TypeMapAttributeData
/// True for 2-arg unconditional entries (ACW types, essential runtime types).
/// </summary>
public bool IsUnconditional => TargetTypeReference == null;

/// <summary>
/// 1-based array rank when this entry should use a <c>__ArrayMapRank{value}</c>
/// sentinel as its <c>TGroup</c> instead of the default model anchor.
/// </summary>
public int? AnchorRank { get; init; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,21 @@ static class ModelBuilder
/// <param name="peers">Scanned Java peer types (typically from a single input assembly).</param>
/// <param name="outputPath">Output .dll path — used to derive assembly/module names if not specified.</param>
/// <param name="assemblyName">Explicit assembly name. If null, derived from <paramref name="outputPath"/>.</param>
public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, string outputPath, string? assemblyName = null)
/// <param name="maxArrayRank">
/// 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)
{
if (peers is null) {
throw new ArgumentNullException (nameof (peers));
}
if (outputPath is null) {
throw new ArgumentNullException (nameof (outputPath));
}
if (maxArrayRank < 0) {
throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0.");
}

assemblyName ??= Path.GetFileNameWithoutExtension (outputPath);
string moduleName = Path.GetFileName (outputPath);
Expand All @@ -56,6 +63,9 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
AssemblyName = assemblyName,
ModuleName = moduleName,
};
if (maxArrayRank > 0) {
model.MaxArrayRank = maxArrayRank;
}

// Invoker types are NOT emitted as separate proxies or TypeMap entries —
// they only appear as a TypeRef in the interface proxy's get_InvokerType property.
Expand Down Expand Up @@ -89,6 +99,10 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
}

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

if (maxArrayRank > 0) {
EmitArrayEntries (model, jniName, peersForName, maxArrayRank);
}
}

// Compute IgnoresAccessChecksTo from cross-assembly references
Expand Down Expand Up @@ -392,4 +406,45 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr

static string AssemblyQualify (string typeName, string assemblyName)
=> $"{typeName}, {assemblyName}";

/// <summary>
/// Emits per-rank <c>[L&lt;jni&gt;;</c>-shaped TypeMap entries for one peer, anchored to
/// the per-assembly <c>__ArrayMapRank{N}</c> sentinels. Skips open generics, primitive
/// JNI keyword keys (handled by the legacy primitive-array path), and alias groups.
/// </summary>
static void EmitArrayEntries (TypeMapAssemblyData model, string jniName, List<JavaPeerInfo> peersForName, int maxArrayRank)
{
if (jniName.Length == 1 && IsJniPrimitiveKeyword (jniName [0])) {
return;
}
if (peersForName.Count != 1) {
return;
}

var peer = peersForName [0];
if (peer.IsGenericDefinition) {
return;
}

for (int rank = 1; rank <= maxArrayRank; rank++) {
string arrayTypeRef = AssemblyQualify (peer.ManagedTypeName + Brackets (rank), peer.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
JniName = jniName,
ProxyTypeReference = arrayTypeRef,
TargetTypeReference = arrayTypeRef,
AnchorRank = rank,
});
}
}

static string Brackets (int rank) => rank switch {
1 => "[]",
2 => "[][]",
3 => "[][][]",
_ => string.Concat (Enumerable.Repeat ("[]", rank)),
};

static bool IsJniPrimitiveKeyword (char c)
=> c == 'Z' || c == 'B' || c == 'C' || c == 'S' || c == 'I'
|| c == 'J' || c == 'F' || c == 'D' || c == 'V';
}
Loading