Skip to content

[TrimmableTypeMap] Implement alias support in codegen and runtime#11122

Merged
simonrozsival merged 9 commits intomainfrom
dev/simonrozsival/11103-trimmable-typemap-aliases
Apr 21, 2026
Merged

[TrimmableTypeMap] Implement alias support in codegen and runtime#11122
simonrozsival merged 9 commits intomainfrom
dev/simonrozsival/11103-trimmable-typemap-aliases

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

Summary

Implements trimmable typemap alias support end-to-end, covering both generated metadata/code and runtime lookup/activation.

Fixes #11103
Part of #10788

Changes

New: IJavaPeerAliases interface (JavaPeerProxy.cs)

  • string[] Aliases { get; } — lists the exact TypeMap keys for an alias group

Build-time (ModelBuilder + Emitter)

For alias groups (≥2 .NET types sharing a JNI name, e.g., JavaCollection / JavaCollection<T>):

  • Generates an alias holder class extending JavaPeerProxy + IJavaPeerAliases
  • Base JNI name entry → alias holder (self-referencing trim target)
  • Each type gets [0]-based indexed entry: "jni/Name[0]", "jni/Name[1]", ...
  • TypeMapAssociation links each type to the alias holder (keeps holder alive when any alias survives trimming)
  • TargetType / CreateInstance throw NotSupportedException (alias holder is never used for peer creation)
  • Also adds self-application (AddCustomAttribute on typeDefHandle) for all proxy types — required for GetCustomAttribute<JavaPeerProxy>() to work at runtime

Runtime (TrimmableTypeMap.cs)

  • GetProxyForManagedType(): if (proxy is IJavaPeerAliases aliases) → iterate exact keys from Aliases property
  • GetAllTypesForJniName(): detect alias holder, enumerate listed keys
  • ActivateInstance(): same alias holder detection
  • Zero cost for non-alias types — single is check that's false, no dictionary probing

Generated output for an alias group

// TypeMap entries:
[assembly: TypeMap<Object>("some/JavaType", typeof(JavaType_Aliases), typeof(JavaType_Aliases))]
[assembly: TypeMap<Object>("some/JavaType[0]", typeof(A_Proxy), typeof(A))]
[assembly: TypeMap<Object>("some/JavaType[1]", typeof(B_Proxy), typeof(B))]

// Keep alias holder alive when any alias type survives trimming:
[assembly: TypeMapAssociation<Object>(typeof(A), typeof(JavaType_Aliases))]
[assembly: TypeMapAssociation<Object>(typeof(B), typeof(JavaType_Aliases))]

// Alias holder — IS a JavaPeerProxy, implements IJavaPeerAliases:
[JavaType_Aliases]
class JavaType_Aliases : JavaPeerProxy, IJavaPeerAliases
{
    public override Type TargetType => throw new NotSupportedException();
    public override IJavaPeerable? CreateInstance(...) => throw new NotSupportedException();
    public string[] Aliases => new[] { "some/JavaType[0]", "some/JavaType[1]" };
}

Tests

331 passing (+9 new):

  • 3 alias fixture types (3-way alias group mirroring JavaCollection/JavaCollection<T> pattern)
  • Model builder tests for alias holders, 3-way aliases, mixed activation
  • Fixture scanner tests verifying alias detection from real assemblies
  • Integration test verifying alias holder class + IJavaPeerAliases in emitted PE

Copilot AI review requested due to automatic review settings April 16, 2026 13:55
@simonrozsival simonrozsival added the copilot `copilot-cli` or other AIs were used to author this label Apr 16, 2026
Copy link
Copy Markdown
Contributor

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

Implements end-to-end alias support for the Trimmable TypeMap pipeline (codegen + runtime), enabling multiple managed types to share a single JNI name via an alias-holder indirection and indexed alias keys.

Changes:

  • Add IJavaPeerAliases and generate alias-holder proxy types + indexed TypeMap entries + TypeMapAssociation records for alias groups.
  • Update runtime lookup/activation paths (TrimmableTypeMap*) to expand alias holders into their concrete alias entries.
  • Add/expand unit tests and fixture types covering 2-way/3-way aliases and emission round-trips.

Reviewed changes

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

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs Adds fixture types that intentionally share a JNI name to form a 3-way alias group.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs Updates/extends model-builder tests for indexed alias entries, alias holders, and associations.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs Adds a round-trip emission test asserting alias-related typemap artifacts are present in emitted metadata.
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs Switches to enumerating all types for a JNI name (to support alias expansion).
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs Adds alias-aware type enumeration and alias resolution for proxy lookup and activation.
src/Mono.Android/Java.Interop/JavaPeerProxy.cs Introduces IJavaPeerAliases used by generated alias-holder proxy types.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs Emits alias-holder types and self-applies proxy/holder attributes for AOT-safe GetCustomAttribute instantiation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs Generates alias holders, base-entry indirection, indexed alias keys, and associations for duplicate JNI-name groups.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs Extends the model to carry alias-holder emission data.

Comment thread src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs Outdated
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/11103-trimmable-typemap-aliases branch 7 times, most recently from 9e8ae03 to 64d3228 Compare April 16, 2026 15:31
simonrozsival and others added 8 commits April 18, 2026 22:25
…1103)

Add alias holder approach for typemap aliases: when multiple .NET types
map to the same JNI name, generate an alias holder class extending
JavaPeerProxy + IJavaPeerAliases with explicit alias key list.

Build-time:
- IJavaPeerAliases interface with string[] Aliases property
- AliasHolderData in the codegen model
- ModelBuilder generates alias holder, [0]-based indexed entries,
  TypeMapAssociation links to holder
- Emitter emits alias holder class with TargetType/CreateInstance
  throwing NotSupportedException, Aliases property returning key array
- Self-application (AddCustomAttribute) for proxy and alias holder types

Runtime:
- GetProxyForManagedType: if (proxy is IJavaPeerAliases) -> iterate
  exact keys from Aliases property (zero cost for non-alias types)
- GetAllTypesForJniName: detect alias holder, enumerate listed keys
- ActivateInstance: same alias holder detection

Tests:
- 3 alias fixture types (3-way alias group)
- Model builder tests for alias holders, 3-way aliases, mixed activation
- Fixture scanner and integration tests

Fixes: #11103

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

- Rename ResolveAlias -> GetProxyFromAliases, use IsAssignableFrom
- GetProxyForJavaType: return sentinel for alias holders (not real proxies)
- TryGetProxyFromHierarchy: resolve alias groups when targetType is available
- Add GetAliasesForJniName helper
- Fix NotSupportedException IL: push string arg before newobj

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Don't extend JavaPeerProxy for alias holders — this keeps the fast path
(GetCustomAttribute<JavaPeerProxy>()) clean with zero alias checks.

- Replace IJavaPeerAliases interface with JavaPeerAliasesAttribute
- Alias holder is now a plain class (extends Object, not JavaPeerProxy)
- [JavaPeerAliases] attribute encodes alias keys in the metadata blob
- No ctor/CreateInstance/TargetType methods on alias holder (not needed)
- Runtime: only checks JavaPeerAliasesAttribute when JavaPeerProxy is null

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace TryGetTargetType(string, out Type?) with
  TryGetTargetTypes(string, out Type[]?) — returns all surviving
  target types for alias groups, false when no mapping or all trimmed
- GetProxyFromAliases: exact type equality (==) not IsAssignableFrom
- TryGetProxyFromHierarchy: resolve aliases even without targetType
  (returns first surviving proxy)
- TypeManager: single TryGetTargetTypes call, no alias awareness
- Remove dead methods: GetTargetTypesForAliasedJniName, GetAliasedTargetTypes
- Add GetFirstProxyFromAliases for targetType-less resolution
- 3 new integration tests:
  - alias holder extends Object not JavaPeerProxy
  - JavaPeerAliasesAttribute deserializes to correct keys
  - proxy types have self-applied attribute (MethodDef ctor)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A single-proxy-per-JNI-name cache violates the principle that one JNI
name can map to multiple types. Replace with GetProxyForJniClass(string,
Type?) which handles both direct entries and alias groups in a single
call, with no per-JNI-name caching.

- Remove _peerProxyCache (cached single proxy per JNI name)
- Remove GetProxyForJavaType (returned single proxy)
- Remove GetAliasesForJniName (no longer needed as separate method)
- Add GetProxyForJniClass(className, targetType) that resolves direct
  proxies or alias groups based on targetType
- Simplify TryGetProxyFromHierarchy to single GetProxyForJniClass call

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetProxiesForJniName to resolve all proxies (including aliases)
and register natives for each ACW proxy. Previously, alias holders
returned null from GetCustomAttribute<JavaPeerProxy>() and native
registration was silently skipped for aliased types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Commit 64d3228 accidentally bumped external/Java.Interop and
external/xamarin-android-tools submodule pointers. The newer
xamarin-android-tools commit removed the 'log' parameter from
Files.ExtractAll, causing CI build failures:

  ResolveLibraryProjectImports.cs(300,13): error CS1739: The best
  overload for 'ExtractAll' does not have a parameter named 'log'

Reset both submodules back to origin/main.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/11103-trimmable-typemap-aliases branch from 348a100 to 2b159e5 Compare April 18, 2026 20:34
@simonrozsival
Copy link
Copy Markdown
Member Author

/review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

Android PR Reviewer completed successfully!

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Needs Verification

The alias support implementation is well-structured — the JavaPeerAliasesAttribute / alias holder pattern cleanly separates alias groups from the fast non-alias path, and the runtime handles both paths correctly with appropriate caching.

Summary

Severity Count
⚠️ Warning 2
💡 Suggestion 3

Key findings

  1. ⚠️ Fragile handle resolutionEmitJavaPeerAliasesAttribute re-derives the TypeDefinitionHandle from GetRowCount instead of using the handle returned by AddTypeDefinition. Capture and pass it explicitly.

  2. ⚠️ UCO/callback boolean encoding unifiedEncodeClrTypeForCallback was removed, and callback MemberRefs now use the same byte encoding as UCO wrappers. Previous tests specifically verified that callbacks used sbyte for boolean to match MCW n_* method signatures. Needs confirmation that the MCW signatures were also updated.

  3. 💡 Generic alias matchingGetProxyForJniClass uses exact Type equality for alias groups, which misses open generic → closed generic matches. The fallback path handles this correctly, but generic aliases always take the slower path.

Positive callouts

  • Clean separation of alias holders from proxy types — alias holders extend Object (not JavaPeerProxy), keeping GetCustomAttribute<JavaPeerProxy>() as a zero-cost filter for non-alias entries.
  • Good test coverage: model builder tests, assembly generator tests, and fixture-based alias tests all verify the new codegen.
  • The self-application fix (using MethodDefinitionHandle instead of MemberReferenceHandle for the proxy's own custom attribute) is a nice improvement.
  • Thorough OnRegisterNatives update to register natives for ALL proxies in an alias group, not just the first.

Note on scope

This PR also includes significant TestRunner infrastructure changes (removing DryRun, NoExclusions, changing filter/count mechanics) that appear orthogonal to the trimmable typemap alias feature. Consider splitting those into a separate PR for cleaner review history.

Generated by Android PR Reviewer for issue #11122 · ● 21.1M

… alias matching

- EmitAliasHolderType: capture TypeDefinitionHandle from AddTypeDefinition
  and pass it explicitly to EmitJavaPeerAliasesAttribute instead of
  re-deriving via GetRowCount(TableIndex.TypeDef)
- GetProxyForJniClass/GetProxyFromAliases: use TargetTypeMatches() to
  handle open generic → closed generic matching

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/11103-trimmable-typemap-aliases branch from d9bbcac to f4a3a69 Compare April 21, 2026 15:17
@simonrozsival
Copy link
Copy Markdown
Member Author

Test failure unrelated (Mono.Android.NET_Tests-Interpreter)

@simonrozsival simonrozsival merged commit e7f34cc into main Apr 21, 2026
2 of 3 checks passed
@simonrozsival simonrozsival deleted the dev/simonrozsival/11103-trimmable-typemap-aliases branch April 21, 2026 17:40
Copy link
Copy Markdown
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

👍

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

Labels

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.

[TrimmableTypeMap] Implement alias support in codegen and runtime

3 participants