-
Notifications
You must be signed in to change notification settings - Fork 573
[TrimmableTypeMap] Fix String and nullable array proxies #12017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bc83180
a52bec6
a732fc1
eda8c3e
1479d0e
960b936
63fd113
097ff82
bd2dc4b
405fee6
7a66dfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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", | ||
|
|
@@ -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)); | ||
|
|
@@ -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. | ||
|
|
@@ -109,12 +131,13 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri | |
|
|
||
| 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); | ||
| } | ||
|
|
||
|
|
@@ -541,6 +564,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) { | ||
|
|
@@ -569,7 +612,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), | ||
|
|
@@ -585,7 +628,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); | ||
| } | ||
|
|
@@ -616,7 +659,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 | ||
|
|
@@ -665,6 +708,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) { | ||
|
|
@@ -694,6 +744,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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 💡 Code organization — The three emission blocks in Rule: Code duplication / consolidation |
||
| for (int rank = 1; rank <= maxArrayRank; rank++) { | ||
| var proxy = new ArrayProxyData { | ||
| TypeName = $"Nullable_{nullablePrimitive.Name}_ArrayProxy{rank}", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 💡 Naming — The Rule: Consistency |
||
| 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 { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.