You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Summary
This PR adds the two runtime managers that make the **trimmable typemap** a fully functional, reflection-free peer-resolution path:
- **`TrimmableTypeMapValueManager`** — creates and tracks Java↔managed peers without reflection or `Activator`.
- **`TrimmableTypeMapTypeManager`** — resolves managed↔Java type mappings by delegating to the generated `TrimmableTypeMap`.
Both are selected through the existing `RuntimeFeature.TrimmableTypeMap` switch and wired into `JNIEnvInit`. The trimmable path is **opt-in**; the NativeAOT default remains `managed`, and Mono/CoreCLR defaults are unchanged. Also included is the `JavaConvert` collection-factory refactor and a generator fix that the trimmable managers depend on.
## Background
The classic Java.Interop runtime resolves the managed `Type` for a Java instance (and vice-versa) using `Type.GetType`, `MakeGenericType`, and `Activator`-style construction. That is incompatible with trimming and NativeAOT: the trimmer can't see which types are reachable, and the types can't be created at runtime under AOT.
The **trimmable typemap** replaces those reflection calls with a build-time-generated map of `JavaPeerProxy` objects (produced by `Microsoft.Android.Sdk.TrimmableTypeMap`). Each proxy knows how to construct a specific peer and describes its JNI ↔ managed association statically. Earlier PRs landed the generator, the proxy shapes (array / generic-base / interface / unresolvable peers), and the CoreCLR `JavaMarshal` value-manager split. **This PR adds the runtime managers that consume that generated map.**
## What's in this PR
### 1. `TrimmableTypeMapValueManager` (new)
A `JniRuntime.JniValueManager` that performs peer creation with **no reflection**:
- **Peer lifetime** (`AddPeer` / `PeekPeer` / `RemovePeer` / `FinalizePeer` / `CollectPeers` / `GetSurfacedPeers`) is delegated to `JavaMarshalRegisteredPeers`, i.e. the CoreCLR `JavaMarshal` GC-bridge machinery. `WaitForGCBridgeProcessing` is intentionally a no-op (documented: the wait can't close the bridge race on CoreCLR, where JNI wrapper threads hold their own `JniObjectReference` copies).
- **`CreatePeer`** resolves the requested target type (mapping `object`/`IJavaPeerable` → `Java.Interop.JavaObject`, `Exception` → `JavaException`) and asks `TrimmableTypeMap.Instance.CreateInstance (handle, resolvedType)` to build the peer from a generated proxy.
- **`ActivatePeer`** throws `PlatformNotSupportedException` — reflection-based activation is not part of this path.
- **`NotFoundFallback`** carefully reproduces the base `JniValueManager.CreatePeer` contract so `JavaCast`/`JavaAs` still surface the correct outcome when no proxy is found:
- target type has no Java mapping → `ArgumentException`
- Java instance not assignable to the target's Java class → `null` (so `JavaAs` returns null / `JavaCast` throws `InvalidCastException`)
- compatible classes but no proxy → `NotSupportedException` (a genuine generator gap, with a message pointing at the missing proxy)
The assignability check honors `RuntimeFeature.IsAssignableFromCheck` and mirrors the legacy cast diagnostic when assembly logging is enabled.
### 2. `TrimmableTypeMapTypeManager` (new)
A `JniRuntime.JniTypeManager` that has exactly two live responsibilities and throws for everything else it doesn't need:
- **Managed → Java** via `GetTypeSignatureCore`, backed by a `ConcurrentDictionary<Type, JniTypeSignature>` cache.
- **Java → managed** via `GetTypes` / `GetTypeForSimpleReference`, delegating to `TrimmableTypeMap`.
- **Array handling** diverges by runtime: NativeAOT reads a pre-generated array-proxy map (types can't be built at runtime), while CoreCLR builds array/generic types dynamically to save app size (suppressions are scoped to the CoreCLR-only branch).
### 3. `JNIEnvInit` wiring
`CreateValueManager` / `CreateTypeManager` now return the trimmable managers when `RuntimeFeature.TrimmableTypeMap` is set. The existing Mono / CoreCLR / managed selection is preserved, and the manager constructions were refactored into small local helpers so trimming suppressions (`IL2026` / `IL3050`) apply only to the exact branch that needs them. `RegisterJniNatives` is likewise gated so the reflection-based JNI registration path isn't emitted for the trimmable typemap.
### 4. `JavaConvert` collection-factory refactor
Generic collection marshalling (`IDictionary<,>` → `JavaDictionary<,>`, `IList<>` → `JavaList<>`, `ICollection<>` → `JavaCollection<>`) is split into two branches: a **factory-based converter** on the trimmable path (no `MakeGenericType`) and the classic `MakeGenericType` path elsewhere, with the reflection-requiring code isolated behind narrowly-scoped suppressions. Also adds `Nullable<T>` converter handling (null reference → null value).
### 5. Generator fix (`ModelBuilder`)
Emits a managed→Java typemap entry for **self-peer types** (`[JniTypeSignature(GenerateJavaPeer=false)]` or MCW bindings with no activation ctor). These are constructed managed-side with `new`, but their JNI name must still resolve so the correct Java class is instantiated; without the association they fell back to the generic `mono.android.runtime.JavaObject` peer and threw `ArrayStoreException` when placed into a typed Java array.
### 6. Cleanup
Removes the dead `TrimmableTypeMap` branch from `JavaMarshalValueManager` (that logic now lives in the dedicated `TrimmableTypeMapValueManager`).
## Behavioral impact
- **Opt-in only.** With `RuntimeFeature.TrimmableTypeMap` unset, behavior is unchanged. NativeAOT still defaults to `managed`.
- No new user-facing / localized strings; error messages point at the generator when a proxy is genuinely missing.
## Status
All earlier prerequisites have **merged into `main`**, and this PR is **rebased on latest `main`**, so it is no longer stacked or blocked — it contains only the value/type-manager implementations (plus the supporting `JavaConvert` and generator changes) on top of them:
- #11799 — CoreCLR `JavaMarshal` split + Java.Interop bump
- #11753 array proxies · #11749 generic base · #11751 unresolvable peers · #11769 interface proxies
- #11794 multidex/manifest base · #11796 manifest parity · #11798 R8 keep
## Testing
- `TrimmableTypeMapTypeManagerTests` and `TypeMapModelBuilderTests` updated/extended for the new type resolution and the self-peer generator fix.
- Export tests enabled for the trimmable typemap; `TrimmableTypeMapUnsupported` cases excluded.
- NativeAOT warning-count and CoreCLR `apkdesc` baselines updated to match.
[UnconditionalSuppressMessage("Trimming","IL2026",Justification="Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")]
397
-
[UnconditionalSuppressMessage("Trimming","IL2075",Justification="Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")]
[UnconditionalSuppressMessage("Trimming","IL2057",Justification="Type.GetType() can never statically know the string value parsed from parameter 'methods'.")]
499
-
[UnconditionalSuppressMessage("Trimming","IL2067",Justification="Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
500
-
[UnconditionalSuppressMessage("Trimming","IL2070",Justification="GetMethods can never statically know the string value parsed from parameter 'methods'.")]
501
-
[UnconditionalSuppressMessage("Trimming","IL2072",Justification="Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
0 commit comments