[TrimmableTypeMap] Remove generated array proxy codegen and JavaPeerContainerFactory#12126
Conversation
…ontainerFactory PR #12030 added runtime-only AOT-safe factories (SafeArrayFactory, SafeJavaCollectionFactory, ValueTypeFactory) that fully replace the old generated array-proxy code path and JavaPeerContainerFactory. That PR explicitly deferred removing the now-dead generator/runtime machinery to a follow-up; this is that follow-up. Runtime (src/Mono.Android): - Remove the JavaArrayProxy attribute base class and JavaPeerProxy.GetContainerFactory(). - Delete JavaPeerContainerFactory.cs (and its Mono.Android.csproj entry). - Remove TrimmableTypeMap.TryGetArrayProxy, the array-proxy cache/sentinel, MissingJavaArrayProxy, dead managed-type-key helpers, and the per-rank array-map Initialize overloads (keep the 2-arg single and []-aggregate ones). - Drop ITypeMap.TryGetArrayProxyType and the per-rank array-map constructors/field from AggregateTypeMap and SingleUniverseTypeMap. - Update PublicAPI.Unshipped.txt for all API levels. Generator (Microsoft.Android.Sdk.TrimmableTypeMap) + MSBuild: - Remove ArrayProxyData/PrimitiveArrayProxyData, MaxArrayRank, AnchorRank, EmitArrayProxyType, __ArrayMapRank emission, and all maxArrayRank plumbing through the generator, the GenerateTrimmableTypeMap task, and the .targets. - Remove now-dead scanner array-entry infrastructure (GenerateArrayEntries, ReferencedTypeNamesByAssembly). Tests: - Remove the legacy TryGetArrayProxy_* device tests and the array-proxy generator/model/scanner unit tests and build-test expectations. Validated: Microsoft.Android.Sdk.TrimmableTypeMap builds and its 581 unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
With GetContainerFactory() removed, JavaPeerProxy<T> no longer constructs T (it only passes typeof(T) to the base), so the constructor DynamicallyAccessedMembers annotation on T is no longer needed. Remove the Constructors const and the now-unused System.Diagnostics.CodeAnalysis using. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
…all proxies JavaPeerProxy<T> only existed to auto-populate TargetType from typeof(T) and (previously) to expose GetContainerFactory()/root T's constructors. With GetContainerFactory() and the DynamicallyAccessedMembers annotation on T gone, the generic base carries no remaining value: peers are always activated through the generated CreateInstance / InvokerType, never by reflecting over T. - Remove the JavaPeerProxy<T> class (and its PublicAPI entries). - Generator now emits every proxy (concrete class, interface, open generic) deriving from the non-generic JavaPeerProxy base with the (string, Type) ctor, removing the JavaPeerProxy`1 type reference and the base-type branch. - Update model/test comments and the base-ctor-chain assertions accordingly. Validated: Microsoft.Android.Sdk.TrimmableTypeMap builds; 581 unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
…xy entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
There is only one JavaPeerProxy base class and the generic variant was never shipped, so comments, the emitter field name (_javaPeerProxyNonGenericRef -> _javaPeerProxyRef), and test names/InlineData no longer need to describe the base as "non-generic". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
Collapsing every proxy onto the single JavaPeerProxy base removed the only reader of the JavaPeerProxyData.IsInterface model property (it previously selected the base type). It is now write-only, so drop the property and its assignment in ModelBuilder. JavaPeerInfo.IsInterface (the scanner model) is unaffected and still drives ACW gating and JCW generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
With AnchorRank/per-rank array-map entries gone, every TypeMap and TypeMapAssociation attribute is emitted against the single default anchor. The per-anchor ctor-ref caches (_typeMapAttr2ArgCtorRefByAnchor, _typeMapAttr3ArgCtorRefByAnchor, _typeMapAssociationAttrCtorRefByAnchor) and their GetOrAdd* helpers had no remaining callers, so remove them; the default-anchor ctor refs are still built directly in the *CtorRef emitters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
…ref helpers After removing the per-anchor caches, AddTypeMapAttr2ArgCtorRef and AddTypeMapAttr3ArgCtorRef each have a single caller that always passes _anchorTypeHandle. Drop the parameter and reference _anchorTypeHandle directly, matching EmitTypeMapAssociationAttributeCtorRef. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1240235b-7dc1-4d1d-8046-ca78ced61d6e
There was a problem hiding this comment.
Pull request overview
This PR is a cleanup follow-up to the trimmable typemap work: it removes the legacy generated array-proxy machinery and JavaPeerContainerFactory/JavaPeerProxy<T> infrastructure, consolidating the runtime + generator around the newer runtime-safe factories and a single non-generic JavaPeerProxy base.
Changes:
- Removes runtime support for generated array proxies and container factories (
JavaArrayProxy,JavaPeerContainerFactory,TrimmableTypeMap.TryGetArrayProxy, and per-rank array-map plumbing). - Simplifies the generator/MSBuild pipeline by deleting max-array-rank plumbing and array-entry emission, and making all proxies derive from
JavaPeerProxywith(string, Type)ctor chaining. - Updates tests and PublicAPI tracking to match the removed symbols and updated proxy base behavior.
Show a summary per file
| File | Description |
|---|---|
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs | Drops legacy TryGetArrayProxy_* device tests now that array-proxy support is removed. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaPeerProxyTests.cs | Removes coverage for the deleted generic JavaPeerProxy<T> base. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs | Removes assertions tied to the deleted array-entry scanning flags. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs | Removes array-entry model tests and updates proxy base-type commentary. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs | Updates generator tests to validate the single JavaPeerProxy base + 2-arg ctor chaining. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs | Removes root-generator tests for per-rank array sentinel emission/target attributes. |
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | Stops caching the removed _AndroidTrimmableTypeMapMaxArrayRank property. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs | Removes build assertions that depended on array-rank sentinels and Cecil-based inspection. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs | Deletes the MaxArrayRank task parameter and stops passing it to the generator. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets | Removes _AndroidTrimmableTypeMapMaxArrayRank defaults and task parameter wiring. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets | Removes MaxArrayRank parameter wiring for CoreCLR typemap generation. |
| src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt | Removes unshipped API entries for deleted array/container proxy types and members. |
| src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt | Removes unshipped JavaArrayProxy entries for API-36 surface. |
| src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt | Removes unshipped JavaArrayProxy entries for API-36.1 surface. |
| src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt | Removes unshipped JavaArrayProxy entries for API-35 surface. |
| src/Mono.Android/Mono.Android.csproj | Removes compilation of the deleted JavaPeerContainerFactory.cs. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Removes array-proxy lookup/cache and overloads taking per-rank array maps. |
| src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs | Removes per-universe/per-rank array-map storage and lookup. |
| src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs | Removes the array-proxy lookup contract from the typemap abstraction. |
| src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs | Removes array-proxy lookup aggregation across universes. |
| src/Mono.Android/Java.Interop/JavaPeerProxy.cs | Deletes GetContainerFactory(), JavaPeerProxy<T>, and JavaArrayProxy definitions. |
| src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs | Deletes the container-factory implementation file. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs | Removes maxArrayRank argument/validation and stops threading it through generation. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs | Removes framework-peer array-entry marking logic and related initialization. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs | Removes the GenerateArrayEntries flag from the scanner model. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs | Removes referenced-type-name indexing that only supported array-entry decisions. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs | Removes maxArrayRank from assembly generator API and model building. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs | Removes rank sentinels + array-proxy emission and standardizes proxy base emission. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs | Removes array-sentinel targeting and array-map initialization IL emission. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs | Removes array-entry and primitive-array-proxy model generation. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs | Removes array-proxy model types, MaxArrayRank, and rank-anchored association data. |
Copilot's findings
- Files reviewed: 31/31 changed files
- Comments generated: 1
| var proxyBaseType = (EntityHandle) _javaPeerProxyRef; | ||
| var baseCtorRef = _pe.AddMemberRef (_javaPeerProxyRef, ".ctor", | ||
| sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2, | ||
| rt => rt.Void (), | ||
| p => { |
NUnit instantiates JavaListTest<T> reflectively from the
[TestFixture(typeof(...))] arguments, so the trimmable/NativeAOT typemap
never sees the concrete `new T ()` in Setup and trims the parameterless
constructors of JavaList / JavaList<string> (the trimmable typemap only
roots the (IntPtr, JniHandleOwnership) activation constructor of Java
peers). That made all 15 JavaListTest cases fail on the
NativeAOTTrimmable flavor with:
System.MissingMethodException : MissingConstructor_Name, Android.Runtime.JavaList
Since there are exactly two fixtures, switch on typeof(T) and call the
constructors directly instead of relying on the `new()` constraint, which
cannot be honored under NativeAOT reflection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9ec4f09-ffc5-4f66-99bf-ec40ea2728e8
The trimmable typemap defaulted _AndroidTrimmableTypeMapMaxArrayRank to 3
whenever dynamic code was unavailable (NativeAOT / PublishAot), which emits
per-element-type __ArrayMapRank{N} array-proxy types. Those proxies are dead
code at runtime: array and collection creation now goes through the AOT-safe
SafeArrayFactory / ValueTypeFactory (added in #12030), and
TrimmableTypeMap.TryGetArrayProxy has no callers.
Generating them only bloats NativeAOT ILC compilation. On multi-ABI apps this
pushed ILC past the test-harness build cap, timing out NativeAOT lanes
(Check9PatchFilesAreProcessed, WorkManager, BundleToolTests) that passed
quickly (5-13 min) before this default became NativeAOT.
Default MaxArrayRank to 0 on every runtime (still overridable via
$(_AndroidTrimmableTypeMapMaxArrayRank)). This is a subset of the codegen
removal in #12126; the per-type JavaPeerContainerFactory<T> bloat is not
addressed here and still needs #12126.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: da26833c-c9ea-41a6-b8d7-7a87a60ce8fc
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM (with one 💡 nit)
This is a clean, well-scoped follow-up to #12030 that deletes the now-dead generated array-proxy / JavaPeerContainerFactory machinery. I reviewed the runtime, generator, MSBuild task/targets, public API, and test changes independently before reading the description.
What I verified
- No dangling references. Repo-wide sweep for every removed symbol (
TryGetArrayProxy,JavaArrayProxy,JavaPeerContainerFactory,JavaPeerProxy<T>,GetContainerFactory,__ArrayMapRank,MaxArrayRank,_AndroidTrimmableTypeMapMaxArrayRank,GenerateArrayEntries,ArrayProxyData,IsInterfacemodel prop, per-anchor ctor-ref caches) returns zero hits outside of the deletions themselves. - Consistent plumbing removal.
maxArrayRankis dropped cleanly throughGenerateTrimmableTypeMaptask →.targets→TrimmableTypeMapGenerator→ both generators, and theInitialize(...)overloads that carried array maps are gone with their callers updated to the 2-arg / no-array forms. - Proxy base consolidation. Every proxy now derives from the single non-generic
JavaPeerProxy(string, Type)base. This matches the prioruseNonGenericBasebranch (open generics + interfaces already used it), and the retained comment correctly documents whyTargetTypestays correct without reflecting overT. JavaListTestfix. The reflection-freenew()workaround for the NativeAOT NUnit fixture is reasonable and well-commented; theNotSupportedExceptiondefault guards against silently mishandling a new fixture type.- PublicAPI.Unshipped.txt updated for all API levels.
CI
Checks were still pending (no results yet) at review time. The PR carries the ready-to-review label noting any failures are expected-flaky. Please confirm the full Mono.Android build + device tests go green before merge, since the authoring environment couldn't run them.
Nit
One inline 💡 suggestion about an unused TypeSpec now emitted in the shared-universe path — trivial, not blocking.
Nice, thorough cleanup. 🎯
Generated by Android PR Reviewer for #12126 · 154 AIC · ⌖ 13 AIC · ⊞ 6.8K
Comment /review to run again
| var initializeRef = AddInitializeSingleNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); | ||
| EmitInitializeWithSingleTypeMapNoArrays (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef, assemblyName); | ||
| } | ||
| var initializeRef = AddInitializeSingleNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); |
There was a problem hiding this comment.
🤖 💡 Generator codegen — After removing the array path, externalDictTypeSpec (computed a few lines up, unconditionally) is now only consumed in the aggregate else branch. In the shared-universe path (this branch — the common Release/AOT default) it's unused, so every shared-universe root assembly still gets a spurious IReadOnlyDictionary<string, Type> TypeSpec added to its PE metadata. Consider moving the var externalDictTypeSpec = MakeIReadOnlyDictTypeSpec (...) line into the else branch alongside proxyDictTypeSpec so it's only emitted when actually referenced.
(Trivial; harmless correctness-wise — just avoids an unused metadata token.)
Summary
Follow-up to #12030, which added the runtime-only AOT-safe factories (
SafeArrayFactory,SafeJavaCollectionFactory,ValueTypeFactory) that fully replace the old generated array-proxy code path andJavaPeerContainerFactory. That PR explicitly deferred removing the now-dead generator/runtime machinery to a follow-up — this is that follow-up. It also drops other machinery that is now unused: the genericJavaPeerProxy<T>base and theDynamicallyAccessedMembersannotation it carried.Background — why this existed, and why we're removing it
The trimmable typemap originally avoided reflection entirely on the marshaling path. To create
T[],JavaList<T>,JavaCollection<T>, andJavaDictionary<K,V>in an AOT-safe way (noMakeGenericType/MakeArrayType/Activator), the generator emitted, per binding type:JavaPeerContainerFactory<T>(rooted from every non-generic peer viaJavaPeerProxy<T>.GetContainerFactory()) — added in [TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert #10967 / [TrimmableTypeMap] Eliminate JavaPeerContainerFactory<T> per-type bloat #11234, and__ArrayMapRank{N}TypeMap groups — added in [TrimmableTypeMap] Add trimmable array proxy mapping #11753 (with array handling in [TrimmableTypeMap] Improve array handling #11238 and private rank anchors in [TrimmableTypeMap] Keep array rank anchors private #11297).This construction was AOT-safe but expensive in app size: it forced a unique
JavaPeerContainerFactory<T>and the cascadingnew T[]/new JavaList<T>/new JavaCollection<T>/new JavaDictionary<T,*>instantiations for every peer type — even ones never used as a collection/array element. For a typical .NET MAUI app (~2000 binding types) that's ~2000 factory instantiations plus their generic cascades — a substantial NativeAOT size hit. See #11234 for the full analysis.#12030 switched this to reflection (
Type.MakeArrayType()/Type.MakeGenericType()+Array.CreateInstanceFromArrayType) done in a trimming- and AOT-safe way: the calls are constrained to shapes NativeAOT can build (reference types canonicalize to__Canon; exact value-type arrays/instantiations are rooted viaValueTypeFactory), with theRequiresDynamicCode/RequiresUnreferencedCodesuppressions isolated to those safe factories. The reflection path costs a small amount of startup work but removes the per-type generated bloat entirely — and it simplifies the generator and runtime a great deal.This PR deletes the machinery that #12030 made redundant.
Changes
Runtime (
src/Mono.Android)JavaArrayProxyattribute base class andJavaPeerProxy.GetContainerFactory().JavaPeerContainerFactory.cs(and itsMono.Android.csprojentry).TrimmableTypeMap.TryGetArrayProxy, the array-proxy cache/sentinel,MissingJavaArrayProxy, dead managed-type-key helpers, and the per-rank array-mapInitializeoverloads (keep the 2-arg single and[]-aggregate overloads that the generator actually emits).ITypeMap.TryGetArrayProxyTypeand the per-rank array-map constructors/field fromAggregateTypeMapandSingleUniverseTypeMap.JavaPeerProxy<T>base entirely. It only existed to auto-populateTargetTypefromtypeof(T)and to rootT's constructors (via[DynamicallyAccessedMembers]) for the removedGetContainerFactory(). Peers are always activated through the generatedCreateInstance/InvokerType, never by reflecting overT, so the annotation and the generic base are no longer needed.PublicAPI.Unshipped.txtfor all API levels.Generator (
Microsoft.Android.Sdk.TrimmableTypeMap) + MSBuildArrayProxyData/PrimitiveArrayProxyData,MaxArrayRank,AnchorRank,EmitArrayProxyType,__ArrayMapRankemission, and allmaxArrayRankplumbing through the generator, theGenerateTrimmableTypeMaptask, and the.targets.GenerateArrayEntries,ReferencedTypeNamesByAssembly).JavaPeerProxybase with the(string, Type)ctor; removed the generic type reference and the base-type selection branch, the now-deadJavaPeerProxyData.IsInterfacemodel property, and the per-anchorTypeMapctor-ref caches left over from the rank-anchor mechanism.Tests / docs
TryGetArrayProxy_*device tests and the array-proxy generator/model/scanner unit tests and build-test expectations.Related work
JavaPeerContainerFactory<T>per-type bloat).JavaPeerContainerFactory/JavaPeerProxy), [TrimmableTypeMap] Add trimmable array proxy mapping #11753 (array proxy mapping), [TrimmableTypeMap] Improve array handling #11238 (array handling), [TrimmableTypeMap] Keep array rank anchors private #11297 (private rank anchors).Validation
Microsoft.Android.Sdk.TrimmableTypeMapbuilds; 581 generator unit tests pass.Mono.Androidbuild + device tests to be validated in CI (requires a prepared local SDK not available in the authoring environment).