[Trimming] Use assembly-qualified name in ResourceDesignerAttribute#11439
Conversation
Emit ResourceDesignerAttribute with typeof(Resource) for app resource designers so ResourceIdManager can use the attribute-provided Type instead of Assembly.GetType(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates how the runtime locates the application Resource designer type by introducing a Type-based ResourceDesignerAttribute API and emitting typeof(Resource) in generated designer code, allowing ResourceIdManager to avoid string-based reflection.
Changes:
- Added
ResourceDesignerAttribute(Type resourceType)plusResourceTypeproperty, and updated PublicAPI baselines. - Updated resource designer generation to emit
typeof(global::...Resource)for application designers (while keeping string form for libraries). - Updated
ResourceIdManagerto use the newResourceTypeproperty.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs | Updates comments/documentation for how library designer attributes are discovered. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Expected/GenerateDesignerFileWithLibraryReferenceExpected.cs | Updates expected generated designer output to use typeof(Resource) for application designers. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Expected/GenerateDesignerFileWithElevenStyleableAttributesExpected.cs | Updates expected generated designer output to use typeof(Resource) for application designers. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Expected/GenerateDesignerFileExpected.cs | Updates expected generated designer output to use typeof(Resource) for application designers. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs | Emits typeof(Resource) for application assemblies and a string type name for library assemblies. |
| src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt | Records new ResourceDesignerAttribute(Type) and ResourceType API as unshipped. |
| src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt | Records new ResourceDesignerAttribute(Type) and ResourceType API as unshipped. |
| src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt | Records new ResourceDesignerAttribute(Type) and ResourceType API as unshipped. |
| src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt | Records new ResourceDesignerAttribute(Type) and ResourceType API as unshipped. |
| src/Mono.Android/Android.Runtime/ResourceIdManager.cs | Switches application resource designer lookup from Assembly.GetType(FullName) to ResourceDesignerAttribute.ResourceType. |
| src/Mono.Android/Android.Runtime/ResourceDesignerAttribute.cs | Adds a Type-based constructor and ResourceType property while retaining the string-based constructor. |
Mark the legacy string-based ResourceDesignerAttribute constructor as requiring dynamic code, and emit the Type-based constructor from generated resource designers so internal code avoids the dynamic-code path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the legacy string constructor behavior in ResourceDesignerAttribute, but route it through a dynamic-code-marked constructor and a localized Assembly.GetType fallback. The Type constructor remains the safe path used by generated resource designers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When ResourceDesignerAttribute is constructed with a Type, ignore assemblies other than the resource type's declaring assembly instead of asserting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The legacy string-based ResourceDesignerAttribute path reaches Assembly.GetType(string), which is annotated RequiresUnreferencedCode. Mark the compatibility constructor accordingly while keeping the Type constructor as the safe generated path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only return from ResourceIdManager when ResourceDesignerAttribute resolves a non-null resource type, allowing mismatched Type-based attributes to be skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ResourceDesignerAttribute may now be generated with typeof(Resource). When importing resource designer attributes from metadata, use Type.FullName for Type fixed arguments while preserving legacy string arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Decode ResourceDesignerAttribute type arguments with a local provider so DummyCustomAttributeProvider keeps its existing behavior while resource designer imports still handle typeof(Resource). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the local ResourceDesignerAttributeTypeProvider and read the serialized fixed attribute argument directly so both string and typeof(Resource) constructors are handled without changing the shared dummy provider. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document why reading the first fixed argument as a serialized string works for both string and System.Type ResourceDesignerAttribute constructors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a build-task test proving ResourceDesignerImportGenerator imports library resources when ResourceDesignerAttribute is emitted with typeof(Resource), and remove the experimental provider strategy from ResourceDesignerAttribute. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — Clean trimming improvement
Summary: This PR adds a Type-based constructor to ResourceDesignerAttribute so the resource designer can pass typeof(Resource) instead of a string name, enabling the trimmer to statically track the type reference and eliminating Assembly.GetType() at runtime. The old string-based constructor is preserved with [RequiresUnreferencedCode] for backward compatibility.
What I verified:
- ✅ The strategy pattern (
IResourceTypeProvider) cleanly separates the two constructor paths without code duplication - ✅
DynamicallyAccessedMembersannotations are correctly propagated through the type hierarchy - ✅ The raw blob parsing in
ResourceDesignerImportGeneratorcorrectly handles both old (string) and new (typeof) custom attribute encodings, since both are SerString-encoded in ECMA-335 - ✅
LinkDesignerBase.FindResourceDesigner(Cecil-based, not modified) continues to work because Cecil'sTypeReference.ToString()returns the namespace-qualified name, matching the old string format - ✅
TypeResourceTypeProvider.GetResourceTypeFromAssemblyhas a sensible assembly equality check preventing cross-assembly type mismatches - ✅ New test covers the import generator decoding path for the typeof-based attribute
- ✅ PublicAPI files updated for all API levels
- ✅ CI is green
Issue counts: 0 ❌ errors · 1
Generated by Android PR Reviewer for issue #11439 · ● 3.2M
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Decode System.Type custom attribute arguments through DummyCustomAttributeProvider so ResourceDesignerImportGenerator can use the shared GetCustomAttributeArguments() helper for both typeof(Resource) and string ResourceDesignerAttribute constructors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename the private metadata decoder marker to systemTypeSentinel so it is clear the value is only an identity token for DecodeValue() type checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit c42f89d.
This reverts commit 99877b0.
Document that legacy ResourceDesignerAttribute string values may be namespace-qualified and suppress the expected IL2026 in the AndroidX NativeAOT code-behind test until transitive binding packages rebuild with the Type constructor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — Clean, well-structured trimming improvement
Summary: This PR makes ResourceDesignerAttribute trim-safe by generating assembly-qualified names ("Foo.Resource, Foo") instead of bare type names, enabling Type.GetType() to resolve types without trim-unsafe Assembly.GetType() calls. The fallback path for legacy non-AQN bindings is properly preserved with appropriate trim warning suppressions.
Strengths:
- Clean separation: type resolution logic moved into the attribute itself, simplifying
ResourceIdManager - Backward-compatible: old non-AQN format still works via the fallback path
GetTypeFullNameFromAssemblyQualifiedNameis simple, correct, and reused in bothResourceDesignerImportGeneratorandLinkDesignerBase- Good test coverage: expected files updated, new test verifies AQN round-trip through the import generator
- Well-documented temporary
IL2122suppression with clear plan for removal
Minor suggestions only (3 💡): inconsistent brace style, cache field effectiveness note, and edge-case test idea. No blocking issues found.
| Severity | Count |
|---|---|
| ❌ Error | 0 |
| 0 | |
| 💡 Suggestion | 3 |
Generated by Android PR Reviewer for issue #11439 · ● 11.7M
Refactored FullName property to use auto-implemented property syntax and updated GetResourceTypeFromAssembly method logic.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
GenerateResourceDesignernow emits[assembly: ResourceDesignerAttribute("MyApp.Resource, MyApp", ...)]with an assembly-qualified type name, so the resource designer type can be resolved trim-safely viaType.GetType.ResourceDesignerAttributegains an internalGetResourceTypeFromAssembly(Assembly)helper that:Type.GetType(FullName)when the stored name is assembly-qualified (the new format), and verifies the resolved type lives in the expected assembly.Assembly.GetType(name)for legacy bindings that still ship a non-AQN string. The fallback suppressesIL2026/IL2073because the trimmer already emitsIL2122for the non-AQN value at the call site.ResourceIdManager.GetResourceTypeFromAssemblydelegates to the new attribute helper and drops its own suppressions.ResourceDesignerImportGeneratoraddsGetTypeFullNameFromAssemblyQualifiedNameto strip the, AssemblyNamesuffix when scanning library metadata, so both the new AQN format and existing non-AQN bindings continue to import resource IDs.LinkDesignerBaseuses the same helper when reading the attribute fromCecilduring the trimmer step.Xamarin.Android.Common.targetspassesAssemblyNametoGenerateResourceDesignerso it can build the AQN string.Tests
GenerateDesignerFile*Expected.csbaselines to expect the new"Foo.Foo.Resource, Foo"format.ResourceDesignerImportGeneratorHandlesResourceDesignerAttributeFormatsinManagedResourceParserTests, parameterized over"Library.Resource, Library"and the legacy"Library.Resource", to prove the import generator handles both formats.IL2122to the NativeAOTnoWarnlist inCodeBehindTestsbecause transitive AndroidX/Kotlin binding packages still ship non-AQN resource designer strings (e.g."Xamarin.Kotlin.Resource"); this can be removed once those packages rebuild with this change.Validation
RUNNINGONCI=true ./dotnet-local.sh test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll --filter Name~SuccessfulAndroidXAppManagedResourceParserTests.ResourceDesignerImportGeneratorHandlesResourceDesignerAttributeFormatsPart of #10794.