Skip to content

[TrimmableTypeMap] Make adjustments to the base JniValueManager and JniTypeManager to make implementing the trimmable type map easier#1454

Open
simonrozsival wants to merge 20 commits into
mainfrom
dev/simonrozsival/reuse-value-marshalers-followup
Open

[TrimmableTypeMap] Make adjustments to the base JniValueManager and JniTypeManager to make implementing the trimmable type map easier#1454
simonrozsival wants to merge 20 commits into
mainfrom
dev/simonrozsival/reuse-value-marshalers-followup

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 11, 2026

Copy link
Copy Markdown
Member

Follow-up to #1441.
Prerequisite for dotnet/android#11617.

This keeps the Java.Interop changes focused on the small base hook dotnet/android needs for the trimmable type-map integration. The reflection value manager continues to use value marshalers internally, while Android's generated/trimmable value manager can provide the only production JavaObjectArray<T> element-assignment object-reference path without implementing GetValueMarshaler*().

Changes

  • Add minimal JniValueManager object-reference API for JavaObjectArray<T>.SetElementAt():
    • CreateLocalObjectReferenceArgument(Type type, object? value) returns an owned local JniObjectReference for element assignment. Callers must dispose the returned reference.
  • Make the matching core method abstract so non-reflection value managers can implement this path directly.
  • Keep value marshalers as a ReflectionJniValueManager implementation detail: reflection creates marshaler state, copies out an independent local reference, then destroys the state immediately.
  • Update JavaObjectArray<T> to call the value manager directly instead of calling GetValueMarshaler<T>() in production paths.
  • Simplify JavaObjectArray<T>.Clear() to set array slots to Java null directly; it no longer needs value-manager or value-marshaler state.
  • Remove the earlier exposed proxy/peerable marshaler accessors, broad/generic state overloads, default-value state API, destroy-state API, and ParameterAttributes synchronize from this value-manager object-reference path.
  • Keep ManagedPeer-dependent tests categorized as unsupported for the Android trimmable configuration rather than carrying Android-specific Java fixture workarounds in this PR.
  • Include the small type-manager/test cleanups needed by the dotnet/android integration branch.

Non-goals

  • This PR does not make value marshalers public trimmable API.
  • This PR does not require trimmable Android value managers to implement or use GetValueMarshaler*().
  • This PR does not remove or replace ManagedPeer-dependent Java.Interop test fixtures.

Validation

  • dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -p:Configuration=Debug -m:1 -nodeReuse:false --no-restore -v:minimal
  • From the dotnet/android integration branch:
    • dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal --no-restore (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.

simonrozsival and others added 2 commits June 10, 2026 16:37
The runtime hot path uses GetTypeSignature(), and GetTypeSignatures() is no longer needed for this helper assertion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the object proxy value marshaler under JniValueManager so non-reflection value managers can reuse JavaProxyObject marshaling without duplicating the managed proxy type. Expose the existing object and peerable marshalers to derived value managers, and use ConditionalWeakTable.GetValue for JavaProxyObject caching.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 11, 2026 07:42

Copilot AI 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.

Pull request overview

This PR refactors proxy/peerable value marshaling so it can be reused by JniValueManager implementations which don’t inherit from ReflectionJniValueManager, enabling external value managers (e.g., dotnet/android trimmable type-map managers) to reuse Java.Interop’s proxy/peerable behavior.

Changes:

  • Moves the proxy object value marshaler into the JniValueManager base (and exposes proxy/peerable marshalers to derived managers).
  • Updates proxy caching to use ConditionalWeakTable.GetValue() in JavaProxyObject.
  • Adjusts type-manager tests to use singular type-signature lookup and updates public API tracking.
Show a summary per file
File Description
tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs Simplifies assertions to use GetTypeSignature() only.
src/Java.Interop/PublicAPI.Unshipped.txt Tracks newly exposed marshalers on JniValueManager.
src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs Switches fallback marshaler to the base ObjectValueMarshaler.
src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs Introduces reusable proxy marshaler and exposes proxy/peerable marshalers to derived managers.
src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs Updates built-in marshaler mapping for JavaProxyObject to use the shared marshaler.
src/Java.Interop/Java.Interop/JavaProxyObject.cs Uses ConditionalWeakTable.GetValue() for proxy caching.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment thread src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs
Comment thread src/Java.Interop/Java.Interop/JavaProxyObject.cs Outdated
simonrozsival and others added 6 commits June 11, 2026 11:29
Expose primitive array type signature and value marshaler lookup from the base managers so non-reflection value managers can reuse the full Java.Interop primitive array marshaler set. Mark expression-based value marshaler contract tests as unsupported for trimmable typemap consumers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep JavaProxyObject cache access locked while using ConditionalWeakTable.GetValue. Make non-generic DestroyArgumentState tolerate null values for value-type marshalers, and mark Java.Interop tests that exercise expression-based marshaling, ManagedPeer, or legacy native registration as unsupported for trimmable typemap consumers.

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

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

Java.Interop 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.

⚠️ Needs Changes

The refactoring to make proxy/peerable value marshalers reusable from the base JniValueManager is well-structured. The DestroyArgumentState null-handling fix and ConditionalWeakTable.GetValue() simplification are nice improvements.

Issues

Severity Count Summary
❌ Error 1 T4 templates out of sync with generated files
⚠️ Warning 1 Test coverage reduction without justification
💡 Suggestion 2 Redundant lock, readonly singleton + stale suppression

The blocking issue is that JavaPrimitiveArrays.tt and JniBuiltinMarshalers.tt were not updated to match the changes to their generated .cs files. Regenerating from templates will revert the code changes.

Positive callouts

  • Clean separation of marshaler logic into the base class for reusability
  • Good use of ConditionalWeakTable.GetValue() to simplify caching
  • The DestroyArgumentState null-safety fix properly handles value types
  • Public API surface (PublicAPI.Unshipped.txt) is correctly maintained
  • [Category ("TrimmableTypeMapUnsupported")] annotations are appropriate for tests that depend on reflection-based managers

Generated by Java.Interop PR Reviewer for issue #1454 · ● 6.8M

Comment thread src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs Outdated
Comment thread src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs Outdated
Comment thread tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs
Comment thread src/Java.Interop/Java.Interop/JavaProxyObject.cs Outdated
simonrozsival and others added 9 commits June 12, 2026 10:49
These tests rely on desktop ManagedPeer construction/registration paths that the Android trimmable typemap intentionally does not support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep value marshaler helper classes as top-level internals and remove the primitive-array/type-manager helper API churn from the Java.Interop branch. Android keeps the trimmable-only primitive array handling locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Give CrossReferenceBridge explicit JniPeerMembers so Android can construct the intended Java peer without relying on the desktop ManagedPeer constructor path, and categorize the test as GCBridge instead of unsupported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Android trimmable lane supports method replacement; keep ReplacementTypeUsedForMethodLookup active by categorizing it as Remapping instead of TrimmableTypeMapUnsupported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Android trimmable lane supports this dispatch scenario once the ManagedPeer-dependent Java fixtures are replaced, so categorize MethodBindingTests as MethodBinding instead of unsupported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the Android-trimmable fixture workarounds from the Java.Interop branch and leave ManagedPeer-dependent tests categorized as TrimmableTypeMapUnsupported. Restore JniValueMarshaler and JniTypeManagerTests changes so this PR stays focused.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Let JavaObjectArray request value marshaler state directly from JniValueManager. ReflectionJniValueManager keeps using value marshalers internally, while trimmable Android can override the abstract state APIs without exposing value marshalers on that path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival changed the title Reuse proxy value marshalers from base manager [TrimmableTypeMap] Make adjustments to the base JniValueManager and JniTypeManager to make implementing the trimmable type map easier Jun 12, 2026
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Java.Interop 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.

✅ LGTM — Clean, well-structured abstraction layer

The PR correctly lifts JavaObjectArray's marshaler state operations from JniValueMarshaler<T> direct calls up to JniValueManager virtual dispatch, enabling trimmable value managers to implement these paths without reflection. The architecture follows established patterns in this codebase.

What looks good

  • Clean separation: The JniValueManager base class provides the internal API + abstract core, and ReflectionJniValueManager delegates to the existing marshaler infrastructure — exactly the pattern used for other abstract methods.
  • No in-repo breakage: All JniValueManager subclasses in this repo derive from ReflectionJniValueManager and inherit the new implementations.
  • Public API surface: PublicAPI.Unshipped.txt entries are complete and consistent.
  • Test categorization: The TrimmableTypeMapUnsupported category correctly identifies ManagedPeer-dependent tests for downstream exclusion.
  • GetTypeName fix: Switching from .Name to GetTypeName() for the marshaler type in the test expression string correctly handles generic type names.

Minor suggestions (3 × 💡)

# Concern
1 CreateValueMarshalerState defaults synchronize to ParameterAttributes.In while the underlying marshalers default to 0 — subtle behavioral shift in Clear().
2 ObjectValueMarshaler/PeerableValueMarshaler properties lack doc comments explaining their intended downstream use.
3 Non-generic Type-based overloads have no test coverage within this repo.

CI

Only the CLA check is visible; no build/test CI results found on the PR. The code changes are straightforward delegation — unlikely to break existing behavior — but downstream validation (dotnet/android integration branch) is the key verification gate per the PR description.

Generated by Java.Interop PR Reviewer for issue #1454 · ● 7.1M

Comment thread src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs Outdated
Comment thread src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs Outdated
Comment thread src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs Outdated
simonrozsival and others added 2 commits June 12, 2026 17:06
Keep JavaObjectArray object-reference marshaling on JniValueManager while removing unused state overloads, default-value state APIs, and exposed proxy marshaler accessors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace object-array marshaler state cleanup with a direct JniObjectReference result. The reflection manager now uses the existing marshaler to create and destroy temporary state internally before returning an independent local reference.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename the JavaObjectArray helper hook to CreateLocalObjectReferenceArgument so callers can see that the returned JNI reference is owned and must be disposed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, thanks! label Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, thanks!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants