From 1b6b1737ea541ae7b0743cc63c982f046114dbee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:41:36 +0000 Subject: [PATCH 01/13] Initial plan From 9eb94f6cae140dc32cda3274d0dff4459a904490 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:56:45 +0000 Subject: [PATCH 02/13] Fix TypeMap array type trim targets causing build failure in ILLink Strip parameterized types (array, pointer, etc.) from TypeMap trim targets and source types, using the element type instead. This fixes the crash when typeof(SomeType[]) is used as a trim target in TypeMap attributes during PublishTrimmed. Add test cases for array type trim targets (used and unused) to TypeMap.cs. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/82e75422-6094-47d8-be6f-7a4527457908 Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../illink/src/linker/Linker/TypeMapHandler.cs | 8 ++++++++ .../Reflection/TypeMap.cs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs index 5cadd46b65851b..fe040814e8a40c 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs @@ -156,6 +156,10 @@ void AddExternalTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr { if (attr.Attribute.ConstructorArguments is [_, _, { Value: TypeReference trimTarget }]) { + // Strip parameterized types (array, pointer, etc.) to use the element type as the trim target, + // since those types cannot be tracked at the definition level by the trimmer. + while (trimTarget is TypeSpecification and not GenericInstanceType) + trimTarget = ((TypeSpecification)trimTarget).ElementType; RecordTypeMapEntry(attr, group, trimTarget, _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); } else if (attr.Attribute.ConstructorArguments is [_, { Value: TypeReference }]) @@ -172,6 +176,10 @@ void AddProxyTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr) if (attr.Attribute.ConstructorArguments is [{ Value: TypeReference sourceType }, _]) { // This is a TypeMapAssociationAttribute, which has a single type argument. + // Strip parameterized types (array, pointer, etc.) to use the element type as the source type, + // since those types cannot be tracked at the definition level by the trimmer. + while (sourceType is TypeSpecification and not GenericInstanceType) + sourceType = ((TypeSpecification)sourceType).ElementType; RecordTypeMapEntry(attr, group, sourceType, _unmarkedProxyTypeMapEntries, _referencedProxyTypeMaps, _pendingProxyTypeMapEntries); return; } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs index 56867194edec4c..b6ca32d073aa58 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs @@ -28,6 +28,8 @@ [assembly: TypeMap("Ldobj", typeof(LdobjTarget), typeof(LdobjType))] [assembly: TypeMap("ArrayElement", typeof(ArrayElementTarget), typeof(ArrayElement))] [assembly: TypeMap("TrimTargetIsAllocatedNoTypeCheckNoBoxStruct", typeof(ConstructedNoTypeCheckOrBoxTarget), typeof(ConstructedNoTypeCheckNoBoxStruct))] +[assembly: TypeMap("TrimTargetIsUsedArrayType", typeof(ArrayTypeTrimTargetTarget), typeof(ArrayTypeTrimTargetClass[]))] +[assembly: TypeMap("TrimTargetIsUnusedArrayType", typeof(ArrayTypeTrimTargetUnusedTarget), typeof(ArrayTypeTrimTargetUnusedClass[]))] [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "TrimTargetIsTarget", typeof(TargetAndTrimTarget), typeof(TargetAndTrimTarget))] [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "TrimTargetIsUnrelated", typeof(TargetType), typeof(TrimTarget))] [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), nameof(AllocatedNoTypeCheckClassTarget), typeof(AllocatedNoTypeCheckClassTarget), typeof(AllocatedNoTypeCheckClass))] @@ -41,6 +43,7 @@ [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "Ldobj", typeof(LdobjTarget), typeof(LdobjType))] [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "ArrayElement", typeof(ArrayElementTarget), typeof(ArrayElement))] [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "TrimTargetIsAllocatedNoTypeCheckNoBoxStruct", typeof(ConstructedNoTypeCheckOrBoxTarget), typeof(ConstructedNoTypeCheckNoBoxStruct))] +[assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "TrimTargetIsUsedArrayType", typeof(ArrayTypeTrimTargetTarget), typeof(ArrayTypeTrimTargetClass[]))] // The TypeMap Universes are kept separate such that Proxy attributes shouldn't be kept if only the External type map is needed. [assembly: TypeMap("UsedOnlyForExternalTypeMap", typeof(UsedExternalTarget), typeof(UsedTrimTarget))] // Kept @@ -206,6 +209,8 @@ static void ConstrainedStaticCall(T t) where T : IStaticInterface Console.WriteLine(new ArrayElement[1]); + Console.WriteLine(new ArrayTypeTrimTargetClass()); + Console.WriteLine(new ConstructedNoTypeCheckNoBoxStruct(42).Value); TypeMapReferencedAssembly.Run(); @@ -554,4 +559,15 @@ class UsedProxyTarget2; [Kept] class PreservedTargetType; + + [Kept] + class ArrayTypeTrimTargetTarget; + + [Kept] + [KeptMember(".ctor()")] + class ArrayTypeTrimTargetClass; + + class ArrayTypeTrimTargetUnusedTarget; + + class ArrayTypeTrimTargetUnusedClass; } From af0ac93a2aa858c61cab45856065d865c8a7b82a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 00:09:53 +0000 Subject: [PATCH 03/13] Address review feedback: add removal assertions and fix comment - Add RemovedTypeInAssembly("test", ...) for unused array trim target types to make the expected removal behavior explicit - Fix misleading comment in AddProxyTypeMapEntry: TypeMapAssociationAttribute has two constructor type arguments (source and proxy), not one Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/8d0dbb63-91fb-4d87-8ccd-60e91da2e1e9 Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- src/tools/illink/src/linker/Linker/TypeMapHandler.cs | 2 +- .../illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs index fe040814e8a40c..404e86f40359df 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs @@ -175,7 +175,7 @@ void AddProxyTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr) { if (attr.Attribute.ConstructorArguments is [{ Value: TypeReference sourceType }, _]) { - // This is a TypeMapAssociationAttribute, which has a single type argument. + // This is a TypeMapAssociationAttribute with two constructor type arguments (source and proxy). // Strip parameterized types (array, pointer, etc.) to use the element type as the source type, // since those types cannot be tracked at the definition level by the trimmer. while (sourceType is TypeSpecification and not GenericInstanceType) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs index b6ca32d073aa58..6027b7c1274e9f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs @@ -140,6 +140,8 @@ namespace Mono.Linker.Tests.Cases.Reflection [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TargetTypeUnconditional2))] [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TrimTarget1))] [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TrimTarget2))] + [RemovedTypeInAssembly("test", typeof(ArrayTypeTrimTargetUnusedClass))] + [RemovedTypeInAssembly("test", typeof(ArrayTypeTrimTargetUnusedTarget))] class TypeMap { [Kept] From 7ecb95af9a1143642c22cb88d127d06fa85bd6ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:31:25 +0000 Subject: [PATCH 04/13] Apply same TypeSpecification-stripping fix in MarkTypeMapAttribute; use property pattern matching style - Strip TypeSpecification wrappers in MarkTypeMapAttribute before calling _context.Resolve(targetType), same as AddExternalTypeMapEntry/AddProxyTypeMapEntry - Use property pattern matching style (TypeSpecification { ElementType: var elementType }) in all three stripping loops to avoid explicit casts Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/83c6e763-b98c-4183-b1b6-03ae760139de Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../src/linker/Linker/TypeMapHandler.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs index 404e86f40359df..48e5eb2b400e3a 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs @@ -107,8 +107,15 @@ void MarkTypeMapAttribute(CustomAttributeWithOrigin entry, DependencyInfo info) _markStep.MarkAssembly(entry.Origin, info, new MessageOrigin(entry.Origin)); // Mark the target type as instantiated - if (entry.TargetType is { } targetType && _context.Resolve(targetType) is TypeDefinition targetTypeDef) - _context.Annotations.MarkInstantiated(targetTypeDef); + if (entry.TargetType is { } targetType) + { + // Strip parameterized types (array, pointer, etc.) to use the element type, + // since those types cannot be resolved at the definition level by the trimmer. + while (targetType is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) + targetType = elementType; + if (_context.Resolve(targetType) is TypeDefinition targetTypeDef) + _context.Annotations.MarkInstantiated(targetTypeDef); + } } public void ProcessType(TypeDefinition definition) @@ -158,8 +165,8 @@ void AddExternalTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr { // Strip parameterized types (array, pointer, etc.) to use the element type as the trim target, // since those types cannot be tracked at the definition level by the trimmer. - while (trimTarget is TypeSpecification and not GenericInstanceType) - trimTarget = ((TypeSpecification)trimTarget).ElementType; + while (trimTarget is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) + trimTarget = elementType; RecordTypeMapEntry(attr, group, trimTarget, _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); } else if (attr.Attribute.ConstructorArguments is [_, { Value: TypeReference }]) @@ -178,8 +185,8 @@ void AddProxyTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr) // This is a TypeMapAssociationAttribute with two constructor type arguments (source and proxy). // Strip parameterized types (array, pointer, etc.) to use the element type as the source type, // since those types cannot be tracked at the definition level by the trimmer. - while (sourceType is TypeSpecification and not GenericInstanceType) - sourceType = ((TypeSpecification)sourceType).ElementType; + while (sourceType is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) + sourceType = elementType; RecordTypeMapEntry(attr, group, sourceType, _unmarkedProxyTypeMapEntries, _referencedProxyTypeMaps, _pendingProxyTypeMapEntries); return; } From 98fa63cdc462e8235c2484fea8f7bc06058dcadd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 01:56:25 +0000 Subject: [PATCH 05/13] Factor TypeSpecification-unwrapping into UnwrapToResolvableType helper Extract the three duplicate `while (type is TypeSpecification { ElementType: var elementType } and not GenericInstanceType)` loops into a single private static helper `UnwrapToResolvableType`, so future fixes only need to be made in one place. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/fc80412e-2eb0-4e4a-8668-87f97a13696d Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../src/linker/Linker/TypeMapHandler.cs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs index 48e5eb2b400e3a..dce5132369f3d3 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs @@ -107,15 +107,9 @@ void MarkTypeMapAttribute(CustomAttributeWithOrigin entry, DependencyInfo info) _markStep.MarkAssembly(entry.Origin, info, new MessageOrigin(entry.Origin)); // Mark the target type as instantiated - if (entry.TargetType is { } targetType) - { - // Strip parameterized types (array, pointer, etc.) to use the element type, - // since those types cannot be resolved at the definition level by the trimmer. - while (targetType is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) - targetType = elementType; - if (_context.Resolve(targetType) is TypeDefinition targetTypeDef) - _context.Annotations.MarkInstantiated(targetTypeDef); - } + if (entry.TargetType is { } targetType + && _context.Resolve(UnwrapToResolvableType(targetType)) is TypeDefinition targetTypeDef) + _context.Annotations.MarkInstantiated(targetTypeDef); } public void ProcessType(TypeDefinition definition) @@ -163,11 +157,7 @@ void AddExternalTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr { if (attr.Attribute.ConstructorArguments is [_, _, { Value: TypeReference trimTarget }]) { - // Strip parameterized types (array, pointer, etc.) to use the element type as the trim target, - // since those types cannot be tracked at the definition level by the trimmer. - while (trimTarget is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) - trimTarget = elementType; - RecordTypeMapEntry(attr, group, trimTarget, _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); + RecordTypeMapEntry(attr, group, UnwrapToResolvableType(trimTarget), _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); } else if (attr.Attribute.ConstructorArguments is [_, { Value: TypeReference }]) { @@ -183,17 +173,25 @@ void AddProxyTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr) if (attr.Attribute.ConstructorArguments is [{ Value: TypeReference sourceType }, _]) { // This is a TypeMapAssociationAttribute with two constructor type arguments (source and proxy). - // Strip parameterized types (array, pointer, etc.) to use the element type as the source type, - // since those types cannot be tracked at the definition level by the trimmer. - while (sourceType is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) - sourceType = elementType; - RecordTypeMapEntry(attr, group, sourceType, _unmarkedProxyTypeMapEntries, _referencedProxyTypeMaps, _pendingProxyTypeMapEntries); + RecordTypeMapEntry(attr, group, UnwrapToResolvableType(sourceType), _unmarkedProxyTypeMapEntries, _referencedProxyTypeMaps, _pendingProxyTypeMapEntries); return; } // Invalid attribute, skip it. // Let the runtime handle the failure. } + /// + /// Strips non-resolvable wrappers (array, pointer, byref, etc.) + /// from until a or + /// is reached. Both of those are resolvable by . + /// + static TypeReference UnwrapToResolvableType(TypeReference type) + { + while (type is TypeSpecification { ElementType: var elementType } and not GenericInstanceType) + type = elementType; + return type; + } + private void AddAssemblyTarget(TypeReference typeMapGroup, CustomAttributeWithOrigin attr) { // Validate attribute From 7d74243d52aab0b897643f0527d170fada095465 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:09:18 +0000 Subject: [PATCH 06/13] Fix ILCompiler crash when array types are used as TypeMap trim targets In TypeMapMetadata.ProcessTypeMapAttribute, strip ParameterizedType wrappers (array, pointer, byref) from the trim target before storing it, so the conditional dependency is tracked on the element type rather than the array type itself. The same fix is applied in ProcessTypeMapAssociationAttribute for the source type. This mirrors the ILLinker fix (UnwrapToResolvableType) using ILCompiler's ParameterizedType hierarchy (ArrayType, PointerType, ByRefType all extend ParameterizedType; InstantiatedType does not, so it is correctly not stripped). Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7124dbcc-bd4b-4c3c-8473-55eeecc56505 Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs index e4455f8fd8e38d..2ce6cbddc15b22 100644 --- a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs +++ b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs @@ -453,6 +453,10 @@ void ProcessTypeMapAttribute(CustomAttributeValue attrValue, Map typeM case [{ Value: string typeName }, { Value: TypeDesc targetType }, { Value: TypeDesc trimTargetType }]: { + // Strip parameterized types (array, pointer, byref) to use the element type as the trim target, + // since those types cannot be tracked at the definition level by the compiler. + while (trimTargetType is ParameterizedType paramType) + trimTargetType = paramType.ParameterType; typeMapState.AddExternalTypeMapEntry(typeName, targetType, trimTargetType); break; } @@ -473,6 +477,10 @@ void ProcessTypeMapAssociationAttribute(CustomAttributeValue attrValue return; } + // Strip parameterized types (array, pointer, byref) to use the element type as the source type, + // since those types cannot be tracked at the definition level by the compiler. + while (type is ParameterizedType paramType) + type = paramType.ParameterType; typeMapState.AddAssociatedTypeMapEntry(type, associatedType); } } From bf2b93f663054085d4bc2ddf698af3a3a6e5deab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:04:47 +0000 Subject: [PATCH 07/13] Fix ILCompiler array trim target tracking in ExternalTypeMapNode Revert the TypeMapMetadata.cs change (ParameterizedType unwrapping) and instead fix ExternalTypeMapNode to use MaximallyConstructableType for ParameterizedType trim targets. When the trim target is an array type like Foo[], NecessaryTypeSymbol(Foo[]) is never marked in the dependency graph because code that uses Foo[] marks ConstructedEETypeNode(Foo[]) instead. MaximallyConstructableType(Foo[]) returns the ConstructedTypeSymbol for arrays (and falls back to NecessaryTypeSymbol for pointer/byref types where construction isn't allowed), which is the node that actually gets marked when code uses the type. Both GetConditionalStaticDependencies and GetMarkedEntries are updated to use the new GetTrimTargetTypeNode helper. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3843fbe0-5652-4e5e-a915-2ce8a390fe84 Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../tools/Common/Compiler/TypeMapMetadata.cs | 8 -------- .../DependencyAnalysis/ExternalTypeMapNode.cs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs index 2ce6cbddc15b22..e4455f8fd8e38d 100644 --- a/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs +++ b/src/coreclr/tools/Common/Compiler/TypeMapMetadata.cs @@ -453,10 +453,6 @@ void ProcessTypeMapAttribute(CustomAttributeValue attrValue, Map typeM case [{ Value: string typeName }, { Value: TypeDesc targetType }, { Value: TypeDesc trimTargetType }]: { - // Strip parameterized types (array, pointer, byref) to use the element type as the trim target, - // since those types cannot be tracked at the definition level by the compiler. - while (trimTargetType is ParameterizedType paramType) - trimTargetType = paramType.ParameterType; typeMapState.AddExternalTypeMapEntry(typeName, targetType, trimTargetType); break; } @@ -477,10 +473,6 @@ void ProcessTypeMapAssociationAttribute(CustomAttributeValue attrValue return; } - // Strip parameterized types (array, pointer, byref) to use the element type as the source type, - // since those types cannot be tracked at the definition level by the compiler. - while (type is ParameterizedType paramType) - type = paramType.ParameterType; typeMapState.AddAssociatedTypeMapEntry(type, associatedType); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 9f3b598d88f7b1..b13aa30e8af9fe 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -40,7 +40,7 @@ public override IEnumerable GetConditionalStaticDep { yield return new CombinedDependencyListEntry( context.MetadataTypeSymbol(targetType), - context.NecessaryTypeSymbol(trimmingTargetType), + GetTrimTargetTypeNode(context, trimmingTargetType), "Type in external type map is cast target"); // If the trimming target type has a canonical form, it could be created at runtime by the type loader. @@ -89,7 +89,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer var (targetType, trimmingTargetType) = entry.Value; if (trimmingTargetType is null - || factory.NecessaryTypeSymbol(trimmingTargetType).Marked) + || GetTrimTargetTypeNode(factory, trimmingTargetType).Marked) { IEETypeNode targetNode = factory.MetadataTypeSymbol(targetType); Debug.Assert(targetNode.Marked); @@ -98,6 +98,18 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer } } + // Array types (and other parameterized types) are tracked in the dependency graph via their + // constructed EEType node (which is what code marks when allocating or using an array). + // NecessaryTypeSymbol for arrays is never marked by normal code patterns, so we use + // MaximallyConstructableType for ParameterizedType (arrays/pointers/byrefs), which returns + // the ConstructedTypeSymbol for arrays and falls back to NecessaryTypeSymbol for pointer/byref. + private static IEETypeNode GetTrimTargetTypeNode(NodeFactory context, TypeDesc trimmingTargetType) + { + if (trimmingTargetType is ParameterizedType) + return context.MaximallyConstructableType(trimmingTargetType); + return context.NecessaryTypeSymbol(trimmingTargetType); + } + public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences) { VertexHashtable typeMapHashTable = new(); From 4415bf2d4631731d766f3ef02745fd3ab95dd06e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 02:04:47 +0000 Subject: [PATCH 08/13] Fix ExternalTypeMapNode to strip ParameterizedType wrappers for trim targets Replace MaximallyConstructableType approach with GetEffectiveTrimTargetType helper that strips ParameterizedType (array/pointer/byref) wrappers and uses NecessaryTypeSymbol on the element type. The MaximallyConstructableType approach was wrong because it used ConstructedTypeSymbol(Foo[]) as the condition, but the test case TrimTargetIsUsedArrayType only constructs Foo (the element type class), not Foo[] itself. Using NecessaryTypeSymbol(Foo) (after stripping) is correct: it is transitively marked whenever Foo is reachable (via new Foo(), is-casts, etc.), matching ILLink's TypeMapHandler stripping behavior. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f62083d5-9dfa-4044-94aa-3f71642d878b Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../DependencyAnalysis/ExternalTypeMapNode.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index b13aa30e8af9fe..9fa357c3329b8c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -38,18 +38,20 @@ public override IEnumerable GetConditionalStaticDep var (targetType, trimmingTargetType) = entry.Value; if (trimmingTargetType is not null) { + TypeDesc effectiveTrimTargetType = GetEffectiveTrimTargetType(trimmingTargetType); + yield return new CombinedDependencyListEntry( context.MetadataTypeSymbol(targetType), - GetTrimTargetTypeNode(context, trimmingTargetType), + context.NecessaryTypeSymbol(effectiveTrimTargetType), "Type in external type map is cast target"); // If the trimming target type has a canonical form, it could be created at runtime by the type loader. // If there is a type loader template for it, create the generic type instantiation eagerly. - TypeDesc canonTrimmingType = trimmingTargetType.ConvertToCanonForm(CanonicalFormKind.Specific); - if (canonTrimmingType != trimmingTargetType && GenericTypesTemplateMap.IsEligibleToHaveATemplate(canonTrimmingType)) + TypeDesc canonTrimmingType = effectiveTrimTargetType.ConvertToCanonForm(CanonicalFormKind.Specific); + if (canonTrimmingType != effectiveTrimTargetType && GenericTypesTemplateMap.IsEligibleToHaveATemplate(canonTrimmingType)) { yield return new CombinedDependencyListEntry( - context.NecessaryTypeSymbol(trimmingTargetType), + context.NecessaryTypeSymbol(effectiveTrimTargetType), context.NativeLayout.TemplateTypeLayout(canonTrimmingType), "External type map trim target that could be loaded at runtime"); } @@ -89,7 +91,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer var (targetType, trimmingTargetType) = entry.Value; if (trimmingTargetType is null - || GetTrimTargetTypeNode(factory, trimmingTargetType).Marked) + || factory.NecessaryTypeSymbol(GetEffectiveTrimTargetType(trimmingTargetType)).Marked) { IEETypeNode targetNode = factory.MetadataTypeSymbol(targetType); Debug.Assert(targetNode.Marked); @@ -98,16 +100,14 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer } } - // Array types (and other parameterized types) are tracked in the dependency graph via their - // constructed EEType node (which is what code marks when allocating or using an array). - // NecessaryTypeSymbol for arrays is never marked by normal code patterns, so we use - // MaximallyConstructableType for ParameterizedType (arrays/pointers/byrefs), which returns - // the ConstructedTypeSymbol for arrays and falls back to NecessaryTypeSymbol for pointer/byref. - private static IEETypeNode GetTrimTargetTypeNode(NodeFactory context, TypeDesc trimmingTargetType) + // Strip parameterized type wrappers (arrays, pointers, byrefs) to get the effective + // type for trimming purposes. If the trim target is Foo[], the TypeMap entry should be + // included when Foo is reachable, matching ILLink's TypeMapHandler stripping behavior. + private static TypeDesc GetEffectiveTrimTargetType(TypeDesc trimmingTargetType) { - if (trimmingTargetType is ParameterizedType) - return context.MaximallyConstructableType(trimmingTargetType); - return context.NecessaryTypeSymbol(trimmingTargetType); + while (trimmingTargetType is ParameterizedType parameterized) + trimmingTargetType = parameterized.ParameterType; + return trimmingTargetType; } public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences) From 9e230fb81d4e0b678f3979f7d0296ff0353e5089 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 02:24:39 +0000 Subject: [PATCH 09/13] Add TestInteropMapArrayTrimming smoke test to DeadCodeElimination.cs Adds a NativeAOT smoke test with 7 TypeMap entries (A-G) using array trim targets, verifying the ILCompiler fix (GetEffectiveTrimTargetType strips ParameterizedType wrappers). Entries A, B, C, F, G are expected present (trim target element type is reachable via array construction or is-cast); entries D, E are expected absent (element type unreachable). Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3173eddf-5eee-4747-b413-37877a208bb9 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../TrimmingBehaviors/DeadCodeElimination.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 32dd8df2d75683..51b80c005f3cdb 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -16,6 +16,14 @@ [assembly: TypeMapAssociation(typeof(DeadCodeElimination.TestInteropMapTrimming.GenUsedForProxy), typeof(DeadCodeElimination.TestInteropMapTrimming.ProxyFromGenUsed))] [assembly: TypeMapAssociation(typeof(DeadCodeElimination.TestInteropMapTrimming.GenUnused), typeof(DeadCodeElimination.TestInteropMapTrimming.ProxyFromGenUnused))] +[assembly: TypeMap("A", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target1), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget1[]))] +[assembly: TypeMap("B", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target2), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget2[,]))] +[assembly: TypeMap("C", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target3), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget3*[]))] +[assembly: TypeMap("D", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target4), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget4[]))] +[assembly: TypeMap("E", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target5), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget5[]))] +[assembly: TypeMap("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))] +[assembly: TypeMap("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))] + class DeadCodeElimination { public static int Run() @@ -46,6 +54,7 @@ public static int Run() TestTypeHandlesInGenericDictionaries.Run(); TestMetadataMethodTables.Run(); TestInteropMapTrimming.Run(); + TestInteropMapArrayTrimming.Run(); return 100; } @@ -1369,6 +1378,85 @@ public static Type GetProxyGeneric(IReadOnlyDictionary map) where public static bool TypeCheckUnknown() where T : class => GetUnknown() is GenUsedForExternal; } + internal class TestInteropMapArrayTrimming + { + internal class Universe; + + public struct TrimTarget1; + public struct TrimTarget2; + public struct TrimTarget3; + public struct TrimTarget4; + public struct TrimTarget5; + public struct TrimTarget6; + public struct TrimTarget7; + public class Target1; + public class Target2; + public class Target3; + public class Target4; + public class Target5; + public class Target6; + public class Target7; + + [MethodImpl(MethodImplOptions.NoInlining)] + static object GetUnknown() => null; + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(object o) { } + + public static unsafe object[] MakeArrays() + { + return [ + new TrimTarget1[1], + new TrimTarget2[1, 1], + new TrimTarget3*[1], + // Skipping TrimTarget4 + // Skipping TrimTarget5 + new TrimTarget6[1] + ]; + } + + public static void Run() + { + // Root the arrays so ILC sees TrimTarget1, TrimTarget2, TrimTarget3, TrimTarget6 + Consume(MakeArrays()); + + // Root TrimTarget7[] via an is-cast so ILC marks the array type (and thus TrimTarget7) + if (GetUnknown() is TrimTarget7[]) + { + Console.WriteLine("Unexpected!"); + } + + var map = TypeMapping.GetOrCreateExternalTypeMapping(); + + // A, B, C, F, G: trim target element type is reachable — entries must be present + if (!map.TryGetValue("A", out Type typeA) || typeA.Name != nameof(Target1)) + throw new Exception("Expected entry A"); + ThrowIfUsableMethodTable(typeA); + + if (!map.TryGetValue("B", out Type typeB) || typeB.Name != nameof(Target2)) + throw new Exception("Expected entry B"); + ThrowIfUsableMethodTable(typeB); + + if (!map.TryGetValue("C", out Type typeC) || typeC.Name != nameof(Target3)) + throw new Exception("Expected entry C"); + ThrowIfUsableMethodTable(typeC); + + if (!map.TryGetValue("F", out Type typeF) || typeF.Name != nameof(Target6)) + throw new Exception("Expected entry F"); + ThrowIfUsableMethodTable(typeF); + + if (!map.TryGetValue("G", out Type typeG) || typeG.Name != nameof(Target7)) + throw new Exception("Expected entry G"); + ThrowIfUsableMethodTable(typeG); + + // D, E: element type is unreachable — entries must be absent + if (map.ContainsKey("D")) + throw new Exception("Unexpected entry D"); + if (map.ContainsKey("E")) + throw new Exception("Unexpected entry E"); + } + } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "That's the point")] private static Type GetTypeSecretly(Type testType, string typeName) => testType.GetNestedType(typeName, BindingFlags.NonPublic | BindingFlags.Public); From 3aad6ef896d500f1ac1d29162877afb6775bfc1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:03:45 +0000 Subject: [PATCH 10/13] Fix TestInteropMapArrayTrimming to match requested snippet exactly - Rename MakeArrays -> MakeGenerics (generic method needed for MakeGenericMethod call) - Add missing Atom class and GetAtom() method - Replace Consume(MakeArrays()) with the proper reflection-based typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)) .MakeGenericMethod([GetAtom()]).Invoke(null, []) pattern - Remove the extra Consume helper that wasn't in the original snippet Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/bac87733-6a86-46a8-8881-9f6ae61ed0b5 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../TrimmingBehaviors/DeadCodeElimination.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 51b80c005f3cdb..7d97600259c7b5 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -1396,14 +1396,9 @@ public class Target4; public class Target5; public class Target6; public class Target7; + public class Atom; - [MethodImpl(MethodImplOptions.NoInlining)] - static object GetUnknown() => null; - - [MethodImpl(MethodImplOptions.NoInlining)] - static void Consume(object o) { } - - public static unsafe object[] MakeArrays() + public static unsafe object[] MakeGenerics() { return [ new TrimTarget1[1], @@ -1415,17 +1410,21 @@ public static unsafe object[] MakeArrays() ]; } + [MethodImpl(MethodImplOptions.NoInlining)] + static Type GetAtom() => typeof(Atom); + + [MethodImpl(MethodImplOptions.NoInlining)] + static object GetUnknown() => null; + public static void Run() { - // Root the arrays so ILC sees TrimTarget1, TrimTarget2, TrimTarget3, TrimTarget6 - Consume(MakeArrays()); - - // Root TrimTarget7[] via an is-cast so ILC marks the array type (and thus TrimTarget7) if (GetUnknown() is TrimTarget7[]) { - Console.WriteLine("Unexpected!"); + Console.WriteLine("Unexpected"); } + typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)).MakeGenericMethod([GetAtom()]).Invoke(null, []); + var map = TypeMapping.GetOrCreateExternalTypeMapping(); // A, B, C, F, G: trim target element type is reachable — entries must be present From a1876e3dc57dffa2315855deeeadab22cbec35b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:26:11 +0000 Subject: [PATCH 11/13] Match exact whitespace formatting from the original snippet - TrimTarget1-4 struct declarations now use trailing spaces: TrimTarget1 ; - Array constructors now match: new TrimTarget1 [1], new TrimTarget2 [1,1], new TrimTarget3 *[1] - Closing bracket of return array moved to match: ]; (with leading whitespace) Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7cdf5296-b91d-4997-90ed-3fe071042c48 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../TrimmingBehaviors/DeadCodeElimination.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 7d97600259c7b5..f94db8b4b1040f 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -1382,10 +1382,10 @@ internal class TestInteropMapArrayTrimming { internal class Universe; - public struct TrimTarget1; - public struct TrimTarget2; - public struct TrimTarget3; - public struct TrimTarget4; + public struct TrimTarget1 ; + public struct TrimTarget2 ; + public struct TrimTarget3 ; + public struct TrimTarget4 ; public struct TrimTarget5; public struct TrimTarget6; public struct TrimTarget7; @@ -1401,13 +1401,13 @@ public class Atom; public static unsafe object[] MakeGenerics() { return [ - new TrimTarget1[1], - new TrimTarget2[1, 1], - new TrimTarget3*[1], + new TrimTarget1 [1], + new TrimTarget2 [1,1], + new TrimTarget3 *[1], // Skipping TrimTarget4 // Skipping TrimTarget5 new TrimTarget6[1] - ]; + ]; } [MethodImpl(MethodImplOptions.NoInlining)] From e956687af738c40f856932238d040f36b4566937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 14 Apr 2026 20:37:25 -0700 Subject: [PATCH 12/13] Refactor TypeMap associations for array trimming --- .../TrimmingBehaviors/DeadCodeElimination.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index f94db8b4b1040f..acbb2b008a6cb9 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -16,13 +16,13 @@ [assembly: TypeMapAssociation(typeof(DeadCodeElimination.TestInteropMapTrimming.GenUsedForProxy), typeof(DeadCodeElimination.TestInteropMapTrimming.ProxyFromGenUsed))] [assembly: TypeMapAssociation(typeof(DeadCodeElimination.TestInteropMapTrimming.GenUnused), typeof(DeadCodeElimination.TestInteropMapTrimming.ProxyFromGenUnused))] -[assembly: TypeMap("A", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target1), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget1[]))] -[assembly: TypeMap("B", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target2), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget2[,]))] -[assembly: TypeMap("C", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target3), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget3*[]))] -[assembly: TypeMap("D", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target4), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget4[]))] -[assembly: TypeMap("E", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target5), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget5[]))] -[assembly: TypeMap("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))] -[assembly: TypeMap("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))] +[assembly: TypeMap("A", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target1), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget1[]))] +[assembly: TypeMap("B", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target2), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget2[,]))] +[assembly: TypeMap("C", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target3), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget3*[]))] +[assembly: TypeMap("D", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target4), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget4[]))] +[assembly: TypeMap("E", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target5), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget5[]))] +[assembly: TypeMap("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))] +[assembly: TypeMap("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))] class DeadCodeElimination { @@ -1380,12 +1380,10 @@ public static Type GetProxyGeneric(IReadOnlyDictionary map) where internal class TestInteropMapArrayTrimming { - internal class Universe; - - public struct TrimTarget1 ; - public struct TrimTarget2 ; - public struct TrimTarget3 ; - public struct TrimTarget4 ; + public struct TrimTarget1; + public struct TrimTarget2; + public struct TrimTarget3; + public struct TrimTarget4; public struct TrimTarget5; public struct TrimTarget6; public struct TrimTarget7; @@ -1401,9 +1399,9 @@ public class Atom; public static unsafe object[] MakeGenerics() { return [ - new TrimTarget1 [1], - new TrimTarget2 [1,1], - new TrimTarget3 *[1], + new TrimTarget1[1], + new TrimTarget2[1,1], + new TrimTarget3*[1], // Skipping TrimTarget4 // Skipping TrimTarget5 new TrimTarget6[1] @@ -1425,7 +1423,7 @@ public static void Run() typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)).MakeGenericMethod([GetAtom()]).Invoke(null, []); - var map = TypeMapping.GetOrCreateExternalTypeMapping(); + var map = TypeMapping.GetOrCreateExternalTypeMapping(); // A, B, C, F, G: trim target element type is reachable — entries must be present if (!map.TryGetValue("A", out Type typeA) || typeA.Name != nameof(Target1)) From e794247b824b48f3cf0f868e8180f8db0a94001f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 04:28:48 +0000 Subject: [PATCH 13/13] Fix absent-entry checks: use TryGetValue instead of ContainsKey TypeMapLazyDictionary.ContainsKey throws NotSupportedException. Use TryGetValue (which is properly supported) to check that entries D and E are absent from the map. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b5e7b52a-b2ba-4366-bb40-7b1961844e70 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index acbb2b008a6cb9..b8cfacbf4f4bfe 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -1447,9 +1447,9 @@ public static void Run() ThrowIfUsableMethodTable(typeG); // D, E: element type is unreachable — entries must be absent - if (map.ContainsKey("D")) + if (map.TryGetValue("D", out _)) throw new Exception("Unexpected entry D"); - if (map.ContainsKey("E")) + if (map.TryGetValue("E", out _)) throw new Exception("Unexpected entry E"); } }