Skip to content

[TrimmableTypeMap] Java peer scanner core#10823

Merged
jonathanpeppers merged 7 commits intodotnet:mainfrom
simonrozsival:dev/simonrozsival/java-peer-scanner-core
Feb 18, 2026
Merged

[TrimmableTypeMap] Java peer scanner core#10823
jonathanpeppers merged 7 commits intodotnet:mainfrom
simonrozsival:dev/simonrozsival/java-peer-scanner-core

Conversation

@simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Feb 16, 2026

Sliced from #10805. Adds the core scanner pipeline for the TrimmableTypeMap feature — the component that reads .NET assemblies and discovers all Java peer types using System.Reflection.Metadata.

What this PR adds

New Microsoft.Android.Sdk.TrimmableTypeMap project (netstandard2.0, ~1500 lines) with three logical layers:

1. Data model and metadata type providers

Scanner/JavaPeerInfo.cs, Scanner/SignatureTypeProvider.cs, Scanner/CustomAttributeTypeProvider.cs

  • JavaPeerInfo record — represents a discovered Java peer with its JNI name, marshal methods, activation constructor info, and component attributes
  • SignatureTypeProvider — decodes method signatures to extract parameter types (needed for marshal method and activation constructor matching)
  • CustomAttributeTypeProvider — decodes [Register], [Export], [Activity] etc. attribute blobs; includes a lazy enum type cache and correct nested type resolution

2. AssemblyIndex — per-assembly metadata indexer

Scanner/AssemblyIndex.cs

First phase of the pipeline. Reads a single assembly and builds an index of:

  • [Register]/[Export] attributes on types and methods → RegisterInfo/ExportInfo records
  • Component attributes ([Activity], [Service], [BroadcastReceiver], [ContentProvider]) with their JNI name properties
  • Type definition → Java peer registration mapping

Designed for per-assembly parallelism — each assembly gets its own index.

3. JavaPeerScanner — execution logic

Scanner/JavaPeerScanner.cs

Second phase. Consumes all AssemblyIndex results and produces the final JavaPeerInfo list:

  • Resolves inheritance chains across assemblies to find the nearest registered Java ancestor
  • Detects activation constructors (IntPtr + JniHandleOwnership)
  • Collects marshal methods and exported methods with JNI signatures
  • Merges component attribute metadata and resolves JNI names
  • Flags types for unconditional preservation when component attributes specify non-default JNI names

Pure function: assemblies in → peer info out, no side effects.

Review guide

Three commits, one per layer — review in order:

  1. Data model and metadata type providers — start here for the type system
  2. AssemblyIndex — the per-assembly indexing phase
  3. JavaPeerScanner — the cross-assembly resolution phase

Scanner unit tests follow in #10813.

@simonrozsival simonrozsival added Area: NativeAOT Issues that only occur when using NativeAOT. Area: CoreCLR Issues that only occur when using CoreCLR. copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Feb 16, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/java-peer-scanner-core branch from b6895af to cf78bbe Compare February 16, 2026 15:31
@simonrozsival simonrozsival marked this pull request as ready for review February 16, 2026 15:32
Copilot AI review requested due to automatic review settings February 16, 2026 15:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds the core System.Reflection.Metadata-based Java peer scanning pipeline for the new Microsoft.Android.Sdk.TrimmableTypeMap feature area. This introduces a new netstandard2.0 project responsible for indexing assemblies and producing a JavaPeerInfo model for downstream generators without using Cecil.

Changes:

  • Introduces the Microsoft.Android.Sdk.TrimmableTypeMap project and supporting polyfills/extensions for nullable + modern C# features on netstandard2.0.
  • Adds phase-1 AssemblyIndex (per-assembly metadata indexing) and phase-2 JavaPeerScanner (cross-assembly analysis) to discover Java peer types and marshal methods.
  • Adds minimal SRM type providers (SignatureTypeProvider, CustomAttributeTypeProvider) and the core data model (JavaPeerInfo, MarshalMethodInfo, activation ctor metadata).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs SRM signature decoder producing managed type-name strings.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Main scanner pipeline: builds indices, resolves peers, computes JNI names, collects marshal methods, resolves activation ctors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs Data model for discovered peers, marshal methods, and activation constructor info.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs SRM custom-attribute decoder with enum underlying-type support and nested type handling.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs Per-assembly indexer for type names, [Register] info, and component attribute metadata.
src/Microsoft.Android.Sdk.TrimmableTypeMap/NullableExtensions.cs NRT-friendly IsNullOrEmpty/IsNullOrWhiteSpace helpers for netstandard2.0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj New netstandard2.0 project wiring + package references + InternalsVisibleTo for tests.
src/Microsoft.Android.Sdk.TrimmableTypeMap/CompilerFeaturePolyfills.cs Polyfills for init/required-member compiler features on netstandard2.0.
eng/Versions.props Adds a pinned package version property for System.Reflection.Metadata.

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/java-peer-scanner-core branch 4 times, most recently from 1520328 to 265b874 Compare February 16, 2026 20:54
simonrozsival and others added 3 commits February 16, 2026 22:03
Add the TrimmableTypeMap scanner project (netstandard2.0) with:

- JavaPeerInfo, MarshalMethodInfo, ActivationCtorInfo records — the
  core data model representing Java peer types discovered in assemblies
- SignatureTypeProvider — decodes method signatures from metadata to
  extract parameter types for marshal method and activation constructor
  matching
- CustomAttributeTypeProvider — decodes custom attribute arguments with
  lazy enum type caching and correct nested type resolution
- CompilerFeaturePolyfills — netstandard2.0 shims for required/init
- NullableExtensions — IsNullOrEmpty/IsNullOrWhiteSpace helpers
- System.Reflection.Metadata 11.0.0-preview.1 package reference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add AssemblyIndex — the first phase of the scanner pipeline that reads
a single assembly and indexes all Java peer metadata:

- Discovers [Register], [Export] attributes on types and methods
- Builds RegisterInfo/ExportInfo records from custom attribute blobs
- Resolves TypeAttributeInfo for component attributes ([Activity],
  [Service], [BroadcastReceiver], [ContentProvider]) including their
  JNI name properties
- Maps type definitions to their Java peer registration data for
  downstream consumption by JavaPeerScanner

Key design: uses System.Reflection.Metadata directly (no Cecil) and
produces immutable record types. The index is per-assembly so scanning
can be parallelized across the app closure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add JavaPeerScanner — the second phase that consumes AssemblyIndex
results and produces the final list of JavaPeerInfo for the app:

- Walks all indexed types and resolves their Java peer registrations
- Handles inheritance: traverses base types across assemblies to find
  the nearest registered Java peer ancestor
- Detects activation constructors (IntPtr+JniHandleOwnership) and
  distinguishes between direct declarations and inherited ones
- Collects marshal methods ([Register] on methods) and exported
  methods ([Export]) with their JNI signatures
- Merges component attribute metadata ([Activity], etc.) and resolves
  JNI names from attribute properties
- Flags types for unconditional preservation when component attributes
  with non-default JNI names are present

The scanner is designed as a pure function: assemblies in → peer info
out, with no side effects or global state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/java-peer-scanner-core branch from 265b874 to e709c94 Compare February 16, 2026 21:04
Address unresolved review comments in scanner core:
- make AssemblyIndex.customAttributeTypeProvider private
- simplify component Name parsing via TryGetTypeProperty path
- merge duplicate named-argument helpers into a single generic
  TryGetNamedArgument<T>(..., out T) with notnull constraint
- remove boolean-specific helper and reuse generic method
- simplify enum cache assignment in CustomAttributeTypeProvider
- route JavaPeerScanner attribute decoding through AssemblyIndex helpers

Build verified for Microsoft.Android.Sdk.TrimmableTypeMap.csproj.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply PR10823-style simplifications across TrimmableTypeMap scanner code:
- centralize namespace/nested type name joins in MetadataTypeNameResolver
- reuse those helpers in JavaPeerScanner ResolveTypeReference
- simplify AssemblyIndex register parsing by removing provider overload
  and decoding through a single path
- reduce duplicated DecodeValue call sites via DecodeAttribute helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +114 to +121
static readonly HashSet<string> KnownComponentAttributes = new (StringComparer.Ordinal) {
"ActivityAttribute",
"ServiceAttribute",
"BroadcastReceiverAttribute",
"ContentProviderAttribute",
"ApplicationAttribute",
"InstrumentationAttribute",
};
Copy link
Member

Choose a reason for hiding this comment

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

So, I believe there is an interface, Java.Interop.IJniNameProviderAttribute, that the old code was looking for? All these attributes implement it. I think the idea was if Android introduced a new attribute, it could work without updating this list?

There is probably not any customers using Java.Interop.IJniNameProviderAttribute, but do we need a code path that looks for it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I already had this implemented but it complicates things quite a lot. It's not trivial to check if a type implements an interface with SRM. For now, I chose to ignore it. It shouldn't have practical implications, but I agree that we shold eventually implement it for completeness. I'll make sure to add this to the issue for future work. Is that OK?

Copy link
Member Author

Choose a reason for hiding this comment

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

So efficient way to implement this:

  • only check custom attributes on Java.Lang.Object or Java.Lang.Throwable subclasses
  • ignore attributes which don't have the Name named argument
  • ignore well known attributes (Register, Application, Activity, ...)

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated the "Future work" section of #10788

Move CompilerFeaturePolyfills.cs from the TrimmableTypeMap project folder to src-ThirdParty/System.Runtime.CompilerServices and link it from the csproj via a Compile item, matching the repository pattern for shared polyfills.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival enabled auto-merge (squash) February 17, 2026 20:27
@simonrozsival
Copy link
Member Author

/azp run Xamarin.Android-PR

@azure-pipelines
Copy link

No pipelines are associated with this pull request.

@simonrozsival
Copy link
Member Author

/azp run Xamarin.Android-PR

@azure-pipelines
Copy link

No pipelines are associated with this pull request.

@jonathanpeppers jonathanpeppers merged commit 03926b5 into dotnet:main Feb 18, 2026
27 of 28 checks passed
@simonrozsival simonrozsival deleted the dev/simonrozsival/java-peer-scanner-core branch February 18, 2026 15:44
@simonrozsival
Copy link
Member Author

Part of #10798

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: CoreCLR Issues that only occur when using CoreCLR. Area: NativeAOT Issues that only occur when using NativeAOT. copilot `copilot-cli` or other AIs were used to author this trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments