Skip to content

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

Open
simonrozsival wants to merge 8 commits intomainfrom
dev/simonrozsival/11103-trimmable-typemap-aliases
Open

[TrimmableTypeMap] Implement alias support in codegen and runtime#11122
simonrozsival wants to merge 8 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
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

2 participants