From 004bcffa8d3650ba2cb7ef7235a31c0a99e23cc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 16:21:54 +0000 Subject: [PATCH 01/10] Initial plan From 00f3516aae321c6124906371d28e70590bed4949 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 16:42:26 +0000 Subject: [PATCH 02/10] Add failing ILLink test for new()-constrained generic attribute argument Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- ...nericAttributeArgumentConstructorIsKept.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs new file mode 100644 index 00000000000000..4f3735aa6fb715 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs @@ -0,0 +1,40 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.Attributes +{ + public class NewConstrainedGenericAttributeArgumentConstructorIsKept + { + public static void Main() + { + // Accessing the attributes via reflection forces the attribute (and its + // generic instantiation) to be kept. The new() constraint on the attribute's + // type parameter requires the public parameterless constructor of the + // generic argument to be preserved, otherwise the runtime throws a + // TypeLoadException when materializing the attribute. + typeof(CheckBox).GetCustomAttributes(false); + } + + [Kept] + public class Handler + { + [Kept] + public Handler() { } + } + + [Kept] + [KeptBaseType(typeof(Attribute))] + class MyAttribute : Attribute where T : new() + { + [Kept] + public MyAttribute() { } + } + + [Kept] + [KeptAttributeAttribute(typeof(MyAttribute))] + [My] + class CheckBox + { + } + } +} From 732780f7c46eeb2eee7f49f9761ef0626c3fc33f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 16:57:54 +0000 Subject: [PATCH 03/10] Process generic argument dataflow for custom attribute types to keep new()-constrained ctor Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 8 ++++++++ ...onstrainedGenericAttributeArgumentConstructorIsKept.cs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index fdd086d0dd03b0..7263f133a03d53 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,6 +1188,14 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); TypeReference constructor_type = ca.Constructor.DeclaringType; + + // The attribute type can be a generic instantiation (e.g. [MyAttribute]). + // Process the generic argument data flow for it so that requirements coming from the + // generic parameters (e.g. a new() constraint or DynamicallyAccessedMembers annotations) + // are satisfied on the generic arguments. Otherwise the attribute could fail to construct + // at runtime (for example a new() constraint would be left unsatisfiable). + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); + TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs index 4f3735aa6fb715..3dff3e33d8235e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Attributes @@ -24,7 +25,7 @@ public Handler() { } [Kept] [KeptBaseType(typeof(Attribute))] - class MyAttribute : Attribute where T : new() + class MyAttribute<[KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] T> : Attribute where T : new() { [Kept] public MyAttribute() { } From 72898f2d89a9315817225ef8973da72c80af41eb Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 10:15:06 -0700 Subject: [PATCH 04/10] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 7263f133a03d53..5f51ba8d58cf24 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,12 +1188,6 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); TypeReference constructor_type = ca.Constructor.DeclaringType; - - // The attribute type can be a generic instantiation (e.g. [MyAttribute]). - // Process the generic argument data flow for it so that requirements coming from the - // generic parameters (e.g. a new() constraint or DynamicallyAccessedMembers annotations) - // are satisfied on the generic arguments. Otherwise the attribute could fail to construct - // at runtime (for example a new() constraint would be left unsatisfiable). GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); TypeDefinition? type = Context.Resolve(constructor_type); From 69ba2bfaecd2aad08ba479ec37199f664015546e Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 10:16:17 -0700 Subject: [PATCH 05/10] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 5f51ba8d58cf24..ef6c7edf3fc61e 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1187,9 +1187,8 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); - TypeReference constructor_type = ca.Constructor.DeclaringType; GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); - +TypeReference constructor_type = ca.Constructor.DeclaringType; TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) From 9cb20b93a3f284fc0f502d38ab663e3cb58e5511 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 10:16:46 -0700 Subject: [PATCH 06/10] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index ef6c7edf3fc61e..fae4da6750f6f3 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,7 +1188,7 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); -TypeReference constructor_type = ca.Constructor.DeclaringType; + TypeReference constructor_type = ca.Constructor.DeclaringType; TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) From 9dfc88120c0e011fe566999f3b2b19b1c284681e Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 11:08:39 -0700 Subject: [PATCH 07/10] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index fae4da6750f6f3..5f51ba8d58cf24 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1187,8 +1187,9 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); TypeReference constructor_type = ca.Constructor.DeclaringType; + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); + TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) From 4d726194cb537f5c92944f42033d1df02e093511 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 19:07:41 +0000 Subject: [PATCH 08/10] Address PR feedback on generic attribute dataflow coverage Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- .../Linker.Dataflow/DiagnosticUtilities.cs | 14 ++-- .../src/linker/Linker.Steps/MarkStep.cs | 3 +- .../Linker/MemberReferenceExtensions.cs | 6 ++ .../Dependencies/GenericAttributesDataFlow.il | 46 +++++++++++++ .../Attributes/GenericAttributes.cs | 69 +++++++++++++++++++ ...nericAttributeArgumentConstructorIsKept.cs | 41 ----------- 6 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il delete mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs diff --git a/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs b/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs index 6b1bebb31dfdb2..c6e15e51e6cf2c 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs @@ -10,10 +10,16 @@ static class DiagnosticUtilities internal static string GetParameterNameForErrorMessage(ParameterDefinition parameterDefinition) => string.IsNullOrEmpty(parameterDefinition.Name) ? $"#{parameterDefinition.Index}" : parameterDefinition.Name; - internal static string GetGenericParameterDeclaringMemberDisplayName(GenericParameter genericParameter) => - genericParameter.DeclaringMethod != null ? - genericParameter.DeclaringMethod.GetDisplayName() : - genericParameter.DeclaringType.GetDisplayName(); + internal static string GetGenericParameterDeclaringMemberDisplayName(GenericParameter genericParameter) + { + if (genericParameter.DeclaringMethod is MethodReference declaringMethod) + return declaringMethod.Name; + + if (genericParameter.DeclaringType is TypeReference declaringType) + return declaringType.Name; + + return genericParameter.Name; + } internal static string GetMethodSignatureDisplayName(IMethodSignature methodSignature) => (methodSignature is MethodReference method) ? method.GetDisplayName() : (methodSignature.ToString() ?? string.Empty); diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 5f51ba8d58cf24..4b08374f7f48ab 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,7 +1188,8 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); TypeReference constructor_type = ca.Constructor.DeclaringType; - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); + if (GenericArgumentDataFlow.RequiresGenericArgumentDataFlow(Context.Annotations.FlowAnnotations, constructor_type)) + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); TypeDefinition? type = Context.Resolve(constructor_type); diff --git a/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs b/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs index 71be73a49db94c..7480ec33a19e84 100644 --- a/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs +++ b/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs @@ -30,7 +30,13 @@ public static string GetDisplayName(this MemberReference member) public static string GetNamespaceDisplayName(this MemberReference member) { + if (member == null) + return string.Empty; + var type = member is TypeReference typeReference ? typeReference : member.DeclaringType; + if (type == null) + return string.Empty; + while (type.DeclaringType != null) type = type.DeclaringType; diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il new file mode 100644 index 00000000000000..47a3209e01bf2d --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il @@ -0,0 +1,46 @@ +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) +} +.assembly 'GenericAttributesDataFlow' +{ + .hash algorithm 0x00008004 + .ver 1:0:0:0 +} +.module GenericAttributesDataFlow.dll + +.namespace Mono.Linker.Tests.Cases.Attributes.Dependencies +{ + .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericAttribute`1 + extends [System.Runtime]System.Attribute + { + .param type T + .custom instance void [System.Runtime]System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes) = ( + 01 00 08 00 00 00 00 00 + ) + + .method public hidebysig specialname rtspecialname + instance default void '.ctor' () cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [System.Runtime]System.Attribute::'.ctor'() + IL_0006: ret + } + } + + .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericClass`1 + extends [System.Runtime]System.Object + { + .custom instance void class Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericAttribute`1::'.ctor'() = (01 00 00 00) + + .method public hidebysig specialname rtspecialname + instance default void '.ctor' () cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [System.Runtime]System.Object::'.ctor'() + IL_0006: ret + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs index e8340a5b7d2cc2..0e2673d2063d0f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs @@ -1,8 +1,12 @@ using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes { + [SetupCompileBefore("GenericAttributesDataFlow.dll", new[] { "Dependencies/GenericAttributesDataFlow.il" })] class GenericAttributes { static void Main() @@ -10,6 +14,9 @@ static void Main() new WithGenericAttribute_OfString(); new WithGenericAttribute_OfInt(); new WithConstrainedGenericAttribute(); + typeof(WithNewConstrainedGenericAttribute).GetCustomAttributes(false); + ReflectOnGenericAttributeWithUnannotatedTypeParameter.Test(); + ReflectOnGenericAttributeWithAnnotatedTypeParameter.Test(); } [Kept] @@ -60,6 +67,13 @@ class ConstraintType { } + [Kept] + class TypeWithPublicMethods + { + [Kept] + public void Method() { } + } + [KeptBaseType(typeof(ConstraintType))] class DerivedFromConstraintType : ConstraintType { @@ -72,5 +86,60 @@ class ConstrainedGenericAttribute : Attribute [Kept] public ConstrainedGenericAttribute() { } } + + [Kept] + class Handler + { + [Kept] + public Handler() { } + } + + [Kept] + [KeptAttributeAttribute(typeof(NewConstrainedGenericAttribute))] + [KeptMember(".ctor()")] + [NewConstrainedGenericAttribute] + class WithNewConstrainedGenericAttribute + { + } + + [Kept] + [KeptBaseType(typeof(Attribute))] + class NewConstrainedGenericAttribute<[KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] T> : Attribute + where T : new() + { + [Kept] + public NewConstrainedGenericAttribute() { } + } + + [Kept] + class ReflectOnGenericAttributeWithUnannotatedTypeParameter + { + [Kept] + [ExpectedWarning("IL2091", Tool.Trimmer | Tool.NativeAot, "")] + public static void Test() + { + GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); + } + } + + [Kept] + class ReflectOnGenericAttributeWithAnnotatedTypeParameter< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + [KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + T>() + { + [Kept] + public static void Test() + { + GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); + } + } + + [Kept] + static Type GetDynamicallyAccessedMembersGenericClass(Type typeArgument) + { + return Type.GetType("Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericClass`1, GenericAttributesDataFlow")! + .MakeGenericType(typeArgument); + } } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs deleted file mode 100644 index 3dff3e33d8235e..00000000000000 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Reflection; -using Mono.Linker.Tests.Cases.Expectations.Assertions; - -namespace Mono.Linker.Tests.Cases.Attributes -{ - public class NewConstrainedGenericAttributeArgumentConstructorIsKept - { - public static void Main() - { - // Accessing the attributes via reflection forces the attribute (and its - // generic instantiation) to be kept. The new() constraint on the attribute's - // type parameter requires the public parameterless constructor of the - // generic argument to be preserved, otherwise the runtime throws a - // TypeLoadException when materializing the attribute. - typeof(CheckBox).GetCustomAttributes(false); - } - - [Kept] - public class Handler - { - [Kept] - public Handler() { } - } - - [Kept] - [KeptBaseType(typeof(Attribute))] - class MyAttribute<[KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] T> : Attribute where T : new() - { - [Kept] - public MyAttribute() { } - } - - [Kept] - [KeptAttributeAttribute(typeof(MyAttribute))] - [My] - class CheckBox - { - } - } -} From 3df8a70fbcf1920c60a3305c78bb355c6389066e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:31:56 +0000 Subject: [PATCH 09/10] Get GenericAttributes illink test to green; cover achievable DAM generic-attribute case Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- .../Dependencies/GenericAttributesDataFlow.il | 8 +++- .../Attributes/GenericAttributes.cs | 47 ++++++------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il index 47a3209e01bf2d..4dcf19a31f8d12 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il @@ -11,6 +11,7 @@ .namespace Mono.Linker.Tests.Cases.Attributes.Dependencies { + // A generic attribute whose type parameter requires DynamicallyAccessedMemberTypes.PublicMethods. .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericAttribute`1 extends [System.Runtime]System.Attribute { @@ -29,7 +30,12 @@ } } - .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericClass`1 + // The attribute is applied with the class's own generic parameter as the generic argument. This + // construct cannot be expressed in C# (CS8968: an attribute type argument cannot use type + // parameters), so it is provided in IL. The trimmer must analyze the generic-argument data flow of + // the attribute without crashing and warn (IL2091) because the unannotated generic argument does + // not satisfy the attribute's PublicMethods requirement. + .class public auto ansi beforefieldinit ClassWithUnannotatedTypeParameter`1 extends [System.Runtime]System.Object { .custom instance void class Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericAttribute`1::'.ctor'() = (01 00 00 00) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs index 0e2673d2063d0f..8574fc90e94d33 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs @@ -1,12 +1,17 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes { + // A generic attribute whose type parameter is annotated with DynamicallyAccessedMembers can only be + // applied with a generic-parameter argument in IL (C# forbids type parameters as generic attribute + // arguments - CS8968), so the data-flow scenario is provided by a compiled-before IL assembly. + // The warning originates in that dependency assembly, so it is asserted with LogContains + // (ExpectedWarning only matches origins in the test assembly itself). [SetupCompileBefore("GenericAttributesDataFlow.dll", new[] { "Dependencies/GenericAttributesDataFlow.il" })] + [LogContains("IL2091.*ClassWithUnannotatedTypeParameter", regexMatch: true)] class GenericAttributes { static void Main() @@ -15,8 +20,7 @@ static void Main() new WithGenericAttribute_OfInt(); new WithConstrainedGenericAttribute(); typeof(WithNewConstrainedGenericAttribute).GetCustomAttributes(false); - ReflectOnGenericAttributeWithUnannotatedTypeParameter.Test(); - ReflectOnGenericAttributeWithAnnotatedTypeParameter.Test(); + ReflectOnGenericAttributeWithUnannotatedTypeParameter(); } [Kept] @@ -70,7 +74,6 @@ class ConstraintType [Kept] class TypeWithPublicMethods { - [Kept] public void Method() { } } @@ -96,7 +99,6 @@ public Handler() { } [Kept] [KeptAttributeAttribute(typeof(NewConstrainedGenericAttribute))] - [KeptMember(".ctor()")] [NewConstrainedGenericAttribute] class WithNewConstrainedGenericAttribute { @@ -111,35 +113,16 @@ class NewConstrainedGenericAttribute<[KeptGenericParamAttributes(GenericParamete public NewConstrainedGenericAttribute() { } } + // Reflecting over the generic instantiation keeps the dependency type and forces the trimmer + // to analyze the generic attribute applied to it. [Kept] - class ReflectOnGenericAttributeWithUnannotatedTypeParameter - { - [Kept] - [ExpectedWarning("IL2091", Tool.Trimmer | Tool.NativeAot, "")] - public static void Test() - { - GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); - } - } - - [Kept] - class ReflectOnGenericAttributeWithAnnotatedTypeParameter< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - [KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] - T>() - { - [Kept] - public static void Test() - { - GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); - } - } - - [Kept] - static Type GetDynamicallyAccessedMembersGenericClass(Type typeArgument) + static void ReflectOnGenericAttributeWithUnannotatedTypeParameter() { - return Type.GetType("Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericClass`1, GenericAttributesDataFlow")! - .MakeGenericType(typeArgument); + // ClassWithUnannotatedTypeParameter applies a DynamicallyAccessedMembers-annotated generic + // attribute using its own (unverifiable) generic parameter as the argument, so the trimmer + // must analyze the generic-argument data flow without crashing and warn (IL2091). + Type.GetType("Mono.Linker.Tests.Cases.Attributes.Dependencies.ClassWithUnannotatedTypeParameter`1, GenericAttributesDataFlow")! + .MakeGenericType(typeof(TypeWithPublicMethods)).GetCustomAttributes(false); } } } From 44cee5883ea0dabadc13ab4d827ad5b8aa198d68 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:36:46 -0700 Subject: [PATCH 10/10] Fix IL2089-2095 generic-parameter message regression in trimmer GetGenericParameterDeclaringMemberDisplayName was changed to return the declaring member's simple Name instead of its full display name. That diverged from the Roslyn analyzer (which uses GetDisplayName()) and broke the message text of the generic-parameter annotation-mismatch warnings (IL2089/IL2090/IL2091/IL2095), failing 8 DataFlow linker tests. Restore GetDisplayName() while keeping the null-safe structure (and the GetNamespaceDisplayName null guards) so the custom-attribute generic argument data-flow scenario no longer crashes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs b/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs index c6e15e51e6cf2c..2ab8d65d507331 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs @@ -13,10 +13,10 @@ internal static string GetParameterNameForErrorMessage(ParameterDefinition param internal static string GetGenericParameterDeclaringMemberDisplayName(GenericParameter genericParameter) { if (genericParameter.DeclaringMethod is MethodReference declaringMethod) - return declaringMethod.Name; + return declaringMethod.GetDisplayName(); if (genericParameter.DeclaringType is TypeReference declaringType) - return declaringType.Name; + return declaringType.GetDisplayName(); return genericParameter.Name; }