Skip to content

[TrimmableTypeMap] Implement reflection-free TrimmableTypeMapValueManager and TrimmableTypeMapTypeManager#11617

Open
simonrozsival wants to merge 108 commits into
mainfrom
dev/simonrozsival/java-interop-1441-android
Open

[TrimmableTypeMap] Implement reflection-free TrimmableTypeMapValueManager and TrimmableTypeMapTypeManager#11617
simonrozsival wants to merge 108 commits into
mainfrom
dev/simonrozsival/java-interop-1441-android

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 9, 2026

Copy link
Copy Markdown
Member

Goal

Integrate the Java.Interop value-manager/type-manager split into dotnet/android while keeping the trimmable typemap runtime path reflection-free, AOT-friendly, and covered by targeted tests.

This PR is specifically about making the Android runtime use the new Java.Interop abstractions safely. Broader cleanup such as removing ManagedPeer support or changing NativeAOT runtime policy is intentionally left to follow-up PRs.

Contributes significantly to #10794
Contributes significantly to #11012
Contributes to #8724

What changed

Java.Interop integration

  • Updated the Java.Interop submodule to the follow-up branch for [TrimmableTypeMap] Make adjustments to the base JniValueManager and JniTypeManager to make implementing the trimmable type map easier java-interop#1454.
  • The Java.Interop side now exposes only the minimal object-reference hook needed by JavaObjectArray<T>.SetElementAt():
    • CreateLocalObjectReferenceArgument(Type type, object? value) returns an owned local JniObjectReference for element assignment. Callers must dispose the returned reference.
  • JavaObjectArray<T>.Clear() now directly writes Java null into each slot; it no longer needs value-manager or value-marshaler state.
  • Reflection-backed Java.Interop code still uses value marshalers internally: it creates marshaler state, copies out an independent local reference, then destroys the state immediately.
  • The trimmable Android path does not implement or use GetValueMarshaler*() for JavaObjectArray<T> element assignment.

Runtime manager wiring

  • Switched legacy Mono/CoreCLR Android managers to the Java.Interop ReflectionJniTypeManager / ReflectionJniValueManager base implementations.
  • Added a CoreCLR Android value manager that preserves Android peer registration, GC bridge tracking, activation, and exception unboxing behavior.
  • Added trimmable typemap value/type managers for generated-typemap mode:
    • TrimmableTypeMapTypeManager resolves generated proxy/type-map data without reflection fallback.
    • TrimmableTypeMapValueManager constructs and marshals generated peers without the reflection-backed value manager.
    • Primitive arrays, Java arrays, peerables, proxies, strings, primitive wrappers, and general JavaConvert fallback cases are handled without reflection fallback.
  • Split the large JavaMarshalValueManager.cs implementation into per-type files.
  • Removed the old SimpleValueManager path.

Trimmable typemap build fixes

  • Preserved Microsoft.Android.Runtime.ManagedTypeMapping for linker/type-map steps that still need it.
  • Fixed post-trim Java source/stamp paths for multi-RID CoreCLR builds so RID-specific outputs do not collide or leave stale Java sources behind.
  • Kept [JniAddNativeMethodRegistrationAttribute] diagnostics focused on user assemblies; framework/runtime internal users are handled by generated replacements or intentionally unsupported runtime paths.
  • Removed the unproven RequiresUnreferencedCode annotation from ExportFieldAttribute. [ExportField] is handled by generated trimmable type-map code and does not need to warn simply for constructing the attribute.

Test strategy

  • Trimmable runtime tests exclude TrimmableTypeMapUnsupported via test categories.
  • ManagedPeer-dependent Java.Interop desktop fixture tests remain skipped by category in the trimmable Android lane. This PR does not add Android-local fixture workarounds for those tests; ManagedPeer removal/porting is left to follow-up work.
  • Reflection-manager-only Java.Interop tests remain covered by Java.Interop's standalone test suite; the Android trimmable lane uses generated managers.
  • Added/updated host-side tests for:
    • scanner XA4251 behavior,
    • trimmable runtimeconfig switches,
    • post-trim Java source generation,
    • [Export] / [ExportField] trimmable codegen,
    • trim-warning filtering for generated type-map code.

Review notes / intentional non-goals

  • NativeAOT runtime policy is not broadened in this PR; non-trimmable NativeAOT behavior is left unchanged for a separate PR.
  • ManagedPeer removal is not part of this PR. ManagedPeer-dependent tests are categorized unsupported rather than worked around locally.
  • Java.Interop value marshalers remain implementation detail for reflection-backed Java.Interop behavior; trimmable Android code uses generated managers and direct local object-reference creation.
  • The built-in TypeJniTypeSignature mapping intentionally keeps the current Type.GetTypeCode + explicit nullable typeof checks. Benchmarking showed Nullable.GetUnderlyingType() allocates and should not be used on this path.

Local validation

Validated locally on this branch with:

dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj \
  -p:Configuration=Debug \
  -m:1 \
  -nodeReuse:false \
  --no-restore \
  -v:minimal

dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj \
  -v minimal \
  --no-restore

Results:

  • Java.Interop build passed.
  • Trimmable typemap tests passed (562 passed).
  • dotnet build src/Mono.Android/Mono.Android.csproj -p:Configuration=Debug -p:AndroidSdkDirectory=/Users/simonrozsival/android-toolchain/sdk -m:1 -nodeReuse:false --no-restore -v:minimal compiled Mono.Android.Runtime.dll; the remaining local failure is Android SDK provisioning (extras/android/m2repository.staging and docs.staging missing), not C# or trim-analyzer errors.

Latest update (2026-06-22)

  • Merged latest dotnet/android main into this branch.
  • Updated external/xamarin-android-tools to remove System.IO.Hashing from BaseTasks file hashing helpers. This avoids MSBuild task-host assembly binding failures when local/system SDKs load different System.IO.Hashing / System.Buffers.Binary versions.
  • Changed the trimmable value-manager opaque-object path to use an ACW-generated TrimmableJavaProxyObject instead of falling back to Android.Runtime.JavaObject for arbitrary managed objects.
    • Known primitive/string/array/IJavaObject conversions still use the existing JavaConvert path.
    • Arbitrary opaque managed objects now round-trip through TrimmableJavaProxyObject and unwrap back to the original managed value.
    • The Java peer is generated into mono.android.jar; no duplicate manual java_runtime_trimmable.jar class is added.
    • TrimmableJavaProxyObject derives from Java.Lang.Object so jcw-gen emits Java overrides for equals, hashCode, and toString which call back into the C# proxy implementation.

Additional local validation:

./dotnet-local.sh build --disable-build-servers src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Release --no-restore
./dotnet-local.sh build --disable-build-servers src/java-runtime/java-runtime.csproj -c Release --no-restore
./dotnet-local.sh build --disable-build-servers src/Mono.Android/Mono.Android.csproj -c Release --no-restore
  • Verified mono.android.jar contains net/dot/jni/internal/TrimmableJavaProxyObject.class.
  • Verified java_runtime_trimmable.jar does not contain the trimmable proxy class after cleaning stale intermediates.
  • A trimmable/CoreCLR device run with the ACW proxy reached the test suite; the prior JavaObjectArray<object> failures were gone. Remaining failures were known/unsupported GetValueMarshalerCore cases plus one peer-count assertion (JavaObjectTest.UnregisterFromRuntime) for follow-up triage.
  • Post-merge device install/test retries were blocked by emulator package-manager instability (pm list features / install session broken pipe), not by compilation or duplicate-class errors.

Latest update (2026-06-24): trimmable array proxy generation

The trimmable typemap array path now generates one JavaArrayProxy type per JNI element name and array rank, and uses per-rank typemap groups to look them up without reflection.

For example, android/net/Network arrays are emitted conceptually as:

[assembly: TypeMap<__ArrayMapRank1>(
    "android/net/Network",
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy1),
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy1))]

[assembly: TypeMap<__ArrayMapRank2>(
    "android/net/Network",
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy2),
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy2))]

The external map key remains the bare JNI element name (android/net/Network); the rank is encoded by the typemap group (__ArrayMapRank1, __ArrayMapRank2, etc.). The external entry trims on the generated array proxy itself instead of trimming directly on Network[], which avoids needing duplicate same-key external typemap entries for every supported managed array representation.

Each generated array proxy is self-applied as a JavaArrayProxy attribute and provides:

  • CreateManagedArray(int length), emitted with direct newarr IL for the requested rank.
  • GetArrayTypes(), emitted with direct ldtoken entries for every managed representation that should resolve to the same JNI array shape.

To keep these proxies when any supported representation is used, the generator now also emits TypeMapAssociation entries from every type returned by GetArrayTypes() back to the generated array proxy. For non-primitive arrays this includes the CLR array form plus Java.Interop wrappers, for example:

[assembly: TypeMapAssociation<Java.Lang.Object>(
    typeof (Android.Net.Network[]),
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy1))]

[assembly: TypeMapAssociation<Java.Lang.Object>(
    typeof (Java.Interop.JavaArray<Android.Net.Network>),
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy1))]

[assembly: TypeMapAssociation<Java.Lang.Object>(
    typeof (Java.Interop.JavaObjectArray<Android.Net.Network>),
    typeof (_TypeMap.ArrayProxies.Android_Net_Network_ArrayProxy1))]

Higher ranks use the corresponding nested shapes returned by GetArrayTypes(), such as Network[][], JavaArray<Network>[], and JavaObjectArray<JavaObjectArray<Network>>. Primitive array proxies also get preservation associations for T[], JavaArray<T>, JavaPrimitiveArray<T>, and the concrete primitive wrappers such as JavaSByteArray, with the same rank expansion.

At runtime:

  • JNIEnv.ArrayCreateInstance() calls TrimmableTypeMap.Instance.TryGetArrayProxy(elementType, additionalRank: 1) and then CreateManagedArray() to allocate T[] without Array.CreateInstance()/MakeArrayType() on NativeAOT.
  • TrimmableTypeMapTypeManager.GetTypes() calls the same array-proxy lookup and returns GetArrayTypes() so T[], JavaArray<T>, JavaObjectArray<T>, primitive wrappers, and nested ranks all resolve through the generated metadata.
  • The typemap builder keeps the external entry when the generated proxy is kept; the proxy is kept by the association from whichever array representation user code references.

Additional local validation for this update:

./dotnet-local.sh test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj \
  -c Release --no-restore --filter FullyQualifiedName~Build_EmitArrayEntries

./dotnet-local.sh test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj \
  -c Release --no-restore --filter FullyQualifiedName~FullPipeline_ArrayEntries

./dotnet-local.sh test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj \
  -c Release --no-restore \
  --filter 'FullyQualifiedName~ModelBuilderTests|FullyQualifiedName~TypeMapAssemblyGeneratorTests|FullyQualifiedName~RootTypeMapAssemblyGeneratorTests|FullyQualifiedName~TrimmableTypeMapGeneratorTests'

./dotnet-local.sh build tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -c Release -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false --no-restore

Results:

  • Array model tests passed (21 passed).
  • Array PE round-trip tests passed (2 passed).
  • Generator-related trimmable typemap tests passed (299 passed).
  • The trimmable Mono.Android.NET-Tests project built successfully.

Latest update (2026-06-24): final array proxy trim shape + HelloWorld validation

The generated trimmable typemap code now uses separate mechanisms for normal peer proxies and generated array proxies:

Normal peer proxy lookup

Normal JNI → managed peer entries remain in the normal Java.Lang.Object typemap universe, but managed-type → proxy lookup no longer relies on direct TypeMapAssociation<Object>(typeof(ManagedType), typeof(GeneratedProxy)) entries.

Instead, the generator emits reverse external typemap entries keyed by the managed type name:

[assembly: TypeMap<Object>(
    "android/net/Network",
    typeof(Android_Net_Network_Proxy),
    typeof(Network))]

[assembly: TypeMap<Object>(
    "__managed_type:Android.Net.Network, Mono.Android",
    typeof(Android_Net_Network_Proxy),
    typeof(Network))]

SingleUniverseTypeMap.TryGetProxyType() now resolves these __managed_type: keys from the external typemap. This keeps normal peer proxy preservation controlled by external typemap trim targets instead of proxy-map associations.

Array proxy lookup and preservation

Array entries remain in per-rank typemap groups. The external array map entry trims on the generated array proxy, while rank-scoped TypeMapAssociation entries preserve that proxy from every supported managed array representation:

[assembly: TypeMap<__ArrayMapRank1>(
    "android/net/Network",
    typeof(Android_Net_Network_ArrayProxy1),
    typeof(Android_Net_Network_ArrayProxy1))]

[assembly: TypeMapAssociation<__ArrayMapRank1>(
    typeof(Network[]),
    typeof(Android_Net_Network_ArrayProxy1))]

[assembly: TypeMapAssociation<__ArrayMapRank1>(
    typeof(JavaArray<Network>),
    typeof(Android_Net_Network_ArrayProxy1))]

[assembly: TypeMapAssociation<__ArrayMapRank1>(
    typeof(JavaObjectArray<Network>),
    typeof(Android_Net_Network_ArrayProxy1))]

The generated root loader now requests both maps for each rank group:

TypeMapping.GetOrCreateExternalTypeMapping<__ArrayMapRank1>();
TypeMapping.GetOrCreateProxyTypeMapping<__ArrayMapRank1>(); // return value intentionally ignored

This lets the trimmer observe rank-scoped associations without mixing array-only preservation into the normal Object universe. Primitive array proxies are emitted only from _Java.Interop.TypeMap, which avoids duplicate proxy-map source keys across shared-universe typemap assemblies.

HelloWorld NativeAOT validation

The NativeAOT HelloWorld sample was updated locally to call ConnectivityManager.GetAllNetworks() after startup. With the latest generated typemap code, the sample successfully marshals Network[] and uses the returned network object:

NativeAotFromAndroid: Checking networks...
NativeAotFromAndroid: ConnectivityManager: android.net.ConnectivityManager@dcfe98d
NativeAotFromAndroid: GetAllNetworks...
NativeAotFromAndroid: Networks: 1
NativeAotFromAndroid: Network: WIFI

This validates the generated Android_Net_Network_ArrayProxy1 path end-to-end for NativeAOT: rank lookup, Network[] allocation, JNI array copy, and subsequent use of the returned Network instance.

Additional local validation for the final shape:

  • ModelBuilderTests: passed (135).
  • Generator/root typemap subset: passed (167).
  • Trimmable Mono.Android.NET-Tests project build: passed.
  • HelloWorld NativeAOT sample build: passed.
  • Generated metadata inspection confirmed:
    • TypeMapAssociation<Object>(typeof(Network), typeof(Android_Net_Network_Proxy)) is no longer emitted;
    • TypeMap<Object>("__managed_type:Android.Net.Network, Mono.Android", ...) is emitted;
    • TypeMapAssociation<__ArrayMapRank1>(typeof(Network[]), typeof(Android_Net_Network_ArrayProxy1)) is emitted;
    • duplicate proxy-map association source keys: 0.

Generate the trimmable typemap once before trimming and reuse the generated Java sources instead of running GenerateTrimmableTypeMap again after ILLink. Keep the linked/R2R typemap assembly packaging path from the previous fix, and remove diagnostics that only supported the deleted post-trim Java copy path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/java-interop-1441-android branch from 42a1b8e to 47450cf Compare June 10, 2026 06:17
@simonrozsival

Copy link
Copy Markdown
Member Author

Rebased onto #11622 (external/Java.Interop d7dbad5) and revalidated targeted Mono.Android build locally.\n\n/azp run

@simonrozsival simonrozsival changed the base branch from main to dependabot/submodules/external/Java.Interop-d7dbad5 June 10, 2026 07:11
@simonrozsival

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@simonrozsival

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@simonrozsival

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@simonrozsival simonrozsival changed the base branch from dependabot/submodules/external/Java.Interop-d7dbad5 to main June 11, 2026 10:15
dependabot Bot and others added 17 commits June 11, 2026 12:15
Bumps [external/Java.Interop](https://github.com/dotnet/java-interop) from `b881d21` to `d7dbad5`.
- [Commits](dotnet/java-interop@b881d21...d7dbad5)

---
updated-dependencies:
- dependency-name: external/Java.Interop
  dependency-version: d7dbad5e30a8f03743a508a95c4e9159fe1f6607
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Split the Android JavaMarshal value manager into CoreCLR and trimmable implementations that share peer registration and GC bridge integration through a reusable helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the trimmable typemap value manager on the abstract JniValueManager base, sharing only peer registration and GC bridge state with the CoreCLR value manager. Leave value marshaling unsupported for now until Android has trimmable-specific marshalers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move TrimmableTypeMapTypeManager off ReflectionJniTypeManager and implement type lookup through explicit built-in mappings plus the generated trimmable typemap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove newly added UnconditionalSuppressMessage attributes, propagate Requires annotations from reflection-backed managers, and carry DAM annotations through JavaPeerProxy/TrimmableTypeMap target type metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace newly added suppressions on reflection-backed managers with RequiresUnreferencedCode and RequiresDynamicCode propagation. Leave trimmable value/type managers free of UnconditionalSuppressMessage attributes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace remaining UnconditionalSuppressMessage attributes in the reflection-backed Android manager implementations with RequiresUnreferencedCode/RequiresDynamicCode where appropriate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace invoker lookup and legacy TypeManager peer creation suppressions with RequiresUnreferencedCode/RequiresDynamicCode propagation. Keep GetObject suppression because adding DAM there breaks delegate/reflection table use sites.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Annotate runtime feature switches with FeatureGuard and structure manager factory branches so reflection-backed manager creation is guarded by the relevant runtime feature instead of broad Requires annotations on the factory methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the single-use JavaMarshalReflectionValueManagerBase and keep the shared peer/GC bridge state in JavaMarshalPeerManager, directly delegated by the CoreCLR value manager.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make both TrimmableTypeMapTypeManager RegisterNativeMembers overloads throw UnreachableException directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make unused trimmable JniTypeManager paths fail loudly, remove ManagedPeer from trimmable runtime artifacts, and add an initial AOT-safe value-marshaling implementation for the trimmable value manager.

Update tests and trimmable runtime coverage to use feature switches via AppContext and enable the value-marshaling test bucket for follow-up triage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the Java.Interop proxy and peerable value marshalers from the trimmable value manager instead of duplicating peerable marshaling locally. This also updates the Java.Interop submodule to the follow-up branch with the shared proxy marshaler and re-enables the trimmable tests now covered by the shared marshalers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival and others added 16 commits June 23, 2026 18:42
…a-interop-1441-android

# Conflicts:
#	src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdesc
Remove ManagedPeer from the trimmable Java runtime and disable Java.Interop ManagedPeer native registration for trimmable typemap builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…441-android' into dev/simonrozsival/java-interop-1441-android

# Conflicts:
#	src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 Code Review — ⚠️ Needs Changes

Thorough, well-structured PR that splits the value-manager/type-manager responsibilities and keeps the trimmable typemap path reflection-free. The runtime wiring, generator, and MSBuild changes are coherent. One behavioral question on the legacy marshaling path should be resolved before merge; the other two items are minor.

Findings

  • ⚠️ 1 warningJavaConvert.GetJniHandleConverter: non-generic IDictionary/IList checks are now nested under the IsGenericType guard, silently changing the returned wrapper (JavaDictionary/JavaListJavaCollection) for non-generic targets on the Mono/CoreCLR path. Needs confirmation or a fix (inline).
  • 💡 2 suggestions — typo in a user-visible exception string ("Unsupporte runtime."); duplicated IsMonoRuntime/IsCoreClrRuntime branches in CreateTypeManager (inline).

Strengths

  • Clean error-code lifecycle. XA4254/XA4255 removal is consistent across Resources.resx, Resources.Designer.cs, the docs/ files, and index.md — no orphaned codes.
  • Better trimming annotations. Replacing blanket [UnconditionalSuppressMessage] on AndroidTypeManager/AndroidValueManager with [RequiresDynamicCode]/[RequiresUnreferencedCode] is the correct, more honest annotation.
  • Solid incremental-build redesign. The stamp-based sentinel plus DeleteStaleJavaSources (with the new Execute_DeletesStaleGeneratedJavaSources test) closes the stale-JCW gap, and removing the separate post-trim Java pass simplifies the CoreCLR targets.
  • Deterministic output. The DeterministicHashBuilder refactor and CreateDeterministicContentId are backed by Execute_SameInputs_ProducesByteStableAssemblies and the MVID tests — good reproducibility coverage.
  • Careful port of the peer-tracking/GC-bridge code and good overall test coverage for the generator and task changes.

CI

Linux build ✅ and license/cla ✅; Windows and Mac builds are still in progress and the overall dotnet-android check is queued — CI is not yet fully green. Please confirm the remaining legs pass before merge.

Reviewed against the dotnet/android review guidelines. Verdict reflects the open behavioral question plus in-progress CI; not a blocker on the design, which looks sound.

Generated by Android PR Reviewer for issue #11617 · 2.5K AIC · ⌖ 85.4 AIC · ⊞ 37.9K
Comment /review to run again

Comment thread src/Mono.Android/Java.Interop/JavaConvert.cs Outdated
Comment thread src/Mono.Android/Android.Runtime/JNIEnvInit.cs
simonrozsival and others added 6 commits June 24, 2026 19:00
Add association-based trim preservation for each array representation returned by the generated array proxies, including JavaArray/JavaObjectArray and primitive wrapper forms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit primitive array proxy associations only from the Java.Interop typemap assembly to avoid duplicate proxy-map source entries, and restore non-generic JavaConvert list/dictionary conversion behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit array proxy preservation associations in the matching __ArrayMapRankN typemap group and request the corresponding proxy maps from the generated loader so trimming observes them without mixing array preservation into the normal Object universe.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace normal JavaPeerProxy TypeMapAssociation reverse lookup with managed-type external TypeMap entries so generated peer proxies are still controlled by the external typemap trim target. Keep alias-holder and array proxy associations unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep rank-scoped array proxy associations for alternate array representations, but make the rank external typemap entries trim on the actual array types so array-returning APIs keep their generated array proxies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Android PR Reviewer failed. Please review the logs for details.

Code review complete for the reflection-free TrimmableTypeMapValueManager/TrimmableTypeMapTypeManager change — no significant issues found. Reviewed all in-scope generator/scanner files under src/Microsoft.Android.Sdk.TrimmableTypeMap/ (DeterministicHashBuilder.cs [new], MetadataHelper.cs, ModelBuilder.cs, TypeMapAssemblyEmitter.cs, RootTypeMapAssemblyGenerator.cs, PEAssemblyBuilder.cs, Model/TypeMapAssemblyData.cs, Scanner/JavaPeerScanner.cs, Scanner/ScannerHashingHelper.cs, TrimmableTypeMapGenerator.cs). Verified: (1) DeterministicHashBuilder is deterministic — little-endian int writes, UTF8 byte-length-prefixed strings (collision-safe), idempotent ToHash(), SHA256 disposed; (2) all content-fingerprint/MVID hashing iterates deterministically ordered collections (SortedDictionary/SortedSet/sorted lists), with module name guaranteeing cross-assembly MVID uniqueness; (3) new array-proxy IL/metadata emission keeps MethodDef/Field lists contiguous, overrides are correctly emitted without NewSlot, and the JavaArrayProxy self-attribute pattern matches the established JavaPeerProxy convention; (4) RootTypeMapAssemblyGenerator's GetOrCreateProxyTypeMapping+Pop is intentional registration with a balanced IL stack; (5) _typeMapAssociationAttrOpenRef is initialized before use; (6) ScannerHashingHelper's CRC64 switch to the exactly-sized byte[] array overload preserves the hash value and is required for correctness (ArrayPool would over-hash); (7) removal of the unused generateTypeMapAssemblies parameter is safe. No banned conventions patterns (null-forgiving !, string.Empty, Array.Empty, static string.IsNullOrEmpty) were introduced. Note: external/Java.Interop submodule was not checked out, so referenced Java array-type assembly names could not be independently confirmed, but they follow existing consistent patterns.

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.

This is probably not intentional?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah, copilot changed it for some reason. that needs to be reset. only the java.interop submodule is supposed to be modified.

simonrozsival and others added 2 commits June 24, 2026 23:02
Avoid reusing cached Java peers that do not implement the collection interface requested by FromJniHandle(). This keeps non-generic and generic Java collection wrappers from casting incompatible bound Java peer types such as Java.Util.ArrayList.

Skip generated array-proxy coverage when dynamic code is supported, because CoreCLR trimmable builds do not emit those proxies by default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants