diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index bd432d015d76e6..bc49baef4fe423 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2030,6 +2030,9 @@ internal void MarkRelevantToVariantCasting(TypeDefinition type) { _typeMapHandler.ProcessType(type); Annotations.MarkRelevantToVariantCasting(type); + + if (Context.TryResolve(type.BaseType) is TypeDefinition baseType && !Annotations.IsRelevantToVariantCasting(baseType)) + MarkRelevantToVariantCasting(baseType); } internal void MarkMethodVisibleToReflection(MethodReference method, in DependencyInfo reason, in MessageOrigin origin, bool suppressTrimAnalysisWarnings = false) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/VariantCasting.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/VariantCasting.cs index 1cd765dce5d74f..40489af3c42f06 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/VariantCasting.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/VariantCasting.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System; +using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; @@ -59,11 +60,61 @@ class Boring : Interesting public Boring() { } } + [Kept] + interface IInner + { + [Kept(By = Tool.Trimmer)] // NativeAOT removes interface methods that no constructed type implements + void InnerMethod(); + } + + [Kept] + [KeptInterface(typeof(IInner))] + interface IOuter : IInner + { + [Kept(By = Tool.Trimmer)] + void OuterMethod(); + } + + interface IUnused : IOuter + { + } + + [Kept(By = Tool.Trimmer)] // NativeAOT does not need the metadata of an unconstructed base type + [KeptInterface(typeof(IOuter<>), "T", By = Tool.Trimmer)] + [KeptInterface(typeof(IInner), By = Tool.Trimmer)] + abstract class UninstantiatedBaseWithInterfaces : IOuter + { + [Kept(By = Tool.Trimmer)] + public void OuterMethod() { } + + [Kept(By = Tool.Trimmer)] + void IInner.InnerMethod() { } + } + + // Only reachable through typeof. The interfaces of the base type must be kept together with the ones on this type. + [Kept] + [KeptBaseType(typeof(UninstantiatedBaseWithInterfaces<>), "T")] + [KeptInterface(typeof(IOuter<>), "T")] + [KeptInterface(typeof(IInner))] + class UninstantiatedDerivedWithInterfaces : UninstantiatedBaseWithInterfaces, IUnused + { + } + + [Kept] + static void UseInterfaceMethods(IOuter outer) + { + outer.OuterMethod(); + ((IInner)outer).InnerMethod(); + } + public static void Main() { // These casts need to succeed after trimming IVariant foos = (IVariant)(object)new Boring(); IVariant bases = (IVariant)(object)new Boring(); + + Type type = typeof(UninstantiatedDerivedWithInterfaces<>); + UseInterfaceMethods(null); } } } 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 bf004eec994ec8..a8dd1b3d1ae59b 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 @@ -99,6 +99,10 @@ [assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "ProxyTargetIsAlsoTrimTarget", typeof(ProxyTargetIsAlsoTrimTargetTarget), typeof(ProxyTargetIsAlsoTrimTarget))] [assembly: KeptAttributeAttribute(typeof(TypeMapAssociationAttribute), typeof(ProxyTargetIsAlsoTrimTargetSource), typeof(ProxyTargetIsAlsoTrimTarget))] +// Verify that a type only reachable through typeof (and therefore relevant to variant casting) also processes the type map entries of its base types. +[assembly: TypeMap("BaseOfTypeofOnlyDerived", typeof(BaseOfTypeofOnlyDerivedTarget), typeof(BaseOfTypeofOnlyDerived))] // Kept +[assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), "BaseOfTypeofOnlyDerived", typeof(BaseOfTypeofOnlyDerivedTarget), typeof(BaseOfTypeofOnlyDerived), By = Tool.Trimmer)] + namespace Mono.Linker.Tests.Cases.Reflection { [SetupLinkerAction("link", "System.Private.CoreLib")] // Needed to get the RemoveAttributeInstances in embedded xml @@ -256,6 +260,10 @@ static void ConstrainedStaticCall(T t) where T : IStaticInterface // is processed first via MarkTypeMapAttribute → MarkRequirementsForInstantiatedTypes. _ = new ProxyTargetIsAlsoTrimTargetSource(); Console.WriteLine(new ProxyTargetIsAlsoTrimTarget()); + + // Neither type is instantiated, but typeof makes TypeofOnlyDerived relevant to variant casting, + // which makes its base type relevant to variant casting too, so the base type's type map entries must be kept. + Console.WriteLine(typeof(TypeofOnlyDerived)); } [ExpectBodyModified] @@ -615,4 +623,12 @@ class ProxyTargetIsAlsoTrimTargetTarget; [Kept] [KeptMember(".ctor()")] class ProxyTargetIsAlsoTrimTargetSource; + + [Kept] + class BaseOfTypeofOnlyDerived; + [Kept] + class BaseOfTypeofOnlyDerivedTarget; + [Kept] + [KeptBaseType(typeof(BaseOfTypeofOnlyDerived))] + class TypeofOnlyDerived : BaseOfTypeofOnlyDerived; }