Repository navigation
Description
Description
When a type X is both:
- The proxy target (arg[1]) of a
TypeMapAssociationAttribute<Group> - The trimTarget (arg[2]) of a
TypeMapAttribute<Group>
...the TypeMapAttribute entry is incorrectly trimmed.
Root Cause
TypeMapHandler.MarkTypeMapAttribute calls Annotations.MarkInstantiated(targetTypeDef) directly when marking a TypeMapAssociationAttribute. When the proxy association for type X is marked first (because its source type is instantiated before X), MarkInstantiated(X) is called.
Later, when newobj X is encountered, MarkStep.MarkRequirementsForInstantiatedTypes(X) has an early return:
protected virtual void MarkRequirementsForInstantiatedTypes(TypeDefinition type)
{
if (Annotations.IsInstantiated(type))
return; // Early return — ProcessType never called!
...
_typeMapHandler.ProcessType(type);
}Since IsInstantiated(X) is already true (set by the proxy association), ProcessType(X) is never called. This means X's own TypeMapAttribute entries remain in _unmarkedExternalTypeMapEntries and are silently trimmed.
Conditions
The bug requires:
- A type used as both proxy target and trim target in the same TypeMap universe
- Both external and proxy maps for that universe are used
- The proxy association's source type is instantiated before the trim target type (order-dependent)
In practice this affects types with generic variants mapping to the same ObjC class (e.g., NSOrderedSet/NSOrderedSet<T>, NSArray/NSArray<T>).
Proposed Fix
In MarkTypeMapAttribute, after calling MarkInstantiated, also call _typeMapHandler.ProcessType(targetTypeDef) so that external TypeMap entries are processed regardless of instantiation order. Alternatively, route through MarkRequirementsForInstantiatedTypes but remove or adjust the early return guard so ProcessType is always called.
Note: PR #127452 fixes a related but different bug (associations dropped when target is only instantiated via TypeMap processing). That fix alone does not resolve this issue because MarkRequirementsForInstantiatedTypes still has the IsInstantiated early return that prevents ProcessType from running.
Reproduction
A failing test has been written (see below). The test adds a type ProxyTargetIsAlsoTrimTarget that is both the proxy target of a TypeMapAssociation and the trimTarget of a TypeMap entry. The source type is instantiated before the trim target to trigger the ordering-dependent bug.
// Assembly-level attributes:
[assembly: TypeMap<UsedTypeMap>("ProxyTargetIsAlsoTrimTarget",
typeof(ProxyTargetIsAlsoTrimTargetTarget), typeof(ProxyTargetIsAlsoTrimTarget))]
[assembly: TypeMapAssociation<UsedTypeMap>(
typeof(ProxyTargetIsAlsoTrimTargetSource), typeof(ProxyTargetIsAlsoTrimTarget))]
// In Main — source instantiated BEFORE trim target:
_ = new ProxyTargetIsAlsoTrimTargetSource();
Console.WriteLine(new ProxyTargetIsAlsoTrimTarget());Expected: Both the TypeMapAttribute and TypeMapAssociationAttribute are kept.
Actual: The TypeMapAttribute is trimmed and ProxyTargetIsAlsoTrimTargetTarget is removed.
Also available as a ready-to-be-cherry-picked commit: rolfbjarne@9827813
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
dotnet-policy-service commentedon Apr 28, 2026
Tagging subscribers to this area: @agocke, @dotnet/illink
See info in area-owners.md if you want to be subscribed.
rolfbjarne commentedon Apr 28, 2026
CC @simonrozsival for this one too, related to the previous one, but not quite the same.
Use unconditional TypeMap entries for skipped actual types
Use unconditional TypeMap entries for skipped actual types
Add test for TypeMap entry retained when type is also a proxy target. F…
Fix illink TypeMap proxy retention. Fixes #127004. (#127005)
Fix illink TypeMap proxy retention. Fixes #127004. (#127005)