[TrimmableTypeMap] Skip unresolvable trimmable typemap peers#11751
[TrimmableTypeMap] Skip unresolvable trimmable typemap peers#11751simonrozsival wants to merge 10 commits into
Conversation
Detect Java peer types whose base or interface metadata references a missing type in the resolved assembly set and skip them instead of rooting them into the generated trimmable type map. Log XA4256 with the unresolved type and expected assembly path, and document the warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the trimmable typemap pipeline to tolerate stale/unresolvable binding metadata by skipping Java peers whose base type or implemented interfaces cannot be resolved from the provided assembly set, and surfaces the situation as a new warning (XA4256).
Changes:
- Add XA4256 warning plumbing (logger API + MSBuild task logger) and documentation/resources for the new message.
- Extend typemap inputs to carry an assembly path (
AssemblyInput) so warnings can report the resolved assembly file involved. - Update the typemap scanner/indexing to detect unresolvable base/interface references and skip affected peers rather than failing generation.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs | Updates generator tests for the new AssemblyInput API and adds test-logger support for the new warning. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs | Switches task input wiring to pass AssemblyInput (including assembly path) into the generator and logs XA4256 via resources. |
| src/Xamarin.Android.Build.Tasks/Properties/Resources.resx | Adds the XA4256 localized warning string and parameter documentation. |
| src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs | Regenerates strongly-typed resource accessor for XA4256. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs | Updates generator API to accept IReadOnlyList<AssemblyInput> instead of tuple inputs. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs | Adds resolvability checks for base/interface type references and skips peers while logging XA4256. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs | Tracks assembly path and exported (forwarded) types to improve resolution checks and warning output. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs | Extends logger interface with LogUnresolvableJavaPeerSkippedWarning for XA4256. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs | Introduces AssemblyInput (name, path, PEReader) as the new generator/scanner input shape. |
| Documentation/docs-mobile/messages/xa4256.md | Adds end-user documentation for XA4256 (issue + solution guidance). |
| Documentation/docs-mobile/messages/index.md | Adds XA4256 to the messages index. |
Copilot's findings
Files not reviewed (1)
- src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs: Generated file
- Files reviewed: 10/11 changed files
- Comments generated: 1
Cover Java peers skipped because their base type or implemented interface references a type missing from a resolved assembly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the synthetic assembly paths in the XA4256 regression test platform-neutral. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace legacy new-array expressions in the updated trimmable typemap generator tests with C# collection expressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
⚠️ Needs Changes
Thanks for this — the core idea is solid and the implementation is mostly careful. Skipping peers whose managed base/interface references a type missing from the resolved assembly set (and emitting XA4256 as a warning rather than failing the closed-world NativeAOT build) is the right call, and the metadata-scanning code is generally clean.
What I'd like addressed before merge (details inline):
Warnings (3)
- Patterns / undocumented behavioral change — The new
IsSupportedFrameworkGenericPeergate (lines 273‐278) silently drops all generic-definition framework peers except four allow-listed collection types. This goes beyond "skip unresolvable peers," isn't in the change map, has no dedicated test, and the legacy scanner didn't do it — please confirm intent, document the rationale, add a test, and verifyScannerComparisonTestsparity. - Testing — The new test only exercises direct
TypeRefbase/interface refs. The trickiest new code — the hand-rolled signature-blob parser (IsResolvableSignatureType,GENERICINST/array/generic-param handling) — is untested. Add a case where the generic argument is the unresolvable type. - Performance —
IsResolvableJavaPeerTypere-walks the full base+interface graph with a freshvisitedset per peer; sibling types sharing ancestors re-validate the same types repeatedly. Memoize per(assembly, type)on the scanner instance.
Suggestion (1)
- Readability — Replace the raw
ELEMENT_TYPEbytes in the signature switch withSignatureTypeCode/SignatureTypeKind(or named constants).
Things that look good 👍
AssemblyIndex.ExportedTypeNamescorrectly accounts for type-forwarded types (e.g.System.Objectforwarded fromSystem.Runtime), avoiding false skips.- Nested-type naming is consistent across the
TypesByFullNameandResolveTypeReference/exported-type paths (parent+name,ns.name), so the resolvability check shouldn't false-negative on nested types. - No null-forgiving
!;[NotNullWhen(false)]out-params used correctly; the signature parser'sdefault/empty cases err toward not skipping. - XA4256 is unique, the
Resources.resxentry is grouped/commented correctly, and thexa4256.mddoc + TOC entry are present and consistent. - The retained tuple
Scanoverload is still referenced byJavaPeerScannerTests, so it's not dead code.
CI — Not green yet: Package Tests macOS > Tests > APKs 1 is currently failing and several checks are still in progress (mergeable_state: blocked). This may well be an unrelated flake (APK/device legs are often flaky), but please confirm it's not caused by these changes and get CI to green before merge.
Generated by Android PR Reviewer for issue #11751 · 1.2K AIC · ⌖ 51.8 AIC · ⊞ 37.9K
Comment /review to run again
Remove the framework generic peer allow-list, memoize resolvability checks, use named signature metadata enums, and extend the regression test to cover generic-inst TypeSpec references. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the nested StaleReferenceShape enum public so the public xUnit theory method has a consistent public signature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
🤖 Android PR Review — ⚠️ Needs Changes
Thanks for this — skipping unresolvable trimmable typemap peers and emitting a warning, rather than hard-failing the NativeAOT build on stale cross-version binding metadata, is the right approach. The implementation is solid: the recursive resolvability walk over base types, interfaces, TypeSpecs and generic arguments is memoized (resolvabilityCache), breaks cycles with a per-call visited set, and stays conservative — it only skips when the referenced assembly is in the scanned set but the type is genuinely absent (including type-forwards via the new ExportedTypeNames), so assemblies that simply aren't scanned are still treated as resolvable. The new XA4256 message, docs, and the switch to Ordinal string comparisons are all nice touches.
Blocking
- ❌ The PR doesn't compile.
TrimmableTypeMapGeneratorTests.cs:299hitsCS8506(mismatchedswitch-arm handle types), failingmake jenkins(make: *** [jenkins] Error 2) on the Mac leg — and the same error is already present in the Linux leg's build log. One-line fix inline (annotate the result asEntityHandle). This is the root cause of the current red build.
Suggestion
- 💡 The new theory covers the positive skip paths, but the false-positive guard (referenced assembly not in the scanned set ⇒ must not skip) and the type-forwarding resolution path are untested. Details inline.
CI
- ❌ Mac macOS > Build: failure —
error CS8506in the test project. - ⏳ Linux / Windows Build: still running, but their
make jenkinsstep compiles the same test project and the Linux log already shows the identicalCS8506, so they'll fail the same way until the build break is fixed. - ✅ license/cla.
Once the compile error is fixed and CI goes green, this looks close to mergeable. Severity counts: 1 ❌ error, 1 💡 suggestion.
Generated by Android PR Reviewer for issue #11751 · 1.9K AIC · ⌖ 63.3 AIC · ⊞ 40K
Comment /review to run again
The peerBaseType switch expression mixed TypeReferenceHandle and TypeSpecificationHandle arms, which have no common type (CS8506). Annotate the target as EntityHandle, which both handle types convert to and which is what AddTypeDefinition's baseType parameter expects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The per-scan resolvability cache and the per-peer visited set were keyed by (assembly name, full type name), which rebuilt the full type name string on every type-definition visit including cache hits. Shared ancestors across AndroidX/Material are revalidated for many sibling peers, so this allocated repeatedly on a hot path. Key both by (assembly name, type-definition row) instead. The row is a cheap, stable identifier within an assembly, so no full name is built unless XA4256 is actually logged. Using the handle also removes the generic-name-collapsing ambiguity (e.g. Base<A> vs Base<B>) that a name-based key could introduce in the cycle guard. Also document why the signature parser defaults unrecognized encodings to resolvable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover the two conservative branches of the resolvability walk that keep a Java peer instead of skipping it: - The referenced assembly is not part of the scanned set, so the reference cannot be proven stale (existing behavior preserved). - The referenced type is re-exported via a type-forwarder row and is resolved through AssemblyIndex.ExportedTypeNames. Both assert the peer is kept and no XA4256 is emitted, guarding the resolvability walk (and the new handle-keyed cache) against regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
🤖 Android PR Review — [TrimmableTypeMap] Skip unresolvable trimmable typemap peers
Verdict: ✅ LGTM (pending CI) — submitted as a comment, not an approval.
This is a well-engineered, well-tested change. The new resolvability guard walks the peer's base type, interfaces, and their TypeSpec/generic arguments, skips peers that reference types missing from the loaded assemblies, and emits XA4256 instead of letting NativeAOT fail later.
I independently verified the trickiest part — the interaction between the per-call visited cycle-guard and the shared resolvabilityCache:
falseresults only ever come from a genuinely-missingTypeReference(a global fact), so caching them is always safe.trueresults may include tautological cycle-break edges (class Foo : Bar<Foo>), but since valid base/interface graphs are acyclic those back-edges are always generic arguments, so the memoized value stays correct.- The cache-before-
visitedordering and keying by(AssemblyName, TypeRow)are both sound and keep type-name building off the hot path.
XA4256 is a brand-new code and is consistent across Resources.resx, Resources.Designer.cs, xa4256.md, and the docs TOC. Type-forwarding handling (ExportedTypeNames, StringComparer.Ordinal) matches TypesByFullName's comparer. The new Xunit tests cover the base/interface/generic-arg skip shapes plus the important negative cases (assembly-not-scanned, type-forwarded base).
Findings
| Severity | Count |
|---|---|
| ❌ error | 0 |
| 0 | |
| 💡 suggestion | 2 |
- 💡 Performance — reuse the per-walk
visitedHashSetinstead of allocating one per candidate peer (inline). - 💡 Patterns — question: are generic-parameter constraints intentionally excluded from the resolvability walk? (inline)
Both are optional; neither blocks merge.
Notes
- Earlier review rounds — missing skip test, the framework-generic allow-list, magic-byte constants, per-scan memoization, the
EntityHandle/CS8506 fix, and the not-scanned/forwarded test cases — all appear fully addressed. Nice iteration. - ⏳ At review time, CI (Azure DevOps) was still in progress with no failures. This sign-off is contingent on those checks going green — please don't merge on a red
Xamarin.Android-PR.
Generated by Android PR Reviewer for issue #11751 · 1.6K AIC · ⌖ 48.1 AIC · ⊞ 40K
Comment /review to run again
Address two optional review suggestions: - Generic-definition peers are rooted in the type map (the emitter ldtokens the open-generic target), so a generic-parameter constraint that references a type missing from the resolved assembly set would also fail to resolve at NativeAOT time. Walk constraints alongside the base type and implemented interfaces, and skip the peer with XA4256. Adds a GenericConstraint shape to the skip theory. - Reuse a single 'visited' HashSet across peers (cleared per call) instead of allocating one per candidate peer. The per-scan resolvability cache already memoizes results, so the set only needs to break per-walk cycles. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Goal
Extract the scanner/diagnostics slice from #11617 so the trimmable typemap generator handles stale binding metadata before it reaches NativeAOT.
The trimmable typemap scanner can encounter Java peer types whose managed base type or implemented interface references a type that is missing from the resolved assembly set. This commonly happens when Android binding packages were built against different versions of another binding package. NativeAOT resolves rooted metadata eagerly, so rooting one of these unused stale peers into the generated typemap can fail the app build even when the app never uses the type.
This PR skips those unresolvable peers and reports XA4256 with enough information to identify the skipped type and stale reference.
Change map
AssemblyInputso the scanner keeps the source path for each resolved assembly. The path is used in XA4256 diagnostics.TypeReflookup as a hard missing type when metadata declares it as exported.GenerateTrimmableTypeMap, including skipped peer name, peer assembly, unresolved type, unresolved assembly, and expected assembly path.AssemblyInput.Current design notes
What counts as unresolvable
The scanner only warns when a referenced assembly is present in the resolved assembly set but the referenced type cannot be found in that assembly's type definitions or exported types. If the referenced assembly itself is not present in the scanner cache, the existing behavior is preserved and the reference is ignored for this check.
What is checked
For each candidate Java peer, the scanner walks:
TypeSpecsignatures, including arrays and generic instantiations.If any of those metadata references resolves to a loaded assembly but not to a type/exported type in that assembly, the peer is skipped and XA4256 is logged.
Why skip instead of fail
These peers are often stale binding metadata from packages that are otherwise compatible for the app's actual usage. Skipping the peer keeps unused stale metadata out of the closed-world NativeAOT typemap while still surfacing a warning for projects that do use the skipped type.
Example warning
Review notes / intentional non-goals
Test strategy
This PR updates host generator/scanner coverage for the new
AssemblyInputpath-aware scanner API, XA4256 logger plumbing, and Java peers skipped because unresolved metadata appears through a direct base type, implemented interface, or genericTypeSpecargument.Local validation
dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj --no-restore --nologo