From d6564fb504b29bab0dc2f363a9f062db284a80dc Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 3 Jun 2024 19:39:12 -0700
Subject: [PATCH 01/27] Use recursive-interface-aware types and methods in
 TypeMapInfo

- Rename InterfaceImplementor to RuntimeInterfaceImplementation
- Use 'interface implementation chains' everywhere rather than a single
  interface implementation
---
 .../src/linker/Linker.Steps/MarkStep.cs       |  50 +--
 .../illink/src/linker/Linker/Annotations.cs   |   4 +-
 .../src/linker/Linker/InterfaceImplementor.cs |  59 ----
 .../src/linker/Linker/OverrideInformation.cs  |  24 +-
 .../Linker/RuntimeInterfaceImplementation.cs  |  56 +++
 .../illink/src/linker/Linker/TypeMapInfo.cs   | 117 +++----
 .../linker/Linker/TypeReferenceExtensions.cs  |  25 +-
 .../AttributesTests.g.cs                      |   6 +
 ...terfaces.DefaultInterfaceMethodsTests.g.cs |   6 +
 ...e.Interfaces.RecursiveInterfacesTests.g.cs |  12 +
 .../MetadataTests.g.cs                        |  30 ++
 .../StaticDimOnDerivedInterface.cs            |  57 ++++
 .../RecursiveGenericInterfaces.il             | 322 ++++++++++++++++++
 .../RecursiveGenericInterfacesStatic.il       | 322 ++++++++++++++++++
 .../GenericInterfaceImplementedRecursively.cs |   6 +-
 .../RecursiveGenericInterfaces.cs             |  94 +++++
 .../RecursiveGenericInterfacesStatic.cs       | 125 +++++++
 17 files changed, 1136 insertions(+), 179 deletions(-)
 delete mode 100644 src/tools/illink/src/linker/Linker/InterfaceImplementor.cs
 create mode 100644 src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index 280f1908f79f03..b473064e59a9a8 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -738,18 +738,19 @@ internal static bool IsInterfaceImplementationMarkedRecursively (TypeDefinition
 						return true;
 				}
 			}
-
+			if (type.BaseType is not null && context.Resolve (type.BaseType) is { } baseType)
+				return IsInterfaceImplementationMarkedRecursively (baseType, interfaceType, context);
 			return false;
 		}
 
 		void ProcessDefaultImplementation (OverrideInformation ov, MessageOrigin origin)
 		{
 			Debug.Assert (ov.IsOverrideOfInterfaceMember);
-			if ((!ov.Override.IsStatic && !Annotations.IsInstantiated (ov.InterfaceImplementor.Implementor))
-				|| ov.Override.IsStatic && !Annotations.IsRelevantToVariantCasting (ov.InterfaceImplementor.Implementor))
+			if ((!ov.Override.IsStatic && !Annotations.IsInstantiated (ov.RuntimeInterfaceImplementation.Implementor))
+				|| ov.Override.IsStatic && !Annotations.IsRelevantToVariantCasting (ov.RuntimeInterfaceImplementation.Implementor))
 				return;
 
-			MarkInterfaceImplementation (ov.InterfaceImplementor.InterfaceImplementation, origin);
+			MarkRuntimeInterfaceImplementation (ov.RuntimeInterfaceImplementation, origin);
 		}
 
 		void MarkMarshalSpec (IMarshalInfoProvider spec, in DependencyInfo reason, MessageOrigin origin)
@@ -2398,35 +2399,38 @@ void MarkNamedProperty (TypeDefinition type, string property_name, in Dependency
 
 		void MarkInterfaceImplementations (TypeDefinition type)
 		{
-			var ifaces = Annotations.GetRecursiveInterfaces (type);
+			var ifaces = Annotations.GetRuntimeInterfaces (type);
 			if (ifaces is null)
 				return;
-			foreach (var (ifaceType, impls) in ifaces) {
+			foreach (var runtimeInterface in ifaces) {
 				// Only mark interface implementations of interface types that have been marked.
 				// This enables stripping of interfaces that are never used
-				if (ShouldMarkInterfaceImplementationList (type, impls, ifaceType))
-					MarkInterfaceImplementationList (impls, new MessageOrigin (type));
+				if (ShouldMarkRuntimeInterfaceImplementation (runtimeInterface))
+					MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (type));
 			}
 		}
 
 
-		protected virtual bool ShouldMarkInterfaceImplementationList (TypeDefinition type, List<InterfaceImplementation> ifaces, TypeReference ifaceType)
+		protected virtual bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation)
 		{
+			var type = runtimeInterfaceImplementation.Implementor;
+			var ifaces = runtimeInterfaceImplementation.InterfaceImplementation;
+			var ifaceType = runtimeInterfaceImplementation.InterfaceTypeDefinition;
 			if (ifaces.All (Annotations.IsMarked))
 				return false;
 
 			if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, type))
 				return true;
 
-			if (Context.Resolve (ifaceType) is not TypeDefinition resolvedInterfaceType)
+			if (ifaceType is null)
 				return false;
 
-			if (Annotations.IsMarked (resolvedInterfaceType))
+			if (Annotations.IsMarked (ifaceType))
 				return true;
 
 			// It's hard to know if a com or windows runtime interface will be needed from managed code alone,
 			// so as a precaution we will mark these interfaces once the type is instantiated
-			if (Context.KeepComInterfaces && (resolvedInterfaceType.IsImport || resolvedInterfaceType.IsWindowsRuntime))
+			if (Context.KeepComInterfaces && (ifaceType.IsImport || ifaceType.IsWindowsRuntime))
 				return true;
 
 			return IsFullyPreserved (type);
@@ -2511,9 +2515,8 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat
 
 			// If the interface implementation is not marked, do not mark the implementation method
 			// A type that doesn't implement the interface isn't required to have methods that implement the interface.
-			InterfaceImplementation? iface = overrideInformation.InterfaceImplementor.InterfaceImplementation;
-			if (!((iface is not null && Annotations.IsMarked (iface))
-				|| IsInterfaceImplementationMarkedRecursively (method.DeclaringType, @base.DeclaringType)))
+			// We must check all possible ways the interface could be implemented by the type (through all recursive interface implementations, not just the primary one)
+			if (!(IsInterfaceImplementationMarkedRecursively (overrideInformation.RuntimeInterfaceImplementation.Implementor, @base.DeclaringType)))
 				return false;
 
 			// If the interface method is not marked and the interface doesn't come from a preserved scope, do not mark the implementation method
@@ -2529,12 +2532,12 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat
 			// If the method is static and the implementing type is relevant to variant casting, mark the implementation method.
 			// A static method may only be called through a constrained call if the type is relevant to variant casting.
 			if (@base.IsStatic)
-				return Annotations.IsRelevantToVariantCasting (overrideInformation.InterfaceImplementor.Implementor)
+				return Annotations.IsRelevantToVariantCasting (overrideInformation.RuntimeInterfaceImplementation.Implementor)
 					|| IgnoreScope (@base.DeclaringType.Scope);
 
 			// If the implementing type is marked as instantiated, mark the implementation method.
 			// If the type is not instantiated, do not mark the implementation method
-			return Annotations.IsInstantiated (overrideInformation.InterfaceImplementor.Implementor);
+			return Annotations.IsInstantiated (overrideInformation.RuntimeInterfaceImplementation.Implementor);
 		}
 
 		static bool IsSpecialSerializationConstructor (MethodDefinition method)
@@ -3307,7 +3310,6 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type
 
 			DoAdditionalInstantiatedTypeProcessing (type);
 		}
-
 		void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReference ov)
 		{
 			if (Context.Resolve (ov) is not MethodDefinition resolvedOverride)
@@ -3315,13 +3317,13 @@ void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReferenc
 			if (!resolvedOverride.DeclaringType.IsInterface)
 				return;
 			var interfaceToBeImplemented = ov.DeclaringType;
-
-			var ifaces = Annotations.GetRecursiveInterfaces (method.DeclaringType);
+			var ifaces = Annotations.GetRuntimeInterfaces (method.DeclaringType);
 			if (ifaces is null)
 				return;
+			Debug.Assert (ifaces.Value.SingleOrDefault (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceToBeImplemented, Context)) is not null);
 			foreach (var iface in ifaces) {
-				if (TypeReferenceEqualityComparer.AreEqual (iface.InterfaceType, interfaceToBeImplemented, Context)) {
-					MarkInterfaceImplementationList (iface.ImplementationChain, new MessageOrigin (method.DeclaringType));
+				if (TypeReferenceEqualityComparer.AreEqual (iface.InflatedInterfaceType, interfaceToBeImplemented, Context)) {
+					MarkRuntimeInterfaceImplementation (iface, new MessageOrigin (method.DeclaringType));
 					return;
 				}
 			}
@@ -3753,9 +3755,9 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
 			}
 		}
 
-		void MarkInterfaceImplementationList (List<InterfaceImplementation> ifaces, MessageOrigin origin, DependencyInfo? reason = null)
+		void MarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterface, MessageOrigin origin, DependencyInfo? reason = null)
 		{
-			foreach (var iface in ifaces) {
+			foreach (var iface in runtimeInterface.InterfaceImplementation) {
 				MarkInterfaceImplementation (iface, origin, reason);
 			}
 		}
diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs
index 68c8c320749966..cfcf350bb18040 100644
--- a/src/tools/illink/src/linker/Linker/Annotations.cs
+++ b/src/tools/illink/src/linker/Linker/Annotations.cs
@@ -31,10 +31,10 @@
 
 using System;
 using System.Collections.Generic;
+using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
-using System.Reflection.Metadata.Ecma335;
 using ILLink.Shared.TrimAnalysis;
 using Mono.Cecil;
 using Mono.Cecil.Cil;
@@ -719,7 +719,7 @@ public void EnqueueVirtualMethod (MethodDefinition method)
 				VirtualMethodsWithAnnotationsToValidate.Add (method);
 		}
 
-		internal List<(TypeReference InterfaceType, List<InterfaceImplementation> ImplementationChain)>? GetRecursiveInterfaces (TypeDefinition type)
+		internal ImmutableArray<RuntimeInterfaceImplementation>? GetRuntimeInterfaces (TypeDefinition type)
 		{
 			return TypeMapInfo.GetRecursiveInterfaces (type);
 		}
diff --git a/src/tools/illink/src/linker/Linker/InterfaceImplementor.cs b/src/tools/illink/src/linker/Linker/InterfaceImplementor.cs
deleted file mode 100644
index e981ce872703f7..00000000000000
--- a/src/tools/illink/src/linker/Linker/InterfaceImplementor.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Mono.Cecil;
-
-namespace Mono.Linker
-{
-	public class InterfaceImplementor
-	{
-		/// <summary>
-		/// The type that implements <see cref="InterfaceImplementor.InterfaceType"/>.
-		/// </summary>
-		public TypeDefinition Implementor { get; }
-		/// <summary>
-		/// The .interfaceimpl on <see cref="InterfaceImplementor.Implementor"/>that points to <see cref="InterfaceImplementor.InterfaceType"/>
-		/// </summary>
-		public InterfaceImplementation InterfaceImplementation { get; }
-		/// <summary>
-		/// The type of the interface that is implemented by <see cref="InterfaceImplementor.Implementor"/>
-		/// </summary>
-		public TypeDefinition InterfaceType { get; }
-
-		public InterfaceImplementor (TypeDefinition implementor, InterfaceImplementation interfaceImplementation, TypeDefinition interfaceType, IMetadataResolver resolver)
-		{
-			Implementor = implementor;
-			InterfaceImplementation = interfaceImplementation;
-			InterfaceType = interfaceType;
-			Debug.Assert(resolver.Resolve (interfaceImplementation.InterfaceType) == interfaceType);
-		}
-
-		public static InterfaceImplementor Create(TypeDefinition implementor, TypeDefinition interfaceType, IMetadataResolver resolver)
-		{
-			foreach(InterfaceImplementation iface in implementor.Interfaces) {
-				if (resolver.Resolve(iface.InterfaceType) == interfaceType) {
-					return new InterfaceImplementor(implementor, iface, interfaceType, resolver);
-				}
-			}
-
-			Queue<TypeDefinition> ifacesToCheck = new ();
-			ifacesToCheck.Enqueue(implementor);
-			while (ifacesToCheck.Count > 0) {
-				var currentIface = ifacesToCheck.Dequeue ();
-
-				foreach(InterfaceImplementation ifaceImpl in currentIface.Interfaces) {
-					var iface = resolver.Resolve (ifaceImpl.InterfaceType);
-					if (iface == interfaceType) {
-						return new InterfaceImplementor(implementor, ifaceImpl, interfaceType, resolver);
-					}
-					ifacesToCheck.Enqueue (iface);
-				}
-			}
-			throw new InvalidOperationException ($"Type '{implementor.FullName}' does not implement interface '{interfaceType.FullName}' directly or through any interfaces");
-		}
-	}
-}
diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
index 0727d5d25c19a0..30075f7a697ed8 100644
--- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs
+++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
@@ -1,9 +1,10 @@
 // Copyright (c) .NET Foundation and contributors. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
+using System;
 using System.Diagnostics;
-using Mono.Cecil;
 using System.Diagnostics.CodeAnalysis;
+using Mono.Cecil;
 
 namespace Mono.Linker
 {
@@ -14,28 +15,29 @@ public class OverrideInformation
 
 		public MethodDefinition Override { get; }
 
-		internal InterfaceImplementor? InterfaceImplementor { get; }
+		internal RuntimeInterfaceImplementation? RuntimeInterfaceImplementation { get; }
 
-		internal OverrideInformation (MethodDefinition @base, MethodDefinition @override, InterfaceImplementor? interfaceImplementor = null)
+		internal OverrideInformation (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null)
 		{
 			Base = @base;
 			Override = @override;
-			InterfaceImplementor = interfaceImplementor;
+			RuntimeInterfaceImplementation = runtimeInterface;
 			// Ensure we have an interface implementation if the base method is from an interface and the override method is on a class
-			Debug.Assert(@base.DeclaringType.IsInterface && interfaceImplementor != null
-						|| !@base.DeclaringType.IsInterface && interfaceImplementor == null);
+			Debug.Assert (@base.DeclaringType.IsInterface && runtimeInterface != null
+						|| !@base.DeclaringType.IsInterface && runtimeInterface == null);
 			// Ensure the interfaceImplementor is for the interface we expect
-			Debug.Assert (@base.DeclaringType.IsInterface ? interfaceImplementor!.InterfaceType == @base.DeclaringType : true);
+			Debug.Assert (@base.DeclaringType.IsInterface ? runtimeInterface!.InterfaceTypeDefinition == @base.DeclaringType : true);
 		}
 
+		[Obsolete ("Use RuntimeInterfaceImplementation instead to handle recursive interfaces")]
 		public InterfaceImplementation? MatchingInterfaceImplementation
-			=> InterfaceImplementor?.InterfaceImplementation;
+			=> RuntimeInterfaceImplementation?.InterfaceImplementation[0];
 
 		public TypeDefinition? InterfaceType
-			=> InterfaceImplementor?.InterfaceType;
+			=> RuntimeInterfaceImplementation?.InterfaceTypeDefinition;
 
-		[MemberNotNullWhen (true, nameof (InterfaceImplementor), nameof (MatchingInterfaceImplementation))]
+		[MemberNotNullWhen (true, nameof (RuntimeInterfaceImplementation))]
 		public bool IsOverrideOfInterfaceMember
-			=> InterfaceImplementor != null;
+			=> RuntimeInterfaceImplementation != null;
 	}
 }
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
new file mode 100644
index 00000000000000..9328414f580eff
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace Mono.Linker
+{
+	/// <summary>
+	/// Represents an implementation of an interface on a type that may be directly on the type or on a base type or implemented interface.
+	/// This type is considered to implement the interface at runtime, even though the interface may not be directly on the type.
+	/// </summary>
+	public class RuntimeInterfaceImplementation
+	{
+		/// <summary>
+		/// The type that implements <see cref="RuntimeInterfaceImplementation.InflatedInterfaceType"/>.
+		/// </summary>
+		public TypeDefinition Implementor { get; }
+
+		/// <summary>
+		/// The path of .interfaceimpl on <see cref="RuntimeInterfaceImplementation.Implementor"/> or a base type that terminates with <see cref="RuntimeInterfaceImplementation.InflatedInterfaceType"/>.
+		/// </summary>
+		public ImmutableArray<InterfaceImplementation> InterfaceImplementation { get; }
+
+		/// <summary>
+		/// The type that has the InterfaceImplementation - either the <see cref="Implementor"/> or a base type of it.
+		/// </summary>
+		public TypeReference TypeWithInterfaceImplementation { get; }
+
+		/// <summary>
+		/// The type of the interface that is implemented by <see cref="RuntimeInterfaceImplementation.Implementor"/>.
+		/// This type may be different from the corresponding InterfaceImplementation.InterfaceType if it is generic.
+		/// Generic parameters are replaces with generic arguments from the implementing type.
+		/// Because of this, do not use this for comparisons or resolving. Use <see cref="InterfaceTypeDefinition"/> or <see cref="InterfaceImplementation">.Last().InterfaceType instead.
+		/// </summary>
+		public TypeReference InflatedInterfaceType { get; }
+
+		/// <summary>
+		/// The resolved definition of the interface type implemented by <see cref="RuntimeInterfaceImplementation.Implementor"/>.
+		/// </summary>
+		public TypeDefinition? InterfaceTypeDefinition { get; }
+
+		public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference typeWithFirstIfaceImpl, IEnumerable<InterfaceImplementation> interfaceImplementation, TypeReference inflatedInterfaceType, LinkContext resolver)
+		{
+			Implementor = implementor;
+			TypeWithInterfaceImplementation = typeWithFirstIfaceImpl;
+			InterfaceImplementation = interfaceImplementation.ToImmutableArray ();
+			InflatedInterfaceType = inflatedInterfaceType;
+			InterfaceTypeDefinition = resolver.Resolve (InterfaceImplementation[InterfaceImplementation.Length - 1].InterfaceType);
+		}
+	}
+}
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index 1d7b24e070d148..ec7206addba0fe 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -29,8 +29,8 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-using System;
 using System.Collections.Generic;
+using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
@@ -100,28 +100,28 @@ public void EnsureProcessed (AssemblyDefinition assembly)
 			return ret;
 		}
 
-		public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, InterfaceImplementor? interfaceImplementor)
+		public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, RuntimeInterfaceImplementation? runtimeInterface)
 		{
-			base_methods.AddToList (method, new OverrideInformation (@base, method, interfaceImplementor));
+			base_methods.AddToList (method, new OverrideInformation (@base, method, runtimeInterface));
 		}
 
-		public void AddOverride (MethodDefinition @base, MethodDefinition @override, InterfaceImplementor? interfaceImplementor = null)
+		public void AddOverride (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null)
 		{
-			override_methods.AddToList (@base, new OverrideInformation (@base, @override, interfaceImplementor));
+			override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface));
 		}
 
-		public void AddDefaultInterfaceImplementation (MethodDefinition @base, InterfaceImplementor interfaceImplementor, MethodDefinition defaultImplementationMethod)
+		public void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod)
 		{
-			Debug.Assert(@base.DeclaringType.IsInterface);
-			default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, interfaceImplementor));
+			Debug.Assert (@base.DeclaringType.IsInterface);
+			default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface));
 		}
 
-		Dictionary<TypeDefinition, List<(TypeReference, List<InterfaceImplementation>)>> interfaces = new ();
+		Dictionary<TypeDefinition, ImmutableArray<RuntimeInterfaceImplementation>> interfaces = new ();
 		protected virtual void MapType (TypeDefinition type)
 		{
+			interfaces[type] = GetRecursiveInterfaceImplementations (type);
 			MapVirtualMethods (type);
 			MapInterfaceMethodsInTypeHierarchy (type);
-			interfaces[type] = GetRecursiveInterfaceImplementations (type);
 
 			if (!type.HasNestedTypes)
 				return;
@@ -130,24 +130,24 @@ protected virtual void MapType (TypeDefinition type)
 				MapType (nested);
 		}
 
-		internal List<(TypeReference InterfaceType, List<InterfaceImplementation> ImplementationChain)>? GetRecursiveInterfaces (TypeDefinition type)
+		internal ImmutableArray<RuntimeInterfaceImplementation>? GetRecursiveInterfaces (TypeDefinition type)
 		{
-			EnsureProcessed(type.Module.Assembly);
+			EnsureProcessed (type.Module.Assembly);
 			if (interfaces.TryGetValue (type, out var value))
 				return value;
 			return null;
 		}
 
-		List<(TypeReference InterfaceType, List<InterfaceImplementation> ImplementationChain)> GetRecursiveInterfaceImplementations (TypeDefinition type)
+		ImmutableArray<RuntimeInterfaceImplementation> GetRecursiveInterfaceImplementations (TypeDefinition type)
 		{
-			List<(TypeReference, List<InterfaceImplementation>)> firstImplementationChain = new ();
+			// We'll need at least this many interfaces, so let's preallocate
+			var runtimeIfaces = ImmutableArray.CreateBuilder<RuntimeInterfaceImplementation> (type.Interfaces.Count);
 
-			AddRecursiveInterfaces (type, [], firstImplementationChain, context);
-			Debug.Assert (firstImplementationChain.All (kvp => context.Resolve (kvp.Item1) == context.Resolve (kvp.Item2.Last ().InterfaceType)));
+			AddRecursiveInterfaces (type, type, [], runtimeIfaces, context);
 
-			return firstImplementationChain;
+			return runtimeIfaces.ToImmutable ();
 
-			static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable<InterfaceImplementation> pathToType, List<(TypeReference, List<InterfaceImplementation>)> firstImplementationChain, LinkContext Context)
+			static void AddRecursiveInterfaces (TypeDefinition originalType, TypeReference typeRef, IEnumerable<InterfaceImplementation> pathToType, ImmutableArray<RuntimeInterfaceImplementation>.Builder implementors, LinkContext Context)
 			{
 				var type = Context.TryResolve (typeRef);
 				if (type is null)
@@ -158,11 +158,16 @@ static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable<Interface
 					if (interfaceType is null) {
 						continue;
 					}
-					if (!firstImplementationChain.Any (i => TypeReferenceEqualityComparer.AreEqual (i.Item1, interfaceType, Context))) {
-						firstImplementationChain.Add ((interfaceType, pathToType.Append (iface).ToList ()));
+					if (!implementors.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, Context))) {
+						implementors.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, Context));
 					}
 				}
 
+				if (type.BaseType is not null) {
+					var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, Context)!;
+					AddRecursiveInterfaces (originalType, inflatedBaseType, [], implementors, Context);
+				}
+
 				// Recursive interfaces after all direct interfaces to preserve Inherit/Implement tree order
 				foreach (var iface in type.Interfaces) {
 					// If we can't resolve the interface type we can't find recursive interfaces
@@ -170,24 +175,18 @@ static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable<Interface
 					if (ifaceDirectlyOnType is null) {
 						continue;
 					}
-					AddRecursiveInterfaces (ifaceDirectlyOnType, pathToType.Append (iface), firstImplementationChain, Context);
+					AddRecursiveInterfaces (originalType, ifaceDirectlyOnType, pathToType.Append (iface), implementors, Context);
 				}
 			}
 		}
 
 		void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
 		{
-			if (!type.HasInterfaces)
-				return;
-
+			var inflatedInterfaces = interfaces[type];
 			// Foreach interface and for each newslot virtual method on the interface, try
 			// to find the method implementation and record it.
-			foreach (var interfaceImpl in type.GetInflatedInterfaces (context)) {
-				foreach (MethodReference interfaceMethod in interfaceImpl.InflatedInterface.GetMethods (context)) {
-					MethodDefinition? resolvedInterfaceMethod = context.TryResolve (interfaceMethod);
-					if (resolvedInterfaceMethod == null)
-						continue;
-
+			foreach (var interfaceImpl in inflatedInterfaces) {
+				foreach ((MethodReference interfaceMethod, MethodDefinition resolvedInterfaceMethod) in interfaceImpl.InflatedInterfaceType.GetMethods (context)) {
 					// TODO-NICE: if the interface method is implemented explicitly (with an override),
 					// we shouldn't need to run the below logic. This results in ILLink potentially
 					// keeping more methods than needed.
@@ -196,26 +195,28 @@ void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
 						|| resolvedInterfaceMethod.IsFinal)
 						continue;
 
+					// Note: methods with overrides are annotated in MapVirtualMethods
+
 					// Static methods on interfaces must be implemented only via explicit method-impl record
 					// not by a signature match. So there's no point in running this logic for static methods.
 					if (!resolvedInterfaceMethod.IsStatic) {
 						// Try to find an implementation with a name/sig match on the current type
 						MethodDefinition? exactMatchOnType = TryMatchMethod (type, interfaceMethod);
 						if (exactMatchOnType != null) {
-							AnnotateMethods (resolvedInterfaceMethod, exactMatchOnType, new (type, interfaceImpl.OriginalImpl, resolvedInterfaceMethod.DeclaringType, context));
+							AnnotateMethods (resolvedInterfaceMethod, exactMatchOnType, interfaceImpl);
 							continue;
 						}
 
 						// Next try to find an implementation with a name/sig match in the base hierarchy
 						var @base = GetBaseMethodInTypeHierarchy (type, interfaceMethod);
 						if (@base != null) {
-							AnnotateMethods (resolvedInterfaceMethod, @base, new (type, interfaceImpl.OriginalImpl, resolvedInterfaceMethod.DeclaringType, context));
+							AnnotateMethods (resolvedInterfaceMethod, @base, interfaceImpl);
 							continue;
 						}
 					}
 
 					// Look for a default implementation last.
-					FindAndAddDefaultInterfaceImplementations (type, type, resolvedInterfaceMethod, interfaceImpl.OriginalImpl);
+					FindAndAddDefaultInterfaceImplementations (interfaceImpl, interfaceMethod, resolvedInterfaceMethod);
 				}
 			}
 		}
@@ -245,7 +246,7 @@ void MapVirtualMethod (MethodDefinition method)
 			if (@base == null)
 				return;
 
-			Debug.Assert(!@base.DeclaringType.IsInterface);
+			Debug.Assert (!@base.DeclaringType.IsInterface);
 
 			AnnotateMethods (@base, method);
 		}
@@ -257,17 +258,20 @@ void MapOverrides (MethodDefinition method)
 				if (baseMethod == null)
 					continue;
 				if (baseMethod.DeclaringType.IsInterface) {
-					AnnotateMethods (baseMethod, method, InterfaceImplementor.Create (method.DeclaringType, baseMethod.DeclaringType, context));
+					var found = interfaces.TryGetValue (method.DeclaringType, out var runtimeInterfaces);
+					Debug.Assert (found);
+					var runtimeInterface = runtimeInterfaces.Single (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, baseMethodRef.DeclaringType, context));
+					AnnotateMethods (baseMethod, method, runtimeInterface);
 				} else {
 					AnnotateMethods (baseMethod, method);
 				}
 			}
 		}
 
-		void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, InterfaceImplementor? interfaceImplementor = null)
+		void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null)
 		{
-			AddBaseMethod (@override, @base, interfaceImplementor);
-			AddOverride (@base, @override, interfaceImplementor);
+			AddBaseMethod (@override, @base, runtimeInterface);
+			AddOverride (@base, @override, runtimeInterface);
 		}
 
 		MethodDefinition? GetBaseMethodInTypeHierarchy (MethodDefinition method)
@@ -329,22 +333,29 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Interf
 		/// <param name="implOfInterface">
 		/// The InterfaceImplementation on <paramref name="type"/> that points to the DeclaringType of <paramref name="interfaceMethod"/>.
 		/// </param>
-		void FindAndAddDefaultInterfaceImplementations (TypeDefinition typeThatImplementsInterface, TypeDefinition typeThatMayHaveDIM, MethodDefinition interfaceMethodToBeImplemented, InterfaceImplementation originalInterfaceImpl)
+		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceReference, MethodDefinition interfaceMethodToBeImplemented)
 		{
+			var typeThatImplementsInterface = originalInterfaceImpl.Implementor;
 			// Go over all interfaces, trying to find a method that is an explicit MethodImpl of the
 			// interface method in question.
+			var hasInterfaces = interfaces.TryGetValue (typeThatImplementsInterface, out var runtimeIfaces);
+			if (!hasInterfaces)
+				return;
 
-			foreach (var interfaceImpl in typeThatMayHaveDIM.Interfaces) {
-				var potentialImplInterface = context.TryResolve (interfaceImpl.InterfaceType);
-				if (potentialImplInterface == null)
+			bool findAllPossibleImplementations = inflatedInterfaceReference.DeclaringType is GenericInstanceType;
+			foreach (var interfaceImpl in runtimeIfaces!) {
+				var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition;
+				if (potentialImplInterface is null)
 					continue;
 
+				// We go by MethodDefinition here and may miss the correct implementation by exiting early after finding one.
+				// To correct for this, we'll look for all possible implementations and add them all if the inflatedIntefaceMethodReference is a genericInstance
+				// Otherwise, we're good to stop looking higher in the hierarchy after the first. Note we still need to look at other branches of the hierarchy to find any diamond cases.
 				bool foundImpl = false;
-
 				foreach (var potentialImplMethod in potentialImplInterface.Methods) {
 					if (potentialImplMethod == interfaceMethodToBeImplemented &&
 						!potentialImplMethod.IsAbstract) {
-						AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, new (typeThatImplementsInterface, originalInterfaceImpl, interfaceMethodToBeImplemented.DeclaringType, context), potentialImplMethod);
+						AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, potentialImplMethod);
 						foundImpl = true;
 						break;
 					}
@@ -355,32 +366,22 @@ void FindAndAddDefaultInterfaceImplementations (TypeDefinition typeThatImplement
 					// This method is an override of something. Let's see if it's the method we are looking for.
 					foreach (var baseMethod in potentialImplMethod.Overrides) {
 						if (context.TryResolve (baseMethod) == interfaceMethodToBeImplemented) {
-							AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, new (typeThatImplementsInterface, originalInterfaceImpl, interfaceMethodToBeImplemented.DeclaringType, context), @potentialImplMethod);
-							foundImpl = true;
-							break;
+							AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, @potentialImplMethod);
+							foundImpl = true && !findAllPossibleImplementations;
 						}
 					}
-
 					if (foundImpl) {
 						break;
 					}
 				}
-
-				// We haven't found a MethodImpl on the current interface, but one of the interfaces
-				// this interface requires could still provide it.
-				if (!foundImpl) {
-					FindAndAddDefaultInterfaceImplementations (typeThatImplementsInterface, potentialImplInterface, interfaceMethodToBeImplemented, originalInterfaceImpl);
-				}
 			}
 		}
 
 		MethodDefinition? TryMatchMethod (TypeReference type, MethodReference method)
 		{
-			foreach (var candidate in type.GetMethods (context)) {
-				var md = context.TryResolve (candidate);
-				if (md?.IsVirtual != true)
+			foreach (var (candidate, md) in type.GetMethods (context)) {
+				if (md.IsVirtual != true)
 					continue;
-
 				if (MethodMatch (candidate, method))
 					return md;
 			}
diff --git a/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs b/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs
index 6189eae6d794cb..b1c94aa57309e3 100644
--- a/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs
+++ b/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs
@@ -158,25 +158,6 @@ void parseArrayDimensions (ArrayType at)
 			return typeToInflate;
 		}
 
-		public static IEnumerable<(TypeReference InflatedInterface, InterfaceImplementation OriginalImpl)> GetInflatedInterfaces (this TypeReference typeRef, ITryResolveMetadata resolver)
-		{
-			var typeDef = resolver.TryResolve (typeRef);
-
-			if (typeDef?.HasInterfaces != true)
-				yield break;
-
-			if (typeRef is GenericInstanceType genericInstance) {
-				foreach (var interfaceImpl in typeDef.Interfaces) {
-					// InflateGenericType only returns null when inflating generic parameters (and the generic instance type doesn't resolve).
-					// Here we are not inflating a generic parameter but an interface type reference.
-					yield return (InflateGenericType (genericInstance, interfaceImpl.InterfaceType, resolver), interfaceImpl)!;
-				}
-			} else {
-				foreach (var interfaceImpl in typeDef.Interfaces)
-					yield return (interfaceImpl.InterfaceType, interfaceImpl);
-			}
-		}
-
 		public static TypeReference? InflateGenericType (GenericInstanceType genericInstanceProvider, TypeReference typeToInflate, ITryResolveMetadata resolver)
 		{
 			if (typeToInflate is ArrayType arrayType) {
@@ -276,7 +257,7 @@ private static GenericInstanceType MakeGenericType (GenericInstanceType genericI
 			return result;
 		}
 
-		public static IEnumerable<MethodReference> GetMethods (this TypeReference type, ITryResolveMetadata resolver)
+		public static IEnumerable<(MethodReference, MethodDefinition)> GetMethods (this TypeReference type, ITryResolveMetadata resolver)
 		{
 			TypeDefinition? typeDef = resolver.TryResolve (type);
 			if (typeDef?.HasMethods != true)
@@ -284,10 +265,10 @@ public static IEnumerable<MethodReference> GetMethods (this TypeReference type,
 
 			if (type is GenericInstanceType genericInstanceType) {
 				foreach (var methodDef in typeDef.Methods)
-					yield return MakeMethodReferenceForGenericInstanceType (genericInstanceType, methodDef);
+					yield return (MakeMethodReferenceForGenericInstanceType (genericInstanceType, methodDef), methodDef);
 			} else {
 				foreach (var method in typeDef.Methods)
-					yield return method;
+					yield return (method, method);
 			}
 		}
 
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs
index a912649e9cedc5..dc69ebb930a723 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs
@@ -9,6 +9,12 @@ public sealed partial class AttributesTests : LinkerTestBase
 
 		protected override string TestSuiteName => "Attributes";
 
+		[Fact]
+		public Task AssemblyAttributeAccessesMembers ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task AssemblyAttributeIsRemovedIfOnlyTypesUsedInAssembly ()
 		{
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
index 19189bfcd01705..dc2440a552f56b 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
@@ -63,6 +63,12 @@ public Task StaticDefaultInterfaceMethodOnStruct ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task StaticDimOnDerivedInterface ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task StaticDimProvidedByUnreferencedIfaceInHierarchy ()
 		{
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
index d436348e800bc7..a1f78cc09b63c3 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
@@ -21,6 +21,18 @@ public Task InterfaceImplementedRecursively ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task RecursiveGenericInterfaces ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
+		[Fact]
+		public Task RecursiveGenericInterfacesStatic ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task RecursiveInterfaceKept ()
 		{
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs
index 68e0654e2a445c..9e958d384e9c52 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs
@@ -9,6 +9,12 @@ public sealed partial class MetadataTests : LinkerTestBase
 
 		protected override string TestSuiteName => "Metadata";
 
+		[Fact]
+		public Task DebuggerDisplayNamesAreKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task NamesAreKept ()
 		{
@@ -21,5 +27,29 @@ public Task NamesAreRemoved ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task RootAllAssemblyNamesAreKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
+		[Fact]
+		public Task RootDescriptorNamesAreKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
+		[Fact]
+		public Task RootLibraryAssemblyNamesAreKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
+		[Fact]
+		public Task RootVisibleAssemblyNamesAreKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 	}
 }
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs
new file mode 100644
index 00000000000000..0f45b8aec3aff5
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs
@@ -0,0 +1,57 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+
+namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.DefaultInterfaceMethods
+{
+	class StaticDimOnDerivedInterface
+	{
+		[Kept]
+		public static void Main ()
+		{
+			ITest testImpl = new TestClass ();
+			testImpl.Invoke ();
+
+			CallStaticInvoke<TestClass> ();
+		}
+
+		[Kept]
+		public interface ITestBase
+		{
+			[Kept]
+			static virtual string InvokeStatic () => throw new NotSupportedException ();
+
+			[Kept]
+			string Invoke () => throw new NotSupportedException ();
+		}
+
+		[Kept]
+		[KeptInterface (typeof (ITestBase))]
+		public interface ITest : ITestBase
+		{
+			[Kept]
+			static string ITestBase.InvokeStatic () => "Test";
+
+			[Kept]
+			string ITestBase.Invoke () => "Test";
+		}
+
+		[Kept]
+		[KeptInterface (typeof (ITest))]
+		[KeptInterface (typeof (ITestBase))]
+		public class TestClass : ITest
+		{
+			[Kept]
+			public TestClass () { }
+		}
+
+		[Kept]
+		public static string CallStaticInvoke<T> () where T : ITestBase => T.InvokeStatic ();
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il
new file mode 100644
index 00000000000000..b0cde69b93de9a
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il
@@ -0,0 +1,322 @@
+.assembly extern mscorlib { }
+
+.assembly 'library' { }
+
+.class interface public auto ansi abstract beforefieldinit IBase`3<T, U, V>
+{
+    .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+        01 00 01 00 00
+    )
+    .param type T
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
+            01 00 02 00 00
+        )
+    .param type U
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
+            01 00 02 00 00
+        )
+    .param type V
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
+            01 00 02 00 00
+        )
+    // Methods
+    .method public hidebysig newslot abstract virtual 
+        instance !T GetT () cil managed 
+    {
+    } // end of method IBase`3::GetT
+
+    .method public hidebysig newslot virtual 
+        instance !U GetU () cil managed 
+    {
+        // Method begins at RVA 0x2050
+        // Code size 10 (0xa)
+        .maxstack 1
+        .locals init (
+            [0] !U
+        )
+
+        IL_0000: ldloca.s 0
+        IL_0002: initobj !U
+        IL_0008: ldloc.0
+        IL_0009: ret
+    } // end of method IBase`3::GetU
+
+    .method public hidebysig newslot virtual 
+        instance !V GetV () cil managed 
+    {
+        // Method begins at RVA 0x2068
+        // Code size 10 (0xa)
+        .maxstack 1
+        .locals init (
+            [0] !V
+        )
+
+        IL_0000: ldloca.s 0
+        IL_0002: initobj !V
+        IL_0008: ldloc.0
+        IL_0009: ret
+    } // end of method IBase`3::GetV
+
+} // end of class IBase`3
+
+.class interface public auto ansi abstract beforefieldinit IMiddle`2<T, U>
+    implements class IBase`3<!T, !U, int32>,
+               class IBase`3<!T, !U, float32>
+{
+    .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+        01 00 02 00 00
+    )
+    // Methods
+    .method private  hidebysig virtual 
+        instance int32 'IBase<T,U,System.Int32>.GetV' () cil managed 
+    {
+        .override method instance !2 class IBase`3<!T, !U, int32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IMiddle`2::'IBase<T,U,System.Int32>.GetV'
+
+    .method private  hidebysig virtual 
+        instance float32 'IBase<T,U,System.Single>.GetV' () cil managed 
+    {
+        .override method instance !2 class IBase`3<!T, !U, float32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IMiddle`2::'IBase<T,U,System.Single>.GetV'
+
+} // end of class IMiddle`2
+
+.class interface public auto ansi abstract beforefieldinit IDerived`1<T>
+    implements class IMiddle`2<!T, int64>,
+               class IMiddle`2<!T, float64>
+    {
+    .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+        01 00 02 00 00
+    )
+    // Methods
+    .method private  hidebysig virtual 
+        instance int32 'IBase<T,System.Int64,System.Int32>.GetV' () cil managed 
+    {
+        .override method instance !2 class IBase`3<!T, int64, int32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Int32>.GetV'
+
+    .method private  hidebysig virtual 
+        instance float32 'IBase<T,System.Int64,System.Single>.GetV' () cil managed 
+    {
+        .override method instance !2 class IBase`3<!T, int64, float32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Single>.GetV'
+
+    .method private  hidebysig virtual 
+        instance int64 'IBase<T,System.Int64,System.Int32>.GetU' () cil managed 
+    {
+        .override method instance !1 class IBase`3<!T, int64, int32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Int32>.GetU'
+
+    .method private  hidebysig virtual 
+        instance int64 'IBase<T,System.Int64,System.Single>.GetU' () cil managed 
+    {
+        .override method instance !1 class IBase`3<!T, int64, float32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Single>.GetU'
+
+    .method private  hidebysig virtual 
+        instance float64 'IBase<T,System.Double,System.Int32>.GetU' () cil managed 
+    {
+        .override method instance !1 class IBase`3<!T, float64, int32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Double,System.Int32>.GetU'
+
+    .method private  hidebysig virtual 
+        instance float64 'IBase<T,System.Double,System.Single>.GetU' () cil managed 
+    {
+        .override method instance !1 class IBase`3<!T, float64, float32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Double,System.Single>.GetU'
+
+} // end of class IDerived`1
+
+.class public auto ansi beforefieldinit MyClass
+    extends [mscorlib]System.Object
+    implements class IDerived`1<string>,
+               class IDerived`1<char>
+{
+    // Methods
+    .method private  hidebysig newslot virtual 
+        instance string 'IBase<System.String,System.Int64,System.Single>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method instance !0 class IBase`3<string, int64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Int64,System.Single>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance string 'IBase<System.String,System.Double,System.Int32>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method instance !0 class IBase`3<string, float64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Double,System.Int32>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance string 'IBase<System.String,System.Double,System.Single>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method instance !0 class IBase`3<string, float64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Double,System.Single>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance string 'IBase<System.String,System.Int64,System.Int32>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method instance !0 class IBase`3<string, int64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Int64,System.Int32>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance char 'IBase<System.Char,System.Int64,System.Int32>.GetT' () cil managed 
+    {
+        .override method instance !0 class IBase`3<char, int64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Int64,System.Int32>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance char 'IBase<System.Char,System.Int64,System.Single>.GetT' () cil managed 
+    {
+        .override method instance !0 class IBase`3<char, int64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Int64,System.Single>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance char 'IBase<System.Char,System.Double,System.Int32>.GetT' () cil managed 
+    {
+        .override method instance !0 class IBase`3<char, float64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Double,System.Int32>.GetT'
+
+    .method private  hidebysig newslot virtual 
+        instance char 'IBase<System.Char,System.Double,System.Single>.GetT' () cil managed 
+    {
+        .override method instance !0 class IBase`3<char, float64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Double,System.Single>.GetT'
+
+    .method public hidebysig specialname rtspecialname 
+        instance void .ctor () cil managed 
+    {
+        // Method begins at RVA 0x2081
+        // Code size 7 (0x7)
+        .maxstack 8
+
+        IL_0000: ldarg.0
+        IL_0001: call instance void [mscorlib]System.Object::.ctor()
+        IL_0006: ret
+    } // end of method MyClass::.ctor
+
+} // end of class MyClass
+
+.class public auto ansi beforefieldinit MyDerivedClass
+    extends MyClass
+{
+    // Methods
+    .method public hidebysig specialname rtspecialname 
+        instance void .ctor () cil managed 
+    {
+        // Method begins at RVA 0x20c0
+        // Code size 7 (0x7)
+        .maxstack 8
+
+        IL_0000: ldarg.0
+        IL_0001: call instance void MyClass::.ctor()
+        IL_0006: ret
+    } // end of method MyDerivedClass::.ctor
+
+} // end of class MyDerivedClass
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il
new file mode 100644
index 00000000000000..9a37ab31140c38
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il
@@ -0,0 +1,322 @@
+.assembly extern mscorlib { }
+
+.assembly 'library' { }
+
+.class interface public auto ansi abstract beforefieldinit IBase`3<T, U, V>
+{
+    .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+        01 00 01 00 00
+    )
+    .param type T
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
+            01 00 02 00 00
+        )
+    .param type U
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
+            01 00 02 00 00
+        )
+    .param type V
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
+            01 00 02 00 00
+        )
+    // Methods
+    .method public hidebysig newslot abstract virtual 
+        static !T GetT () cil managed 
+    {
+    } // end of method IBase`3::GetT
+
+    .method public hidebysig newslot virtual 
+        static !U GetU () cil managed 
+    {
+        // Method begins at RVA 0x2050
+        // Code size 10 (0xa)
+        .maxstack 1
+        .locals init (
+            [0] !U
+        )
+
+        IL_0000: ldloca.s 0
+        IL_0002: initobj !U
+        IL_0008: ldloc.0
+        IL_0009: ret
+    } // end of method IBase`3::GetU
+
+    .method public hidebysig newslot virtual 
+        static !V GetV () cil managed 
+    {
+        // Method begins at RVA 0x2068
+        // Code size 10 (0xa)
+        .maxstack 1
+        .locals init (
+            [0] !V
+        )
+
+        IL_0000: ldloca.s 0
+        IL_0002: initobj !V
+        IL_0008: ldloc.0
+        IL_0009: ret
+    } // end of method IBase`3::GetV
+
+} // end of class IBase`3
+
+.class interface public auto ansi abstract beforefieldinit IMiddle`2<T, U>
+    implements class IBase`3<!T, !U, int32>,
+               class IBase`3<!T, !U, float32>
+{
+    .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+        01 00 02 00 00
+    )
+    // Methods
+    .method private hidebysig
+        static int32 'IBase<T,U,System.Int32>.GetV' () cil managed 
+    {
+        .override method !2 class IBase`3<!T, !U, int32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IMiddle`2::'IBase<T,U,System.Int32>.GetV'
+
+    .method private hidebysig
+        static float32 'IBase<T,U,System.Single>.GetV' () cil managed 
+    {
+        .override method !2 class IBase`3<!T, !U, float32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IMiddle`2::'IBase<T,U,System.Single>.GetV'
+
+} // end of class IMiddle`2
+
+.class interface public auto ansi abstract beforefieldinit IDerived`1<T>
+    implements class IMiddle`2<!T, int64>,
+               class IMiddle`2<!T, float64>
+    {
+    .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+        01 00 02 00 00
+    )
+    // Methods
+    .method private hidebysig
+        static int32 'IBase<T,System.Int64,System.Int32>.GetV' () cil managed 
+    {
+        .override method !2 class IBase`3<!T, int64, int32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Int32>.GetV'
+
+    .method private hidebysig
+        static float32 'IBase<T,System.Int64,System.Single>.GetV' () cil managed 
+    {
+        .override method !2 class IBase`3<!T, int64, float32>::GetV()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Single>.GetV'
+
+    .method private hidebysig
+        static int64 'IBase<T,System.Int64,System.Int32>.GetU' () cil managed 
+    {
+        .override method !1 class IBase`3<!T, int64, int32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Int32>.GetU'
+
+    .method private hidebysig
+        static int64 'IBase<T,System.Int64,System.Single>.GetU' () cil managed 
+    {
+        .override method !1 class IBase`3<!T, int64, float32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Int64,System.Single>.GetU'
+
+    .method private hidebysig
+        static float64 'IBase<T,System.Double,System.Int32>.GetU' () cil managed 
+    {
+        .override method !1 class IBase`3<!T, float64, int32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Double,System.Int32>.GetU'
+
+    .method private hidebysig
+        static float64 'IBase<T,System.Double,System.Single>.GetU' () cil managed 
+    {
+        .override method !1 class IBase`3<!T, float64, float32>::GetU()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method IDerived`1::'IBase<T,System.Double,System.Single>.GetU'
+
+} // end of class IDerived`1
+
+.class public auto ansi beforefieldinit MyClass
+    extends [mscorlib]System.Object
+    implements class IDerived`1<string>,
+               class IDerived`1<char>
+{
+    // Methods
+    .method private hidebysig newslot
+        static string 'IBase<System.String,System.Int64,System.Single>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method !0 class IBase`3<string, int64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Int64,System.Single>.GetT'
+
+    .method private  hidebysig newslot
+        static string 'IBase<System.String,System.Double,System.Int32>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method !0 class IBase`3<string, float64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Double,System.Int32>.GetT'
+
+    .method private hidebysig newslot
+        static string 'IBase<System.String,System.Double,System.Single>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method !0 class IBase`3<string, float64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Double,System.Single>.GetT'
+
+    .method private hidebysig newslot
+        static string 'IBase<System.String,System.Int64,System.Int32>.GetT' () cil managed 
+    {
+        .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
+            01 00 01 00 00
+        )
+        .override method !0 class IBase`3<string, int64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.String,System.Int64,System.Int32>.GetT'
+
+    .method private hidebysig newslot
+        static char 'IBase<System.Char,System.Int64,System.Int32>.GetT' () cil managed 
+    {
+        .override method !0 class IBase`3<char, int64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Int64,System.Int32>.GetT'
+
+    .method private hidebysig newslot
+        static char 'IBase<System.Char,System.Int64,System.Single>.GetT' () cil managed 
+    {
+        .override method !0 class IBase`3<char, int64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Int64,System.Single>.GetT'
+
+    .method private hidebysig newslot
+        static char 'IBase<System.Char,System.Double,System.Int32>.GetT' () cil managed 
+    {
+        .override method !0 class IBase`3<char, float64, int32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Double,System.Int32>.GetT'
+
+    .method private hidebysig newslot
+        static char 'IBase<System.Char,System.Double,System.Single>.GetT' () cil managed 
+    {
+        .override method !0 class IBase`3<char, float64, float32>::GetT()
+        // Method begins at RVA 0x207e
+        // Code size 2 (0x2)
+        .maxstack 8
+
+        IL_0000: ldnull
+        IL_0001: throw
+    } // end of method MyClass::'IBase<System.Char,System.Double,System.Single>.GetT'
+
+    .method public hidebysig specialname rtspecialname 
+        instance void .ctor () cil managed 
+    {
+        // Method begins at RVA 0x2081
+        // Code size 7 (0x7)
+        .maxstack 8
+
+        IL_0000: ldarg.0
+        IL_0001: call instance void [mscorlib]System.Object::.ctor()
+        IL_0006: ret
+    } // end of method MyClass::.ctor
+
+} // end of class MyClass
+
+.class public auto ansi beforefieldinit MyDerivedClass
+    extends MyClass
+{
+    // Methods
+    .method public hidebysig specialname rtspecialname 
+        instance void .ctor () cil managed 
+    {
+        // Method begins at RVA 0x20c0
+        // Code size 7 (0x7)
+        .maxstack 8
+
+        IL_0000: ldarg.0
+        IL_0001: call instance void MyClass::.ctor()
+        IL_0006: ret
+    } // end of method MyDerivedClass::.ctor
+
+} // end of class MyDerivedClass
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs
index 6e703f79d4650d..71a772029587ad 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs
@@ -16,14 +16,14 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
 	[Define ("IL_ASSEMBLY_AVAILABLE")]
 	[SetupCompileBefore ("library.dll", new[] { "Dependencies/GenericInterfaceImplementedRecursively.il" })]
 	[SkipILVerify]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddle`1", "library.dll", "Program/IBase`1<T>")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IDerived`1", "library.dll", "Program/IMiddle`1<T>")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "Program/C", "library.dll", "Program/IDerived`1<System.Int32>")]
 #if IL_ASSEMBLY_AVAILABLE
 	[KeptTypeInAssembly ("library.dll", typeof(Program.IBase<>))]
 	[KeptTypeInAssembly ("library.dll", typeof(Program.IMiddle<>))]
-	[KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddle`1", "library.dll", "Program/IBase`1<T>")]
 	[KeptTypeInAssembly ("library.dll", typeof(Program.IDerived<>))]
-	[KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IDerived`1", "library.dll", "Program/IMiddle`1<T>")]
 	[KeptTypeInAssembly ("library.dll", typeof(Program.C))]
-	[KeptInterfaceOnTypeInAssembly ("library.dll", "Program/C", "library.dll", "Program/IDerived`1<System.Int32>")]
 #endif
 	/// <summary>
 	/// This test case is to verify that the linker will keep all the metadata necessary for C to implement IBase when an interfaceImpl isn't directly on C.
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs
new file mode 100644
index 00000000000000..90c1a319c353f7
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs
@@ -0,0 +1,94 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
+{
+	[SetupLinkerArgument ("--skip-unresolved", "true")]
+	[TestCaseRequirements (TestRunCharacteristics.SupportsDefaultInterfaceMethods, "Requires support for default interface methods")]
+	[Define ("IL_ASSEMBLY_AVAILABLE")]
+	[SetupCompileBefore ("library.dll", new[] { "Dependencies/RecursiveGenericInterfaces.il" })]
+	[KeptAllTypesAndMembersInAssembly ("library.dll")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Int32>.GetV", "V IBase`3<T,U,System.Int32>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Single>.GetV", "V IBase`3<T,U,System.Single>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Int32>.GetV", "V IBase`3<T,System.Int64,System.Int32>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Single>.GetV", "V IBase`3<T,System.Int64,System.Single>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Int32>.GetU", "U IBase`3<T,System.Int64,System.Int32>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Single>.GetU", "U IBase`3<T,System.Int64,System.Single>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Double,System.Int32>.GetU", "U IBase`3<T,System.Double,System.Int32>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Double,System.Single>.GetU", "U IBase`3<T,System.Double,System.Single>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Int64,System.Int32>.GetT", "T IBase`3<System.Char,System.Int64,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Double,System.Int32>.GetT", "T IBase`3<System.Char,System.Double,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Int64,System.Single>.GetT", "T IBase`3<System.Char,System.Int64,System.Single>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Double,System.Single>.GetT", "T IBase`3<System.Char,System.Double,System.Single>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Int64,System.Int32>.GetT", "T IBase`3<System.String,System.Int64,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Double,System.Int32>.GetT", "T IBase`3<System.String,System.Double,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Int64,System.Single>.GetT", "T IBase`3<System.String,System.Int64,System.Single>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Double,System.Single>.GetT", "T IBase`3<System.String,System.Double,System.Single>::GetT()")]
+
+	public class RecursiveGenericInterfaces
+	{
+		[Kept]
+		public static void Main ()
+		{
+#if IL_ASSEMBLY_AVAILABLE
+			UseIBase<char, double, float> (new MyDerivedClass ());
+		}
+
+		[Kept]
+		public static void UseIBase<T, U, V> (IBase<T, U, V> myBase)
+		{
+			myBase.GetT ();
+			myBase.GetU ();
+			myBase.GetV ();
+#endif
+		}
+
+		//public interface IBase<T, U, V>
+		//{
+		//	T GetT ();
+		//	U GetU () => default;
+		//	V GetV () => default;
+		//}
+
+		//public interface IMiddle<T, U> : IBase<T, U, int>, IBase<T, U, float>
+		//{
+		//	int IBase<T, U, int>.GetV () => 12;
+		//	float IBase<T, U, float>.GetV () => 12.0f;
+		//}
+
+		//public interface IDerived<T> : IMiddle<T, long>, IMiddle<T, double>
+		//{
+		//	int IBase<T, long, int>.GetV () => 12;
+		//	float IBase<T, long, float>.GetV () => 12.0f;
+
+		//	long IBase<T, long, int>.GetU () => 12;
+		//	long IBase<T, long, float>.GetU () => 12;
+
+		//	double IBase<T, double, int>.GetU () => 12;
+		//	double IBase<T, double, float>.GetU () => 12;
+		//}
+
+		//public class MyClass : IDerived<string>, IDerived<char>
+		//{
+		//	string IBase<string, long, float>.GetT () => throw new NotImplementedException ();
+		//	string IBase<string, double, int>.GetT () => throw new NotImplementedException ();
+		//	string IBase<string, double, float>.GetT () => throw new NotImplementedException ();
+		//	string IBase<string, long, int>.GetT () => throw new NotImplementedException ();
+		//	char IBase<char, long, int>.GetT () => throw new NotImplementedException ();
+		//	char IBase<char, long, float>.GetT () => throw new NotImplementedException ();
+		//	char IBase<char, double, int>.GetT () => throw new NotImplementedException ();
+		//	char IBase<char, double, float>.GetT () => throw new NotImplementedException ();
+		//}
+
+		//public class MyDerivedClass : MyClass
+		//{ }
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs
new file mode 100644
index 00000000000000..28fa0a4f3e4c16
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs
@@ -0,0 +1,125 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
+{
+	[SetupLinkerArgument ("--skip-unresolved", "true")]
+	[TestCaseRequirements (TestRunCharacteristics.SupportsDefaultInterfaceMethods | TestRunCharacteristics.SupportsStaticInterfaceMethods, "Requires support for default and static interface methods")]
+	[Define ("IL_ASSEMBLY_AVAILABLE")]
+	[SetupCompileBefore ("library.dll", new[] { "Dependencies/RecursiveGenericInterfacesStatic.il" })]
+	[KeptAllTypesAndMembersInAssembly ("library.dll")]
+	[KeptMemberInAssembly ("library.dll", "IBase`3", "GetT()", "GetU()", "GetV()")]
+	[KeptTypeInAssembly ("library.dll", "IMiddle`2")]
+	// Below isn't strictly necessary, but we keep them since the interface is generic and we haven't hardened generic interface handling to only keep the single closest DIM.
+	// We use method definition to match the .override to the required DIM. However, one DIM might be for a different generic instance than we are searching for.
+	// Because of this, we keep all generic interface DIMs that may be the DIM we need.
+	[KeptMemberInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Int32>.GetV()", "IBase<T,U,System.Single>.GetV()")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3<T,U,System.Int32>")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3<T,U,System.Single>")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Int32>.GetV", "V IBase`3<T,U,System.Int32>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Single>.GetV", "V IBase`3<T,U,System.Single>::GetV()")]
+	[KeptTypeInAssembly ("library.dll", "IDerived`1")]
+	[KeptMemberInAssembly ("library.dll", "IDerived`1",
+			"IBase<T,System.Int64,System.Int32>.GetV()",
+			"IBase<T,System.Int64,System.Single>.GetV()",
+			"IBase<T,System.Int64,System.Int32>.GetU()",
+			"IBase<T,System.Int64,System.Single>.GetU()",
+			"IBase<T,System.Double,System.Int32>.GetU()",
+			"IBase<T,System.Double,System.Single>.GetU()")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2<T,System.Int64>")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2<T,System.Double>")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Int32>.GetV", "V IBase`3<T,System.Int64,System.Int32>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Single>.GetV", "V IBase`3<T,System.Int64,System.Single>::GetV()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Int32>.GetU", "U IBase`3<T,System.Int64,System.Int32>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Single>.GetU", "U IBase`3<T,System.Int64,System.Single>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Double,System.Int32>.GetU", "U IBase`3<T,System.Double,System.Int32>::GetU()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Double,System.Single>.GetU", "U IBase`3<T,System.Double,System.Single>::GetU()")]
+	[KeptTypeInAssembly ("library.dll", "MyClass")]
+	[KeptMemberInAssembly ("library.dll", "MyClass",
+			"IBase<System.Char,System.Int64,System.Int32>.GetT()",
+			"IBase<System.Char,System.Double,System.Int32>.GetT()",
+			"IBase<System.Char,System.Int64,System.Single>.GetT()",
+			"IBase<System.Char,System.Double,System.Single>.GetT()",
+			"IBase<System.String,System.Int64,System.Int32>.GetT()",
+			"IBase<System.String,System.Double,System.Int32>.GetT()",
+			"IBase<System.String,System.Int64,System.Single>.GetT()",
+			"IBase<System.String,System.Double,System.Single>.GetT()")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "MyClass", "library.dll", "IDerived`1<System.Char>")]
+	[KeptInterfaceOnTypeInAssembly ("library.dll", "MyClass", "library.dll", "IDerived`1<System.String>")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Int64,System.Int32>.GetT", "T IBase`3<System.Char,System.Int64,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Double,System.Int32>.GetT", "T IBase`3<System.Char,System.Double,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Int64,System.Single>.GetT", "T IBase`3<System.Char,System.Int64,System.Single>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.Char,System.Double,System.Single>.GetT", "T IBase`3<System.Char,System.Double,System.Single>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Int64,System.Int32>.GetT", "T IBase`3<System.String,System.Int64,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Double,System.Int32>.GetT", "T IBase`3<System.String,System.Double,System.Int32>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Int64,System.Single>.GetT", "T IBase`3<System.String,System.Int64,System.Single>::GetT()")]
+	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Double,System.Single>.GetT", "T IBase`3<System.String,System.Double,System.Single>::GetT()")]
+	public class RecursiveGenericInterfacesStatic
+	{
+		[Kept]
+		public static void Main ()
+		{
+
+#if IL_ASSEMBLY_AVAILABLE
+			UseIBase<char, double, float, MyDerivedClass> ();
+			_ = new MyDerivedClass ();
+		}
+
+		[Kept]
+		public static void UseIBase<T, U, V, TBase> () where TBase: IBase<T, U, V>
+		{
+			TBase.GetT ();
+			TBase.GetU ();
+			TBase.GetV ();
+#endif
+		}
+
+		//public interface IBase<T, U, V>
+		//{
+		//	static abstract T GetT ();
+		//	static virtual U GetU () => default;
+		//	static virtual V GetV () => default;
+		//}
+
+		//public interface IMiddle<T, U> : IBase<T, U, int>, IBase<T, U, float>
+		//{
+		//	static int IBase<T, U, int>.GetV () => 12;
+		//	static float IBase<T, U, float>.GetV () => 12.0f;
+		//}
+
+		//public interface IDerived<T> : IMiddle<T, long>, IMiddle<T, double>
+		//{
+		//	static int IBase<T, long, int>.GetV () => 12;
+		//	static float IBase<T, long, float>.GetV () => 12.0f;
+
+		//	static long IBase<T, long, int>.GetU () => 12;
+		//	static long IBase<T, long, float>.GetU () => 12;
+
+		//	static double IBase<T, double, int>.GetU () => 12;
+		//	static double IBase<T, double, float>.GetU () => 12;
+		//}
+
+		//public class MyClass : IDerived<string>, IDerived<char>
+		//{
+		//	static string IBase<string, long, float>.GetT () => throw new NotImplementedException ();
+		//	static string IBase<string, double, int>.GetT () => throw new NotImplementedException ();
+		//	static string IBase<string, double, float>.GetT () => throw new NotImplementedException ();
+		//	static string IBase<string, long, int>.GetT () => throw new NotImplementedException ();
+		//	static char IBase<char, long, int>.GetT () => throw new NotImplementedException ();
+		//	static char IBase<char, long, float>.GetT () => throw new NotImplementedException ();
+		//	static char IBase<char, double, int>.GetT () => throw new NotImplementedException ();
+		//	static char IBase<char, double, float>.GetT () => throw new NotImplementedException ();
+		//}
+
+		//public class MyDerivedClass : MyClass
+		//{ }
+	}
+}

From 8e94b0ef8a40fda9ae12c0e895a4ca713084385f Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 11 Jun 2024 14:21:05 -0700
Subject: [PATCH 02/27] Use RuntimeInterfaceImplementations in
 MethodBodyScanner and ICustomMarshaler marking

---
 .../src/linker/Linker.Steps/MarkStep.cs       | 36 +++++++------------
 .../src/linker/Linker/MethodBodyScanner.cs    | 31 +++++-----------
 2 files changed, 20 insertions(+), 47 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index b473064e59a9a8..dbb7cc87d70409 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -2600,31 +2600,18 @@ void MarkCustomMarshalerGetInstance (TypeDefinition type, in DependencyInfo reas
 
 		void MarkICustomMarshalerMethods (TypeDefinition inputType, in DependencyInfo reason, MessageOrigin origin)
 		{
-			TypeDefinition? type = inputType;
-			do {
-				if (!type.HasInterfaces)
-					continue;
-
-				foreach (var iface in type.Interfaces) {
-					var iface_type = iface.InterfaceType;
-					if (!iface_type.IsTypeOf ("System.Runtime.InteropServices", "ICustomMarshaler"))
-						continue;
-
-					//
-					// Instead of trying to guess where to find the interface declaration ILLink walks
-					// the list of implemented interfaces and resolve the declaration from there
-					//
-					var tdef = Context.Resolve (iface_type);
-					if (tdef == null) {
-						return;
-					}
+			var runtimeInterfaces = Annotations.GetRuntimeInterfaces (inputType);
+			if (runtimeInterfaces is null)
+				return;
+			foreach (var runtimeInterface in runtimeInterfaces) {
 
-					MarkMethodsIf (tdef.Methods, m => !m.IsStatic, reason, origin);
+				var iface_type = runtimeInterface.InterfaceTypeDefinition;
+				if (false == iface_type?.IsTypeOf ("System.Runtime.InteropServices", "ICustomMarshaler"))
+					continue;
+				MarkMethodsIf (iface_type!.Methods, m => !m.IsStatic, reason, origin);
 
-					MarkInterfaceImplementation (iface, new MessageOrigin (type));
-					return;
-				}
-			} while ((type = Context.TryResolve (type.BaseType)) != null);
+				MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (Context.Resolve (runtimeInterface.TypeWithInterfaceImplementation)));
+			}
 		}
 
 		bool IsNonEmptyStaticConstructor (MethodDefinition method)
@@ -3310,6 +3297,7 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type
 
 			DoAdditionalInstantiatedTypeProcessing (type);
 		}
+
 		void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReference ov)
 		{
 			if (Context.Resolve (ov) is not MethodDefinition resolvedOverride)
@@ -3647,7 +3635,7 @@ void MarkInterfacesNeededByBodyStack (MethodIL methodIL)
 				return;
 
 			foreach (var (implementation, type) in implementations)
-				MarkInterfaceImplementation (implementation, new MessageOrigin (type));
+				MarkRuntimeInterfaceImplementation (implementation, new MessageOrigin (type));
 		}
 
 		bool InstructionRequiresReflectionMethodBodyScannerForFieldAccess (Instruction instruction)
diff --git a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs
index b3a230f1947959..41e1f1464506b4 100644
--- a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs
+++ b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs
@@ -63,7 +63,7 @@ public InterfacesOnStackScanner (LinkContext context)
 			this.context = context;
 		}
 
-		public IEnumerable<(InterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodIL methodIL)
+		public IEnumerable<(RuntimeInterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodIL methodIL)
 		{
 			var possibleStackTypes = AllPossibleStackTypes (methodIL);
 			if (possibleStackTypes.Count == 0)
@@ -73,7 +73,7 @@ public InterfacesOnStackScanner (LinkContext context)
 			if (interfaceTypes.Length == 0)
 				return null;
 
-			var interfaceImplementations = new HashSet<(InterfaceImplementation, TypeDefinition)> ();
+			var interfaceImplementations = new HashSet<(RuntimeInterfaceImplementation, TypeDefinition)> ();
 
 			// If a type could be on the stack in the body and an interface it implements could be on the stack on the body
 			// then we need to mark that interface implementation.  When this occurs it is not safe to remove the interface implementation from the type
@@ -139,31 +139,16 @@ HashSet<TypeDefinition> AllPossibleStackTypes (MethodIL methodIL)
 			return types;
 		}
 
-		void AddMatchingInterfaces (HashSet<(InterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes)
+		void AddMatchingInterfaces (HashSet<(RuntimeInterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes)
 		{
-			if (!type.HasInterfaces)
+			var runtimeInterfaces = context.Annotations.GetRuntimeInterfaces (type);
+			if (runtimeInterfaces is null)
 				return;
-
-			foreach (var interfaceType in interfaceTypes) {
-				if (HasInterface (type, interfaceType, out InterfaceImplementation? implementation))
-					results.Add ((implementation, type));
-			}
-		}
-
-		bool HasInterface (TypeDefinition type, TypeDefinition interfaceType, [NotNullWhen (true)] out InterfaceImplementation? implementation)
-		{
-			implementation = null;
-			if (!type.HasInterfaces)
-				return false;
-
-			foreach (var iface in type.Interfaces) {
-				if (context.TryResolve (iface.InterfaceType) == interfaceType) {
-					implementation = iface;
-					return true;
+			foreach (var iface in runtimeInterfaces) {
+				if (interfaceTypes.Contains (iface.InterfaceTypeDefinition)) {
+					results.Add ((iface, type));
 				}
 			}
-
-			return false;
 		}
 
 		void AddFromGenericInstance (HashSet<TypeDefinition> set, IGenericInstance instance)

From cf304f206c1905c6d24d9aec9d1dcaf9b9aa9bc0 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 11 Jun 2024 15:04:49 -0700
Subject: [PATCH 03/27] Add remarks regarding checking for marked ifaceImpls
 with RuntimeInterfaceImpl

---
 .../src/linker/Linker/RuntimeInterfaceImplementation.cs      | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
index 9328414f580eff..b9286a1bf0afd9 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -14,6 +14,11 @@ namespace Mono.Linker
 	/// Represents an implementation of an interface on a type that may be directly on the type or on a base type or implemented interface.
 	/// This type is considered to implement the interface at runtime, even though the interface may not be directly on the type.
 	/// </summary>
+	/// <remarks>
+	/// This type should be used for marking, but should NOT be used to check if a runtime interface implementation has been marked.
+	/// This type represents the most direct way an interface may be implemented, but it may be implemented in a less direct way that is not represented here.
+	/// You should check all possible implementation 'paths' to determine if an interface is implemented, for example <see cref="MarkStep.IsInterfaceImplementationMarkedRecursively"/>.
+	/// </remarks>
 	public class RuntimeInterfaceImplementation
 	{
 		/// <summary>

From fcbfbb72b4c19b46a233c46971685ddded76065a Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 11 Jun 2024 15:36:59 -0700
Subject: [PATCH 04/27] Fix ApiCompat failures

---
 src/tools/illink/src/linker/Linker/OverrideInformation.cs       | 1 -
 .../illink/src/linker/Linker/RuntimeInterfaceImplementation.cs  | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
index 30075f7a697ed8..6eeba6e9e41aa1 100644
--- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs
+++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
@@ -29,7 +29,6 @@ internal OverrideInformation (MethodDefinition @base, MethodDefinition @override
 			Debug.Assert (@base.DeclaringType.IsInterface ? runtimeInterface!.InterfaceTypeDefinition == @base.DeclaringType : true);
 		}
 
-		[Obsolete ("Use RuntimeInterfaceImplementation instead to handle recursive interfaces")]
 		public InterfaceImplementation? MatchingInterfaceImplementation
 			=> RuntimeInterfaceImplementation?.InterfaceImplementation[0];
 
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
index b9286a1bf0afd9..87aa7cd1fda879 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -19,7 +19,7 @@ namespace Mono.Linker
 	/// This type represents the most direct way an interface may be implemented, but it may be implemented in a less direct way that is not represented here.
 	/// You should check all possible implementation 'paths' to determine if an interface is implemented, for example <see cref="MarkStep.IsInterfaceImplementationMarkedRecursively"/>.
 	/// </remarks>
-	public class RuntimeInterfaceImplementation
+	internal class RuntimeInterfaceImplementation
 	{
 		/// <summary>
 		/// The type that implements <see cref="RuntimeInterfaceImplementation.InflatedInterfaceType"/>.

From e5e0679608be6391b19231c8a2b97339869b8c5c Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 11 Jun 2024 16:35:40 -0700
Subject: [PATCH 05/27] Fix visibility and sealed warnings

---
 src/tools/illink/src/linker/Linker.Steps/MarkStep.cs       | 4 ++--
 .../src/linker/Linker/RuntimeInterfaceImplementation.cs    | 2 +-
 src/tools/illink/src/linker/Linker/TypeMapInfo.cs          | 7 +++----
 3 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index dbb7cc87d70409..95c550137c9327 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -32,6 +32,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
@@ -2410,8 +2411,7 @@ void MarkInterfaceImplementations (TypeDefinition type)
 			}
 		}
 
-
-		protected virtual bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation)
+		internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation)
 		{
 			var type = runtimeInterfaceImplementation.Implementor;
 			var ifaces = runtimeInterfaceImplementation.InterfaceImplementation;
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
index 87aa7cd1fda879..292e0da0bd426d 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -19,7 +19,7 @@ namespace Mono.Linker
 	/// This type represents the most direct way an interface may be implemented, but it may be implemented in a less direct way that is not represented here.
 	/// You should check all possible implementation 'paths' to determine if an interface is implemented, for example <see cref="MarkStep.IsInterfaceImplementationMarkedRecursively"/>.
 	/// </remarks>
-	internal class RuntimeInterfaceImplementation
+	internal sealed class RuntimeInterfaceImplementation
 	{
 		/// <summary>
 		/// The type that implements <see cref="RuntimeInterfaceImplementation.InflatedInterfaceType"/>.
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index ec7206addba0fe..3a4c19ecc54fa2 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -38,7 +38,6 @@
 
 namespace Mono.Linker
 {
-
 	public class TypeMapInfo
 	{
 		readonly HashSet<AssemblyDefinition> assemblies = new HashSet<AssemblyDefinition> ();
@@ -100,17 +99,17 @@ public void EnsureProcessed (AssemblyDefinition assembly)
 			return ret;
 		}
 
-		public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, RuntimeInterfaceImplementation? runtimeInterface)
+		internal void AddBaseMethod (MethodDefinition method, MethodDefinition @base, RuntimeInterfaceImplementation? runtimeInterface)
 		{
 			base_methods.AddToList (method, new OverrideInformation (@base, method, runtimeInterface));
 		}
 
-		public void AddOverride (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null)
+		internal void AddOverride (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null)
 		{
 			override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface));
 		}
 
-		public void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod)
+		internal void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod)
 		{
 			Debug.Assert (@base.DeclaringType.IsInterface);
 			default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface));

From 451b677f1f78e12d2935e69e4097447a8b88cb07 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Wed, 12 Jun 2024 14:06:42 -0700
Subject: [PATCH 06/27] Add DAM annotations for recursive interfaces

---
 .../src/System/Reflection/Context/Custom/CustomType.cs          | 2 ++
 .../src/System/Reflection/Context/Projection/ProjectingType.cs  | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
index 95ee34ab4504b6..56ab55515ce194 100644
--- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
+++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
@@ -44,6 +44,7 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o)
             return IsAssignableFrom(objectType);
         }
 
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
         public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
         {
             // list of properties on this type according to the underlying ReflectionContext
@@ -139,6 +140,7 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
             return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers);
         }
 
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
         public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
         {
             // list of methods on this type according to the underlying ReflectionContext
diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
index 0ac9bb0de89f38..e5fd04e198a230 100644
--- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
+++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
@@ -206,11 +206,13 @@ public override EventInfo[] GetEvents(BindingFlags bindingAttr)
             return _projector.Project(base.GetEvents(bindingAttr), _projector.ProjectEvent);
         }
 
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
         public override FieldInfo? GetField(string name, BindingFlags bindingAttr)
         {
             return _projector.ProjectField(base.GetField(name, bindingAttr));
         }
 
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
         public override FieldInfo[] GetFields(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetFields(bindingAttr), _projector.ProjectField);

From 1e1451aaf6f8d9fa6c7eb47d8187493c1252be53 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Thu, 13 Jun 2024 11:02:00 -0700
Subject: [PATCH 07/27] Add additional test cases and annotate reflection types
 with ifdefs

---
 .../Reflection/Context/Custom/CustomType.cs   |  4 +
 .../Context/Projection/ProjectingType.cs      | 40 +++++++++
 ...dInterfaceImplementationRemovedOverride.cs | 89 +++++++++++++++++++
 3 files changed, 133 insertions(+)

diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
index 56ab55515ce194..0641a9ff4e22e8 100644
--- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
+++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
@@ -44,7 +44,9 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o)
             return IsAssignableFrom(objectType);
         }
 
+#if NET6_0_OR_GREATER
         [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
+#endif
         public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
         {
             // list of properties on this type according to the underlying ReflectionContext
@@ -140,7 +142,9 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
             return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers);
         }
 
+#if NET6_0_OR_GREATER
         [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
+#endif
         public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
         {
             // list of methods on this type according to the underlying ReflectionContext
diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
index e5fd04e198a230..4f02989adde576 100644
--- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
+++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
@@ -78,6 +78,9 @@ public override IList<CustomAttributeData> GetCustomAttributesData()
             return _projector.Project(base.GetCustomAttributesData(), _projector.ProjectCustomAttributeData);
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)]
+#endif
         public override EventInfo[] GetEvents()
         {
             return _projector.Project(base.GetEvents(), _projector.ProjectEvent);
@@ -105,6 +108,15 @@ public override InterfaceMapping GetInterfaceMap(Type interfaceType)
             return _projector.ProjectInterfaceMapping(base.GetInterfaceMap(interfaceType));
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(
+            DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields |
+            DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
+            DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents |
+            DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
+            DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors |
+            DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)]
+#endif
         public override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr)
         {
             StringComparison comparisonType = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase
@@ -186,6 +198,9 @@ public override bool IsSubclassOf(Type c)
             return _projector.ProjectConstructor(base.GetConstructorImpl(bindingAttr, binder, callConvention, types, modifiers));
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
+#endif
         public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetConstructors(bindingAttr), _projector.ProjectConstructor);
@@ -196,23 +211,33 @@ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
             return _projector.ProjectType(base.GetElementType());
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)]
+#endif
         public override EventInfo? GetEvent(string name, BindingFlags bindingAttr)
         {
             return _projector.ProjectEvent(base.GetEvent(name, bindingAttr));
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)]
+#endif
         public override EventInfo[] GetEvents(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetEvents(bindingAttr), _projector.ProjectEvent);
         }
 
+#if NET6_0_OR_GREATER
         [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
+#endif
         public override FieldInfo? GetField(string name, BindingFlags bindingAttr)
         {
             return _projector.ProjectField(base.GetField(name, bindingAttr));
         }
 
+#if NET6_0_OR_GREATER
         [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
+#endif
         public override FieldInfo[] GetFields(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetFields(bindingAttr), _projector.ProjectField);
@@ -228,6 +253,15 @@ public override Type[] GetInterfaces()
             return _projector.Project(base.GetInterfaces(), _projector.ProjectType);
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(
+            DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields |
+            DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
+            DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents |
+            DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
+            DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors |
+            DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)]
+#endif
         public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
         {
             MethodInfo[] methods = GetMethods(bindingAttr);
@@ -266,6 +300,9 @@ public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
             return _projector.ProjectMethod(base.GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers));
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
+#endif
         public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetMethods(bindingAttr), _projector.ProjectMethod);
@@ -281,6 +318,9 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr)
             return _projector.Project(base.GetNestedTypes(bindingAttr), _projector.ProjectType);
         }
 
+#if NET6_0_OR_GREATER
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
+#endif
         public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetProperties(bindingAttr), _projector.ProjectProperty);
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs
index 01541dd73a17b8..499a08d5218570 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs
@@ -22,6 +22,8 @@ public static void Main ()
 			GenericInterfaceGenericType.Test ();
 			PrivateExplicitImplementationReflectedOver.Test ();
 			InheritedInterfaces.Test ();
+			DimWithOverride.Test ();
+			Generics.Test ();
 		}
 
 		[Kept]
@@ -274,5 +276,92 @@ class UsedDirectly : IDerived
 				public static void M () { }
 			}
 		}
+
+		[Kept]
+		public class DimWithOverride
+		{
+			[Kept]
+			public static void Test ()
+			{
+				CallM<MyDerivedClass> ();
+			}
+
+			[Kept]
+			static void CallM<T> () where T : IBase
+			{
+				T.M ();
+			}
+
+			[Kept]
+			interface IBase
+			{
+				[Kept]
+				static abstract void M ();
+			}
+
+			[Kept]
+			[KeptInterface (typeof (IBase))]
+			interface IDerived : IBase
+			{
+				[Kept]
+				static void IBase.M () { }
+			}
+
+			[Kept]
+			[KeptInterface (typeof (IDerived))]
+			[KeptInterface (typeof (IBase))]
+			class MyClass : IDerived
+			{
+			}
+
+			[Kept]
+			[KeptBaseType (typeof (MyClass))]
+			[KeptInterface (typeof (IBase))]
+			class MyDerivedClass : MyClass, IBase
+			{
+			}
+		}
+
+		[Kept]
+		public class Generics
+		{
+			[Kept]
+			public static void Test ()
+			{
+				UseIGeneric<MyType> ();
+			}
+
+			[Kept]
+			static void UseIGeneric<T> () where T : IGeneric<int>
+			{
+				T.GetT ();
+			}
+
+			[Kept]
+			interface IGeneric<T>
+			{
+				[Kept]
+				static abstract T GetT ();
+			}
+
+			[Kept]
+			[KeptInterface (typeof (IGeneric<int>))]
+			[KeptInterface (typeof (IGeneric<float>))]
+			[KeptInterface (typeof (IGeneric<string>))]
+			class MyType : IGeneric<int>, IGeneric<float>, IGeneric<string>
+			{
+				[Kept]
+				[KeptOverride (typeof (IGeneric<int>))]
+				static int IGeneric<int>.GetT () => 0;
+
+				[Kept]
+				[KeptOverride (typeof (IGeneric<float>))]
+				static float IGeneric<float>.GetT () => 0;
+
+				[Kept]
+				[KeptOverride (typeof (IGeneric<string>))]
+				static string IGeneric<string>.GetT () => "";
+			}
+		}
 	}
 }

From ac5a42a28a9dc8ca0f2c16ad0cabecb22a208218 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Thu, 13 Jun 2024 14:17:07 -0700
Subject: [PATCH 08/27] Use suppressions instead of annotating in
 System.Reflection.Context

---
 .../src/ILLink/ILLink.Suppressions.xml        | 50 ++++++++++++++++++-
 .../Reflection/Context/Custom/CustomType.cs   |  6 ---
 .../Context/Projection/ProjectingType.cs      | 42 ----------------
 3 files changed, 49 insertions(+), 49 deletions(-)

diff --git a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml
index 6e691bf6bc961d..8df6b6dd9fa8b8 100644
--- a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml
+++ b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml
@@ -523,5 +523,53 @@
       <property name="Scope">member</property>
       <property name="Target">M:System.Reflection.Context.Delegation.DelegatingType.GetInterface(System.String,System.Boolean)</property>
     </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetField(System.String,System.Reflection.BindingFlags)</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetFields(System.Reflection.BindingFlags)</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetMembers(System.Reflection.BindingFlags)</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetMethods(System.Reflection.BindingFlags)</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetProperties(System.Reflection.BindingFlags)</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetDefaultMembers()</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Custom.CustomType.GetProperties(System.Reflection.BindingFlags)</property>
+    </attribute>
+    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
+      <argument>ILLink</argument>
+      <argument>IL2094</argument>
+      <property name="Scope">member</property>
+      <property name="Target">M:System.Reflection.Context.Custom.CustomType.GetMethods(System.Reflection.BindingFlags)</property>
+    </attribute>
   </assembly>
-</linker>
\ No newline at end of file
+</linker>
diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
index 0641a9ff4e22e8..95ee34ab4504b6 100644
--- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
+++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs
@@ -44,9 +44,6 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o)
             return IsAssignableFrom(objectType);
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
-#endif
         public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
         {
             // list of properties on this type according to the underlying ReflectionContext
@@ -142,9 +139,6 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
             return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers);
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
-#endif
         public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
         {
             // list of methods on this type according to the underlying ReflectionContext
diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
index 4f02989adde576..0ac9bb0de89f38 100644
--- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
+++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs
@@ -78,9 +78,6 @@ public override IList<CustomAttributeData> GetCustomAttributesData()
             return _projector.Project(base.GetCustomAttributesData(), _projector.ProjectCustomAttributeData);
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)]
-#endif
         public override EventInfo[] GetEvents()
         {
             return _projector.Project(base.GetEvents(), _projector.ProjectEvent);
@@ -108,15 +105,6 @@ public override InterfaceMapping GetInterfaceMap(Type interfaceType)
             return _projector.ProjectInterfaceMapping(base.GetInterfaceMap(interfaceType));
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(
-            DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields |
-            DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
-            DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents |
-            DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
-            DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors |
-            DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)]
-#endif
         public override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr)
         {
             StringComparison comparisonType = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase
@@ -198,9 +186,6 @@ public override bool IsSubclassOf(Type c)
             return _projector.ProjectConstructor(base.GetConstructorImpl(bindingAttr, binder, callConvention, types, modifiers));
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
-#endif
         public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetConstructors(bindingAttr), _projector.ProjectConstructor);
@@ -211,33 +196,21 @@ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
             return _projector.ProjectType(base.GetElementType());
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)]
-#endif
         public override EventInfo? GetEvent(string name, BindingFlags bindingAttr)
         {
             return _projector.ProjectEvent(base.GetEvent(name, bindingAttr));
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)]
-#endif
         public override EventInfo[] GetEvents(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetEvents(bindingAttr), _projector.ProjectEvent);
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
-#endif
         public override FieldInfo? GetField(string name, BindingFlags bindingAttr)
         {
             return _projector.ProjectField(base.GetField(name, bindingAttr));
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
-#endif
         public override FieldInfo[] GetFields(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetFields(bindingAttr), _projector.ProjectField);
@@ -253,15 +226,6 @@ public override Type[] GetInterfaces()
             return _projector.Project(base.GetInterfaces(), _projector.ProjectType);
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(
-            DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields |
-            DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods |
-            DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents |
-            DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
-            DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors |
-            DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)]
-#endif
         public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
         {
             MethodInfo[] methods = GetMethods(bindingAttr);
@@ -300,9 +264,6 @@ public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
             return _projector.ProjectMethod(base.GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers));
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
-#endif
         public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetMethods(bindingAttr), _projector.ProjectMethod);
@@ -318,9 +279,6 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr)
             return _projector.Project(base.GetNestedTypes(bindingAttr), _projector.ProjectType);
         }
 
-#if NET6_0_OR_GREATER
-        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
-#endif
         public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
         {
             return _projector.Project(base.GetProperties(bindingAttr), _projector.ProjectProperty);

From 4e1f6f9e86b30e4a278069567296b8c6a68826ed Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 17 Jun 2024 11:39:37 -0700
Subject: [PATCH 09/27] Add test and fix for failure in System.Text.Json

When the base type has an interface and an unmarked derived type
overrides a method on that interface, we would mark the overriding
method without checking if the type is even marked. We will check if the
declaring type is marked before marking the override.
---
 .../src/linker/Linker.Steps/MarkStep.cs       |  3 +
 .../BaseTypeMarkedInterfaceDerivedNotKept.cs  | 56 +++++++++++++++++++
 2 files changed, 59 insertions(+)
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index 95c550137c9327..9731e51ea5ec2d 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -2513,6 +2513,9 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat
 			if (Annotations.IsMarked (method))
 				return false;
 
+			if (!Annotations.IsMarked (method.DeclaringType))
+				return false;
+
 			// If the interface implementation is not marked, do not mark the implementation method
 			// A type that doesn't implement the interface isn't required to have methods that implement the interface.
 			// We must check all possible ways the interface could be implemented by the type (through all recursive interface implementations, not just the primary one)
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs
new file mode 100644
index 00000000000000..999d0c2855d426
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs
@@ -0,0 +1,56 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+
+namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
+{
+	class BaseTypeMarkedInterfaceDerivedNotKept
+	{
+		[Kept]
+		public static void Main ()
+		{
+			IFoo foo = new UsedDerived ();
+			foo.Method ();
+		}
+
+		[Kept]
+		[KeptInterface (typeof (IFoo))]
+		[KeptMember (".ctor()")]
+		abstract class BaseType : IFoo
+		{
+			[Kept]
+			public abstract void Method ();
+		}
+
+		[Kept]
+		[KeptBaseType (typeof (BaseType))]
+		[KeptMember (".ctor()")]
+		class UsedDerived : BaseType
+		{
+			[Kept]
+			public override void Method ()
+			{
+			}
+		}
+
+		class UnusedDerived : BaseType
+		{
+			public override void Method ()
+			{
+			}
+		}
+
+		[Kept]
+		interface IFoo
+		{
+			[Kept]
+			void Method ();
+		}
+	}
+}

From f8dad6ba114d0d94b7f6b918427ab87fe2ceb573 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 17 Jun 2024 13:56:59 -0700
Subject: [PATCH 10/27] Check for implementor to be marked, not
 overridingMethod.DeclaringType

---
 src/tools/illink/src/linker/Linker.Steps/MarkStep.cs |  2 +-
 ...eritance.Interfaces.RecursiveInterfacesTests.g.cs | 12 ++++++++++++
 ...tance.Interfaces.StaticInterfaceMethodsTests.g.cs |  6 ++++++
 3 files changed, 19 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 9731e51ea5ec2d..9ac54b5f36d81f 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -2513,7 +2513,7 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat
 			if (Annotations.IsMarked (method))
 				return false;
 
-			if (!Annotations.IsMarked (method.DeclaringType))
+			if (!Annotations.IsMarked (overrideInformation.RuntimeInterfaceImplementation.Implementor))
 				return false;
 
 			// If the interface implementation is not marked, do not mark the implementation method
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
index a1f78cc09b63c3..1dbdf7bd5c9ac5 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
@@ -9,6 +9,12 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase
 
 		protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces";
 
+		[Fact]
+		public Task BaseTypeMarkedInterfaceDerivedNotKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task GenericInterfaceImplementedRecursively ()
 		{
@@ -21,6 +27,12 @@ public Task InterfaceImplementedRecursively ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task OverrideOfRecursiveInterfaceIsRemoved ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task RecursiveGenericInterfaces ()
 		{
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs
index 12c5692019fdae..531c0cb0d7d08a 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs
@@ -7,6 +7,12 @@ namespace ILLink.RoslynAnalyzer.Tests.Inheritance.Interfaces
 	public sealed partial class StaticInterfaceMethodsTests : LinkerTestBase
 	{
 
+		[Fact]
+		public Task InstanceMethodsWithOverridesSwept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task OverrideInCopyAssembly ()
 		{

From 2aca6c4c13af009499dd303446be6863bd46d73c Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 17 Jun 2024 15:28:42 -0700
Subject: [PATCH 11/27] PR feedback:

- Make methods local methods with captured variables
- Inline local variables
- Only store interfaces in runtime interfaces dictionary if the type
  implements 1 or more interfaces
- Rename variables
---
 .../illink/src/linker/Linker/TypeMapInfo.cs   | 78 ++++++++++---------
 1 file changed, 43 insertions(+), 35 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index 3a4c19ecc54fa2..4cc31ce50c01bf 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -109,16 +109,12 @@ internal void AddOverride (MethodDefinition @base, MethodDefinition @override, R
 			override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface));
 		}
 
-		internal void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod)
-		{
-			Debug.Assert (@base.DeclaringType.IsInterface);
-			default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface));
-		}
-
-		Dictionary<TypeDefinition, ImmutableArray<RuntimeInterfaceImplementation>> interfaces = new ();
+		Dictionary<TypeDefinition, ImmutableArray<RuntimeInterfaceImplementation>> _runtimeInterfaceImpls = new ();
 		protected virtual void MapType (TypeDefinition type)
 		{
-			interfaces[type] = GetRecursiveInterfaceImplementations (type);
+			var runtimeInterfaces = GetRecursiveInterfaceImplementations (type);
+			if (runtimeInterfaces is not null)
+				_runtimeInterfaceImpls[type] = runtimeInterfaces.Value;
 			MapVirtualMethods (type);
 			MapInterfaceMethodsInTypeHierarchy (type);
 
@@ -132,59 +128,67 @@ protected virtual void MapType (TypeDefinition type)
 		internal ImmutableArray<RuntimeInterfaceImplementation>? GetRecursiveInterfaces (TypeDefinition type)
 		{
 			EnsureProcessed (type.Module.Assembly);
-			if (interfaces.TryGetValue (type, out var value))
+			if (_runtimeInterfaceImpls.TryGetValue (type, out var value))
 				return value;
 			return null;
 		}
 
-		ImmutableArray<RuntimeInterfaceImplementation> GetRecursiveInterfaceImplementations (TypeDefinition type)
+		ImmutableArray<RuntimeInterfaceImplementation>? GetRecursiveInterfaceImplementations (TypeDefinition originalType)
 		{
 			// We'll need at least this many interfaces, so let's preallocate
-			var runtimeIfaces = ImmutableArray.CreateBuilder<RuntimeInterfaceImplementation> (type.Interfaces.Count);
+			ImmutableArray<RuntimeInterfaceImplementation>.Builder runtimeIfaces = ImmutableArray.CreateBuilder<RuntimeInterfaceImplementation> (originalType.Interfaces.Count);
 
-			AddRecursiveInterfaces (type, type, [], runtimeIfaces, context);
+			List<InterfaceImplementation> interfaces = new List<InterfaceImplementation> (16);
+			AddRecursiveInterfaces (originalType, interfaces);
 
+			if (runtimeIfaces.Count == 0)
+				return null;
 			return runtimeIfaces.ToImmutable ();
 
-			static void AddRecursiveInterfaces (TypeDefinition originalType, TypeReference typeRef, IEnumerable<InterfaceImplementation> pathToType, ImmutableArray<RuntimeInterfaceImplementation>.Builder implementors, LinkContext Context)
+			void AddRecursiveInterfaces (TypeReference typeRef, List<InterfaceImplementation> pathToType)
 			{
-				var type = Context.TryResolve (typeRef);
+				var type = context.TryResolve (typeRef);
 				if (type is null)
 					return;
 				// Get all explicit interfaces of this type
 				foreach (var iface in type.Interfaces) {
-					var interfaceType = iface.InterfaceType.TryInflateFrom (typeRef, Context);
+					var interfaceType = iface.InterfaceType.TryInflateFrom (typeRef, context);
 					if (interfaceType is null) {
 						continue;
 					}
-					if (!implementors.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, Context))) {
-						implementors.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, Context));
+					if (!runtimeIfaces.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, context))) {
+						pathToType.Add (iface);
+						runtimeIfaces.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, context));
+						pathToType.RemoveAt (pathToType.Count - 1);
 					}
 				}
 
 				if (type.BaseType is not null) {
-					var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, Context)!;
-					AddRecursiveInterfaces (originalType, inflatedBaseType, [], implementors, Context);
+					var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, context)!;
+					AddRecursiveInterfaces (inflatedBaseType, []);
 				}
 
 				// Recursive interfaces after all direct interfaces to preserve Inherit/Implement tree order
 				foreach (var iface in type.Interfaces) {
 					// If we can't resolve the interface type we can't find recursive interfaces
-					var ifaceDirectlyOnType = iface.InterfaceType.TryInflateFrom (typeRef, Context);
+					var ifaceDirectlyOnType = iface.InterfaceType.TryInflateFrom (typeRef, context);
 					if (ifaceDirectlyOnType is null) {
 						continue;
 					}
-					AddRecursiveInterfaces (originalType, ifaceDirectlyOnType, pathToType.Append (iface), implementors, Context);
+					pathToType.Add (iface);
+					AddRecursiveInterfaces (ifaceDirectlyOnType, pathToType);
+					pathToType.RemoveAt (pathToType.Count - 1);
 				}
 			}
 		}
 
 		void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
 		{
-			var inflatedInterfaces = interfaces[type];
+			if (!_runtimeInterfaceImpls.TryGetValue (type, out var runtimeInterfaceImpls))
+				return;
 			// Foreach interface and for each newslot virtual method on the interface, try
 			// to find the method implementation and record it.
-			foreach (var interfaceImpl in inflatedInterfaces) {
+			foreach (var interfaceImpl in runtimeInterfaceImpls) {
 				foreach ((MethodReference interfaceMethod, MethodDefinition resolvedInterfaceMethod) in interfaceImpl.InflatedInterfaceType.GetMethods (context)) {
 					// TODO-NICE: if the interface method is implemented explicitly (with an override),
 					// we shouldn't need to run the below logic. This results in ILLink potentially
@@ -257,7 +261,7 @@ void MapOverrides (MethodDefinition method)
 				if (baseMethod == null)
 					continue;
 				if (baseMethod.DeclaringType.IsInterface) {
-					var found = interfaces.TryGetValue (method.DeclaringType, out var runtimeInterfaces);
+					var found = _runtimeInterfaceImpls.TryGetValue (method.DeclaringType, out var runtimeInterfaces);
 					Debug.Assert (found);
 					var runtimeInterface = runtimeInterfaces.Single (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, baseMethodRef.DeclaringType, context));
 					AnnotateMethods (baseMethod, method, runtimeInterface);
@@ -332,17 +336,15 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Runtim
 		/// <param name="implOfInterface">
 		/// The InterfaceImplementation on <paramref name="type"/> that points to the DeclaringType of <paramref name="interfaceMethod"/>.
 		/// </param>
-		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceReference, MethodDefinition interfaceMethodToBeImplemented)
+		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceMethod, MethodDefinition interfaceMethodDef)
 		{
-			var typeThatImplementsInterface = originalInterfaceImpl.Implementor;
 			// Go over all interfaces, trying to find a method that is an explicit MethodImpl of the
 			// interface method in question.
-			var hasInterfaces = interfaces.TryGetValue (typeThatImplementsInterface, out var runtimeIfaces);
-			if (!hasInterfaces)
+			if (!_runtimeInterfaceImpls.TryGetValue (originalInterfaceImpl.Implementor, out var runtimeIfaces))
 				return;
 
-			bool findAllPossibleImplementations = inflatedInterfaceReference.DeclaringType is GenericInstanceType;
-			foreach (var interfaceImpl in runtimeIfaces!) {
+			bool findAllPossibleImplementations = inflatedInterfaceMethod.DeclaringType is GenericInstanceType;
+			foreach (var interfaceImpl in runtimeIfaces) {
 				var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition;
 				if (potentialImplInterface is null)
 					continue;
@@ -352,9 +354,9 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 				// Otherwise, we're good to stop looking higher in the hierarchy after the first. Note we still need to look at other branches of the hierarchy to find any diamond cases.
 				bool foundImpl = false;
 				foreach (var potentialImplMethod in potentialImplInterface.Methods) {
-					if (potentialImplMethod == interfaceMethodToBeImplemented &&
+					if (potentialImplMethod == interfaceMethodDef &&
 						!potentialImplMethod.IsAbstract) {
-						AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, potentialImplMethod);
+						AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, potentialImplMethod);
 						foundImpl = true;
 						break;
 					}
@@ -364,8 +366,8 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 
 					// This method is an override of something. Let's see if it's the method we are looking for.
 					foreach (var baseMethod in potentialImplMethod.Overrides) {
-						if (context.TryResolve (baseMethod) == interfaceMethodToBeImplemented) {
-							AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, @potentialImplMethod);
+						if (context.TryResolve (baseMethod) == interfaceMethodDef) {
+							AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, @potentialImplMethod);
 							foundImpl = true && !findAllPossibleImplementations;
 						}
 					}
@@ -374,12 +376,18 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 					}
 				}
 			}
+
+			void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod)
+			{
+				Debug.Assert (@base.DeclaringType.IsInterface);
+				default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface));
+			}
 		}
 
 		MethodDefinition? TryMatchMethod (TypeReference type, MethodReference method)
 		{
 			foreach (var (candidate, md) in type.GetMethods (context)) {
-				if (md.IsVirtual != true)
+				if (!md.IsVirtual)
 					continue;
 				if (MethodMatch (candidate, method))
 					return md;

From bb308c7e99e1576625bcba9052ad6d51a4f76bb9 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 17 Jun 2024 17:34:46 -0700
Subject: [PATCH 12/27] Separate out generic DIM matching fix

---
 src/tools/illink/src/linker/Linker/TypeMapInfo.cs  |  5 ++---
 .../RecursiveGenericInterfacesStatic.cs            | 14 ++------------
 2 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index 4cc31ce50c01bf..8e5056cb07edbb 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -336,14 +336,13 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Runtim
 		/// <param name="implOfInterface">
 		/// The InterfaceImplementation on <paramref name="type"/> that points to the DeclaringType of <paramref name="interfaceMethod"/>.
 		/// </param>
-		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceMethod, MethodDefinition interfaceMethodDef)
+		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference _, MethodDefinition interfaceMethodDef)
 		{
 			// Go over all interfaces, trying to find a method that is an explicit MethodImpl of the
 			// interface method in question.
 			if (!_runtimeInterfaceImpls.TryGetValue (originalInterfaceImpl.Implementor, out var runtimeIfaces))
 				return;
 
-			bool findAllPossibleImplementations = inflatedInterfaceMethod.DeclaringType is GenericInstanceType;
 			foreach (var interfaceImpl in runtimeIfaces) {
 				var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition;
 				if (potentialImplInterface is null)
@@ -368,7 +367,7 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 					foreach (var baseMethod in potentialImplMethod.Overrides) {
 						if (context.TryResolve (baseMethod) == interfaceMethodDef) {
 							AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, @potentialImplMethod);
-							foundImpl = true && !findAllPossibleImplementations;
+							foundImpl = true;
 						}
 					}
 					if (foundImpl) {
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs
index 28fa0a4f3e4c16..b01ee019c6b93e 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs
@@ -15,33 +15,23 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
 	[TestCaseRequirements (TestRunCharacteristics.SupportsDefaultInterfaceMethods | TestRunCharacteristics.SupportsStaticInterfaceMethods, "Requires support for default and static interface methods")]
 	[Define ("IL_ASSEMBLY_AVAILABLE")]
 	[SetupCompileBefore ("library.dll", new[] { "Dependencies/RecursiveGenericInterfacesStatic.il" })]
-	[KeptAllTypesAndMembersInAssembly ("library.dll")]
 	[KeptMemberInAssembly ("library.dll", "IBase`3", "GetT()", "GetU()", "GetV()")]
 	[KeptTypeInAssembly ("library.dll", "IMiddle`2")]
 	// Below isn't strictly necessary, but we keep them since the interface is generic and we haven't hardened generic interface handling to only keep the single closest DIM.
 	// We use method definition to match the .override to the required DIM. However, one DIM might be for a different generic instance than we are searching for.
 	// Because of this, we keep all generic interface DIMs that may be the DIM we need.
-	[KeptMemberInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Int32>.GetV()", "IBase<T,U,System.Single>.GetV()")]
+	[KeptMemberInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Int32>.GetV()")]
 	[KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3<T,U,System.Int32>")]
 	[KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3<T,U,System.Single>")]
 	[KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Int32>.GetV", "V IBase`3<T,U,System.Int32>::GetV()")]
-	[KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase<T,U,System.Single>.GetV", "V IBase`3<T,U,System.Single>::GetV()")]
 	[KeptTypeInAssembly ("library.dll", "IDerived`1")]
 	[KeptMemberInAssembly ("library.dll", "IDerived`1",
 			"IBase<T,System.Int64,System.Int32>.GetV()",
-			"IBase<T,System.Int64,System.Single>.GetV()",
-			"IBase<T,System.Int64,System.Int32>.GetU()",
-			"IBase<T,System.Int64,System.Single>.GetU()",
-			"IBase<T,System.Double,System.Int32>.GetU()",
-			"IBase<T,System.Double,System.Single>.GetU()")]
+			"IBase<T,System.Int64,System.Int32>.GetU()")]
 	[KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2<T,System.Int64>")]
 	[KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2<T,System.Double>")]
 	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Int32>.GetV", "V IBase`3<T,System.Int64,System.Int32>::GetV()")]
-	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Single>.GetV", "V IBase`3<T,System.Int64,System.Single>::GetV()")]
 	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Int32>.GetU", "U IBase`3<T,System.Int64,System.Int32>::GetU()")]
-	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Int64,System.Single>.GetU", "U IBase`3<T,System.Int64,System.Single>::GetU()")]
-	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Double,System.Int32>.GetU", "U IBase`3<T,System.Double,System.Int32>::GetU()")]
-	[KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase<T,System.Double,System.Single>.GetU", "U IBase`3<T,System.Double,System.Single>::GetU()")]
 	[KeptTypeInAssembly ("library.dll", "MyClass")]
 	[KeptMemberInAssembly ("library.dll", "MyClass",
 			"IBase<System.Char,System.Int64,System.Int32>.GetT()",

From 48484488cea6aee913789e5ff0ef34bce375a1e0 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Wed, 26 Jun 2024 09:25:10 -0700
Subject: [PATCH 13/27] Remove unused parameter, Add test case

---
 .../illink/src/linker/Linker/TypeMapInfo.cs   |  7 ++--
 .../BaseProvidesInterfaceMethod.cs            | 36 +++++++++++++++++++
 2 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index 8e5056cb07edbb..a43f37fdd6bd81 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -219,7 +219,7 @@ void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
 					}
 
 					// Look for a default implementation last.
-					FindAndAddDefaultInterfaceImplementations (interfaceImpl, interfaceMethod, resolvedInterfaceMethod);
+					FindAndAddDefaultInterfaceImplementations (interfaceImpl, resolvedInterfaceMethod);
 				}
 			}
 		}
@@ -336,7 +336,7 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Runtim
 		/// <param name="implOfInterface">
 		/// The InterfaceImplementation on <paramref name="type"/> that points to the DeclaringType of <paramref name="interfaceMethod"/>.
 		/// </param>
-		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference _, MethodDefinition interfaceMethodDef)
+		void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodDefinition interfaceMethodDef)
 		{
 			// Go over all interfaces, trying to find a method that is an explicit MethodImpl of the
 			// interface method in question.
@@ -348,9 +348,6 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 				if (potentialImplInterface is null)
 					continue;
 
-				// We go by MethodDefinition here and may miss the correct implementation by exiting early after finding one.
-				// To correct for this, we'll look for all possible implementations and add them all if the inflatedIntefaceMethodReference is a genericInstance
-				// Otherwise, we're good to stop looking higher in the hierarchy after the first. Note we still need to look at other branches of the hierarchy to find any diamond cases.
 				bool foundImpl = false;
 				foreach (var potentialImplMethod in potentialImplInterface.Methods) {
 					if (potentialImplMethod == interfaceMethodDef &&
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs
index 081430458926b7..6fe4ac5d8fd55a 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs
@@ -11,6 +11,7 @@ class BaseProvidesInterfaceMethod
 		public static void Main ()
 		{
 			CallMethod<Derived> ();
+			CallMN<D> ();
 		}
 
 		[Kept]
@@ -38,5 +39,40 @@ class Base
 		// Compiler generates private explicit implementation that calls Base.Method()
 		class Derived : Base, IFoo
 		{ }
+
+		[Kept]
+		static void CallMN<T> () where T : I
+		{
+			T.M ();
+			T.N ();
+		}
+
+		[Kept]
+		interface I
+		{
+			[Kept]
+			static abstract string M ();
+			[Kept]
+			static abstract string N ();
+		}
+
+		[Kept]
+		[KeptInterface (typeof (I))]
+		class B : I
+		{
+			[Kept]
+			public static string M () => "B.M";
+			[Kept]
+			static string I.N () => "B's I.N";
+		}
+
+		[Kept]
+		[KeptBaseType (typeof (B))]
+		[KeptInterface (typeof (I))]
+		class D : B, I
+		{
+			[Kept]
+			static string I.N () => "D's I.N";
+		}
 	}
 }

From dec9745975ecee04155c0cc3d85bd41753558086 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Wed, 26 Jun 2024 17:27:43 -0700
Subject: [PATCH 14/27] Mark all implementation chains for interface impls

- All chains of interfaceImpls are tracked in a
  RuntimeInterfaceImplementation
- Reused previously calculated runtime interfaces of base types and
  explicit interfaces and inflates the relevant types to calculate the
  types of derived/implementing types.
- Updates tests with new behavior. Many cases where interfaces were
  swept are now marked as a result of marking all paths to an interface.
- This implementation will blow up if there are ever cycles in a type
  hierarchy.
---
 .../src/linker/Linker.Steps/MarkStep.cs       | 28 +++----
 .../illink/src/linker/Linker/Annotations.cs   |  5 ++
 .../Linker/InterfaceImplementationChain.cs    | 49 ++++++++++++
 .../illink/src/linker/Linker/LinkContext.cs   |  2 +-
 .../src/linker/Linker/OverrideInformation.cs  |  2 +-
 .../Linker/RuntimeInterfaceImplementation.cs  | 74 ++++++++++++++-----
 .../illink/src/linker/Linker/TypeMapInfo.cs   | 71 ++++++++----------
 .../DataFlow/GenericParameterDataFlow.cs      |  2 +-
 .../UnusedDefaultInterfaceImplementation.cs   |  4 +
 ...icInterfaceWithMethodManyBaseInterfaces.cs |  9 ++-
 ...ClassImplementingInterfaceMethodsNested.cs |  5 +-
 .../NestedInterfaces1.cs                      |  3 +-
 .../NestedInterfaces2.cs                      |  6 +-
 .../NestedInterfaces3.cs                      |  7 +-
 ...tructImplementingInterfaceMethodsNested.cs |  5 +-
 .../RecursiveInterfaceKept.cs                 |  8 +-
 16 files changed, 195 insertions(+), 85 deletions(-)
 create mode 100644 src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index 9ac54b5f36d81f..dde982e81ab0d5 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -160,6 +160,7 @@ internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeH
 			DependencyKind.ReturnTypeMarshalSpec,
 			DependencyKind.DynamicInterfaceCastableImplementation,
 			DependencyKind.XmlDescriptor,
+			DependencyKind.InterfaceImplementationOnType,
 		};
 
 		static readonly DependencyKind[] _methodReasons = new DependencyKind[] {
@@ -712,13 +713,6 @@ void MarkMethodIfNeededByBaseMethod (MethodDefinition method, MessageOrigin orig
 			}
 		}
 
-		/// <summary>
-		/// Returns true if <paramref name="type"/> implements <paramref name="interfaceType"/> and the interface implementation is marked,
-		/// or if any marked interface implementations on <paramref name="type"/> are interfaces that implement <paramref name="interfaceType"/> and that interface implementation is marked
-		/// </summary>
-		bool IsInterfaceImplementationMarkedRecursively (TypeDefinition type, TypeDefinition interfaceType)
-			=> IsInterfaceImplementationMarkedRecursively (type, interfaceType, Context);
-
 		/// <summary>
 		/// Returns true if <paramref name="type"/> implements <paramref name="interfaceType"/> and the interface implementation is marked,
 		/// or if any marked interface implementations on <paramref name="type"/> are interfaces that implement <paramref name="interfaceType"/> and that interface implementation is marked
@@ -2413,13 +2407,12 @@ void MarkInterfaceImplementations (TypeDefinition type)
 
 		internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation)
 		{
-			var type = runtimeInterfaceImplementation.Implementor;
-			var ifaces = runtimeInterfaceImplementation.InterfaceImplementation;
+			var implementinType = runtimeInterfaceImplementation.Implementor;
 			var ifaceType = runtimeInterfaceImplementation.InterfaceTypeDefinition;
-			if (ifaces.All (Annotations.IsMarked))
+			if (Annotations.IsMarked (runtimeInterfaceImplementation))
 				return false;
 
-			if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, type))
+			if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, implementinType))
 				return true;
 
 			if (ifaceType is null)
@@ -2433,7 +2426,7 @@ internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplemen
 			if (Context.KeepComInterfaces && (ifaceType.IsImport || ifaceType.IsWindowsRuntime))
 				return true;
 
-			return IsFullyPreserved (type);
+			return IsFullyPreserved (implementinType);
 		}
 
 		void MarkGenericParameterProvider (IGenericParameterProvider provider, MessageOrigin origin)
@@ -2519,7 +2512,7 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat
 			// If the interface implementation is not marked, do not mark the implementation method
 			// A type that doesn't implement the interface isn't required to have methods that implement the interface.
 			// We must check all possible ways the interface could be implemented by the type (through all recursive interface implementations, not just the primary one)
-			if (!(IsInterfaceImplementationMarkedRecursively (overrideInformation.RuntimeInterfaceImplementation.Implementor, @base.DeclaringType)))
+			if (!overrideInformation.RuntimeInterfaceImplementation.IsAnyImplementationMarked (Annotations, Context))
 				return false;
 
 			// If the interface method is not marked and the interface doesn't come from a preserved scope, do not mark the implementation method
@@ -2613,7 +2606,7 @@ void MarkICustomMarshalerMethods (TypeDefinition inputType, in DependencyInfo re
 					continue;
 				MarkMethodsIf (iface_type!.Methods, m => !m.IsStatic, reason, origin);
 
-				MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (Context.Resolve (runtimeInterface.TypeWithInterfaceImplementation)));
+				MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (inputType));
 			}
 		}
 
@@ -3748,8 +3741,11 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
 
 		void MarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterface, MessageOrigin origin, DependencyInfo? reason = null)
 		{
-			foreach (var iface in runtimeInterface.InterfaceImplementation) {
-				MarkInterfaceImplementation (iface, origin, reason);
+			Annotations.Mark (runtimeInterface);
+			foreach (var iface in runtimeInterface.InterfaceImplementationChains) {
+				MarkType (iface.TypeWithInterfaceImplementation, reason ?? new DependencyInfo (DependencyKind.InterfaceImplementationOnType, origin.Provider), origin);
+				foreach (var impl in iface.InterfaceImplementations)
+					MarkInterfaceImplementation (impl, origin, reason);
 			}
 		}
 
diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs
index cfcf350bb18040..5d6c4c68ed4ea4 100644
--- a/src/tools/illink/src/linker/Linker/Annotations.cs
+++ b/src/tools/illink/src/linker/Linker/Annotations.cs
@@ -72,6 +72,7 @@ public partial class AnnotationStore
 		protected readonly HashSet<MethodDefinition> indirectly_called = new HashSet<MethodDefinition> ();
 		protected readonly HashSet<TypeDefinition> types_relevant_to_variant_casting = new HashSet<TypeDefinition> ();
 		readonly HashSet<IMemberDefinition> reflection_used = new ();
+		readonly HashSet<RuntimeInterfaceImplementation> _marked_runtime_interface_implementations = new ();
 
 		public AnnotationStore (LinkContext context)
 		{
@@ -723,5 +724,9 @@ public void EnqueueVirtualMethod (MethodDefinition method)
 		{
 			return TypeMapInfo.GetRecursiveInterfaces (type);
 		}
+
+		internal bool IsMarked (RuntimeInterfaceImplementation runtimeInterface) => _marked_runtime_interface_implementations.Contains (runtimeInterface);
+
+		internal void Mark (RuntimeInterfaceImplementation runtimeInterface) => _marked_runtime_interface_implementations.Add (runtimeInterface);
 	}
 }
diff --git a/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs
new file mode 100644
index 00000000000000..dda9dbe78591b5
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs
@@ -0,0 +1,49 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Immutable;
+using System.Diagnostics;
+using Mono.Cecil;
+
+namespace Mono.Linker
+{
+	internal sealed class InterfaceImplementationChain
+	{
+		/// <summary>
+		/// The type that has the InterfaceImplementation - either the <see cref="Implementor"/> or a base type of it.
+		/// </summary>
+		public TypeReference TypeWithInterfaceImplementation { get; }
+
+		/// <summary>
+		/// The path of .interfaceimpl on <see cref="RuntimeInterfaceImplementation.Implementor"/> or a base type that terminates with <see cref="RuntimeInterfaceImplementation.InflatedInterfaceType"/>.
+		/// </summary>
+		public ImmutableArray<InterfaceImplementation> InterfaceImplementations { get; }
+
+		public InterfaceImplementationChain (TypeReference typeWithInterfaceImplementation, ImmutableArray<InterfaceImplementation> interfaceImplementation)
+		{
+			TypeWithInterfaceImplementation = typeWithInterfaceImplementation;
+			InterfaceImplementations = interfaceImplementation;
+		}
+
+		/// <summary>
+		/// Returns true if all the .interfaceImpls in the chain and the  are marked.
+		/// </summary>
+		/// <param name="annotations"></param>
+		/// <returns></returns>
+		public bool IsMarked (AnnotationStore annotations, ITryResolveMetadata context)
+		{
+			var typeDef = context.TryResolve (TypeWithInterfaceImplementation);
+			// If we have the .interfaceImpls on this type, it must be resolvable
+			Debug.Assert (typeDef is not null);
+			if (!annotations.IsMarked (typeDef))
+				return false;
+
+			foreach (var impl in InterfaceImplementations) {
+				if (!annotations.IsMarked (impl))
+					return false;
+			}
+
+			return true;
+		}
+	}
+}
diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs
index 9e6ec519d3054e..1c48f91c433051 100644
--- a/src/tools/illink/src/linker/Linker/LinkContext.cs
+++ b/src/tools/illink/src/linker/Linker/LinkContext.cs
@@ -199,7 +199,7 @@ internal TypeNameResolver TypeNameResolver {
 		public SerializationMarker SerializationMarker { get; }
 
 		public LinkContext (Pipeline pipeline, ILogger logger, string outputDirectory)
-			: this(pipeline, logger, outputDirectory, new UnintializedContextFactory ())
+			: this (pipeline, logger, outputDirectory, new UnintializedContextFactory ())
 		{
 		}
 
diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
index 6eeba6e9e41aa1..f4f667b20b2b29 100644
--- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs
+++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
@@ -30,7 +30,7 @@ internal OverrideInformation (MethodDefinition @base, MethodDefinition @override
 		}
 
 		public InterfaceImplementation? MatchingInterfaceImplementation
-			=> RuntimeInterfaceImplementation?.InterfaceImplementation[0];
+			=> RuntimeInterfaceImplementation?.InterfaceImplementationChains[0].InterfaceImplementations[0];
 
 		public TypeDefinition? InterfaceType
 			=> RuntimeInterfaceImplementation?.InterfaceTypeDefinition;
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
index 292e0da0bd426d..e7916e2011c41f 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -27,35 +27,75 @@ internal sealed class RuntimeInterfaceImplementation
 		public TypeDefinition Implementor { get; }
 
 		/// <summary>
-		/// The path of .interfaceimpl on <see cref="RuntimeInterfaceImplementation.Implementor"/> or a base type that terminates with <see cref="RuntimeInterfaceImplementation.InflatedInterfaceType"/>.
+		/// All the chains of .interfaceImpls that cause <see cref="Implementor"/> to implement <see cref="InflatedInterfaceType"/>
 		/// </summary>
-		public ImmutableArray<InterfaceImplementation> InterfaceImplementation { get; }
+		public ImmutableArray<InterfaceImplementationChain> InterfaceImplementationChains { get; }
 
 		/// <summary>
-		/// The type that has the InterfaceImplementation - either the <see cref="Implementor"/> or a base type of it.
-		/// </summary>
-		public TypeReference TypeWithInterfaceImplementation { get; }
-
-		/// <summary>
-		/// The type of the interface that is implemented by <see cref="RuntimeInterfaceImplementation.Implementor"/>.
-		/// This type may be different from the corresponding InterfaceImplementation.InterfaceType if it is generic.
-		/// Generic parameters are replaces with generic arguments from the implementing type.
-		/// Because of this, do not use this for comparisons or resolving. Use <see cref="InterfaceTypeDefinition"/> or <see cref="InterfaceImplementation">.Last().InterfaceType instead.
+		/// The inflated interface type reference that is implemented by <see cref="Implementor"/>.
 		/// </summary>
 		public TypeReference InflatedInterfaceType { get; }
 
 		/// <summary>
-		/// The resolved definition of the interface type implemented by <see cref="RuntimeInterfaceImplementation.Implementor"/>.
+		/// The <see cref="TypeDefinition"/> of <see cref="InflatedInterfaceType"/>
 		/// </summary>
 		public TypeDefinition? InterfaceTypeDefinition { get; }
 
-		public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference typeWithFirstIfaceImpl, IEnumerable<InterfaceImplementation> interfaceImplementation, TypeReference inflatedInterfaceType, LinkContext resolver)
+		public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference interfaceType, TypeDefinition? interfaceTypeDefinition, IEnumerable<InterfaceImplementationChain> interfaceImplementations)
 		{
 			Implementor = implementor;
-			TypeWithInterfaceImplementation = typeWithFirstIfaceImpl;
-			InterfaceImplementation = interfaceImplementation.ToImmutableArray ();
-			InflatedInterfaceType = inflatedInterfaceType;
-			InterfaceTypeDefinition = resolver.Resolve (InterfaceImplementation[InterfaceImplementation.Length - 1].InterfaceType);
+			InterfaceImplementationChains = interfaceImplementations.ToImmutableArray ();
+			InflatedInterfaceType = interfaceType;
+			InterfaceTypeDefinition = interfaceTypeDefinition;
+		}
+
+		public bool IsAnyImplementationMarked (AnnotationStore annotations, ITryResolveMetadata context)
+		{
+			if (annotations.IsMarked (this))
+				return true;
+			foreach (var chain in InterfaceImplementationChains) {
+				if (chain.IsMarked (annotations, context)) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// Returns a list of InterfaceImplementationChains for a derived type of <see cref="Implementor"/>.
+		/// </summary>
+		public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (TypeReference baseTypeRef, ITryResolveMetadata context)
+		{
+			// This is only valid for classes
+			Debug.Assert (Implementor.IsClass);
+			Debug.Assert (Implementor == context.TryResolve (baseTypeRef));
+
+			var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (baseTypeRef, context);
+			Debug.Assert (inflatedInterfaceType is not null);
+
+			foreach (var impl in InterfaceImplementationChains) {
+				var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, context);
+				Debug.Assert (inflatedImplProvider is not null);
+
+				yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations));
+			}
+		}
+
+		/// <summary>
+		/// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of <see cref="Implementor"/>.
+		/// </summary>
+		public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainForImplementingType (TypeDefinition typeThatImplementsImplementor, InterfaceImplementation impl, ITryResolveMetadata context)
+		{
+			Debug.Assert (Implementor.IsInterface);
+			Debug.Assert (typeThatImplementsImplementor.Interfaces.Contains (impl));
+			Debug.Assert (context.TryResolve (impl.InterfaceType) == Implementor);
+
+			var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (impl.InterfaceType, context);
+			Debug.Assert (inflatedInterfaceType is not null);
+
+			foreach (var existingImpl in InterfaceImplementationChains) {
+				yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeThatImplementsImplementor, existingImpl.InterfaceImplementations.Insert (0, impl)));
+			}
 		}
 	}
 }
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index a43f37fdd6bd81..61f788806f3147 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -112,9 +112,7 @@ internal void AddOverride (MethodDefinition @base, MethodDefinition @override, R
 		Dictionary<TypeDefinition, ImmutableArray<RuntimeInterfaceImplementation>> _runtimeInterfaceImpls = new ();
 		protected virtual void MapType (TypeDefinition type)
 		{
-			var runtimeInterfaces = GetRecursiveInterfaceImplementations (type);
-			if (runtimeInterfaces is not null)
-				_runtimeInterfaceImpls[type] = runtimeInterfaces.Value;
+			_runtimeInterfaceImpls[type] = GetRecursiveInterfaceImplementations (type);
 			MapVirtualMethods (type);
 			MapInterfaceMethodsInTypeHierarchy (type);
 
@@ -133,53 +131,48 @@ protected virtual void MapType (TypeDefinition type)
 			return null;
 		}
 
-		ImmutableArray<RuntimeInterfaceImplementation>? GetRecursiveInterfaceImplementations (TypeDefinition originalType)
+
+		ImmutableArray<RuntimeInterfaceImplementation> GetRecursiveInterfaceImplementations (TypeDefinition originalType)
 		{
-			// We'll need at least this many interfaces, so let's preallocate
-			ImmutableArray<RuntimeInterfaceImplementation>.Builder runtimeIfaces = ImmutableArray.CreateBuilder<RuntimeInterfaceImplementation> (originalType.Interfaces.Count);
+			if (_runtimeInterfaceImpls.TryGetValue (originalType, out var runtimeIfaces)) {
+				return runtimeIfaces;
+			}
 
-			List<InterfaceImplementation> interfaces = new List<InterfaceImplementation> (16);
-			AddRecursiveInterfaces (originalType, interfaces);
+			Dictionary<TypeReference, List<InterfaceImplementationChain>> interfaceTypeToImplChainMap = new (new TypeReferenceEqualityComparer (context));
 
-			if (runtimeIfaces.Count == 0)
-				return null;
-			return runtimeIfaces.ToImmutable ();
+			foreach (var explicitIface in originalType.Interfaces) {
+				// Add runtimeIface for this Iface
+				var resolvedInterfaceType = context.TryResolve (explicitIface.InterfaceType);
+				interfaceTypeToImplChainMap.AddToList (explicitIface.InterfaceType, new InterfaceImplementationChain (originalType, [explicitIface]));
 
-			void AddRecursiveInterfaces (TypeReference typeRef, List<InterfaceImplementation> pathToType)
-			{
-				var type = context.TryResolve (typeRef);
-				if (type is null)
-					return;
-				// Get all explicit interfaces of this type
-				foreach (var iface in type.Interfaces) {
-					var interfaceType = iface.InterfaceType.TryInflateFrom (typeRef, context);
-					if (interfaceType is null) {
-						continue;
-					}
-					if (!runtimeIfaces.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, context))) {
-						pathToType.Add (iface);
-						runtimeIfaces.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, context));
-						pathToType.RemoveAt (pathToType.Count - 1);
-					}
+				if (resolvedInterfaceType is null) {
+					continue;
 				}
 
-				if (type.BaseType is not null) {
-					var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, context)!;
-					AddRecursiveInterfaces (inflatedBaseType, []);
+				var recursiveIFaces = GetRecursiveInterfaceImplementations (resolvedInterfaceType);
+
+				foreach (var recursiveIface in recursiveIFaces) {
+					var impls = recursiveIface.CreateImplementationChainForImplementingType (originalType, explicitIface, context);
+					foreach (var impl in impls) {
+						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
+					}
 				}
+			}
 
-				// Recursive interfaces after all direct interfaces to preserve Inherit/Implement tree order
-				foreach (var iface in type.Interfaces) {
-					// If we can't resolve the interface type we can't find recursive interfaces
-					var ifaceDirectlyOnType = iface.InterfaceType.TryInflateFrom (typeRef, context);
-					if (ifaceDirectlyOnType is null) {
-						continue;
+			if (originalType.BaseType is not null && context.TryResolve (originalType.BaseType) is { } baseTypeDef) {
+				var baseTypeIfaces = GetRecursiveInterfaceImplementations (baseTypeDef);
+				foreach (var recursiveIface in baseTypeIfaces) {
+					var impls = recursiveIface.CreateImplementationChainsForDerivedType (originalType.BaseType, context);
+					foreach (var impl in impls) {
+						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
 					}
-					pathToType.Add (iface);
-					AddRecursiveInterfaces (ifaceDirectlyOnType, pathToType);
-					pathToType.RemoveAt (pathToType.Count - 1);
 				}
 			}
+
+			if (interfaceTypeToImplChainMap.Count == 0)
+				return ImmutableArray<RuntimeInterfaceImplementation>.Empty;
+
+			return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.Resolve (kvp.Key), kvp.Value)).ToImmutableArray ();
 		}
 
 		void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs
index 2cbd62f6d43432..c36de63d7f8925 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs
@@ -915,7 +915,7 @@ static void TestGenericParameterFlowsToDelegateMethodDeclaringTypeInstance<T> ()
 			Action a = instance.InstanceMethod;
 		}
 
-		class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>
+		class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T>
 		{
 			public static void Method ()
 			{
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs
index 23b029f2a34771..b89a50708476c4 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs
@@ -20,8 +20,11 @@ interface IFoo
 			void InterfaceMethod ();
 		}
 
+		[Kept] // Should be removable
+		[KeptInterface (typeof (IFoo))]
 		interface IDefaultImpl : IFoo
 		{
+			[Kept]
 			void IFoo.InterfaceMethod ()
 			{
 			}
@@ -29,6 +32,7 @@ void IFoo.InterfaceMethod ()
 
 		[Kept]
 		[KeptInterface (typeof (IFoo))]
+		[KeptInterface (typeof (IDefaultImpl))] // Should be removable
 		class Foo : IDefaultImpl
 		{
 			[Kept]
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs
index f9b338ead868e1..c85b9871a2f58d 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs
@@ -17,10 +17,15 @@ interface IFoo<T>
 			void Method (T arg);
 		}
 
+		[Kept] // Should be able to be removed
+		[KeptInterface (typeof (IFoo<>))]
 		interface IFoo2<T> : IFoo<T>
 		{
 		}
 
+		[Kept] // Should be able to be removed
+		[KeptInterface (typeof (IFoo<>))]
+		[KeptInterface (typeof (IFoo2<>))]
 		interface IFoo3<T> : IFoo2<T>
 		{
 		}
@@ -39,8 +44,10 @@ public void Method (object arg)
 		[KeptMember (".ctor()")]
 		[KeptBaseType (typeof (BaseFoo))]
 		[KeptInterface (typeof (IFoo<object>))]
+		[KeptInterface (typeof (IFoo2<object>))] // Should be removable
+		[KeptInterface (typeof (IFoo3<object>))] // Should be removable
 		class FooWithBase : BaseFoo, IFoo3<object>
 		{
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs
index c449911f6e3b43..14b2a7c76ce6bb 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs
@@ -17,6 +17,8 @@ interface IFoo
 			void Foo ();
 		}
 
+		[Kept]
+		[KeptInterface (typeof (IFoo))]
 		interface IBar : IFoo
 		{
 			void Bar ();
@@ -25,6 +27,7 @@ interface IBar : IFoo
 		[Kept]
 		[KeptMember (".ctor()")]
 		[KeptInterface (typeof (IFoo))]
+		[KeptInterface (typeof (IBar))]
 		class A : IBar
 		{
 			[Kept]
@@ -37,4 +40,4 @@ public void Bar ()
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs
index 434a4174bbea6f..9101c793b96173 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs
@@ -46,6 +46,7 @@ interface IBase3 : IBase2
 		/// </summary>
 		[Kept]
 		[KeptInterface (typeof (IBase3))]
+		[KeptInterface (typeof (IBase2))] // Should be removable
 		[KeptInterface (typeof (IBase))]
 		class Foo : IBase3
 		{
@@ -55,4 +56,4 @@ public void Method ()
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs
index 93f50accac1170..a23078cd33c291 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs
@@ -25,6 +25,8 @@ interface IBase
 		{
 		}
 
+		[Kept] // Should be removable
+		[KeptInterface (typeof (IBase))]
 		interface IBase2 : IBase
 		{
 			void Method ();
@@ -32,12 +34,14 @@ interface IBase2 : IBase
 
 		[Kept]
 		[KeptInterface (typeof (IBase))]
+		[KeptInterface (typeof (IBase2))] // Should be removable
 		interface IBase3 : IBase2
 		{
 		}
 
 		[Kept]
 		[KeptInterface (typeof (IBase3))]
+		[KeptInterface (typeof (IBase2))] // Should be removable
 		[KeptInterface (typeof (IBase))]
 		class Foo : IBase3
 		{
@@ -46,4 +50,4 @@ public void Method ()
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs
index 5bc09dc62f22b4..40f259dd79da9b 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs
@@ -25,6 +25,8 @@ interface IBase
 		{
 		}
 
+		[Kept] // Should be removable
+		[KeptInterface (typeof (IBase))]
 		interface IBase2 : IBase
 		{
 			void Method ();
@@ -32,12 +34,14 @@ interface IBase2 : IBase
 
 		[Kept]
 		[KeptInterface (typeof (IBase))]
+		[KeptInterface (typeof (IBase2))] // Should be removable
 		interface IBase3 : IBase2
 		{
 		}
 
 		[Kept]
 		[KeptInterface (typeof (IBase))]
+		[KeptInterface (typeof (IBase2))] // Should be removable
 		class Base : IBase2
 		{
 			public void Method ()
@@ -48,6 +52,7 @@ public void Method ()
 		[Kept]
 		[KeptBaseType (typeof (Base))]
 		[KeptInterface (typeof (IBase3))]
+		[KeptInterface (typeof (IBase2))] // Should be removable
 		[KeptInterface (typeof (IBase))]
 		class Foo : Base, IBase3, IBase2
 		{
@@ -56,4 +61,4 @@ public void Method ()
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs
index 8e438aeda7c8eb..8f685def3fa922 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs
@@ -17,6 +17,8 @@ interface IFoo
 			void Foo ();
 		}
 
+		[Kept]
+		[KeptInterface (typeof (IFoo))]
 		interface IBar : IFoo
 		{
 			void Bar ();
@@ -24,6 +26,7 @@ interface IBar : IFoo
 
 		[Kept]
 		[KeptInterface (typeof (IFoo))]
+		[KeptInterface (typeof (IBar))]
 		struct A : IBar
 		{
 			[Kept]
@@ -36,4 +39,4 @@ public void Bar ()
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs
index 56e08d67be74f7..833ace2676c41f 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs
@@ -20,13 +20,13 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
 	[KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Library.I0100), "library.dll", typeof (Library.I010))]
 	[KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Library.I010), "library.dll", typeof (Library.I01))]
 	[KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Library.I01), "library.dll", typeof (Library.I0))]
-	[RemovedTypeInAssembly("library.dll", typeof(Library.I00))]
-	[RemovedTypeInAssembly("library.dll", typeof(Library.I000))]
-	[RemovedInterfaceOnTypeInAssembly("library.dll", typeof (Library.MyClass), "library.dll", typeof (Library.I000))]
+	[KeptTypeInAssembly("library.dll", typeof(Library.I00))]
+	[KeptTypeInAssembly("library.dll", typeof(Library.I000))]
+	[KeptInterfaceOnTypeInAssembly("library.dll", typeof (Library.MyClass), "library.dll", typeof (Library.I000))]
 #endif
 	public class RecursiveInterfaceKept
 	{
-		public static void Main()
+		public static void Main ()
 		{
 #if IL_ASSEMBLY_AVAILABLE
 			Library.I0 _ = new Library.MyClass();

From b6f101a0f1fb3845db8ff76283a6a5336bf515dd Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Fri, 28 Jun 2024 10:34:45 -0700
Subject: [PATCH 15/27] Add runtimeInterface assertions to tests

---
 .../Linker/MethodReferenceExtensions.cs       |   4 +-
 .../illink/src/linker/Linker/TypeMapInfo.cs   |   9 +-
 .../Assertions/BaseTypeMapInfoAttribute.cs    |   7 +
 .../HasRuntimeInterfaceAttribute.cs           |  23 ++
 .../RuntimeInterfaceOnTypeInAssembly.cs       |  15 +
 .../OverrideOfRecursiveInterfaceIsRemoved.cs  |   3 +
 .../TestCasesRunner/AssemblyChecker.cs        |  74 +++-
 .../TestCasesRunner/ResultChecker.cs          | 372 ++++++++++--------
 .../TestCasesRunner/TypeMapInfoValidation.cs  |  15 +
 .../Trimming.Tests.Shared/TestResolver.cs     |   8 +-
 10 files changed, 347 insertions(+), 183 deletions(-)
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs

diff --git a/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs b/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs
index 4240d3277a0028..fe2c58e821ef28 100644
--- a/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs
+++ b/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs
@@ -71,7 +71,7 @@ public static string GetDisplayName (this MethodReference method)
 			return sb.ToString ();
 		}
 
-		public static TypeReference? GetReturnType (this MethodReference method, LinkContext context)
+		internal static TypeReference? GetReturnType (this MethodReference method, ITryResolveMetadata context)
 		{
 			if (method.DeclaringType is GenericInstanceType genericInstance)
 				return TypeReferenceExtensions.InflateGenericType (genericInstance, method.ReturnType, context);
@@ -84,7 +84,7 @@ public static bool ReturnsVoid (this IMethodSignature method)
 			return method.ReturnType.WithoutModifiers ().MetadataType == MetadataType.Void;
 		}
 
-		public static TypeReference? GetInflatedParameterType (this MethodReference method, int parameterIndex, LinkContext context)
+		internal static TypeReference? GetInflatedParameterType (this MethodReference method, int parameterIndex, ITryResolveMetadata context)
 		{
 #pragma warning disable RS0030 // MethodReference.Parameters is banned -- it's best to leave this as is for now
 			if (method.DeclaringType is GenericInstanceType genericInstance)
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index 61f788806f3147..eba929d5fdeb2b 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -41,7 +41,7 @@ namespace Mono.Linker
 	public class TypeMapInfo
 	{
 		readonly HashSet<AssemblyDefinition> assemblies = new HashSet<AssemblyDefinition> ();
-		readonly LinkContext context;
+		readonly ITryResolveMetadata context;
 		protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> base_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
 		protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> override_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
 		protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> default_interface_implementations = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
@@ -51,6 +51,11 @@ public TypeMapInfo (LinkContext context)
 			this.context = context;
 		}
 
+		internal TypeMapInfo (ITryResolveMetadata context)
+		{
+			this.context = context;
+		}
+
 		public void EnsureProcessed (AssemblyDefinition assembly)
 		{
 			if (!assemblies.Add (assembly))
@@ -172,7 +177,7 @@ ImmutableArray<RuntimeInterfaceImplementation> GetRecursiveInterfaceImplementati
 			if (interfaceTypeToImplChainMap.Count == 0)
 				return ImmutableArray<RuntimeInterfaceImplementation>.Empty;
 
-			return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.Resolve (kvp.Key), kvp.Value)).ToImmutableArray ();
+			return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.TryResolve (kvp.Key), kvp.Value)).ToImmutableArray ();
 		}
 
 		void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs
new file mode 100644
index 00000000000000..a9bcbcd5b341ee
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs
@@ -0,0 +1,7 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Mono.Linker.Tests.Cases.Expectations.Assertions
+{
+	public class BaseTypeMapInfoAttribute : BaseExpectedLinkedBehaviorAttribute { }
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs
new file mode 100644
index 00000000000000..8d77a80e146a76
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs
@@ -0,0 +1,23 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mono.Linker.Tests.Cases.Expectations.Assertions
+{
+	[AttributeUsage (AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+	public class HasRuntimeInterfaceAttribute : Attribute
+	{
+		public HasRuntimeInterfaceAttribute (Type interfaceType, params Type[] implementationChains)
+		{
+		}
+
+		public HasRuntimeInterfaceAttribute (string interfaceType, params string[] implementationChains)
+		{
+		}
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs
new file mode 100644
index 00000000000000..b0425ffc221d97
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs
@@ -0,0 +1,15 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Mono.Linker.Tests.Cases.Expectations.Assertions
+{
+	[AttributeUsage (AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+	public class RuntimeInterfaceOnTypeInAssembly : BaseTypeMapInfoAttribute
+	{
+		public RuntimeInterfaceOnTypeInAssembly (string dllName, string type, string interfaceType, params string[] implementationChains)
+		{
+		}
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs
index 4beafaa1d9d96e..6ef9d8848beef6 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs
@@ -30,6 +30,9 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
 	[RemovedOverrideOnMethodInAssembly ("library.dll", "Program/A", "O", "System.Void Program/IMiddleUnused::O()")]
 	[RemovedMemberInAssembly ("library.dll", "Program/IBaseUnused", "M()")]
 	[RemovedMemberInAssembly ("library.dll", "Program/IMiddleUnused", "O()")]
+	[RuntimeInterfaceOnTypeInAssembly ("library.dll", "Program/A", "Program/IDerived", new string[] { "Program/IDerived" })]
+	[RuntimeInterfaceOnTypeInAssembly ("library.dll", "Program/A", "Program/IBaseUsed", new string[] { "Program/IDerived", "Program/IMiddleUnused", "Program/IBaseUsed" })]
+	[RuntimeInterfaceOnTypeInAssembly ("library.dll", "Program/A", "Program/IBaseUnused", new string[] { "Program/IDerived", "Program/IMiddleUnused", "Program/IBaseUnused" })]
 	public class OverrideOfRecursiveInterfaceIsRemoved
 	{
 		[Kept]
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
index 7d984ef2e48637..791d63b5de38e0 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
@@ -17,6 +17,7 @@ namespace Mono.Linker.Tests.TestCasesRunner
 	partial class AssemblyChecker
 	{
 		readonly AssemblyDefinition originalAssembly, linkedAssembly;
+		TypeMapInfo _originalTypeMapInfo = new TypeMapInfo (new TestResolver ());
 		readonly TrimmedTestCaseResult linkedTestCase;
 
 		HashSet<string> linkedMembers;
@@ -43,6 +44,8 @@ public void Verify ()
 
 			IEnumerable<string> GetFailures ()
 			{
+				foreach (var err in VerifyTypeMapInfo (originalAssembly))
+					yield return err;
 				foreach (var err in VerifyExportedTypes (originalAssembly, linkedAssembly))
 					yield return err;
 				foreach (var err in VerifyCustomAttributes (originalAssembly, linkedAssembly))
@@ -99,6 +102,34 @@ IEnumerable<string> GetFailures ()
 			}
 		}
 
+		private IEnumerable<string> VerifyTypeMapInfo (AssemblyDefinition originalAssembly)
+		{
+			foreach (var type in originalAssembly.AllDefinedTypes ()) {
+				foreach (var att in type.CustomAttributes) {
+					switch (att.AttributeType.Name) {
+					case nameof (HasRuntimeInterfaceAttribute):
+						var expectednterfaceTypeName = att.ConstructorArguments[0].Value as string ?? ((TypeReference) att.ConstructorArguments[0].Value).FullName;
+						var expectedInterfaceChains = ((CustomAttributeArgument[]) att.ConstructorArguments[1].Value).Select (t => t.Value as string ?? ((TypeReference) t.Value).FullName);
+						var runtimeIfaces = _originalTypeMapInfo.GetRecursiveInterfaces (type);
+						var matchingRuntimeIface = runtimeIfaces.FirstOrDefault (i => i.InterfaceType.FullName == expectednterfaceTypeName);
+						if (matchingRuntimeIface == default) {
+							yield return $"Type {type.FullName} does not have runtime interface {expectednterfaceTypeName}";
+							continue;
+						}
+
+						if (expectedInterfaceChains.Any ()) {
+							if (!expectedInterfaceChains.Zip (matchingRuntimeIface.ImplementationChain).All (pair => pair.First == pair.Second.InterfaceType.FullName)) {
+								yield return $"Type {type.FullName} does not have expected implementation chain for runtime interface {expectednterfaceTypeName}";
+							}
+						}
+						break;
+					default:
+						break;
+					}
+				}
+			}
+		}
+
 		static bool IsBackingField (FieldDefinition field) => field.Name.StartsWith ("<") && field.Name.EndsWith (">k__BackingField");
 
 		protected virtual IEnumerable<string> VerifyModule (ModuleDefinition original, ModuleDefinition linked)
@@ -715,26 +746,26 @@ static string FormatInstruction (Instruction instr)
 				throw new NotImplementedException (instr.Operand.GetType ().ToString ());
 
 			default: {
-					string operandString = null;
-					switch (instr.OpCode.OperandType) {
-					case OperandType.InlineField:
-					case OperandType.InlineMethod:
-					case OperandType.InlineType:
-					case OperandType.InlineTok:
-						operandString = instr.Operand switch {
-							FieldReference fieldRef => fieldRef.FullName,
-							MethodReference methodRef => methodRef.FullName,
-							TypeReference typeRef => typeRef.FullName,
-							_ => null
-						};
-						break;
-					}
-
-					if (operandString != null)
-						return $"{instr.OpCode.ToString ()} {operandString}";
-					else
-						return instr.OpCode.ToString ();
+				string operandString = null;
+				switch (instr.OpCode.OperandType) {
+				case OperandType.InlineField:
+				case OperandType.InlineMethod:
+				case OperandType.InlineType:
+				case OperandType.InlineTok:
+					operandString = instr.Operand switch {
+						FieldReference fieldRef => fieldRef.FullName,
+						MethodReference methodRef => methodRef.FullName,
+						TypeReference typeRef => typeRef.FullName,
+						_ => null
+					};
+					break;
 				}
+
+				if (operandString != null)
+					return $"{instr.OpCode.ToString ()} {operandString}";
+				else
+					return instr.OpCode.ToString ();
+			}
 			}
 		}
 
@@ -1186,7 +1217,7 @@ IEnumerable<string> VerifyGenericParameterConstraints (GenericParameter src, Gen
 				yield return $"Mismatch in generic parameter constraints on {src} of {src.Owner}. Input has constraints?: {src.HasConstraints}, Output has constraints?: {linked.HasConstraints}";
 				yield break;
 			}
-			
+
 			if (!src.HasConstraints)
 				yield break;
 
@@ -1222,7 +1253,8 @@ IEnumerable<string> VerifyGenericParameterConstraints (GenericParameter src, Gen
 				yield return string.Join (Environment.NewLine, $"Custom attributes on `{src}' generic parameter constraints are not matching:", missing, extra);
 			}
 
-			static bool IsKeptAttributeOnConstraint (CustomAttribute attr) {
+			static bool IsKeptAttributeOnConstraint (CustomAttribute attr)
+			{
 				if (attr.AttributeType.Name != nameof (KeptAttributeOnConstraintAttribute))
 					return false;
 
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
index f6480e554c3528..4b84a0be17ca7a 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
@@ -72,11 +72,11 @@ protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition
 					}
 					continue;
 				case AssemblyNameReference: {
-						// There should be an AssemblyRef row for this assembly
-						var assemblyRef = linked.MainModule.AssemblyReferences.Single (ar => ar.Name == typeRef.Scope.Name);
-						Assert.IsNotNull (assemblyRef, $"Type reference '{typeRef.FullName}' has a reference to assembly '{typeRef.Scope.Name}' which is not a reference of '{linked.FullName}'");
-						continue;
-					}
+					// There should be an AssemblyRef row for this assembly
+					var assemblyRef = linked.MainModule.AssemblyReferences.Single (ar => ar.Name == typeRef.Scope.Name);
+					Assert.IsNotNull (assemblyRef, $"Type reference '{typeRef.FullName}' has a reference to assembly '{typeRef.Scope.Name}' which is not a reference of '{linked.FullName}'");
+					continue;
+				}
 				default:
 					throw new NotImplementedException ($"Unexpected scope type '{typeRef.Scope.GetType ()}' for type reference '{typeRef.FullName}'");
 				}
@@ -91,6 +91,9 @@ public virtual void Check (TrimmedTestCaseResult linkResult)
 				var original = ResolveOriginalsAssembly (linkResult.ExpectationsAssemblyPath.FileNameWithoutExtension);
 
 				VerifyExitCode (linkResult, original);
+				var errs = VerifyTypeMapOfOtherAssemblies (original);
+				if (errs.Any ())
+					Assert.Fail ($"Errors found in type map validation:\n{string.Join ("\n", errs)}");
 
 				if (!HasAttribute (original, nameof (NoLinkedOutputAttribute))) {
 					Assert.IsTrue (linkResult.OutputAssemblyPath.FileExists (), $"The linked output assembly was not found.  Expected at {linkResult.OutputAssemblyPath}");
@@ -289,6 +292,45 @@ protected virtual void InitialChecking (TrimmedTestCaseResult linkResult, Assemb
 			ValidateTypeRefsHaveValidAssemblyRefs (linked);
 		}
 
+		IEnumerable<string> VerifyTypeMapOfOtherAssemblies (AssemblyDefinition original)
+		{
+			var checks = BuildTypeMapInfoCheckTable (original);
+			List<string> errs = [];
+			try {
+				foreach (var assemblyName in checks.Keys) {
+					var assembly = ResolveOriginalsAssembly (assemblyName);
+					foreach (var check in checks[assemblyName]) {
+						switch (check.AttributeType.Name) {
+						case nameof (RuntimeInterfaceOnTypeInAssembly):
+							object type = check.ConstructorArguments[1].Value;
+							TypeDefinition typeDef = GetOriginalTypeFromInAssemblyAttribute (assemblyName, type);
+							string interfaceName = (check.ConstructorArguments[2].Value as TypeReference)?.FullName ?? (string) check.ConstructorArguments[2].Value;
+							TypeMapInfo typeMapInfo = new TypeMapInfo (new TestResolver ());
+							var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef);
+							var runtimeInterface = runtimeInterfaces.SingleOrDefault (i => i.InterfaceType.FullName == interfaceName);
+							if (runtimeInterface == default)
+								errs.Add ($"Expected type `{typeDef}` to have runtime interface `{interfaceName}`");
+
+							var implementationChains = (check.ConstructorArguments[3].Value as CustomAttributeArgument[])
+								.Select (caa => (caa.Value as TypeReference)?.FullName ?? (string) caa.Value);
+							if (implementationChains.Any ()) {
+								if (!implementationChains.SequenceEqual (runtimeInterface.ImplementationChain.Select (i => i.InterfaceType.FullName)))
+									errs.Add ($"Expected type `{typeDef}` to have interfaceImpl chain `{string.Join (", ", implementationChains)}` for interface `{interfaceName}`");
+							}
+
+							break;
+						default:
+							break;
+						}
+					}
+
+				}
+			} catch (AssemblyResolutionException e) {
+				Assert.Fail ($"Failed to resolve linked assembly `{e.AssemblyReference.Name}`.  It must not exist in any of the input directories:\n\t{_originalsResolver.GetSearchDirectories ().Aggregate ((buff, s) => $"{buff}\n\t{s}")}\n");
+			}
+			return errs;
+		}
+
 		void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original)
 		{
 			var checks = BuildOtherAssemblyCheckTable (original);
@@ -317,7 +359,7 @@ void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original)
 						TypeReference linkedTypeRef = null;
 						try {
 							_linkedTypeNameResolver.TryResolveTypeName (linkedAssembly, expectedTypeName, out linkedTypeRef, out _);
-						} catch (AssemblyResolutionException) {}
+						} catch (AssemblyResolutionException) { }
 						TypeDefinition linkedType = linkedTypeRef?.Resolve ();
 
 						if (linkedType == null && linkedAssembly.MainModule.HasExportedTypes) {
@@ -802,48 +844,48 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge
 					switch (attr.AttributeType.Name) {
 
 					case nameof (LogContainsAttribute): {
-							var expectedMessage = (string) attr.ConstructorArguments[0].Value;
-
-							List<MessageContainer> matchedMessages;
-							if ((bool) attr.ConstructorArguments[1].Value)
-								matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList ();
-							else
-								matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ;
-							if (matchedMessages.Count == 0)
-								missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}");
-
-							foreach (var matchedMessage in matchedMessages)
-								unmatchedMessages.Remove (matchedMessage);
-						}
-						break;
+						var expectedMessage = (string) attr.ConstructorArguments[0].Value;
+
+						List<MessageContainer> matchedMessages;
+						if ((bool) attr.ConstructorArguments[1].Value)
+							matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList ();
+						else
+							matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ;
+						if (matchedMessages.Count == 0)
+							missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}");
+
+						foreach (var matchedMessage in matchedMessages)
+							unmatchedMessages.Remove (matchedMessage);
+					}
+					break;
 
 					case nameof (LogDoesNotContainAttribute): {
-							var unexpectedMessage = (string) attr.ConstructorArguments[0].Value;
-							foreach (var loggedMessage in unmatchedMessages) {
-								bool isRegex = (bool) attr.ConstructorArguments[1].Value;
-								bool foundMatch = isRegex
-									? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage)
-									: loggedMessage.ToString ().Contains (unexpectedMessage);
-
-								if (foundMatch)
-									unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}");
-							}
+						var unexpectedMessage = (string) attr.ConstructorArguments[0].Value;
+						foreach (var loggedMessage in unmatchedMessages) {
+							bool isRegex = (bool) attr.ConstructorArguments[1].Value;
+							bool foundMatch = isRegex
+								? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage)
+								: loggedMessage.ToString ().Contains (unexpectedMessage);
+
+							if (foundMatch)
+								unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}");
 						}
-						break;
+					}
+					break;
 
 					case nameof (ExpectedWarningAttribute) or nameof (UnexpectedWarningAttribute): {
-							var expectedWarningCode = (string) attr.GetConstructorArgumentValue (0);
-							if (!expectedWarningCode.StartsWith ("IL")) {
-								Assert.Fail ($"The warning code specified in {attr.AttributeType.Name} must start with the 'IL' prefix. Specified value: '{expectedWarningCode}'.");
-							}
-							IEnumerable<string> expectedMessageContains = attr.Constructor.Parameters switch {
-							// ExpectedWarningAttribute(string warningCode, params string[] expectedMessages)
-							// ExpectedWarningAttribute(string warningCode, string[] expectedMessages, Tool producedBy, string issueLink)
-							[_, { ParameterType.IsArray: true }, ..]
-								=> ((CustomAttributeArgument[]) attr.ConstructorArguments[1].Value)
-									.Select (caa => (string) caa.Value),
-									// ExpectedWarningAttribute(string warningCode, string expectedMessage1, string expectedMessage2, Tool producedBy, string issueLink)
-									[_, { ParameterType.Name: "String" }, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _]
+						var expectedWarningCode = (string) attr.GetConstructorArgumentValue (0);
+						if (!expectedWarningCode.StartsWith ("IL")) {
+							Assert.Fail ($"The warning code specified in {attr.AttributeType.Name} must start with the 'IL' prefix. Specified value: '{expectedWarningCode}'.");
+						}
+						IEnumerable<string> expectedMessageContains = attr.Constructor.Parameters switch {
+						// ExpectedWarningAttribute(string warningCode, params string[] expectedMessages)
+						// ExpectedWarningAttribute(string warningCode, string[] expectedMessages, Tool producedBy, string issueLink)
+						[_, { ParameterType.IsArray: true }, ..]
+							=> ((CustomAttributeArgument[]) attr.ConstructorArguments[1].Value)
+								.Select (caa => (string) caa.Value),
+								// ExpectedWarningAttribute(string warningCode, string expectedMessage1, string expectedMessage2, Tool producedBy, string issueLink)
+								[_, { ParameterType.Name: "String" }, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _]
 											=> [(string) attr.GetConstructorArgumentValue (1), (string) attr.GetConstructorArgumentValue (2)],
 											// ExpectedWarningAttribute(string warningCode, string expectedMessage, Tool producedBy, string issueLink)
 											[_, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _]
@@ -851,143 +893,143 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge
 										// ExpectedWarningAttribute(string warningCode, Tool producedBy, string issueLink)
 										[_, { ParameterType.Name: "Tool" }, _]
 										=> [],
-								_ => throw new UnreachableException (),
-							};
-							string fileName = (string) attr.GetPropertyValue ("FileName");
-							int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine");
-							int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn");
-							bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode");
+							_ => throw new UnreachableException (),
+						};
+						string fileName = (string) attr.GetPropertyValue ("FileName");
+						int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine");
+						int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn");
+						bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode");
 
-							int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2));
-							string expectedOrigin = null;
-							bool expectedWarningFound = false;
+						int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2));
+						string expectedOrigin = null;
+						bool expectedWarningFound = false;
 
-							foreach (var loggedMessage in unmatchedMessages) {
+						foreach (var loggedMessage in unmatchedMessages) {
 
-								if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber)
-									continue;
+							if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber)
+								continue;
 
-								bool messageNotFound = false;
-								foreach (var expectedMessage in expectedMessageContains) {
-									if (!loggedMessage.Text.Contains (expectedMessage)) {
-										messageNotFound = true;
-										break;
-									}
+							bool messageNotFound = false;
+							foreach (var expectedMessage in expectedMessageContains) {
+								if (!loggedMessage.Text.Contains (expectedMessage)) {
+									messageNotFound = true;
+									break;
 								}
-								if (messageNotFound)
+							}
+							if (messageNotFound)
+								continue;
+
+							if (fileName != null) {
+								if (loggedMessage.Origin == null)
 									continue;
 
-								if (fileName != null) {
-									if (loggedMessage.Origin == null)
+								var actualOrigin = loggedMessage.Origin.Value;
+								if (actualOrigin.FileName != null) {
+									// Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set
+									if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0)
 										continue;
 
-									var actualOrigin = loggedMessage.Origin.Value;
-									if (actualOrigin.FileName != null) {
-										// Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set
-										if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0)
-											continue;
-
-										if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value)
-											continue;
-
-										if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value)
-											continue;
-									} else {
-										// The warning was logged with member/ILoffset, so it didn't have line/column info filled
-										// but it will be computed from PDBs, so instead compare it in a string representation
-										if (expectedOrigin == null) {
-											expectedOrigin = fileName;
-											if (sourceLine.HasValue) {
-												expectedOrigin += "(" + sourceLine.Value;
-												if (sourceColumn.HasValue)
-													expectedOrigin += "," + sourceColumn.Value;
-												expectedOrigin += ")";
-											}
-										}
+									if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value)
+										continue;
 
-										string actualOriginString = actualOrigin.ToString () ?? "";
-										if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase))
-											continue;
+									if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value)
+										continue;
+								} else {
+									// The warning was logged with member/ILoffset, so it didn't have line/column info filled
+									// but it will be computed from PDBs, so instead compare it in a string representation
+									if (expectedOrigin == null) {
+										expectedOrigin = fileName;
+										if (sourceLine.HasValue) {
+											expectedOrigin += "(" + sourceLine.Value;
+											if (sourceColumn.HasValue)
+												expectedOrigin += "," + sourceColumn.Value;
+											expectedOrigin += ")";
+										}
 									}
-								} else if (isCompilerGeneratedCode == true) {
-									if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition)
+
+									string actualOriginString = actualOrigin.ToString () ?? "";
+									if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase))
 										continue;
+								}
+							} else if (isCompilerGeneratedCode == true) {
+								if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition)
+									continue;
 
-									if (attrProvider is IMemberDefinition expectedMember) {
-										string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name;
+								if (attrProvider is IMemberDefinition expectedMember) {
+									string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name;
 
-										if (actualName.StartsWith (expectedMember.DeclaringType.FullName) &&
-											(actualName.Contains ("<" + expectedMember.Name + ">") ||
-											 actualName.EndsWith ("get_" + expectedMember.Name) ||
-											 actualName.EndsWith ("set_" + expectedMember.Name))) {
+									if (actualName.StartsWith (expectedMember.DeclaringType.FullName) &&
+										(actualName.Contains ("<" + expectedMember.Name + ">") ||
+										 actualName.EndsWith ("get_" + expectedMember.Name) ||
+										 actualName.EndsWith ("set_" + expectedMember.Name))) {
+										expectedWarningFound = true;
+										unmatchedMessages.Remove (loggedMessage);
+										break;
+									}
+									if (memberDefinition is not MethodDefinition)
+										continue;
+									if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) {
+										if (memberDefinition.Name == ".cctor" &&
+											(expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) {
 											expectedWarningFound = true;
 											unmatchedMessages.Remove (loggedMessage);
 											break;
 										}
-										if (memberDefinition is not MethodDefinition)
-											continue;
-										if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) {
-											if (memberDefinition.Name == ".cctor" &&
-												(expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) {
-												expectedWarningFound = true;
-												unmatchedMessages.Remove (loggedMessage);
-												break;
-											}
-											if (memberDefinition.Name == ".ctor" &&
-												(expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) {
-												expectedWarningFound = true;
-												unmatchedMessages.Remove (loggedMessage);
-												break;
-											}
-										}
-									} else if (attrProvider is AssemblyDefinition expectedAssembly) {
-										// Allow assembly-level attributes to match warnings from compiler-generated Main
-										if (memberDefinition.Name == "<Main>$" &&
-											memberDefinition.DeclaringType.FullName == "Program" &&
-											memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) {
+										if (memberDefinition.Name == ".ctor" &&
+											(expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) {
 											expectedWarningFound = true;
 											unmatchedMessages.Remove (loggedMessage);
 											break;
 										}
 									}
-									continue;
-								} else {
-									if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) {
+								} else if (attrProvider is AssemblyDefinition expectedAssembly) {
+									// Allow assembly-level attributes to match warnings from compiler-generated Main
+									if (memberDefinition.Name == "<Main>$" &&
+										memberDefinition.DeclaringType.FullName == "Program" &&
+										memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) {
 										expectedWarningFound = true;
 										unmatchedMessages.Remove (loggedMessage);
 										break;
 									}
-									continue;
 								}
-
-								expectedWarningFound = true;
-								unmatchedMessages.Remove (loggedMessage);
-								break;
+								continue;
+							} else {
+								if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) {
+									expectedWarningFound = true;
+									unmatchedMessages.Remove (loggedMessage);
+									break;
+								}
+								continue;
 							}
 
-							var expectedOriginString = fileName == null
-								? attrProvider switch {
-									MethodDefinition method => method.GetDisplayName (),
-									IMemberDefinition member => member.FullName,
-									AssemblyDefinition asm => asm.Name.Name,
-									_ => throw new NotImplementedException ()
-								} + ": "
-								: "";
-
-							if (!expectedWarningFound)
-								missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" +
-								$"warning {expectedWarningCode}: {expectedOriginString}" +
-								$"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " +
-								$"but no such message was found.");
+							expectedWarningFound = true;
+							unmatchedMessages.Remove (loggedMessage);
+							break;
 						}
-						break;
+
+						var expectedOriginString = fileName == null
+							? attrProvider switch {
+								MethodDefinition method => method.GetDisplayName (),
+								IMemberDefinition member => member.FullName,
+								AssemblyDefinition asm => asm.Name.Name,
+								_ => throw new NotImplementedException ()
+							} + ": "
+							: "";
+
+						if (!expectedWarningFound)
+							missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" +
+							$"warning {expectedWarningCode}: {expectedOriginString}" +
+							$"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " +
+							$"but no such message was found.");
+					}
+					break;
 
 					case nameof (ExpectedNoWarningsAttribute): {
-							// Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above)
-							// and then do the negative check on the rest.
-							expectedNoWarningsAttributes.Add ((attrProvider, attr));
-							break;
-						}
+						// Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above)
+						// and then do the negative check on the rest.
+						expectedNoWarningsAttributes.Add ((attrProvider, attr));
+						break;
+					}
 					}
 				}
 			}
@@ -1211,18 +1253,38 @@ protected TypeDefinition GetOriginalTypeFromInAssemblyAttribute (string assembly
 			return originalType;
 		}
 
+
+		bool IsTypeInOtherAssemblyAssertion (CustomAttribute attr)
+		{
+			if (false == attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseInAssemblyAttribute)))
+				return false;
+
+			Tool? toolTarget = (Tool?) (int?) attr.GetPropertyValue ("Tool");
+			if (toolTarget is not null && !toolTarget.Value.HasFlag (Tool.Trimmer))
+				return false;
+
+			return true;
+		}
+
+		bool IsTypeMapInfoInOtherAssemblyAssertion (CustomAttribute attr)
+		{
+			return attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseTypeMapInfoAttribute)) ?? false;
+		}
+
+		Dictionary<string, List<CustomAttribute>> BuildTypeMapInfoCheckTable (AssemblyDefinition original)
+			=> BuildChecksTable (original, IsTypeMapInfoInOtherAssemblyAssertion);
+
 		Dictionary<string, List<CustomAttribute>> BuildOtherAssemblyCheckTable (AssemblyDefinition original)
+			=> BuildChecksTable (original, IsTypeInOtherAssemblyAssertion);
+
+		Dictionary<string, List<CustomAttribute>> BuildChecksTable (AssemblyDefinition original, Func<CustomAttribute, bool> predicate)
 		{
 			var checks = new Dictionary<string, List<CustomAttribute>> ();
 
 			foreach (var typeWithRemoveInAssembly in original.AllDefinedTypes ()) {
-				foreach (var attr in typeWithRemoveInAssembly.CustomAttributes.Where (IsTypeInOtherAssemblyAssertion)) {
-					var assemblyName = (string) attr.ConstructorArguments[0].Value;
-
-					Tool? toolTarget = (Tool?) (int?) attr.GetPropertyValue ("Tool");
-					if (toolTarget is not null && !toolTarget.Value.HasFlag (Tool.Trimmer))
-						continue;
+				foreach (var attr in typeWithRemoveInAssembly.CustomAttributes.Where (predicate)) {
 
+					var assemblyName = (string) attr.ConstructorArguments[0].Value;
 					if (!checks.TryGetValue (assemblyName, out List<CustomAttribute> checksForAssembly))
 						checks[assemblyName] = checksForAssembly = new List<CustomAttribute> ();
 
@@ -1238,10 +1300,6 @@ protected virtual void UnhandledOtherAssemblyAssertion (string expectedTypeName,
 			throw new NotImplementedException ($"Type {expectedTypeName}, has an unknown other assembly attribute of type {checkAttrInAssembly.AttributeType}");
 		}
 
-		bool IsTypeInOtherAssemblyAssertion (CustomAttribute attr)
-		{
-			return attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseInAssemblyAttribute)) ?? false;
-		}
 
 		static bool HasAttribute (ICustomAttributeProvider caProvider, string attributeName)
 		{
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
new file mode 100644
index 00000000000000..298e96124598f9
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
@@ -0,0 +1,15 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mono.Linker.Tests.TestCasesRunner
+{
+	class TypeMapInfoValidation
+	{
+	}
+}
diff --git a/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs b/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs
index af37fcfbe6487d..2d76fc7a8c8942 100644
--- a/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs
+++ b/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs
@@ -5,8 +5,14 @@
 
 namespace Mono.Linker.Tests.TestCasesRunner
 {
-	struct TestResolver : ITryResolveMetadata
+	struct TestResolver : ITryResolveMetadata, IMetadataResolver
 	{
+		public TypeDefinition Resolve (TypeReference type) => type.Resolve ();
+
+		public FieldDefinition Resolve (FieldReference field) => field.Resolve ();
+
+		public MethodDefinition Resolve (MethodReference method) => method.Resolve ();
+
 		public MethodDefinition TryResolve (MethodReference methodReference) => methodReference.Resolve ();
 
 		public TypeDefinition TryResolve (TypeReference typeReference) => typeReference.Resolve ();

From 66a3bcf1cd0057dac0944250f72fad3e90bb5d54 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Fri, 28 Jun 2024 15:12:59 -0700
Subject: [PATCH 16/27] Add tests for TypeMapInfo

---
 .../Assertions/IsOverrideOfAttribute.cs       | 15 +++++++
 ...ethodOverridesMethodInAssemblyAttribute.cs | 22 ++++++++++
 .../InterfaceVariants.cs                      |  4 ++
 .../SimpleMethod.cs                           |  3 +-
 .../RecursiveGenericInterfaces.cs             |  3 +-
 .../TestCasesRunner/AssemblyChecker.cs        | 41 ++++++++++++-------
 .../TestCasesRunner/ResultChecker.cs          | 18 +++-----
 .../TestCasesRunner/TypeMapInfoValidation.cs  | 39 +++++++++++++++++-
 8 files changed, 114 insertions(+), 31 deletions(-)
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs

diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs
new file mode 100644
index 00000000000000..e468492a000b3e
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs
@@ -0,0 +1,15 @@
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mono.Linker.Tests.Cases.Expectations.Assertions
+{
+	[AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
+	public class IsOverrideOfAttribute : BaseTypeMapInfoAttribute
+	{
+		public IsOverrideOfAttribute (string methodFullName)
+		{ }
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs
new file mode 100644
index 00000000000000..7760f981833c42
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs
@@ -0,0 +1,22 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mono.Linker.Tests.Cases.Expectations.Assertions
+{
+	class MethodOverridesMethodInAssemblyAttribute : BaseTypeMapInfoAttribute
+	{
+		public MethodOverridesMethodInAssemblyAttribute (
+			string overridingAssemblyName,
+			string overridingTypeName,
+			string overridingMethodName,
+			string baseAssemblyName,
+			string baseMethodName)
+		{ }
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs
index b19208bd563d64..26519f7f37e71b 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs
@@ -136,15 +136,19 @@ public class UninstantiatedPublicClassWithInterface :
 			internal UninstantiatedPublicClassWithInterface () { }
 
 			[Kept]
+			[IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicInterface::PublicInterfaceMethod()")]
 			public void PublicInterfaceMethod () { }
 
 			[Kept]
+			[IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicInterface::ExplicitImplementationPublicInterfaceMethod()")]
 			void IPublicInterface.ExplicitImplementationPublicInterfaceMethod () { }
 
 			[Kept]
+			[IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicStaticInterface::PublicStaticInterfaceMethod()")]
 			public static void PublicStaticInterfaceMethod () { }
 
 			[Kept]
+			[IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicStaticInterface::ExplicitImplementationPublicStaticInterfaceMethod()")]
 			static void IPublicStaticInterface.ExplicitImplementationPublicStaticInterfaceMethod () { }
 
 			public void InternalInterfaceMethod () { }
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs
index a395d4197c1113..34b817a76e25b5 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs
@@ -22,6 +22,7 @@ interface IFoo
 		class BaseFoo
 		{
 			[Kept]
+			[IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType.BaseProvidesInterfaceMember.SimpleMethod/IFoo::Method()")]
 			public void Method ()
 			{
 			}
@@ -35,4 +36,4 @@ class FooWithBase : BaseFoo, IFoo
 		{
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs
index 90c1a319c353f7..398e0fbdb5ee06 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs
@@ -32,7 +32,8 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces
 	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Double,System.Int32>.GetT", "T IBase`3<System.String,System.Double,System.Int32>::GetT()")]
 	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Int64,System.Single>.GetT", "T IBase`3<System.String,System.Int64,System.Single>::GetT()")]
 	[KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase<System.String,System.Double,System.Single>.GetT", "T IBase`3<System.String,System.Double,System.Single>::GetT()")]
-
+	[RuntimeInterfaceOnTypeInAssembly ("library.dll", "MyClass", "IBase`3<System.Char,System.Int64,System.Int32>", ["IDerived`1<System.Char>", "IMiddle`2<T,System.Int64>", "IBase`3<T,U,System.Int32>"])]
+	[RuntimeInterfaceOnTypeInAssembly ("library.dll", "MyClass", "IBase`3<System.String,System.Int64,System.Int32>", ["IDerived`1<System.String>", "IMiddle`2<T,System.Int64>", "IBase`3<T,U,System.Int32>"])]
 	public class RecursiveGenericInterfaces
 	{
 		[Kept]
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
index 791d63b5de38e0..f4cd4f8ad45d18 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
@@ -26,11 +26,12 @@ partial class AssemblyChecker
 		readonly HashSet<string> verifiedGeneratedTypes = new HashSet<string> ();
 		bool checkNames;
 
-		public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, TrimmedTestCaseResult linkedTestCase)
+		public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, TrimmedTestCaseResult linkedTestCase, TypeMapInfo typeMapInfo)
 		{
 			this.originalAssembly = original;
 			this.linkedAssembly = linked;
 			this.linkedTestCase = linkedTestCase;
+			this._originalTypeMapInfo = typeMapInfo;
 
 			checkNames = original.MainModule.GetTypeReferences ().Any (attr =>
 				attr.Name == nameof (RemovedNameValueAttribute));
@@ -108,25 +109,35 @@ private IEnumerable<string> VerifyTypeMapInfo (AssemblyDefinition originalAssemb
 				foreach (var att in type.CustomAttributes) {
 					switch (att.AttributeType.Name) {
 					case nameof (HasRuntimeInterfaceAttribute):
-						var expectednterfaceTypeName = att.ConstructorArguments[0].Value as string ?? ((TypeReference) att.ConstructorArguments[0].Value).FullName;
-						var expectedInterfaceChains = ((CustomAttributeArgument[]) att.ConstructorArguments[1].Value).Select (t => t.Value as string ?? ((TypeReference) t.Value).FullName);
-						var runtimeIfaces = _originalTypeMapInfo.GetRecursiveInterfaces (type);
-						var matchingRuntimeIface = runtimeIfaces.FirstOrDefault (i => i.InterfaceType.FullName == expectednterfaceTypeName);
-						if (matchingRuntimeIface == default) {
-							yield return $"Type {type.FullName} does not have runtime interface {expectednterfaceTypeName}";
-							continue;
-						}
-
-						if (expectedInterfaceChains.Any ()) {
-							if (!expectedInterfaceChains.Zip (matchingRuntimeIface.ImplementationChain).All (pair => pair.First == pair.Second.InterfaceType.FullName)) {
-								yield return $"Type {type.FullName} does not have expected implementation chain for runtime interface {expectednterfaceTypeName}";
-							}
-						}
+						var expectedInterfaceTypeName = att.ConstructorArguments[0].Value as string ?? ((TypeReference) att.ConstructorArguments[0].Value).FullName;
+						var expectedInterfaceChain = ((CustomAttributeArgument[]) att.ConstructorArguments[1].Value).Select (t => t.Value as string ?? ((TypeReference) t.Value).FullName);
+						var errs = TypeMapInfoValidation.ValidateRuntimeInterfaces (_originalTypeMapInfo, type, expectedInterfaceTypeName, expectedInterfaceChain);
+						foreach (var err in errs)
+							yield return err;
 						break;
+
 					default:
 						break;
 					}
 				}
+				foreach (var method in type.Methods) {
+					foreach (var att in method.CustomAttributes) {
+						switch (att.AttributeType.Name) {
+						case nameof (IsOverrideOfAttribute):
+							string expectedOverriddenMethodName = att.ConstructorArguments.Count switch {
+								1 => (string) att.ConstructorArguments[0].Value,
+								_ => throw new NotImplementedException ($"Unexpected number of arguments in {att.AttributeType.Name} attribute: {att.ConstructorArguments.Count}")
+							};
+							var errs = TypeMapInfoValidation.ValidateMethodIsOverrideOf (_originalTypeMapInfo, method, expectedOverriddenMethodName);
+							foreach (var err in errs)
+								yield return err;
+
+							break;
+						default:
+							break;
+						}
+					}
+				}
 			}
 		}
 
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
index 4b84a0be17ca7a..baa39b75989ded 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
@@ -30,6 +30,7 @@ public class ResultChecker
 		readonly TypeNameResolver _linkedTypeNameResolver;
 		readonly ReaderParameters _originalReaderParameters;
 		readonly ReaderParameters _linkedReaderParameters;
+		readonly TypeMapInfo _originalTypeMapInfo;
 
 		public ResultChecker ()
 			: this (new TestCaseAssemblyResolver (), new TestCaseAssemblyResolver (),
@@ -50,6 +51,7 @@ public ResultChecker (BaseAssemblyResolver originalsResolver, BaseAssemblyResolv
 			_linkedTypeNameResolver = new TypeNameResolver (new TestResolver (), new TestAssemblyNameResolver (_linkedResolver));
 			_originalReaderParameters = originalReaderParameters;
 			_linkedReaderParameters = linkedReaderParameters;
+			_originalTypeMapInfo = new TypeMapInfo (new TestResolver ());
 		}
 
 		protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition linked)
@@ -147,7 +149,7 @@ void VerifyILOfOtherAssemblies (TrimmedTestCaseResult linkResult)
 
 		protected virtual AssemblyChecker CreateAssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, TrimmedTestCaseResult linkedTestCase)
 		{
-			return new AssemblyChecker (original, linked, linkedTestCase);
+			return new AssemblyChecker (original, linked, linkedTestCase, _originalTypeMapInfo);
 		}
 
 		void InitializeResolvers (TrimmedTestCaseResult linkedResult)
@@ -305,19 +307,9 @@ IEnumerable<string> VerifyTypeMapOfOtherAssemblies (AssemblyDefinition original)
 							object type = check.ConstructorArguments[1].Value;
 							TypeDefinition typeDef = GetOriginalTypeFromInAssemblyAttribute (assemblyName, type);
 							string interfaceName = (check.ConstructorArguments[2].Value as TypeReference)?.FullName ?? (string) check.ConstructorArguments[2].Value;
-							TypeMapInfo typeMapInfo = new TypeMapInfo (new TestResolver ());
-							var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef);
-							var runtimeInterface = runtimeInterfaces.SingleOrDefault (i => i.InterfaceType.FullName == interfaceName);
-							if (runtimeInterface == default)
-								errs.Add ($"Expected type `{typeDef}` to have runtime interface `{interfaceName}`");
-
-							var implementationChains = (check.ConstructorArguments[3].Value as CustomAttributeArgument[])
+							var expectedImplChain = (check.ConstructorArguments[3].Value as CustomAttributeArgument[])
 								.Select (caa => (caa.Value as TypeReference)?.FullName ?? (string) caa.Value);
-							if (implementationChains.Any ()) {
-								if (!implementationChains.SequenceEqual (runtimeInterface.ImplementationChain.Select (i => i.InterfaceType.FullName)))
-									errs.Add ($"Expected type `{typeDef}` to have interfaceImpl chain `{string.Join (", ", implementationChains)}` for interface `{interfaceName}`");
-							}
-
+							errs.AddRange (TypeMapInfoValidation.ValidateRuntimeInterfaces (_originalTypeMapInfo, typeDef, interfaceName, expectedImplChain));
 							break;
 						default:
 							break;
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
index 298e96124598f9..e21e45d6448889 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
@@ -6,10 +6,47 @@
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Mono.Cecil;
+using Mono.Linker.Tests.Cases.Inheritance.AbstractClasses.NoKeptCtor.OverrideRemoval;
 
 namespace Mono.Linker.Tests.TestCasesRunner
 {
-	class TypeMapInfoValidation
+	internal class TypeMapInfoValidation
 	{
+		public static IEnumerable<string> ValidateRuntimeInterfaces (TypeMapInfo typeMapInfo, TypeDefinition typeDef, string expectedInterfaceName, IEnumerable<string> expectedImplChain)
+		{
+			var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef);
+			if (!runtimeInterfaces.HasValue) {
+				yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`, but it has none");
+				yield break;
+			}
+			var runtimeInterface = runtimeInterfaces.Value.SingleOrDefault (i => i.InflatedInterfaceType.FullName == expectedInterfaceName);
+			if (runtimeInterface == default) {
+				yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`");
+				yield break;
+			}
+
+			if (expectedImplChain.Any ()) {
+				var matchingImplementationChainCount = runtimeInterface.InterfaceImplementationChains
+					.Count (chain => ImplChainMatches (chain, expectedImplChain));
+				if (matchingImplementationChainCount == 0) {
+					yield return ($"Type {typeDef.FullName} does not have expected implementation chain for runtime interface `{expectedInterfaceName}`: {string.Join ("->", expectedImplChain.Select (i => $"`{i}`"))}");
+					yield break;
+				}
+			}
+
+			static bool ImplChainMatches (InterfaceImplementationChain chain, IEnumerable<string> expectedChain)
+			{
+				return chain.InterfaceImplementations.Select (i => i.InterfaceType.FullName).SequenceEqual (expectedChain);
+			}
+		}
+
+		public static IEnumerable<string> ValidateMethodIsOverrideOf (TypeMapInfo typeMapInfo, MethodDefinition methodDef, string expectedOverriddenMethodName)
+		{
+			var overriddenMethods = typeMapInfo.GetBaseMethods (methodDef);
+			if (overriddenMethods is null || !overriddenMethods.Select (o => o.Base.FullName).Contains (expectedOverriddenMethodName))
+				yield return $"Expected method `{methodDef}` to be an override of `{expectedOverriddenMethodName}`";
+		}
 	}
 }

From e524b22531a6793e735a0f9c01937bab3cab8c6b Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 12 Aug 2024 16:24:11 -0700
Subject: [PATCH 17/27] Move RuntimeInterfaces algorithm to its own class

---
 .../linker/Linker.Dataflow/FlowAnnotations.cs |   4 +-
 .../src/linker/Linker.Steps/MarkStep.cs       | 100 ++++++++--------
 .../ValidateVirtualMethodAnnotationsStep.cs   |   4 +-
 .../Linker/RuntimeInterfaceImplementation.cs  |  37 ------
 .../Linker/RuntimeInterfacesAlgorithm.cs      | 110 ++++++++++++++++++
 .../illink/src/linker/Linker/TypeMapInfo.cs   |  78 +++----------
 ...e.Interfaces.RecursiveInterfacesTests.g.cs |  18 +++
 7 files changed, 199 insertions(+), 152 deletions(-)
 create mode 100644 src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs

diff --git a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs
index 5f106d831e6754..42a0f0b447a9a5 100644
--- a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs
+++ b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs
@@ -544,8 +544,8 @@ void ValidateMethodGenericParametersHaveNoAnnotations (DynamicallyAccessedMember
 
 		void LogValidationWarning (IMetadataTokenProvider provider, IMetadataTokenProvider baseProvider, OverrideInformation ov)
 		{
-			IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.InterfaceImplementor.Implementor != ov.Override.DeclaringType)
-				? ov.InterfaceImplementor.Implementor
+			IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.RuntimeInterfaceImplementation.Implementor != ov.Override.DeclaringType)
+				? ov.RuntimeInterfaceImplementation.Implementor
 				: ov.Override;
 			Debug.Assert (provider.GetType () == baseProvider.GetType ());
 			Debug.Assert (!(provider is GenericParameter genericParameter) || genericParameter.DeclaringMethod != null);
diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index c8d046af3e9b83..e663b50ff284c4 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -1483,17 +1483,17 @@ bool ProcessLazyAttributes ()
 				string attributeFullName = customAttribute.Constructor.DeclaringType.FullName;
 				switch (attributeFullName) {
 				case "System.Diagnostics.DebuggerDisplayAttribute": {
-						TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
-						if (targetType != null)
-							MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin);
-						break;
-					}
+					TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
+					if (targetType != null)
+						MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin);
+					break;
+				}
 				case "System.Diagnostics.DebuggerTypeProxyAttribute": {
-						TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
-						if (targetType != null)
-							MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin);
-						break;
-					}
+					TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
+					if (targetType != null)
+						MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin);
+					break;
+				}
 				}
 			}
 
@@ -2336,12 +2336,12 @@ void MarkInterfaceImplementations (TypeDefinition type)
 
 		internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation)
 		{
-			var implementinType = runtimeInterfaceImplementation.Implementor;
+			var implementingType = runtimeInterfaceImplementation.Implementor;
 			var ifaceType = runtimeInterfaceImplementation.InterfaceTypeDefinition;
 			if (Annotations.IsMarked (runtimeInterfaceImplementation))
 				return false;
 
-			if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, implementinType))
+			if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, implementingType))
 				return true;
 
 			if (ifaceType is null)
@@ -2355,7 +2355,7 @@ internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplemen
 			if (Context.KeepComInterfaces && (ifaceType.IsImport || ifaceType.IsWindowsRuntime))
 				return true;
 
-			return IsFullyPreserved (implementinType);
+			return IsFullyPreserved (implementingType);
 		}
 
 		void MarkGenericParameterProvider (IGenericParameterProvider provider, MessageOrigin origin)
@@ -3595,47 +3595,47 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
 				break;
 
 			case OperandType.InlineMethod: {
-					(DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch {
-						Code.Jmp => (DependencyKind.DirectCall, false),
-						Code.Call => (DependencyKind.DirectCall, false),
-						Code.Callvirt => (DependencyKind.VirtualCall, false),
-						Code.Newobj => (DependencyKind.Newobj, false),
-						Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true),
-						Code.Ldftn => (DependencyKind.Ldftn, true),
-						_ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}")
-					};
-
-					MethodReference methodReference = (MethodReference) instruction.Operand;
-
-					requiresReflectionMethodBodyScanner |=
-						ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference);
-
-					origin = new MessageOrigin (origin, instruction.Offset);
-					if (markForReflectionAccess) {
-						MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin);
-					} else {
-						MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin);
-					}
-					break;
+				(DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch {
+					Code.Jmp => (DependencyKind.DirectCall, false),
+					Code.Call => (DependencyKind.DirectCall, false),
+					Code.Callvirt => (DependencyKind.VirtualCall, false),
+					Code.Newobj => (DependencyKind.Newobj, false),
+					Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true),
+					Code.Ldftn => (DependencyKind.Ldftn, true),
+					_ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}")
+				};
+
+				MethodReference methodReference = (MethodReference) instruction.Operand;
+
+				requiresReflectionMethodBodyScanner |=
+					ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference);
+
+				origin = new MessageOrigin (origin, instruction.Offset);
+				if (markForReflectionAccess) {
+					MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin);
+				} else {
+					MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin);
 				}
+				break;
+			}
 
 			case OperandType.InlineTok: {
-					object token = instruction.Operand;
-					Debug.Assert (instruction.OpCode.Code == Code.Ldtoken);
-					var reason = new DependencyInfo (DependencyKind.Ldtoken, method);
-					origin = new MessageOrigin (origin, instruction.Offset);
-
-					if (token is TypeReference typeReference) {
-						// Error will be reported as part of MarkType
-						if (Context.TryResolve (typeReference) is TypeDefinition type)
-							MarkTypeVisibleToReflection (typeReference, type, reason, origin);
-					} else if (token is MethodReference methodReference) {
-						MarkMethodVisibleToReflection (methodReference, reason, origin);
-					} else {
-						MarkFieldVisibleToReflection ((FieldReference) token, reason, origin);
-					}
-					break;
+				object token = instruction.Operand;
+				Debug.Assert (instruction.OpCode.Code == Code.Ldtoken);
+				var reason = new DependencyInfo (DependencyKind.Ldtoken, method);
+				origin = new MessageOrigin (origin, instruction.Offset);
+
+				if (token is TypeReference typeReference) {
+					// Error will be reported as part of MarkType
+					if (Context.TryResolve (typeReference) is TypeDefinition type)
+						MarkTypeVisibleToReflection (typeReference, type, reason, origin);
+				} else if (token is MethodReference methodReference) {
+					MarkMethodVisibleToReflection (methodReference, reason, origin);
+				} else {
+					MarkFieldVisibleToReflection ((FieldReference) token, reason, origin);
 				}
+				break;
+			}
 
 			case OperandType.InlineType:
 				var operand = (TypeReference) instruction.Operand;
diff --git a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs
index aa9c7ee0d9f09e..4d68e20620e265 100644
--- a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs
@@ -51,8 +51,8 @@ void ValidateMethodRequiresUnreferencedCodeAreSame (OverrideInformation ov)
 					nameof (RequiresUnreferencedCodeAttribute),
 					method.GetDisplayName (),
 					baseMethod.GetDisplayName ());
-				IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.InterfaceImplementor.Implementor != method.DeclaringType)
-					? ov.InterfaceImplementor.Implementor
+				IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.RuntimeInterfaceImplementation.Implementor != method.DeclaringType)
+					? ov.RuntimeInterfaceImplementation.Implementor
 					: method;
 				Context.LogWarning (origin, DiagnosticId.RequiresUnreferencedCodeAttributeMismatch, message);
 			}
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
index e7916e2011c41f..5e770cb0e275ca 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -60,42 +60,5 @@ public bool IsAnyImplementationMarked (AnnotationStore annotations, ITryResolveM
 			}
 			return false;
 		}
-
-		/// <summary>
-		/// Returns a list of InterfaceImplementationChains for a derived type of <see cref="Implementor"/>.
-		/// </summary>
-		public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (TypeReference baseTypeRef, ITryResolveMetadata context)
-		{
-			// This is only valid for classes
-			Debug.Assert (Implementor.IsClass);
-			Debug.Assert (Implementor == context.TryResolve (baseTypeRef));
-
-			var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (baseTypeRef, context);
-			Debug.Assert (inflatedInterfaceType is not null);
-
-			foreach (var impl in InterfaceImplementationChains) {
-				var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, context);
-				Debug.Assert (inflatedImplProvider is not null);
-
-				yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations));
-			}
-		}
-
-		/// <summary>
-		/// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of <see cref="Implementor"/>.
-		/// </summary>
-		public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainForImplementingType (TypeDefinition typeThatImplementsImplementor, InterfaceImplementation impl, ITryResolveMetadata context)
-		{
-			Debug.Assert (Implementor.IsInterface);
-			Debug.Assert (typeThatImplementsImplementor.Interfaces.Contains (impl));
-			Debug.Assert (context.TryResolve (impl.InterfaceType) == Implementor);
-
-			var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (impl.InterfaceType, context);
-			Debug.Assert (inflatedInterfaceType is not null);
-
-			foreach (var existingImpl in InterfaceImplementationChains) {
-				yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeThatImplementsImplementor, existingImpl.InterfaceImplementations.Insert (0, impl)));
-			}
-		}
 	}
 }
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
new file mode 100644
index 00000000000000..bf604aaf60675c
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
@@ -0,0 +1,110 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Cecil;
+
+namespace Mono.Linker.Linker
+{
+	internal struct RuntimeInterfacesAlgorithm
+	{
+		readonly ITryResolveMetadata _context;
+		readonly Dictionary<TypeDefinition, ImmutableArray<RuntimeInterfaceImplementation>> _runtimeInterfaceImpls = new ();
+
+		public RuntimeInterfacesAlgorithm (ITryResolveMetadata context)
+		{
+			this._context = context;
+		}
+
+		public ImmutableArray<RuntimeInterfaceImplementation> GetRuntimeInterfaceImplementations (TypeDefinition originalType)
+		{
+			if (_runtimeInterfaceImpls.TryGetValue (originalType, out var runtimeIfaces)) {
+				return runtimeIfaces;
+			}
+
+			Dictionary<TypeReference, List<InterfaceImplementationChain>> interfaceTypeToImplChainMap = new (new TypeReferenceEqualityComparer (_context));
+
+			foreach (var explicitIface in originalType.Interfaces) {
+				interfaceTypeToImplChainMap.AddToList (explicitIface.InterfaceType, new InterfaceImplementationChain (originalType, [explicitIface]));
+
+				var resolvedInterfaceType = _context.TryResolve (explicitIface.InterfaceType);
+				if (resolvedInterfaceType is null) {
+					continue;
+				}
+
+				var recursiveIFaces = GetRuntimeInterfaceImplementations (resolvedInterfaceType);
+
+				foreach (var recursiveIface in recursiveIFaces) {
+					var impls = PrependInterfaceImplToChains (recursiveIface, originalType, explicitIface);
+					foreach (var impl in impls) {
+						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
+					}
+				}
+			}
+
+			if (originalType.BaseType is not null && _context.TryResolve (originalType.BaseType) is { } baseTypeDef) {
+				var baseTypeIfaces = GetRuntimeInterfaceImplementations (baseTypeDef);
+				foreach (var recursiveIface in baseTypeIfaces) {
+					var impls = CreateImplementationChainsForDerivedType (recursiveIface, originalType.BaseType);
+					foreach (var impl in impls) {
+						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
+					}
+				}
+			}
+
+			if (interfaceTypeToImplChainMap.Count == 0)
+				return ImmutableArray<RuntimeInterfaceImplementation>.Empty;
+
+			ImmutableArray<RuntimeInterfaceImplementation>.Builder builder = ImmutableArray.CreateBuilder<RuntimeInterfaceImplementation> (interfaceTypeToImplChainMap.Count);
+			foreach (var kvp in interfaceTypeToImplChainMap) {
+				builder.Add (new (originalType, kvp.Key, _context.TryResolve (kvp.Key), kvp.Value));
+			}
+			var runtimeInterfaces = builder.MoveToImmutable ();
+			_runtimeInterfaceImpls[originalType] = runtimeInterfaces;
+			return runtimeInterfaces;
+
+		}
+
+		/// <summary>
+		/// Returns a list of InterfaceImplementationChains for a derived type of <see cref="Implementor"/>.
+		/// </summary>
+		IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (RuntimeInterfaceImplementation runtimeImpl, TypeReference baseTypeRef)
+		{
+			// This is only valid for classes
+			Debug.Assert (runtimeImpl.Implementor.IsClass);
+			Debug.Assert (runtimeImpl.Implementor == _context.TryResolve (baseTypeRef));
+
+			var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (baseTypeRef, _context);
+			Debug.Assert (inflatedInterfaceType is not null);
+
+			foreach (var impl in runtimeImpl.InterfaceImplementationChains) {
+				var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, _context);
+				Debug.Assert (inflatedImplProvider is not null);
+				yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations));
+			}
+		}
+
+		/// <summary>
+		/// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of <see cref="Implementor"/>.
+		/// </summary>
+		IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> PrependInterfaceImplToChains (RuntimeInterfaceImplementation runtimeImpl, TypeDefinition typeWithPrependedImpl, InterfaceImplementation implToPrepend)
+		{
+			Debug.Assert (runtimeImpl.Implementor.IsInterface);
+			Debug.Assert (_context.TryResolve (implToPrepend.InterfaceType) == runtimeImpl.Implementor);
+			Debug.Assert (typeWithPrependedImpl.Interfaces.Contains (implToPrepend));
+
+			var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (implToPrepend.InterfaceType, _context);
+			Debug.Assert (inflatedInterfaceType is not null);
+
+			foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) {
+				yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeWithPrependedImpl, existingImpl.InterfaceImplementations.Insert (0, implToPrepend)));
+			}
+		}
+	}
+}
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index eba929d5fdeb2b..ae0942a19c8387 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -35,6 +35,7 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Mono.Cecil;
+using Mono.Linker.Linker;
 
 namespace Mono.Linker
 {
@@ -45,15 +46,18 @@ public class TypeMapInfo
 		protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> base_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
 		protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> override_methods = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
 		protected readonly Dictionary<MethodDefinition, List<OverrideInformation>> default_interface_implementations = new Dictionary<MethodDefinition, List<OverrideInformation>> ();
+		readonly RuntimeInterfacesAlgorithm _recursiveInterfaces;
 
 		public TypeMapInfo (LinkContext context)
 		{
 			this.context = context;
+			_recursiveInterfaces = new (context);
 		}
 
 		internal TypeMapInfo (ITryResolveMetadata context)
 		{
 			this.context = context;
+			_recursiveInterfaces = new (context);
 		}
 
 		public void EnsureProcessed (AssemblyDefinition assembly)
@@ -114,10 +118,8 @@ internal void AddOverride (MethodDefinition @base, MethodDefinition @override, R
 			override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface));
 		}
 
-		Dictionary<TypeDefinition, ImmutableArray<RuntimeInterfaceImplementation>> _runtimeInterfaceImpls = new ();
 		protected virtual void MapType (TypeDefinition type)
 		{
-			_runtimeInterfaceImpls[type] = GetRecursiveInterfaceImplementations (type);
 			MapVirtualMethods (type);
 			MapInterfaceMethodsInTypeHierarchy (type);
 
@@ -130,60 +132,14 @@ protected virtual void MapType (TypeDefinition type)
 
 		internal ImmutableArray<RuntimeInterfaceImplementation>? GetRecursiveInterfaces (TypeDefinition type)
 		{
-			EnsureProcessed (type.Module.Assembly);
-			if (_runtimeInterfaceImpls.TryGetValue (type, out var value))
-				return value;
-			return null;
-		}
-
-
-		ImmutableArray<RuntimeInterfaceImplementation> GetRecursiveInterfaceImplementations (TypeDefinition originalType)
-		{
-			if (_runtimeInterfaceImpls.TryGetValue (originalType, out var runtimeIfaces)) {
-				return runtimeIfaces;
-			}
-
-			Dictionary<TypeReference, List<InterfaceImplementationChain>> interfaceTypeToImplChainMap = new (new TypeReferenceEqualityComparer (context));
-
-			foreach (var explicitIface in originalType.Interfaces) {
-				// Add runtimeIface for this Iface
-				var resolvedInterfaceType = context.TryResolve (explicitIface.InterfaceType);
-				interfaceTypeToImplChainMap.AddToList (explicitIface.InterfaceType, new InterfaceImplementationChain (originalType, [explicitIface]));
-
-				if (resolvedInterfaceType is null) {
-					continue;
-				}
-
-				var recursiveIFaces = GetRecursiveInterfaceImplementations (resolvedInterfaceType);
-
-				foreach (var recursiveIface in recursiveIFaces) {
-					var impls = recursiveIface.CreateImplementationChainForImplementingType (originalType, explicitIface, context);
-					foreach (var impl in impls) {
-						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
-					}
-				}
-			}
-
-			if (originalType.BaseType is not null && context.TryResolve (originalType.BaseType) is { } baseTypeDef) {
-				var baseTypeIfaces = GetRecursiveInterfaceImplementations (baseTypeDef);
-				foreach (var recursiveIface in baseTypeIfaces) {
-					var impls = recursiveIface.CreateImplementationChainsForDerivedType (originalType.BaseType, context);
-					foreach (var impl in impls) {
-						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
-					}
-				}
-			}
-
-			if (interfaceTypeToImplChainMap.Count == 0)
-				return ImmutableArray<RuntimeInterfaceImplementation>.Empty;
-
-			return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.TryResolve (kvp.Key), kvp.Value)).ToImmutableArray ();
+			return _recursiveInterfaces.GetRuntimeInterfaceImplementations (type);
 		}
 
 		void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
 		{
-			if (!_runtimeInterfaceImpls.TryGetValue (type, out var runtimeInterfaceImpls))
-				return;
+			var runtimeInterfaceImpls = _recursiveInterfaces.GetRuntimeInterfaceImplementations (type);
+			if (runtimeInterfaceImpls.Length == 0) return;
+
 			// Foreach interface and for each newslot virtual method on the interface, try
 			// to find the method implementation and record it.
 			foreach (var interfaceImpl in runtimeInterfaceImpls) {
@@ -259,8 +215,8 @@ void MapOverrides (MethodDefinition method)
 				if (baseMethod == null)
 					continue;
 				if (baseMethod.DeclaringType.IsInterface) {
-					var found = _runtimeInterfaceImpls.TryGetValue (method.DeclaringType, out var runtimeInterfaces);
-					Debug.Assert (found);
+					var runtimeInterfaces = _recursiveInterfaces.GetRuntimeInterfaceImplementations (method.DeclaringType);
+					Debug.Assert (runtimeInterfaces.Length > 0);
 					var runtimeInterface = runtimeInterfaces.Single (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, baseMethodRef.DeclaringType, context));
 					AnnotateMethods (baseMethod, method, runtimeInterface);
 				} else {
@@ -338,20 +294,19 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 		{
 			// Go over all interfaces, trying to find a method that is an explicit MethodImpl of the
 			// interface method in question.
-			if (!_runtimeInterfaceImpls.TryGetValue (originalInterfaceImpl.Implementor, out var runtimeIfaces))
+			var runtimeIfaces = _recursiveInterfaces.GetRuntimeInterfaceImplementations (originalInterfaceImpl.Implementor);
+			if (runtimeIfaces.Length == 0)
 				return;
 
-			foreach (var interfaceImpl in runtimeIfaces) {
-				var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition;
+			foreach (var runtimeIface in runtimeIfaces) {
+				var potentialImplInterface = runtimeIface.InterfaceTypeDefinition;
 				if (potentialImplInterface is null)
 					continue;
 
-				bool foundImpl = false;
 				foreach (var potentialImplMethod in potentialImplInterface.Methods) {
-					if (potentialImplMethod == interfaceMethodDef &&
-						!potentialImplMethod.IsAbstract) {
+					if (potentialImplMethod == interfaceMethodDef && !potentialImplMethod.IsAbstract) {
+						// The interface method definition provides a default implementation.
 						AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, potentialImplMethod);
-						foundImpl = true;
 						break;
 					}
 
@@ -359,6 +314,7 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o
 						continue;
 
 					// This method is an override of something. Let's see if it's the method we are looking for.
+					bool foundImpl = false;
 					foreach (var baseMethod in potentialImplMethod.Overrides) {
 						if (context.TryResolve (baseMethod) == interfaceMethodDef) {
 							AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, @potentialImplMethod);
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
index 35cc0b65c0417b..1dbdf7bd5c9ac5 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
@@ -9,6 +9,12 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase
 
 		protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces";
 
+		[Fact]
+		public Task BaseTypeMarkedInterfaceDerivedNotKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task GenericInterfaceImplementedRecursively ()
 		{
@@ -27,6 +33,18 @@ public Task OverrideOfRecursiveInterfaceIsRemoved ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task RecursiveGenericInterfaces ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
+		[Fact]
+		public Task RecursiveGenericInterfacesStatic ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task RecursiveInterfaceKept ()
 		{

From a8dfbd397ad01cafe7fb465bc1de40b525b8979e Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Mon, 12 Aug 2024 16:24:33 -0700
Subject: [PATCH 18/27] Inline the runtime interfaces helpers to avoid
 confusing naming

---
 .../Linker/RuntimeInterfacesAlgorithm.cs      | 64 ++++++-------------
 1 file changed, 19 insertions(+), 45 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
index bf604aaf60675c..2fc3ccd94833cd 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
@@ -38,22 +38,31 @@ public ImmutableArray<RuntimeInterfaceImplementation> GetRuntimeInterfaceImpleme
 					continue;
 				}
 
+				// Add the recursive interfaces for each explicit interface, prepending the explicit interface on `originalType` to the chain
 				var recursiveIFaces = GetRuntimeInterfaceImplementations (resolvedInterfaceType);
+				foreach (var runtimeImpl in recursiveIFaces) {
+					// Inflate the interface type with the explicit interfaceImpl reference
+					var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (explicitIface.InterfaceType, _context);
+					Debug.Assert (inflatedInterfaceType is not null);
 
-				foreach (var recursiveIface in recursiveIFaces) {
-					var impls = PrependInterfaceImplToChains (recursiveIface, originalType, explicitIface);
-					foreach (var impl in impls) {
-						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
+					foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) {
+						interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (originalType, existingImpl.InterfaceImplementations.Insert (0, explicitIface)));
 					}
 				}
 			}
 
 			if (originalType.BaseType is not null && _context.TryResolve (originalType.BaseType) is { } baseTypeDef) {
 				var baseTypeIfaces = GetRuntimeInterfaceImplementations (baseTypeDef);
-				foreach (var recursiveIface in baseTypeIfaces) {
-					var impls = CreateImplementationChainsForDerivedType (recursiveIface, originalType.BaseType);
-					foreach (var impl in impls) {
-						interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain);
+				foreach (var runtimeImpl in baseTypeIfaces) {
+					// Inflate the interface type with the base type reference
+					var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (originalType.BaseType, _context);
+					Debug.Assert (inflatedInterfaceType is not null);
+
+					foreach (var impl in runtimeImpl.InterfaceImplementationChains) {
+						// Inflate the provider for the first .impl - this could be a different recursive base type for each chain
+						var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (originalType.BaseType, _context);
+						Debug.Assert (inflatedImplProvider is not null);
+						interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations));
 					}
 				}
 			}
@@ -61,50 +70,15 @@ public ImmutableArray<RuntimeInterfaceImplementation> GetRuntimeInterfaceImpleme
 			if (interfaceTypeToImplChainMap.Count == 0)
 				return ImmutableArray<RuntimeInterfaceImplementation>.Empty;
 
+			// Build the ImmutableArray and cache it
 			ImmutableArray<RuntimeInterfaceImplementation>.Builder builder = ImmutableArray.CreateBuilder<RuntimeInterfaceImplementation> (interfaceTypeToImplChainMap.Count);
 			foreach (var kvp in interfaceTypeToImplChainMap) {
 				builder.Add (new (originalType, kvp.Key, _context.TryResolve (kvp.Key), kvp.Value));
 			}
 			var runtimeInterfaces = builder.MoveToImmutable ();
 			_runtimeInterfaceImpls[originalType] = runtimeInterfaces;
-			return runtimeInterfaces;
-
-		}
-
-		/// <summary>
-		/// Returns a list of InterfaceImplementationChains for a derived type of <see cref="Implementor"/>.
-		/// </summary>
-		IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (RuntimeInterfaceImplementation runtimeImpl, TypeReference baseTypeRef)
-		{
-			// This is only valid for classes
-			Debug.Assert (runtimeImpl.Implementor.IsClass);
-			Debug.Assert (runtimeImpl.Implementor == _context.TryResolve (baseTypeRef));
-
-			var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (baseTypeRef, _context);
-			Debug.Assert (inflatedInterfaceType is not null);
-
-			foreach (var impl in runtimeImpl.InterfaceImplementationChains) {
-				var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, _context);
-				Debug.Assert (inflatedImplProvider is not null);
-				yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations));
-			}
-		}
-
-		/// <summary>
-		/// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of <see cref="Implementor"/>.
-		/// </summary>
-		IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> PrependInterfaceImplToChains (RuntimeInterfaceImplementation runtimeImpl, TypeDefinition typeWithPrependedImpl, InterfaceImplementation implToPrepend)
-		{
-			Debug.Assert (runtimeImpl.Implementor.IsInterface);
-			Debug.Assert (_context.TryResolve (implToPrepend.InterfaceType) == runtimeImpl.Implementor);
-			Debug.Assert (typeWithPrependedImpl.Interfaces.Contains (implToPrepend));
 
-			var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (implToPrepend.InterfaceType, _context);
-			Debug.Assert (inflatedInterfaceType is not null);
-
-			foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) {
-				yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeWithPrependedImpl, existingImpl.InterfaceImplementations.Insert (0, implToPrepend)));
-			}
+			return runtimeInterfaces;
 		}
 	}
 }

From 01e45182eac5f56a7b053b10e2f67a1065906d1b Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 13 Aug 2024 08:10:17 -0700
Subject: [PATCH 19/27] Undo unrelated formatting

---
 .../src/linker/Linker.Steps/MarkStep.cs       | 94 +++++++++----------
 .../illink/src/linker/Linker/TypeMapInfo.cs   |  3 +-
 2 files changed, 49 insertions(+), 48 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index e663b50ff284c4..96e770d116810d 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -1483,17 +1483,17 @@ bool ProcessLazyAttributes ()
 				string attributeFullName = customAttribute.Constructor.DeclaringType.FullName;
 				switch (attributeFullName) {
 				case "System.Diagnostics.DebuggerDisplayAttribute": {
-					TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
-					if (targetType != null)
-						MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin);
-					break;
-				}
+						TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
+						if (targetType != null)
+							MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin);
+						break;
+					}
 				case "System.Diagnostics.DebuggerTypeProxyAttribute": {
-					TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
-					if (targetType != null)
-						MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin);
-					break;
-				}
+						TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider);
+						if (targetType != null)
+							MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin);
+						break;
+					}
 				}
 			}
 
@@ -3595,47 +3595,47 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
 				break;
 
 			case OperandType.InlineMethod: {
-				(DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch {
-					Code.Jmp => (DependencyKind.DirectCall, false),
-					Code.Call => (DependencyKind.DirectCall, false),
-					Code.Callvirt => (DependencyKind.VirtualCall, false),
-					Code.Newobj => (DependencyKind.Newobj, false),
-					Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true),
-					Code.Ldftn => (DependencyKind.Ldftn, true),
-					_ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}")
-				};
-
-				MethodReference methodReference = (MethodReference) instruction.Operand;
-
-				requiresReflectionMethodBodyScanner |=
-					ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference);
-
-				origin = new MessageOrigin (origin, instruction.Offset);
-				if (markForReflectionAccess) {
-					MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin);
-				} else {
-					MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin);
+					(DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch {
+						Code.Jmp => (DependencyKind.DirectCall, false),
+						Code.Call => (DependencyKind.DirectCall, false),
+						Code.Callvirt => (DependencyKind.VirtualCall, false),
+						Code.Newobj => (DependencyKind.Newobj, false),
+						Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true),
+						Code.Ldftn => (DependencyKind.Ldftn, true),
+						_ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}")
+					};
+
+					MethodReference methodReference = (MethodReference) instruction.Operand;
+
+					requiresReflectionMethodBodyScanner |=
+						ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference);
+
+					origin = new MessageOrigin (origin, instruction.Offset);
+					if (markForReflectionAccess) {
+						MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin);
+					} else {
+						MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin);
+					}
+					break;
 				}
-				break;
-			}
 
 			case OperandType.InlineTok: {
-				object token = instruction.Operand;
-				Debug.Assert (instruction.OpCode.Code == Code.Ldtoken);
-				var reason = new DependencyInfo (DependencyKind.Ldtoken, method);
-				origin = new MessageOrigin (origin, instruction.Offset);
-
-				if (token is TypeReference typeReference) {
-					// Error will be reported as part of MarkType
-					if (Context.TryResolve (typeReference) is TypeDefinition type)
-						MarkTypeVisibleToReflection (typeReference, type, reason, origin);
-				} else if (token is MethodReference methodReference) {
-					MarkMethodVisibleToReflection (methodReference, reason, origin);
-				} else {
-					MarkFieldVisibleToReflection ((FieldReference) token, reason, origin);
+					object token = instruction.Operand;
+					Debug.Assert (instruction.OpCode.Code == Code.Ldtoken);
+					var reason = new DependencyInfo (DependencyKind.Ldtoken, method);
+					origin = new MessageOrigin (origin, instruction.Offset);
+
+					if (token is TypeReference typeReference) {
+						// Error will be reported as part of MarkType
+						if (Context.TryResolve (typeReference) is TypeDefinition type)
+							MarkTypeVisibleToReflection (typeReference, type, reason, origin);
+					} else if (token is MethodReference methodReference) {
+						MarkMethodVisibleToReflection (methodReference, reason, origin);
+					} else {
+						MarkFieldVisibleToReflection ((FieldReference) token, reason, origin);
+					}
+					break;
 				}
-				break;
-			}
 
 			case OperandType.InlineType:
 				var operand = (TypeReference) instruction.Operand;
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index bada865b78483a..d552e96f6ff06d 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -138,7 +138,8 @@ protected virtual void MapType (TypeDefinition type)
 		void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type)
 		{
 			var runtimeInterfaceImpls = _recursiveInterfaces.GetRuntimeInterfaceImplementations (type);
-			if (runtimeInterfaceImpls.Length == 0) return;
+			if (runtimeInterfaceImpls.Length == 0)
+				return;
 
 			// Foreach interface and for each newslot virtual method on the interface, try
 			// to find the method implementation and record it.

From df044848c00e35015040dc6426da5ec7acfb60eb Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 13 Aug 2024 09:49:04 -0700
Subject: [PATCH 20/27] Revert "Implement type name resolution for ILLink
 analyzer (#106209)"

This reverts commit e5b1d021938d41b589feeb5b6e9dcb55035dfda9.
---
 .../DynamicallyAccessedMembersAnalyzer.cs     |   6 +-
 .../ILLink.RoslynAnalyzer.csproj              |   4 -
 .../TrimAnalysis/GenericArgumentDataFlow.cs   |  23 ++-
 .../TrimAnalysis/HandleCallAction.cs          |   5 +-
 .../TrimAnalysis/ReflectionAccessAnalyzer.cs  |  14 +-
 ...RequireDynamicallyAccessedMembersAction.cs |  31 +---
 .../TrimAnalysisAssignmentPattern.cs          |   7 +-
 ...TrimAnalysisGenericInstantiationPattern.cs |   7 +-
 .../TrimAnalysisMethodCallPattern.cs          |   5 +-
 .../TrimAnalysisReflectionAccessPattern.cs    |   3 +-
 .../TrimAnalysis/TrimAnalysisVisitor.cs       |   8 +-
 .../TrimAnalysis/TypeNameResolver.cs          | 149 ------------------
 .../DynamicallyAccessedMembersCodeFixTests.cs |  28 ++--
 .../TestCaseCompilation.cs                    |   2 +-
 .../DataFlow/AssemblyQualifiedNameDataflow.cs |   2 +-
 .../DataFlow/AttributeConstructorDataflow.cs  |   2 +-
 .../DataFlow/AttributeFieldDataflow.cs        |   2 +-
 .../DataFlow/AttributePropertyDataflow.cs     |   8 +-
 .../GenericParameterWarningLocation.cs        |   8 +-
 .../DataFlow/GetTypeDataFlow.cs               |   6 +-
 .../Reflection/TypeUsedViaReflection.cs       | 145 +----------------
 .../TestCasesRunner/ResultChecker.cs          |   8 +-
 22 files changed, 80 insertions(+), 393 deletions(-)
 delete mode 100644 src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs

diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs
index c9342122aa6de4..66b71ab4f9c385 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs
@@ -52,7 +52,6 @@ public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics ()
 			diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedTypeNameInTypeGetType));
 			diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedParameterInMethodCreateInstance));
 			diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed));
-			diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.TypeNameIsNotAssemblyQualified));
 			diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ReturnValueDoesNotMatchFeatureGuards));
 			diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.InvalidFeatureGuard));
 
@@ -118,12 +117,11 @@ public override void Initialize (AnalysisContext context)
 
 					var location = GetPrimaryLocation (type.Locations);
 
-					var typeNameResolver = new TypeNameResolver (context.Compilation);
 					if (type.BaseType is INamedTypeSymbol baseType)
-						GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, baseType, context.ReportDiagnostic);
+						GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, baseType, context.ReportDiagnostic);
 
 					foreach (var interfaceType in type.Interfaces)
-						GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, interfaceType, context.ReportDiagnostic);
+						GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, interfaceType, context.ReportDiagnostic);
 				}, SymbolKind.NamedType);
 				context.RegisterSymbolAction (context => {
 					VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol);
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj
index c3b0b1d1847b48..bc410523d5d716 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj
@@ -15,13 +15,10 @@
          optimize the analyzer even in Debug builds. Note: we still use the
          Debug configuration to get Debug asserts. -->
     <Optimize>true</Optimize>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <ItemGroup>
     <Compile Include="$(CoreLibSharedDir)System/Index.cs" />
-    <Compile Include="$(LibrariesProjectRoot)\Common\src\System\Reflection\Metadata\TypeNameHelpers.cs" />
-    <Compile Include="$(LibrariesProjectRoot)\Common\src\System\Text\ValueStringBuilder.cs" />
   </ItemGroup>
 
   <ItemGroup>
@@ -31,7 +28,6 @@
       <PrivateAssets>all</PrivateAssets>
       <ExcludeAssets>contentfiles</ExcludeAssets> <!-- We include our own copy of the ClosedAttribute to work in source build -->
     </PackageReference>
-    <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
   </ItemGroup>
 
   <Import Project="..\ILLink.Shared\ILLink.Shared.projitems" Label="Shared" />
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs
index 84a6d31d3fa85b..8eac76a2993396 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs
@@ -12,29 +12,28 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
 {
 	internal static class GenericArgumentDataFlow
 	{
-		public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, INamedTypeSymbol type, Action<Diagnostic>? reportDiagnostic)
+		public static void ProcessGenericArgumentDataFlow (Location location, INamedTypeSymbol type, Action<Diagnostic> reportDiagnostic)
 		{
-			ProcessGenericArgumentDataFlow (typeNameResolver, location, type.TypeArguments, type.TypeParameters, reportDiagnostic);
+			ProcessGenericArgumentDataFlow (location, type.TypeArguments, type.TypeParameters, reportDiagnostic);
 		}
 
-		public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, IMethodSymbol method, Action<Diagnostic>? reportDiagnostic)
+		public static void ProcessGenericArgumentDataFlow (Location location, IMethodSymbol method, Action<Diagnostic> reportDiagnostic)
 		{
-			ProcessGenericArgumentDataFlow (typeNameResolver, location, method.TypeArguments, method.TypeParameters, reportDiagnostic);
+			ProcessGenericArgumentDataFlow (location, method.TypeArguments, method.TypeParameters, reportDiagnostic);
 
-			ProcessGenericArgumentDataFlow (typeNameResolver, location, method.ContainingType, reportDiagnostic);
+			ProcessGenericArgumentDataFlow (location, method.ContainingType, reportDiagnostic);
 		}
 
-		public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, IFieldSymbol field, Action<Diagnostic>? reportDiagnostic)
+		public static void ProcessGenericArgumentDataFlow (Location location, IFieldSymbol field, Action<Diagnostic> reportDiagnostic)
 		{
-			ProcessGenericArgumentDataFlow (typeNameResolver, location, field.ContainingType, reportDiagnostic);
+			ProcessGenericArgumentDataFlow (location, field.ContainingType, reportDiagnostic);
 		}
 
 		static void ProcessGenericArgumentDataFlow (
-			TypeNameResolver typeNameResolver,
 			Location location,
 			ImmutableArray<ITypeSymbol> typeArguments,
 			ImmutableArray<ITypeParameterSymbol> typeParameters,
-			Action<Diagnostic>? reportDiagnostic)
+			Action<Diagnostic> reportDiagnostic)
 		{
 			var diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
 			for (int i = 0; i < typeArguments.Length; i++) {
@@ -43,14 +42,14 @@ static void ProcessGenericArgumentDataFlow (
 				var genericParameterValue = new GenericParameterValue (typeParameters[i]);
 				if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) {
 					SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol (typeArgument)!;
-					var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver);
-					var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer);
+					var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
+					var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer);
 					requireDynamicallyAccessedMembersAction.Invoke (genericArgumentValue, genericParameterValue);
 				}
 
 				// Recursively process generic argument data flow on the generic argument if it itself is generic
 				if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType)
-					ProcessGenericArgumentDataFlow (typeNameResolver, location, namedTypeArgument, reportDiagnostic);
+					ProcessGenericArgumentDataFlow (location, namedTypeArgument, reportDiagnostic);
 			}
 		}
 
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs
index 53294dc3c32a9e..3e8d00d5d050f8 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs
@@ -28,7 +28,6 @@ internal partial struct HandleCallAction
 		ValueSetLattice<SingleValue> _multiValueLattice;
 
 		public HandleCallAction (
-			TypeNameResolver typeNameResolver,
 			Location location,
 			ISymbol owningSymbol,
 			IOperation operation,
@@ -40,8 +39,8 @@ public HandleCallAction (
 			_isNewObj = operation.Kind == OperationKind.ObjectCreation;
 			_diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
 			_annotations = FlowAnnotations.Instance;
-			_reflectionAccessAnalyzer = new (reportDiagnostic, typeNameResolver);
-			_requireDynamicallyAccessedMembersAction = new (typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer);
+			_reflectionAccessAnalyzer = new (reportDiagnostic);
+			_requireDynamicallyAccessedMembersAction = new (_diagnosticContext, _reflectionAccessAnalyzer);
 			_multiValueLattice = multiValueLattice;
 		}
 
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs
index c53e28ff336fdb..51d684816418d3 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs
@@ -15,15 +15,8 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
 	readonly struct ReflectionAccessAnalyzer
 	{
 		readonly Action<Diagnostic>? _reportDiagnostic;
-		readonly TypeNameResolver _typeNameResolver;
 
-		public ReflectionAccessAnalyzer (
-			Action<Diagnostic>? reportDiagnostic,
-			TypeNameResolver typeNameResolver)
-		{
-			_reportDiagnostic = reportDiagnostic;
-			_typeNameResolver = typeNameResolver;
-		}
+		public ReflectionAccessAnalyzer (Action<Diagnostic>? reportDiagnostic) => _reportDiagnostic = reportDiagnostic;
 
 #pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
 		internal void GetReflectionAccessDiagnostics (Location location, ITypeSymbol typeSymbol, DynamicallyAccessedMemberTypes requiredMemberTypes, bool declaredOnly = false)
@@ -145,10 +138,5 @@ void GetDiagnosticsForField (Location location, IFieldSymbol fieldSymbol)
 				diagnosticContext.AddDiagnostic (DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, fieldSymbol.GetDisplayName ());
 			}
 		}
-
-		internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out ITypeSymbol? type)
-		{
-			return _typeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out type, needsAssemblyName);
-		}
 	}
 }
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs
index 91fdb54ef83944..5226aad4f0320b 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs
@@ -1,55 +1,40 @@
 // Copyright (c) .NET Foundation and contributors. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
-using System;
-using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
-using System.Reflection.Metadata;
-using Microsoft.CodeAnalysis;
 using ILLink.RoslynAnalyzer.TrimAnalysis;
 using ILLink.Shared.TypeSystemProxy;
-using System.Collections.Immutable;
 
 namespace ILLink.Shared.TrimAnalysis
 {
 	internal partial struct RequireDynamicallyAccessedMembersAction
 	{
-		readonly Location _location;
-		readonly Action<Diagnostic>? _reportDiagnostic;
 		readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer;
-		readonly TypeNameResolver _typeNameResolver;
 #pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
 #pragma warning disable IDE0060 // Unused parameters - should be removed once methods are actually implemented
 
 		public RequireDynamicallyAccessedMembersAction (
-			TypeNameResolver typeNameResolver,
-			Location location,
-			Action<Diagnostic>? reportDiagnostic,
+			DiagnosticContext diagnosticContext,
 			ReflectionAccessAnalyzer reflectionAccessAnalyzer)
 		{
-			_typeNameResolver = typeNameResolver;
-			_location = location;
-			_reportDiagnostic = reportDiagnostic;
+			_diagnosticContext = diagnosticContext;
 			_reflectionAccessAnalyzer = reflectionAccessAnalyzer;
-			_diagnosticContext = new (location, reportDiagnostic);
 		}
 
 		public partial bool TryResolveTypeNameAndMark (string typeName, bool needsAssemblyName, out TypeProxy type)
 		{
-			var diagnosticContext = new DiagnosticContext (_location, _reportDiagnostic);
-			if (_reflectionAccessAnalyzer.TryResolveTypeNameAndMark (typeName, diagnosticContext, needsAssemblyName, out ITypeSymbol? foundType)) {
-				if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType)
-					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (_typeNameResolver, _location, namedType, _reportDiagnostic);
+			// TODO: Implement type name resolution to type symbol
+			// https://github.com/dotnet/runtime/issues/95118
 
-				type = new TypeProxy (foundType);
-				return true;
-			}
+			// Important corner cases:
+			//   IL2105 (see it's occurences in the tests) - non-assembly qualified type name which doesn't resolve warns
+			//     - will need to figure out what analyzer should do around this.
 
 			type = default;
 			return false;
 		}
 
 		private partial void MarkTypeForDynamicallyAccessedMembers (in TypeProxy type, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes) =>
-			_reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (_location, type.Type, dynamicallyAccessedMemberTypes);
+			_reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (_diagnosticContext.Location, type.Type, dynamicallyAccessedMemberTypes);
 	}
 }
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs
index 7f410b7e7c4bcf..46793d6b448c61 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs
@@ -55,7 +55,7 @@ public TrimAnalysisAssignmentPattern Merge (
 
 		public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnostic> reportDiagnostic)
 		{
-			var location = Operation.Syntax.GetLocation ();
+			var diagnosticContext = new DiagnosticContext (Operation.Syntax.GetLocation (), reportDiagnostic);
 			if (context.EnableTrimAnalyzer &&
 				!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
 				!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
@@ -66,9 +66,8 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
 						if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers)
 							throw new NotImplementedException ();
 
-						var typeNameResolver = new TypeNameResolver (context.Compilation);
-						var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver);
-						var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer);
+						var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
+						var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer);
 						requireDynamicallyAccessedMembersAction.Invoke (sourceValue, targetWithDynamicallyAccessedMembers);
 					}
 				}
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs
index c026270ffd2c7b..f0245634948c0e 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs
@@ -49,18 +49,17 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
 				!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
 				!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
 				var location = Operation.Syntax.GetLocation ();
-				var typeNameResolver = new TypeNameResolver (context.Compilation);
 				switch (GenericInstantiation) {
 				case INamedTypeSymbol type:
-					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, type, reportDiagnostic);
+					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, type, reportDiagnostic);
 					break;
 
 				case IMethodSymbol method:
-					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, method, reportDiagnostic);
+					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, method, reportDiagnostic);
 					break;
 
 				case IFieldSymbol field:
-					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, field, reportDiagnostic);
+					GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, field, reportDiagnostic);
 					break;
 				}
 			}
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs
index 409c8ecaeb4144..707bffbd133581 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs
@@ -73,11 +73,10 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
 		{
 			var location = Operation.Syntax.GetLocation ();
 			if (context.EnableTrimAnalyzer &&
-				!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
+				!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) &&
 				!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute))
 			{
-				var typeNameResolver = new TypeNameResolver (context.Compilation);
-				TrimAnalysisVisitor.HandleCall (typeNameResolver, Operation, OwningSymbol, CalledMethod, Instance, Arguments, location, reportDiagnostic, default, out var _);
+				TrimAnalysisVisitor.HandleCall(Operation, OwningSymbol, CalledMethod, Instance, Arguments, location, reportDiagnostic, default, out var _);
 			}
 
 			var diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs
index 2ab2a0f85b8a8e..c432ccfecffc50 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs
@@ -46,8 +46,7 @@ public TrimAnalysisReflectionAccessPattern Merge (
 		public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnostic> reportDiagnostic)
 		{
 			var location = Operation.Syntax.GetLocation ();
-			var typeNameResolver = new TypeNameResolver (context.Compilation);
-			var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver);
+			var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
 			if (context.EnableTrimAnalyzer &&
 				!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
 				!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
index d98d172296aae8..58c3c97c550202 100644
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
+++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
@@ -43,8 +43,6 @@ internal sealed class TrimAnalysisVisitor : LocalDataFlowVisitor<
 
 		FeatureChecksVisitor _featureChecksVisitor;
 
-		readonly TypeNameResolver _typeNameResolver;
-
 		public TrimAnalysisVisitor (
 			Compilation compilation,
 			LocalStateAndContextLattice<MultiValue, FeatureContext, ValueSetLattice<SingleValue>, FeatureContextLattice> lattice,
@@ -59,7 +57,6 @@ public TrimAnalysisVisitor (
 			_multiValueLattice = lattice.LocalStateLattice.Lattice.ValueLattice;
 			TrimAnalysisPatterns = trimAnalysisPatterns;
 			_featureChecksVisitor = new FeatureChecksVisitor (dataFlowAnalyzerContext);
-			_typeNameResolver = new TypeNameResolver (compilation);
 		}
 
 		public override FeatureChecksValue GetConditionValue (IOperation branchValueOperation, StateValue state)
@@ -294,7 +291,7 @@ public override MultiValue HandleMethodCall (
 			//   Especially with DAM on type, this can lead to incorrectly analyzed code (as in unknown type which leads
 			//   to noise). ILLink has the same problem currently: https://github.com/dotnet/linker/issues/1952
 
-			HandleCall (_typeNameResolver, operation, OwningSymbol, calledMethod, instance, arguments, Location.None, null, _multiValueLattice, out MultiValue methodReturnValue);
+			HandleCall (operation, OwningSymbol, calledMethod, instance, arguments, Location.None, null, _multiValueLattice, out MultiValue methodReturnValue);
 
 			// This will copy the values if necessary
 			TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern (
@@ -318,7 +315,6 @@ public override MultiValue HandleMethodCall (
 		}
 
 		internal static void HandleCall(
-			TypeNameResolver typeNameResolver,
 			IOperation operation,
 			ISymbol owningSymbol,
 			IMethodSymbol calledMethod,
@@ -329,7 +325,7 @@ internal static void HandleCall(
 			ValueSetLattice<SingleValue> multiValueLattice,
 			out MultiValue methodReturnValue)
 		{
-			var handleCallAction = new HandleCallAction (typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic);
+			var handleCallAction = new HandleCallAction (location, owningSymbol, operation, multiValueLattice, reportDiagnostic);
 			MethodProxy method = new (calledMethod);
 			var intrinsicId = Intrinsics.GetIntrinsicIdForMethod (method);
 			if (!handleCallAction.Invoke (method, instance, arguments, intrinsicId, out methodReturnValue))
diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs
deleted file mode 100644
index a91797e4316ebc..00000000000000
--- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection.Metadata;
-using Microsoft.CodeAnalysis;
-using ILLink.RoslynAnalyzer.TrimAnalysis;
-using ILLink.Shared.TypeSystemProxy;
-using System.Collections.Immutable;
-
-namespace ILLink.Shared.TrimAnalysis
-{
-	internal struct TypeNameResolver
-	{
-		readonly Compilation _compilation;
-
-		static readonly TypeNameParseOptions s_typeNameParseOptions = new () { MaxNodes = int.MaxValue };
-
-		public TypeNameResolver (Compilation compilation)
-		{
-			_compilation = compilation;
-		}
-
-		public bool TryResolveTypeName (
-			string typeNameString,
-			in DiagnosticContext diagnosticContext,
-			out ITypeSymbol? type,
-			bool needsAssemblyName)
-		{
-			type = null;
-			if (!TypeName.TryParse (typeNameString.AsSpan (), out TypeName? typeName, s_typeNameParseOptions))
-				return false;
-
-			if (needsAssemblyName && !IsFullyQualified (typeName)) {
-				diagnosticContext.AddDiagnostic (DiagnosticId.TypeNameIsNotAssemblyQualified, typeNameString);
-				return false;
-			}
-
-			type = ResolveTypeName (_compilation.Assembly, typeName);
-			return type != null;
-
-			static bool IsFullyQualified (TypeName typeName)
-			{
-				if (typeName.AssemblyName is null)
-					return false;
-
-				if (typeName.IsArray || typeName.IsPointer || typeName.IsByRef)
-					return IsFullyQualified (typeName.GetElementType ());
-
-				if (typeName.IsConstructedGenericType) {
-					foreach (var arg in typeName.GetGenericArguments ()) {
-						if (!IsFullyQualified (arg))
-							return false;
-					}
-				}
-
-				return true;
-			}
-		}
-
-		ITypeSymbol? ResolveTypeName (IAssemblySymbol assembly, TypeName typeName)
-		{
-			if (typeName.IsSimple)
-				return GetSimpleType (assembly, typeName);
-
-			if (typeName.IsConstructedGenericType)
-				return GetGenericType (assembly, typeName);
-
-			if (typeName.IsArray || typeName.IsPointer || typeName.IsByRef)
-			{
-				if (ResolveTypeName (assembly, typeName.GetElementType ()) is not ITypeSymbol type)
-					return null;
-
-				if (typeName.IsArray)
-					return typeName.IsSZArray ? _compilation.CreateArrayTypeSymbol (type) : _compilation.CreateArrayTypeSymbol (type, typeName.GetArrayRank ());
-
-				// Roslyn doesn't have a representation for byref types
-				// (the byrefness is considered part of the symbol, not its type)
-				if (typeName.IsByRef)
-					return null;
-
-				if (typeName.IsPointer)
-					return _compilation.CreatePointerTypeSymbol (type);
-
-				Debug.Fail ("Unreachable");
-				return null;
-			}
-
-			return null;
-		}
-
-		private ITypeSymbol? GetSimpleType (IAssemblySymbol assembly, TypeName typeName)
-		{
-			IAssemblySymbol module = assembly;
-			if (typeName.AssemblyName is AssemblyNameInfo assemblyName) {
-				if (ResolveAssembly (assemblyName) is not IAssemblySymbol resolvedAssembly)
-					return null;
-				module = resolvedAssembly;
-			}
-
-			if (GetSimpleTypeFromModule (typeName, module) is ITypeSymbol type)
-				return type;
-
-			// The analyzer doesn't see the core library, so can't fall back to lookup up types in corelib.
-			return null;
-		}
-
-		private static ITypeSymbol? GetSimpleTypeFromModule (TypeName typeName, IAssemblySymbol module)
-		{
-			string fullName = TypeNameHelpers.Unescape (typeName.FullName);
-			return module.GetTypeByMetadataName (fullName);
-		}
-
-		private ITypeSymbol? GetGenericType (IAssemblySymbol assembly, TypeName typeName)
-		{
-			if (ResolveTypeName (assembly, typeName.GetGenericTypeDefinition ()) is not INamedTypeSymbol typeDefinition)
-				return null;
-
-			ImmutableArray<TypeName> typeArguments = typeName.GetGenericArguments ();
-			ITypeSymbol[] instantiation = new ITypeSymbol[typeArguments.Length];
-			for (int i = 0; i < typeArguments.Length; i++)
-			{
-				if (ResolveTypeName (assembly, typeArguments[i]) is not ITypeSymbol type)
-					return null;
-				instantiation[i] = type;
-			}
-			return typeDefinition.Construct (instantiation);
-		}
-
-		IAssemblySymbol? ResolveAssembly (AssemblyNameInfo? assemblyName)
-		{
-			if (assemblyName is null)
-				return null;
-
-			if (_compilation.Assembly.Name == assemblyName.Name)
-				return _compilation.Assembly;
-
-			foreach (var metadataReference in _compilation.References) {
-				if (_compilation.GetAssemblyOrModuleSymbol (metadataReference) is not IAssemblySymbol asmSym)
-					continue;
-				if (asmSym.Name == assemblyName.Name)
-					return asmSym;
-			}
-			return null;
-		}
-	}
-}
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs
index 9468360d241119..1f7017bff311b4 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs
@@ -101,7 +101,7 @@ class C
 				[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
 				static string M(Type t) {
 					M2(t);
-					return "Foo, test";
+					return "Foo";
 				}
 
 				static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {}
@@ -117,7 +117,7 @@ class C
 				[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
 				static string M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {
 					M2(t);
-					return "Foo, test";
+					return "Foo";
 				}
 
 				static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {}
@@ -187,7 +187,7 @@ class C
 				[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
 				static string M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) {
 					M2(t);
-					return "Foo, test";
+					return "Foo";
 				}
 
 				static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {}
@@ -1067,7 +1067,7 @@ await VerifyDynamicallyAccessedMembersCodeFix (
 		}
 
 		[Fact]
-		public async Task CodeFix_IL2075_ReturnAttributeLeavesOnCodeFix ()
+		public async Task CodeFix_IL2075_ReutrnAttributeLeavesOnCodeFix ()
 		{
 			var test = $$$"""
 			namespace System
@@ -1078,7 +1078,7 @@ class C : TestSystemTypeBase
 					public static string Main()
 					{
 						GetC().GetMethod("Foo");
-						return "Foo, test";
+						return "Foo";
 					}
 
 					private static Type GetC ()
@@ -1097,7 +1097,7 @@ class C : TestSystemTypeBase
 					public static string Main()
 					{
 						GetC().GetMethod("Foo");
-						return "Foo, test";
+						return "Foo";
 					}
 
 			        [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
@@ -1608,7 +1608,7 @@ public static void Main()
 					private string M1()
 					{
 						M2(this);
-						return "Foo, test";
+						return "Foo";
 					}
 
 					private static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t)
@@ -1633,7 +1633,7 @@ public static void Main()
 			        private string M1()
 					{
 						M2(this);
-						return "Foo, test";
+						return "Foo";
 					}
 
 					private static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t)
@@ -1676,7 +1676,7 @@ class C : TestSystemTypeBase
 				{
 					public static void Main()
 					{
-						new C().M1("Foo, test");
+						new C().M1("Foo");
 					}
 
 					[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)]
@@ -1700,7 +1700,7 @@ class C : TestSystemTypeBase
 				{
 					public static void Main()
 					{
-						new C().M1("Foo, test");
+						new C().M1("Foo");
 					}
 
 					[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)]
@@ -1736,7 +1736,7 @@ await VerifyDynamicallyAccessedMembersCodeFix (
 					// /0/Test0.cs(193,4): warning IL2065: Value passed to implicit 'this' parameter of method 'System.C.M1()' can not be statically determined
 					// and may not meet 'DynamicallyAccessedMembersAttribute' requirements.
 					VerifyCS.Diagnostic(DiagnosticId.ImplicitThisCannotBeStaticallyDetermined)
-						.WithSpan(193, 4, 193, 27)
+						.WithSpan(193, 4, 193, 21)
 						.WithArguments("System.C.M1(String)")
 				});
 		}
@@ -1858,7 +1858,7 @@ class C : TestSystemTypeBase
 				{
 					public static void Main()
 					{
-						new C().M1("Foo, test");
+						new C().M1("Foo");
 					}
 
 					[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
@@ -1878,7 +1878,7 @@ class C : TestSystemTypeBase
 				{
 					public static void Main()
 					{
-						new C().M1("Foo, test");
+						new C().M1("Foo");
 					}
 
 					[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
@@ -1907,7 +1907,7 @@ await VerifyDynamicallyAccessedMembersCodeFix (
 					// /0/Test0.cs(193,4): warning IL2065: Value passed to implicit 'this' parameter of method 'System.C.M1()' can not be statically determined
 					// and may not meet 'DynamicallyAccessedMembersAttribute' requirements.
 					VerifyCS.Diagnostic(DiagnosticId.ImplicitThisCannotBeStaticallyDetermined)
-						.WithSpan(193, 4, 193, 27)
+						.WithSpan(193, 4, 193, 21)
 						.WithArguments("System.C.M1(String)")
 				});
 		}
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs
index 58c57155aaada7..1a51ba84083287 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs
@@ -44,7 +44,7 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel
 			var sources = new List<SyntaxTree> () { src };
 			sources.AddRange (additionalSources ?? Array.Empty<SyntaxTree> ());
 			var comp = CSharpCompilation.Create (
-				assemblyName: "test",
+				assemblyName: Guid.NewGuid ().ToString ("N"),
 				syntaxTrees: sources,
 				references: SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences().Add(mdRef).AddRange(additionalReferences),
 				new CSharpCompilationOptions (consoleApplication ? OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary));
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs
index 85330b4b7e60f1..c32a472e625bcc 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs
@@ -58,7 +58,7 @@ static void TestConstructors ()
 			RequireNothing (type);
 		}
 
-		[ExpectedWarning ("IL2122", "Type 'System.Invalid.TypeName' is not assembly qualified. Type name strings used for dynamically accessing a type should be assembly qualified.")]
+		[ExpectedWarning ("IL2122", "Type 'System.Invalid.TypeName' is not assembly qualified. " + "Type name strings used for dynamically accessing a type should be assembly qualified.", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 		static void TestUnqualifiedTypeNameWarns ()
 		{
 			RequirePublicConstructors ("System.Invalid.TypeName");
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs
index 04746ae30a4ddf..b6d5eed8170f16 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs
@@ -25,7 +25,7 @@ class AttributeConstructorDataflow
 		[KeepsPublicMethods ("Mono.Linker.Tests.Cases.DataFlow.AttributeConstructorDataflow+ClassWithKeptPublicMethods, test")]
 		[KeepsPublicFields (null, null)]
 		[TypeArray (new Type[] { typeof (AttributeConstructorDataflow) })]
-		[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")]
+		[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/linker/issues/2273")]
 		public static void Main ()
 		{
 			typeof (AttributeConstructorDataflow).GetMethod ("Main").GetCustomAttribute (typeof (KeepsPublicConstructorAttribute));
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs
index bb3c1d4bebb1aa..e7683d1f33806a 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs
@@ -40,7 +40,7 @@ public static void TestKeepsPublicMethods ()
 
 		[Kept]
 		[KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))]
-		[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")]
+		[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 		[KeepsPublicMethods (TypeName = "Mono.Linker.Tests.Cases.DataFlow.AttributeFieldDataflow+ClassWithKeptPublicMethods, test")]
 		public static void TestKeepsPublicMethodsString ()
 		{
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs
index ba094199740ea1..de7186fc7f54a4 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs
@@ -55,7 +55,8 @@ public static void TestKeepsPublicMethods ()
 
 			[Kept]
 			[KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))]
-			[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethodsKeptByName--")]
+            // Trimmer/NativeAot only for now
+            [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethodsKeptByName--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			[KeepsPublicMethods (TypeName = "Mono.Linker.Tests.Cases.DataFlow.AttributePropertyDataflow+AttributesOnMethod+ClassWithKeptPublicMethodsKeptByName, test")]
 			public static void TestKeepsPublicMethodsByName ()
 			{
@@ -210,7 +211,8 @@ class AttributeWithConditionalExpression
 		{
 			[Kept]
 			[KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))]
-			[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")]
+			// Trimmer/NativeAot only for now
+			[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			[KeepsPublicMethods (TypeName = 1 + 1 == 2 ? "Mono.Linker.Tests.Cases.DataFlow.AttributePropertyDataflow+AttributeWithConditionalExpression+ClassWithKeptPublicMethodsKeptByName, test" : null)]
 			public static void Test ()
 			{
@@ -222,7 +224,7 @@ public static void Test ()
 			// where the owning symbol is not a method.
 			[Kept]
 			[KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))]
-			[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")]
+			[ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			[KeepsPublicMethods (TypeName = 1 + 1 == 2 ? "Mono.Linker.Tests.Cases.DataFlow.AttributePropertyDataflow+AttributeWithConditionalExpression+ClassWithKeptPublicMethodsKeptByName, test" : null)]
 			public static int field;
 
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs
index 5809d062835327..f28802c174d116 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs
@@ -1372,7 +1372,7 @@ class AnnotatedString
 			{
 				static void MethodWithAnnotatedParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] string typeName) { }
 
-				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")]
+				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 				static void AnnotatedParameter ()
 				{
 					MethodWithAnnotatedParameter ("Mono.Linker.Tests.Cases.DataFlow.GenericParameterWarningLocation+MethodBody+TypeWithPrivateMethods`1[["
@@ -1380,7 +1380,7 @@ static void AnnotatedParameter ()
 						+ "]], test");
 				}
 
-				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")]
+				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 				[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
 				static string AnnotatedReturnValue ()
 				{
@@ -1392,7 +1392,7 @@ static string AnnotatedReturnValue ()
 				[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
 				static string _annotatedField;
 
-				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")]
+				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 				static void AnnotatedField ()
 				{
 					_annotatedField = "Mono.Linker.Tests.Cases.DataFlow.GenericParameterWarningLocation+MethodBody+TypeWithPrivateMethods`1[["
@@ -1410,7 +1410,7 @@ public static void Test ()
 
 			class TypeGetType
 			{
-				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")]
+				[ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 				static void SpecificType ()
 				{
 					Type.GetType ("Mono.Linker.Tests.Cases.DataFlow.GenericParameterWarningLocation+MethodBody+TypeWithPrivateMethods`1[["
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs
index 5bb64c5e9c2cf5..31ef05ef3e55bf 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs
@@ -168,8 +168,8 @@ public void Method1 () { }
 			[RequiresUnreferencedCode ("--Method2--")]
 			public void Method2 () { }
 
-			[ExpectedWarning ("IL2026", "--Method1--")]
-			[ExpectedWarning ("IL2026", "--Method2--")]
+			[ExpectedWarning ("IL2026", "--Method1--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
+			[ExpectedWarning ("IL2026", "--Method2--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			public static void Test ()
 			{
 				Type.GetType ("Mono.Linker.Tests.Cases.DataFlow." + nameof (GetTypeDataFlow) + "+" + nameof (TypeWithWarnings)).RequiresPublicMethods ();
@@ -183,7 +183,7 @@ class OverConstTypeName
 			[RequiresUnreferencedCode ("--Method1--")]
 			public void Method1 () { }
 
-			[ExpectedWarning ("IL2026", "--Method1--")]
+			[ExpectedWarning ("IL2026", "--Method1--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			public static void Test ()
 			{
 				Type.GetType (s_ConstTypeName).RequiresPublicMethods ();
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs
index 2d0e72e03af832..9dc4df8ff039cd 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs
@@ -67,7 +67,6 @@ public static void TestNull ()
 		{
 			const string reflectionTypeKeptString = null;
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
@@ -75,180 +74,119 @@ public static void TestEmptyString ()
 		{
 			const string reflectionTypeKeptString = "";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (Full))]
 		public class Full { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (Full))]
 		public static void TestFullString ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Full, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (Generic<T>))]
 		public class Generic<T> { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", "Generic")]
 		public static void TestGenericString ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Generic`1, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (GenericInstantiation<T>))]
 		public class GenericInstantiation<T> { }
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (GenericArgument))]
 		public class GenericArgument { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", "GenericInstantiation")]
 		public static void TestGenericInstantiation ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericInstantiation`1[[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArgument]]";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (GenericInstantiationFullString<T>))]
 		public class GenericInstantiationFullString<T> { }
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (GenericArgumentFullString))]
 		public class GenericArgumentFullString { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", "GenericInstantiationFullString")]
 		public static void TestGenericInstantiationFullString ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericInstantiationFullString`1["
 					+ "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArgumentFullString, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]"
 				+ "], test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (GenericInstantiationOverCoreLib<T>))]
 		public class GenericInstantiationOverCoreLib<T> { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", "GenericInstantiationOverCoreLib", Tool.Trimmer | Tool.NativeAot, "Analyzer can't resolve type names from corelib")]
 		public static void TestGenericInstantiationOverCoreLib ()
 		{
 			// Note: the argument type should not be assembly-qualified for this test, which is checking that
 			// we can resolve non-assembly-qualified generic argument types from corelib.
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericInstantiationOverCoreLib`1[[System.String]], test";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (FullConst))]
 		public class FullConst { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (FullConst))]
 		public static void TestFullStringConst ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+FullConst, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (TypeAsmName))]
 		public class TypeAsmName { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (TypeAsmName))]
 		public static void TestTypeAsmName ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+TypeAsmName, test";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (AType))]
 		public class AType { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (AType))]
 		public static void TestType ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AType";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-#if !NATIVEAOT
-		[KeptMember (".ctor()")]
-#endif
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (Pointer))]
 		public class Pointer { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (Pointer), Tool.Trimmer, "https://github.com/dotnet/runtime/issues/106214")]
 		public static void TestPointer ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Pointer*";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-#if !NATIVEAOT
-		[KeptMember (".ctor()")]
-#endif
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (Reference))]
 		public class Reference { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (Reference), Tool.Trimmer, "https://github.com/dotnet/runtime/issues/106214")]
 		public static void TestReference ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Reference&";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (Array))]
 		public class Array { }
 
 		[Kept]
@@ -256,12 +194,9 @@ public static void TestArray ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Array[]";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (ArrayOfArray))]
 		public class ArrayOfArray { }
 
 		[Kept]
@@ -269,13 +204,10 @@ public static void TestArrayOfArray ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+ArrayOfArray[][]";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (MultiDimensionalArray))]
 		public class MultiDimensionalArray { }
 
 		[Kept]
@@ -283,12 +215,9 @@ public static void TestMultiDimensionalArray ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+MultiDimensionalArray[,]";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (MultiDimensionalArrayFullString))]
 		public class MultiDimensionalArrayFullString { }
 
 		[Kept]
@@ -296,12 +225,9 @@ public static void TestMultiDimensionalArrayFullString ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+MultiDimensionalArrayFullString[,], test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (MultiDimensionalArrayAsmName))]
 		public class MultiDimensionalArrayAsmName { }
 
 		[Kept]
@@ -309,7 +235,6 @@ public static void TestMultiDimensionalArrayAsmName ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+MultiDimensionalArrayAsmName[,], test";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
@@ -319,9 +244,6 @@ class Nested1
 			class N2
 			{
 				[Kept]
-				[KeptMember (".ctor()")]
-				[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-				[RequiresUnreferencedCode (nameof (N3))]
 				class N3
 				{
 				}
@@ -329,39 +251,26 @@ class N3
 		}
 
 		[Kept]
-		[ExpectedWarning ("IL2026", "N3")]
 		static void TestDeeplyNested ()
 		{
 			var typeKept = Type.GetType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Nested1+N2+N3");
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
 		class TypeOfToKeep { }
 
 		[Kept]
 		static void TestTypeOf ()
 		{
 			var typeKept = typeof (TypeOfToKeep);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (TypeFromBranchA))]
 		class TypeFromBranchA { }
-
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (TypeFromBranchB))]
 		class TypeFromBranchB { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (TypeFromBranchA))]
-		[ExpectedWarning ("IL2026", nameof (TypeFromBranchB))]
 		static void TestTypeFromBranch (int b)
 		{
 			string name = null;
@@ -377,7 +286,6 @@ static void TestTypeFromBranch (int b)
 			}
 
 			var typeKept = Type.GetType (name);
-			RequireConstructor (typeKept);
 		}
 
 		public class CaseInsensitive { }
@@ -400,7 +308,6 @@ static void TestTypeUsingCaseUnknownByTheLinker ()
 			bool hideCase = GetCase ();
 			const string reflectionTypeKeptString = "mono.linker.tests.cases.reflection.TypeUsedViaReflection+CaseUnknown, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false, hideCase);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
@@ -420,38 +327,27 @@ static void TestTypeUsingCaseUnknownByTheLinker2 ()
 		{
 			const string reflectionTypeKeptString = "mono.linker.tests.cases.reflection.TypeUsedViaReflection+CaseUnknown2, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			var typeKept = Type.GetType (reflectionTypeKeptString, false, fieldHideCase);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (OverloadWith3Parameters))]
 		public class OverloadWith3Parameters { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (OverloadWith3Parameters))]
 		static void TestTypeOverloadWith3Parameters ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith3Parameters";
 			var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly);
-			RequireConstructor (typeKept);
 		}
 
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (OverloadWith4Parameters))]
 		public class OverloadWith4Parameters { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (OverloadWith4Parameters))]
 		static void TestTypeOverloadWith4Parameters ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith4Parameters";
 			var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false);
-			RequireConstructor (typeKept);
 		}
 
 		public class OverloadWith5ParametersWithIgnoreCase { }
@@ -465,22 +361,16 @@ static void TestTypeOverloadWith5ParametersWithIgnoreCase ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith5ParametersWithIgnoreCase";
 			var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false, true);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (OverloadWith5ParametersWithIgnoreCase))]
 		public class OverloadWith5ParametersWithoutIgnoreCase { }
 
 		[Kept]
-		[ExpectedWarning ("IL2026", nameof (OverloadWith5ParametersWithIgnoreCase))]
 		static void TestTypeOverloadWith5ParametersWithoutIgnoreCase ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith5ParametersWithoutIgnoreCase";
 			var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false, false);
-			RequireConstructor (typeKept);
 		}
 
 		/// <summary>
@@ -525,22 +415,16 @@ static void TestUnknownIgnoreCase5Params (int num)
 			const string reflectionTypeKeptString = "mono.linker.tests.cases.reflection.TypeUsedViaReflection+CaseUnknown2, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
 			bool unknownValue = num + 1 == 1;
 			var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false, unknownValue);
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
-		[KeptMember (".ctor()")]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode ("GenericTypeWithAnnotations_OuterType")]
 		public class GenericTypeWithAnnotations_OuterType<
 			[KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))]
-			[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.NonPublicProperties)] T>
+		[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.NonPublicProperties)] T>
 		{
 		}
 
 		[Kept]
-		[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
-		[RequiresUnreferencedCode (nameof (GenericTypeWithAnnotations_InnerType))]
 		public class GenericTypeWithAnnotations_InnerType
 		{
 			[Kept]
@@ -551,16 +435,12 @@ private static void PrivateMethod () { }
 		}
 
 		[Kept]
-		[ExpectedWarning ("IL2026", "GenericTypeWithAnnotations_OuterType")]
-		[ExpectedWarning ("IL2026", nameof (GenericTypeWithAnnotations_InnerType), "PrivateProperty.get")]
-		[ExpectedWarning ("IL2026", nameof (GenericTypeWithAnnotations_InnerType), "PrivateProperty.set")]
 		static void TestGenericTypeWithAnnotations ()
 		{
 			const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericTypeWithAnnotations_OuterType`1["
 					+ "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericTypeWithAnnotations_InnerType, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]"
 				+ "], test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
-			var typeKept = Type.GetType (reflectionTypeKeptString);
-			RequireConstructor (typeKept);
+			Type.GetType (reflectionTypeKeptString);
 		}
 
 		[Kept]
@@ -613,36 +493,33 @@ static void TestInvalidTypeCombination ()
 		static void TestEscapedTypeName ()
 		{
 			var typeKept = Type.GetType ("Library.Not\\+Nested, EscapedTypeNames");
-			RequireConstructor (typeKept);
 			typeKept = Type.GetType ("Library.Not\\+Nested+Nes\\\\ted, EscapedTypeNames");
-			RequireConstructor (typeKept);
 			typeKept = Type.GetType ("Library.Not\\+Nested+Nes/ted, EscapedTypeNames");
-			RequireConstructor (typeKept);
 		}
 
 		[Kept]
 		class AssemblyTypeResolutionBehavior
 		{
 			[Kept]
-			[ExpectedWarning ("IL2122")]
+			[ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			static void TestRequireTypeInSameAssemblyAsGetType () {
 				RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.Dependencies.TypeDefinedInSameAssemblyAsGetType");
 			}
 
 			[Kept]
-			[ExpectedWarning ("IL2122")]
+			[ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			static void TestRequireTypeInSameAssemblyAsCallToRequireType () {
 				RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+TypeDefinedInSameAssemblyAsCallToRequireType");
 			}
 
 			[Kept]
-			[ExpectedWarning ("IL2122")]
+			[ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			static void TestRequireTypeWithNonAssemblyQualifiedGenericArguments () {
 				RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1[[System.Int32]], test");
 			}
 
 			[Kept]
-			[ExpectedWarning ("IL2122")]
+			[ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			static void TestRequireTypeWithNonAssemblyQualifiedArrayType () {
 				RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1["
 						+ "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+ArrayElementGenericArgumentType]"
@@ -650,7 +527,7 @@ static void TestRequireTypeWithNonAssemblyQualifiedArrayType () {
 			}
 
 			[Kept]
-			[ExpectedWarning ("IL2122")]
+			[ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			static void TestRequireTypeWithNonAssemblyQualifiedPointerType () {
 				RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1["
 						+ "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+PointerElementGenericArgumentType]"
@@ -658,7 +535,7 @@ static void TestRequireTypeWithNonAssemblyQualifiedPointerType () {
 			}
 
 			[Kept]
-			[ExpectedWarning ("IL2122")]
+			[ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")]
 			static void TestRequireTypeWithNonAssemblyQualifiedByRefType () {
 				RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1["
 						+ "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+ByRefElementGenericArgumentType]"
@@ -685,11 +562,5 @@ class PointerElementGenericArgumentType {}
 
 			class ByRefElementGenericArgumentType {}
 		}
-
-		[Kept]
-		static void RequireConstructor (
-			[KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))]
-			[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
-			Type type) { }
 	}
 }
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
index 6cb174eccbc447..0bcd402179bce0 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
@@ -1079,6 +1079,7 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge
 
 				int? unexpectedWarningCodeNumber = unexpectedWarningCode == null ? null : int.Parse (unexpectedWarningCode.Substring (2));
 
+				MessageContainer? unexpectedWarningMessage = null;
 				foreach (var mc in unmatchedMessages) {
 					if (mc.Category != MessageCategory.Warning)
 						continue;
@@ -1090,7 +1091,12 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge
 					if (attrProvider is IMemberDefinition attrMember && (mc.Origin?.Provider is IMemberDefinition member) && member.FullName.Contains (attrMember.FullName) != true)
 						continue;
 
-					unexpectedMessageWarnings.Add ($"Unexpected warning found: {mc}");
+					unexpectedWarningMessage = mc;
+					break;
+				}
+
+				if (unexpectedWarningMessage is not null) {
+					unexpectedMessageWarnings.Add ($"Unexpected warning found: {unexpectedWarningMessage}");
 				}
 			}
 

From ce27822ef979b2b59202c0f441df10aef063779f Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Thu, 15 Aug 2024 18:03:54 -0700
Subject: [PATCH 21/27] Don't warn on methods implementing iface methods if the
 iface is not explicitly implemented

---
 .../src/linker/Linker.Steps/MarkStep.cs       |   9 +-
 .../ValidateVirtualMethodAnnotationsStep.cs   |   5 +
 .../illink/src/linker/Linker/Annotations.cs   |   2 +-
 .../Linker/InterfaceImplementationChain.cs    |   6 +
 .../src/linker/Linker/MethodBodyScanner.cs    |   2 -
 .../src/linker/Linker/OverrideInformation.cs  |   4 +
 .../Linker/RuntimeInterfaceImplementation.cs  |   5 +
 .../illink/src/linker/Linker/TypeMapInfo.cs   |   2 +-
 .../RequiresCapabilityTests.cs                |   6 +
 ...ProvidesInterfaceMethodRequiresMismatch.cs | 166 ++++++++++++++++++
 ...actBaseWithNoMethodsImplementsInterface.il | 101 +++++++++++
 .../TestCasesRunner/TypeMapInfoValidation.cs  |   4 +-
 12 files changed, 298 insertions(+), 14 deletions(-)
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs
 create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il

diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index 96e770d116810d..193596246d5a74 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -2324,8 +2324,6 @@ void MarkNamedProperty (TypeDefinition type, string property_name, in Dependency
 		void MarkInterfaceImplementations (TypeDefinition type)
 		{
 			var ifaces = Annotations.GetRuntimeInterfaces (type);
-			if (ifaces is null)
-				return;
 			foreach (var runtimeInterface in ifaces) {
 				// Only mark interface implementations of interface types that have been marked.
 				// This enables stripping of interfaces that are never used
@@ -2526,10 +2524,7 @@ void MarkCustomMarshalerGetInstance (TypeDefinition type, in DependencyInfo reas
 		void MarkICustomMarshalerMethods (TypeDefinition inputType, in DependencyInfo reason, MessageOrigin origin)
 		{
 			var runtimeInterfaces = Annotations.GetRuntimeInterfaces (inputType);
-			if (runtimeInterfaces is null)
-				return;
 			foreach (var runtimeInterface in runtimeInterfaces) {
-
 				var iface_type = runtimeInterface.InterfaceTypeDefinition;
 				if (false == iface_type?.IsTypeOf ("System.Runtime.InteropServices", "ICustomMarshaler"))
 					continue;
@@ -3233,9 +3228,7 @@ void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReferenc
 				return;
 			var interfaceToBeImplemented = ov.DeclaringType;
 			var ifaces = Annotations.GetRuntimeInterfaces (method.DeclaringType);
-			if (ifaces is null)
-				return;
-			Debug.Assert (ifaces.Value.SingleOrDefault (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceToBeImplemented, Context)) is not null);
+			Debug.Assert (ifaces.SingleOrDefault (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceToBeImplemented, Context)) is not null);
 			foreach (var iface in ifaces) {
 				if (TypeReferenceEqualityComparer.AreEqual (iface.InflatedInterfaceType, interfaceToBeImplemented, Context)) {
 					MarkRuntimeInterfaceImplementation (iface, new MessageOrigin (method.DeclaringType));
diff --git a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs
index 4d68e20620e265..820d5ca1adc329 100644
--- a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs
@@ -16,6 +16,8 @@ protected override void Process ()
 				var baseOverrideInformations = annotations.GetBaseMethods (method);
 				if (baseOverrideInformations != null) {
 					foreach (var baseOv in baseOverrideInformations) {
+						if (baseOv.IsOverrideOfInterfaceMember && !baseOv.RuntimeInterfaceImplementation.HasExplicitImplementation)
+							continue;
 						annotations.FlowAnnotations.ValidateMethodAnnotationsAreSame (baseOv);
 						ValidateMethodRequiresUnreferencedCodeAreSame (baseOv);
 					}
@@ -30,6 +32,9 @@ protected override void Process ()
 						if (annotations.VirtualMethodsWithAnnotationsToValidate.Contains (overrideInformation.Override))
 							continue;
 
+						if (overrideInformation.IsOverrideOfInterfaceMember && !overrideInformation.RuntimeInterfaceImplementation.HasExplicitImplementation)
+							continue;
+
 						annotations.FlowAnnotations.ValidateMethodAnnotationsAreSame (overrideInformation);
 						ValidateMethodRequiresUnreferencedCodeAreSame (overrideInformation);
 					}
diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs
index 569e69ade0d2a2..ba8ad7330f12a7 100644
--- a/src/tools/illink/src/linker/Linker/Annotations.cs
+++ b/src/tools/illink/src/linker/Linker/Annotations.cs
@@ -707,7 +707,7 @@ public void EnqueueVirtualMethod (MethodDefinition method)
 				VirtualMethodsWithAnnotationsToValidate.Add (method);
 		}
 
-		internal ImmutableArray<RuntimeInterfaceImplementation>? GetRuntimeInterfaces (TypeDefinition type)
+		internal ImmutableArray<RuntimeInterfaceImplementation> GetRuntimeInterfaces (TypeDefinition type)
 		{
 			return TypeMapInfo.GetRecursiveInterfaces (type);
 		}
diff --git a/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs
index dda9dbe78591b5..f0e93e06687254 100644
--- a/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs
+++ b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs
@@ -19,8 +19,14 @@ internal sealed class InterfaceImplementationChain
 		/// </summary>
 		public ImmutableArray<InterfaceImplementation> InterfaceImplementations { get; }
 
+		/// <summary>
+		/// Returns true if the interface implementation is directly on the implementing type.
+		/// </summary>
+		public bool IsExplicitInterfaceImplementation => InterfaceImplementations.Length == 1;
+
 		public InterfaceImplementationChain (TypeReference typeWithInterfaceImplementation, ImmutableArray<InterfaceImplementation> interfaceImplementation)
 		{
+			Debug.Assert (interfaceImplementation.Length > 0);
 			TypeWithInterfaceImplementation = typeWithInterfaceImplementation;
 			InterfaceImplementations = interfaceImplementation;
 		}
diff --git a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs
index 41e1f1464506b4..bf2fdb5d5f9a94 100644
--- a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs
+++ b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs
@@ -142,8 +142,6 @@ HashSet<TypeDefinition> AllPossibleStackTypes (MethodIL methodIL)
 		void AddMatchingInterfaces (HashSet<(RuntimeInterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes)
 		{
 			var runtimeInterfaces = context.Annotations.GetRuntimeInterfaces (type);
-			if (runtimeInterfaces is null)
-				return;
 			foreach (var iface in runtimeInterfaces) {
 				if (interfaceTypes.Contains (iface.InterfaceTypeDefinition)) {
 					results.Add ((iface, type));
diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
index f4f667b20b2b29..fd03529a704c86 100644
--- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs
+++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs
@@ -38,5 +38,9 @@ public TypeDefinition? InterfaceType
 		[MemberNotNullWhen (true, nameof (RuntimeInterfaceImplementation))]
 		public bool IsOverrideOfInterfaceMember
 			=> RuntimeInterfaceImplementation != null;
+
+		[MemberNotNullWhen (true, nameof (RuntimeInterfaceImplementation))]
+		internal bool IsInterfaceMethodProvidedByBaseType
+			=> IsOverrideOfInterfaceMember && (Override.DeclaringType != RuntimeInterfaceImplementation.Implementor);
 	}
 }
diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
index 5e770cb0e275ca..433a39e01be94d 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs
@@ -41,6 +41,11 @@ internal sealed class RuntimeInterfaceImplementation
 		/// </summary>
 		public TypeDefinition? InterfaceTypeDefinition { get; }
 
+		/// <summary>
+		/// Returns true if an interface implementation is directly on the implementing type.
+		/// </summary>
+		public bool HasExplicitImplementation => InterfaceImplementationChains.Any (c => c.IsExplicitInterfaceImplementation && c.TypeWithInterfaceImplementation == Implementor);
+
 		public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference interfaceType, TypeDefinition? interfaceTypeDefinition, IEnumerable<InterfaceImplementationChain> interfaceImplementations)
 		{
 			Implementor = implementor;
diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
index d552e96f6ff06d..98fdd5b56ebea8 100644
--- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
+++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs
@@ -130,7 +130,7 @@ protected virtual void MapType (TypeDefinition type)
 				MapType (nested);
 		}
 
-		internal ImmutableArray<RuntimeInterfaceImplementation>? GetRecursiveInterfaces (TypeDefinition type)
+		internal ImmutableArray<RuntimeInterfaceImplementation> GetRecursiveInterfaces (TypeDefinition type)
 		{
 			return _recursiveInterfaces.GetRuntimeInterfaceImplementations (type);
 		}
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs
index 3f722ad634e3d4..db08bb45f445a7 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs
@@ -123,5 +123,11 @@ public Task SuppressRequires ()
 		{
 			return RunTest (nameof (SuppressRequires));
 		}
+
+		[Fact]
+		public Task BaseProvidesInterfaceMethodRequiresMismatch ()
+		{
+			return RunTest (nameof (BaseProvidesInterfaceMethodRequiresMismatch));
+		}
 	}
 }
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs
new file mode 100644
index 00000000000000..a3432ac34697ee
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs
@@ -0,0 +1,166 @@
+﻿// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.RequiresCapability
+{
+	[ExpectedNoWarnings]
+	[SkipKeptItemsValidation]
+	[SetupCompileBefore ("library.dll", new[] { "Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il" })]
+	[Define ("IL_ASSEMBLY_AVAILABLE")]
+	class BaseProvidesInterfaceMethodRequiresMismatch
+	{
+		[RequiresUnreferencedCode ("Message")]
+		public static void Main ()
+		{
+			BaseDoesNotImplementINoRuc.Run ();
+			BaseDoesNotImplementIRuc.Run ();
+			BaseImplementsINoRuc.Run ();
+			ThreeLevelsInheritance.Run ();
+			DerivedOverridesImplementingMethod.Run ();
+#if IL_ASSEMBLY_AVAILABLE
+			AbstractBaseWithNoMethodsImplementsInterface.Run ();
+#endif
+		}
+
+		interface INoRuc
+		{
+			void M ();
+		}
+
+		interface IRuc
+		{
+			[RequiresUnreferencedCode ("Message")]
+			void MRuc ();
+		}
+
+		public class BaseDoesNotImplementINoRuc
+		{
+			public class C
+			{
+				[RequiresUnreferencedCode ("Message")]
+				public void M ()
+				{
+				}
+			}
+
+			[ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")]
+			public class D : C, INoRuc
+			{
+			}
+
+			public static void Run ()
+			{
+				((INoRuc) new D ()).M ();
+			}
+		}
+
+		public class BaseDoesNotImplementIRuc
+		{
+			public class C
+			{
+				public void MRuc () { }
+			}
+
+			[ExpectedWarning ("IL2046", "C.MRuc()", "IRuc.MRuc()")]
+			public class D : C, IRuc
+			{
+			}
+
+			[RequiresUnreferencedCode ("Message")]
+			public static void Run ()
+			{
+				((IRuc) new D ()).MRuc ();
+			}
+		}
+
+		public class BaseImplementsINoRuc
+		{
+			class C : INoRuc
+			{
+				[RequiresUnreferencedCode ("Message")]
+				[ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")]
+				public void M ()
+				{
+				}
+			}
+
+			[ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")]
+			class D : C, INoRuc
+			{
+			}
+
+			class D2 : C
+			{
+			}
+
+			public static void Run ()
+			{
+				((INoRuc) new D ()).M ();
+				((INoRuc) new D2 ()).M ();
+			}
+		}
+
+		public class ThreeLevelsInheritance
+		{
+			public class Base
+			{
+				[RequiresUnreferencedCode ("Message")]
+				public void M () { }
+			}
+
+			[ExpectedWarning ("IL2046", "Base.M()", "INoRuc.M()")]
+			public class Middle : Base, INoRuc
+			{
+			}
+
+			public class Derived : Middle
+			{
+			}
+
+			public static void Run ()
+			{
+				((INoRuc) new Derived ()).M ();
+			}
+		}
+
+		public class DerivedOverridesImplementingMethod
+		{
+			class C : INoRuc
+			{
+				[RequiresUnreferencedCode ("Message")]
+				[ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")]
+				public virtual void M ()
+				{
+				}
+			}
+
+			class D : C, INoRuc
+			{
+				[RequiresUnreferencedCode ("Message")]
+				[ExpectedWarning ("IL2046", "D.M()", "INoRuc.M()")]
+				public override void M () { }
+			}
+
+			class D2 : C
+			{
+				[RequiresUnreferencedCode ("Message")]
+				public override void M () { }
+			}
+
+			public static void Run ()
+			{
+				((INoRuc) new D ()).M ();
+				((INoRuc) new D2 ()).M ();
+			}
+		}
+	}
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il
new file mode 100644
index 00000000000000..cb8797d62f91ad
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il
@@ -0,0 +1,101 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+.assembly extern 'mscorlib' { }
+
+.assembly 'library' { }
+
+.class public auto ansi beforefieldinit AbstractBaseWithNoMethodsImplementsInterface
+    extends [mscorlib]System.Object
+{
+    // Methods
+    .method public hidebysig static
+        void Run () cil managed
+    {
+        .custom instance void [System.Runtime]System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute::.ctor(string) = (
+            01 00 03 52 55 43 00 00
+        )
+        // Method begins at RVA 0x2050
+        // Code size 16 (0x10)
+        .maxstack 8
+
+        IL_0000: newobj instance void AbstractBaseWithNoMethodsImplementsInterface/Derived::.ctor()
+        IL_0005: castclass AbstractBaseWithNoMethodsImplementsInterface/I
+        IL_000a: callvirt instance void AbstractBaseWithNoMethodsImplementsInterface/I::M()
+        IL_000f: ret
+    } // end of method Program::Main
+
+    .method public hidebysig specialname rtspecialname
+        instance void .ctor () cil managed
+    {
+        // Method begins at RVA 0x2061
+        // Code size 7 (0x7)
+        .maxstack 8
+
+        IL_0000: ldarg.0
+        IL_0001: call instance void [mscorlib]System.Object::.ctor()
+        IL_0006: ret
+    } // end of method Program::.ctor
+
+
+    // Nested Types
+    .class nested assembly auto ansi abstract beforefieldinit Base
+        extends [mscorlib]System.Object
+        implements AbstractBaseWithNoMethodsImplementsInterface/I
+    {
+        // Methods
+        .method family hidebysig specialname rtspecialname
+            instance void .ctor () cil managed
+        {
+            // Method begins at RVA 0x2061
+            // Code size 7 (0x7)
+            .maxstack 8
+
+            IL_0000: ldarg.0
+            IL_0001: call instance void [mscorlib]System.Object::.ctor()
+            IL_0006: ret
+        } // end of method Base::.ctor
+
+    } // end of class Base
+
+    .class interface nested assembly auto ansi abstract beforefieldinit I
+    {
+        // Methods
+        .method public hidebysig newslot abstract virtual
+            instance void M () cil managed
+        {
+            .custom instance void [mscorlib]System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute::.ctor(string) = (
+                01 00 03 52 55 43 00 00 // "RUC"
+            )
+        } // end of method I::M
+
+    } // end of class I
+
+    .class nested assembly auto ansi beforefieldinit Derived
+        extends AbstractBaseWithNoMethodsImplementsInterface/Base
+    {
+        // Methods
+        .method public virtual hidebysig
+            instance void M () cil managed
+        {
+            // Method begins at RVA 0x2069
+            .maxstack 8
+
+            IL_0000: ret
+        } // end of method Derived::M
+
+        .method public hidebysig specialname rtspecialname
+            instance void .ctor () cil managed
+        {
+            // Method begins at RVA 0x2075
+            // Code size 7 (0x7)
+            .maxstack 8
+
+            IL_0000: ldarg.0
+            IL_0001: call instance void AbstractBaseWithNoMethodsImplementsInterface/Base::.ctor()
+            IL_0006: ret
+        } // end of method Derived::.ctor
+
+    } // end of class Derived
+
+} // end of class Program
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
index e21e45d6448889..d5249aa8ec89e7 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs
@@ -17,11 +17,11 @@ internal class TypeMapInfoValidation
 		public static IEnumerable<string> ValidateRuntimeInterfaces (TypeMapInfo typeMapInfo, TypeDefinition typeDef, string expectedInterfaceName, IEnumerable<string> expectedImplChain)
 		{
 			var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef);
-			if (!runtimeInterfaces.HasValue) {
+			if (runtimeInterfaces is []) {
 				yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`, but it has none");
 				yield break;
 			}
-			var runtimeInterface = runtimeInterfaces.Value.SingleOrDefault (i => i.InflatedInterfaceType.FullName == expectedInterfaceName);
+			var runtimeInterface = runtimeInterfaces.SingleOrDefault (i => i.InflatedInterfaceType.FullName == expectedInterfaceName);
 			if (runtimeInterface == default) {
 				yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`");
 				yield break;

From db02cfbf3f2ca7381dd42c3314dc672ef9dcdc83 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Fri, 16 Aug 2024 10:50:41 -0700
Subject: [PATCH 22/27] Undo unrelated analyzer changes

---
 ...nterfaces.DefaultInterfaceMethodsTests.g.cs |  6 ------
 ...ce.Interfaces.RecursiveInterfacesTests.g.cs | 18 ------------------
 ...eProvidesInterfaceMethodRequiresMismatch.cs |  2 ++
 3 files changed, 2 insertions(+), 24 deletions(-)

diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
index dc2440a552f56b..19189bfcd01705 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
@@ -63,12 +63,6 @@ public Task StaticDefaultInterfaceMethodOnStruct ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
-		[Fact]
-		public Task StaticDimOnDerivedInterface ()
-		{
-			return RunTest (allowMissingWarnings: true);
-		}
-
 		[Fact]
 		public Task StaticDimProvidedByUnreferencedIfaceInHierarchy ()
 		{
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
index 1dbdf7bd5c9ac5..35cc0b65c0417b 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
@@ -9,12 +9,6 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase
 
 		protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces";
 
-		[Fact]
-		public Task BaseTypeMarkedInterfaceDerivedNotKept ()
-		{
-			return RunTest (allowMissingWarnings: true);
-		}
-
 		[Fact]
 		public Task GenericInterfaceImplementedRecursively ()
 		{
@@ -33,18 +27,6 @@ public Task OverrideOfRecursiveInterfaceIsRemoved ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
-		[Fact]
-		public Task RecursiveGenericInterfaces ()
-		{
-			return RunTest (allowMissingWarnings: true);
-		}
-
-		[Fact]
-		public Task RecursiveGenericInterfacesStatic ()
-		{
-			return RunTest (allowMissingWarnings: true);
-		}
-
 		[Fact]
 		public Task RecursiveInterfaceKept ()
 		{
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs
index a3432ac34697ee..0ee0ad00f44e83 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs
@@ -16,6 +16,8 @@ namespace Mono.Linker.Tests.Cases.RequiresCapability
 	[SkipKeptItemsValidation]
 	[SetupCompileBefore ("library.dll", new[] { "Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il" })]
 	[Define ("IL_ASSEMBLY_AVAILABLE")]
+	//
+	[LogContains ("IL2046: AbstractBaseWithNoMethodsImplementsInterface.Derived.M()", ProducedBy = Tool.None)]
 	class BaseProvidesInterfaceMethodRequiresMismatch
 	{
 		[RequiresUnreferencedCode ("Message")]

From d88b2195d57fce5ad19ff6ffe2c7618d358b861f Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Fri, 16 Aug 2024 10:55:20 -0700
Subject: [PATCH 23/27] Undo unrelated formatting

---
 .../TestCasesRunner/ResultChecker.cs          | 292 +++++++++---------
 1 file changed, 146 insertions(+), 146 deletions(-)

diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
index 0bcd402179bce0..07404a6ec617f9 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs
@@ -75,11 +75,11 @@ protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition
 					}
 					continue;
 				case AssemblyNameReference: {
-					// There should be an AssemblyRef row for this assembly
-					var assemblyRef = linked.MainModule.AssemblyReferences.Single (ar => ar.Name == typeRef.Scope.Name);
-					Assert.IsNotNull (assemblyRef, $"Type reference '{typeRef.FullName}' has a reference to assembly '{typeRef.Scope.Name}' which is not a reference of '{linked.FullName}'");
-					continue;
-				}
+						// There should be an AssemblyRef row for this assembly
+						var assemblyRef = linked.MainModule.AssemblyReferences.Single (ar => ar.Name == typeRef.Scope.Name);
+						Assert.IsNotNull (assemblyRef, $"Type reference '{typeRef.FullName}' has a reference to assembly '{typeRef.Scope.Name}' which is not a reference of '{linked.FullName}'");
+						continue;
+					}
 				default:
 					throw new NotImplementedException ($"Unexpected scope type '{typeRef.Scope.GetType ()}' for type reference '{typeRef.FullName}'");
 				}
@@ -881,48 +881,48 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge
 					switch (attr.AttributeType.Name) {
 
 					case nameof (LogContainsAttribute): {
-						var expectedMessage = (string) attr.ConstructorArguments[0].Value;
-
-						List<MessageContainer> matchedMessages;
-						if ((bool) attr.ConstructorArguments[1].Value)
-							matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList ();
-						else
-							matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ;
-						if (matchedMessages.Count == 0)
-							missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}");
-
-						foreach (var matchedMessage in matchedMessages)
-							unmatchedMessages.Remove (matchedMessage);
-					}
-					break;
+							var expectedMessage = (string) attr.ConstructorArguments[0].Value;
+
+							List<MessageContainer> matchedMessages;
+							if ((bool) attr.ConstructorArguments[1].Value)
+								matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList ();
+							else
+								matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ;
+							if (matchedMessages.Count == 0)
+								missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}");
+
+							foreach (var matchedMessage in matchedMessages)
+								unmatchedMessages.Remove (matchedMessage);
+						}
+						break;
 
 					case nameof (LogDoesNotContainAttribute): {
-						var unexpectedMessage = (string) attr.ConstructorArguments[0].Value;
-						foreach (var loggedMessage in unmatchedMessages) {
-							bool isRegex = (bool) attr.ConstructorArguments[1].Value;
-							bool foundMatch = isRegex
-								? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage)
-								: loggedMessage.ToString ().Contains (unexpectedMessage);
-
-							if (foundMatch)
-								unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}");
+							var unexpectedMessage = (string) attr.ConstructorArguments[0].Value;
+							foreach (var loggedMessage in unmatchedMessages) {
+								bool isRegex = (bool) attr.ConstructorArguments[1].Value;
+								bool foundMatch = isRegex
+									? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage)
+									: loggedMessage.ToString ().Contains (unexpectedMessage);
+
+								if (foundMatch)
+									unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}");
+							}
 						}
-					}
-					break;
+						break;
 
 					case nameof (ExpectedWarningAttribute) or nameof (UnexpectedWarningAttribute): {
-						var expectedWarningCode = (string) attr.GetConstructorArgumentValue (0);
-						if (!expectedWarningCode.StartsWith ("IL")) {
-							Assert.Fail ($"The warning code specified in {attr.AttributeType.Name} must start with the 'IL' prefix. Specified value: '{expectedWarningCode}'.");
-						}
-						IEnumerable<string> expectedMessageContains = attr.Constructor.Parameters switch {
-						// ExpectedWarningAttribute(string warningCode, params string[] expectedMessages)
-						// ExpectedWarningAttribute(string warningCode, string[] expectedMessages, Tool producedBy, string issueLink)
-						[_, { ParameterType.IsArray: true }, ..]
-							=> ((CustomAttributeArgument[]) attr.ConstructorArguments[1].Value)
-								.Select (caa => (string) caa.Value),
-								// ExpectedWarningAttribute(string warningCode, string expectedMessage1, string expectedMessage2, Tool producedBy, string issueLink)
-								[_, { ParameterType.Name: "String" }, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _]
+							var expectedWarningCode = (string) attr.GetConstructorArgumentValue (0);
+							if (!expectedWarningCode.StartsWith ("IL")) {
+								Assert.Fail ($"The warning code specified in {attr.AttributeType.Name} must start with the 'IL' prefix. Specified value: '{expectedWarningCode}'.");
+							}
+							IEnumerable<string> expectedMessageContains = attr.Constructor.Parameters switch {
+							// ExpectedWarningAttribute(string warningCode, params string[] expectedMessages)
+							// ExpectedWarningAttribute(string warningCode, string[] expectedMessages, Tool producedBy, string issueLink)
+							[_, { ParameterType.IsArray: true }, ..]
+								=> ((CustomAttributeArgument[]) attr.ConstructorArguments[1].Value)
+									.Select (caa => (string) caa.Value),
+									// ExpectedWarningAttribute(string warningCode, string expectedMessage1, string expectedMessage2, Tool producedBy, string issueLink)
+									[_, { ParameterType.Name: "String" }, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _]
 											=> [(string) attr.GetConstructorArgumentValue (1), (string) attr.GetConstructorArgumentValue (2)],
 											// ExpectedWarningAttribute(string warningCode, string expectedMessage, Tool producedBy, string issueLink)
 											[_, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _]
@@ -930,143 +930,143 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge
 										// ExpectedWarningAttribute(string warningCode, Tool producedBy, string issueLink)
 										[_, { ParameterType.Name: "Tool" }, _]
 										=> [],
-							_ => throw new UnreachableException (),
-						};
-						string fileName = (string) attr.GetPropertyValue ("FileName");
-						int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine");
-						int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn");
-						bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode");
+								_ => throw new UnreachableException (),
+							};
+							string fileName = (string) attr.GetPropertyValue ("FileName");
+							int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine");
+							int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn");
+							bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode");
 
-						int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2));
-						string expectedOrigin = null;
-						bool expectedWarningFound = false;
+							int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2));
+							string expectedOrigin = null;
+							bool expectedWarningFound = false;
 
-						foreach (var loggedMessage in unmatchedMessages) {
+							foreach (var loggedMessage in unmatchedMessages) {
 
-							if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber)
-								continue;
+								if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber)
+									continue;
 
-							bool messageNotFound = false;
-							foreach (var expectedMessage in expectedMessageContains) {
-								if (!loggedMessage.Text.Contains (expectedMessage)) {
-									messageNotFound = true;
-									break;
+								bool messageNotFound = false;
+								foreach (var expectedMessage in expectedMessageContains) {
+									if (!loggedMessage.Text.Contains (expectedMessage)) {
+										messageNotFound = true;
+										break;
+									}
 								}
-							}
-							if (messageNotFound)
-								continue;
-
-							if (fileName != null) {
-								if (loggedMessage.Origin == null)
+								if (messageNotFound)
 									continue;
 
-								var actualOrigin = loggedMessage.Origin.Value;
-								if (actualOrigin.FileName != null) {
-									// Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set
-									if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0)
-										continue;
-
-									if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value)
+								if (fileName != null) {
+									if (loggedMessage.Origin == null)
 										continue;
 
-									if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value)
-										continue;
-								} else {
-									// The warning was logged with member/ILoffset, so it didn't have line/column info filled
-									// but it will be computed from PDBs, so instead compare it in a string representation
-									if (expectedOrigin == null) {
-										expectedOrigin = fileName;
-										if (sourceLine.HasValue) {
-											expectedOrigin += "(" + sourceLine.Value;
-											if (sourceColumn.HasValue)
-												expectedOrigin += "," + sourceColumn.Value;
-											expectedOrigin += ")";
+									var actualOrigin = loggedMessage.Origin.Value;
+									if (actualOrigin.FileName != null) {
+										// Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set
+										if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0)
+											continue;
+
+										if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value)
+											continue;
+
+										if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value)
+											continue;
+									} else {
+										// The warning was logged with member/ILoffset, so it didn't have line/column info filled
+										// but it will be computed from PDBs, so instead compare it in a string representation
+										if (expectedOrigin == null) {
+											expectedOrigin = fileName;
+											if (sourceLine.HasValue) {
+												expectedOrigin += "(" + sourceLine.Value;
+												if (sourceColumn.HasValue)
+													expectedOrigin += "," + sourceColumn.Value;
+												expectedOrigin += ")";
+											}
 										}
-									}
 
-									string actualOriginString = actualOrigin.ToString () ?? "";
-									if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase))
+										string actualOriginString = actualOrigin.ToString () ?? "";
+										if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase))
+											continue;
+									}
+								} else if (isCompilerGeneratedCode == true) {
+									if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition)
 										continue;
-								}
-							} else if (isCompilerGeneratedCode == true) {
-								if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition)
-									continue;
 
-								if (attrProvider is IMemberDefinition expectedMember) {
-									string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name;
+									if (attrProvider is IMemberDefinition expectedMember) {
+										string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name;
 
-									if (actualName.StartsWith (expectedMember.DeclaringType.FullName) &&
-										(actualName.Contains ("<" + expectedMember.Name + ">") ||
-										 actualName.EndsWith ("get_" + expectedMember.Name) ||
-										 actualName.EndsWith ("set_" + expectedMember.Name))) {
-										expectedWarningFound = true;
-										unmatchedMessages.Remove (loggedMessage);
-										break;
-									}
-									if (memberDefinition is not MethodDefinition)
-										continue;
-									if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) {
-										if (memberDefinition.Name == ".cctor" &&
-											(expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) {
+										if (actualName.StartsWith (expectedMember.DeclaringType.FullName) &&
+											(actualName.Contains ("<" + expectedMember.Name + ">") ||
+											actualName.EndsWith ("get_" + expectedMember.Name) ||
+											actualName.EndsWith ("set_" + expectedMember.Name))) {
 											expectedWarningFound = true;
 											unmatchedMessages.Remove (loggedMessage);
 											break;
 										}
-										if (memberDefinition.Name == ".ctor" &&
-											(expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) {
+										if (memberDefinition is not MethodDefinition)
+											continue;
+										if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) {
+											if (memberDefinition.Name == ".cctor" &&
+												(expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) {
+												expectedWarningFound = true;
+												unmatchedMessages.Remove (loggedMessage);
+												break;
+											}
+											if (memberDefinition.Name == ".ctor" &&
+												(expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) {
+												expectedWarningFound = true;
+												unmatchedMessages.Remove (loggedMessage);
+												break;
+											}
+										}
+									} else if (attrProvider is AssemblyDefinition expectedAssembly) {
+										// Allow assembly-level attributes to match warnings from compiler-generated Main
+										if (memberDefinition.Name == "<Main>$" &&
+											memberDefinition.DeclaringType.FullName == "Program" &&
+											memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) {
 											expectedWarningFound = true;
 											unmatchedMessages.Remove (loggedMessage);
 											break;
 										}
 									}
-								} else if (attrProvider is AssemblyDefinition expectedAssembly) {
-									// Allow assembly-level attributes to match warnings from compiler-generated Main
-									if (memberDefinition.Name == "<Main>$" &&
-										memberDefinition.DeclaringType.FullName == "Program" &&
-										memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) {
+									continue;
+								} else {
+									if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) {
 										expectedWarningFound = true;
 										unmatchedMessages.Remove (loggedMessage);
 										break;
 									}
+									continue;
 								}
-								continue;
-							} else {
-								if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) {
-									expectedWarningFound = true;
-									unmatchedMessages.Remove (loggedMessage);
-									break;
-								}
-								continue;
+
+								expectedWarningFound = true;
+								unmatchedMessages.Remove (loggedMessage);
+								break;
 							}
 
-							expectedWarningFound = true;
-							unmatchedMessages.Remove (loggedMessage);
-							break;
+							var expectedOriginString = fileName == null
+								? attrProvider switch {
+									MethodDefinition method => method.GetDisplayName (),
+									IMemberDefinition member => member.FullName,
+									AssemblyDefinition asm => asm.Name.Name,
+									_ => throw new NotImplementedException ()
+								} + ": "
+								: "";
+
+							if (!expectedWarningFound)
+								missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" +
+								$"warning {expectedWarningCode}: {expectedOriginString}" +
+								$"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " +
+								$"but no such message was found.");
 						}
-
-						var expectedOriginString = fileName == null
-							? attrProvider switch {
-								MethodDefinition method => method.GetDisplayName (),
-								IMemberDefinition member => member.FullName,
-								AssemblyDefinition asm => asm.Name.Name,
-								_ => throw new NotImplementedException ()
-							} + ": "
-							: "";
-
-						if (!expectedWarningFound)
-							missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" +
-							$"warning {expectedWarningCode}: {expectedOriginString}" +
-							$"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " +
-							$"but no such message was found.");
-					}
-					break;
+						break;
 
 					case nameof (ExpectedNoWarningsAttribute): {
-						// Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above)
-						// and then do the negative check on the rest.
-						expectedNoWarningsAttributes.Add ((attrProvider, attr));
-						break;
-					}
+							// Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above)
+							// and then do the negative check on the rest.
+							expectedNoWarningsAttributes.Add ((attrProvider, attr));
+							break;
+						}
 					}
 				}
 			}

From 259d734246d8bb7eebf8ec63b20814c9376acf98 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Fri, 16 Aug 2024 10:58:32 -0700
Subject: [PATCH 24/27] Revert suppression changes

---
 .../src/ILLink/ILLink.Suppressions.xml        | 50 +------------------
 1 file changed, 1 insertion(+), 49 deletions(-)

diff --git a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml
index 8df6b6dd9fa8b8..6e691bf6bc961d 100644
--- a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml
+++ b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml
@@ -523,53 +523,5 @@
       <property name="Scope">member</property>
       <property name="Target">M:System.Reflection.Context.Delegation.DelegatingType.GetInterface(System.String,System.Boolean)</property>
     </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetField(System.String,System.Reflection.BindingFlags)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetFields(System.Reflection.BindingFlags)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetMembers(System.Reflection.BindingFlags)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetMethods(System.Reflection.BindingFlags)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetProperties(System.Reflection.BindingFlags)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Projection.ProjectingType.GetDefaultMembers()</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Custom.CustomType.GetProperties(System.Reflection.BindingFlags)</property>
-    </attribute>
-    <attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
-      <argument>ILLink</argument>
-      <argument>IL2094</argument>
-      <property name="Scope">member</property>
-      <property name="Target">M:System.Reflection.Context.Custom.CustomType.GetMethods(System.Reflection.BindingFlags)</property>
-    </attribute>
   </assembly>
-</linker>
+</linker>
\ No newline at end of file

From 1d769b2932207bd6ba0eae552fd4022cda1b71ae Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Fri, 16 Aug 2024 14:06:00 -0700
Subject: [PATCH 25/27] Undo unrelated formatting

---
 .../DataFlow/GenericParameterDataFlow.cs      |  2 +-
 .../TestCasesRunner/AssemblyChecker.cs        | 38 +++++++++----------
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs
index c36de63d7f8925..2cbd62f6d43432 100644
--- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs
@@ -915,7 +915,7 @@ static void TestGenericParameterFlowsToDelegateMethodDeclaringTypeInstance<T> ()
 			Action a = instance.InstanceMethod;
 		}
 
-		class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T>
+		class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>
 		{
 			public static void Method ()
 			{
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
index f4cd4f8ad45d18..893aca48a5eb46 100644
--- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
@@ -757,26 +757,26 @@ static string FormatInstruction (Instruction instr)
 				throw new NotImplementedException (instr.Operand.GetType ().ToString ());
 
 			default: {
-				string operandString = null;
-				switch (instr.OpCode.OperandType) {
-				case OperandType.InlineField:
-				case OperandType.InlineMethod:
-				case OperandType.InlineType:
-				case OperandType.InlineTok:
-					operandString = instr.Operand switch {
-						FieldReference fieldRef => fieldRef.FullName,
-						MethodReference methodRef => methodRef.FullName,
-						TypeReference typeRef => typeRef.FullName,
-						_ => null
-					};
-					break;
-				}
+					string operandString = null;
+					switch (instr.OpCode.OperandType) {
+					case OperandType.InlineField:
+					case OperandType.InlineMethod:
+					case OperandType.InlineType:
+					case OperandType.InlineTok:
+						operandString = instr.Operand switch {
+							FieldReference fieldRef => fieldRef.FullName,
+							MethodReference methodRef => methodRef.FullName,
+							TypeReference typeRef => typeRef.FullName,
+							_ => null
+						};
+						break;
+					}
 
-				if (operandString != null)
-					return $"{instr.OpCode.ToString ()} {operandString}";
-				else
-					return instr.OpCode.ToString ();
-			}
+					if (operandString != null)
+						return $"{instr.OpCode.ToString ()} {operandString}";
+					else
+						return instr.OpCode.ToString ();
+				}
 			}
 		}
 

From 756d90ea235fcc214d326ec2dbfd31a4bb890599 Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 20 Aug 2024 10:43:08 -0700
Subject: [PATCH 26/27] Add 'as' cast to RuntimeInterfacesAlgorithm

---
 .../illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
index c3d17a0107fb65..f83432d36cf333 100644
--- a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
+++ b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs
@@ -42,7 +42,7 @@ public ImmutableArray<RuntimeInterfaceImplementation> GetRuntimeInterfaceImpleme
 				var recursiveIFaces = GetRuntimeInterfaceImplementations (resolvedInterfaceType);
 				foreach (var runtimeImpl in recursiveIFaces) {
 					// Inflate the interface type with the explicit interfaceImpl reference
-					var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (explicitIface.InterfaceType);
+					var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (explicitIface.InterfaceType as IGenericInstance);
 					foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) {
 						interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (originalType, existingImpl.InterfaceImplementations.Insert (0, explicitIface)));
 					}
@@ -53,10 +53,10 @@ public ImmutableArray<RuntimeInterfaceImplementation> GetRuntimeInterfaceImpleme
 				var baseTypeIfaces = GetRuntimeInterfaceImplementations (baseTypeDef);
 				foreach (var runtimeImpl in baseTypeIfaces) {
 					// Inflate the interface type with the base type reference
-					var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (originalType.BaseType);
+					var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (originalType.BaseType as IGenericInstance);
 					foreach (var impl in runtimeImpl.InterfaceImplementationChains) {
 						// Inflate the provider for the first .impl - this could be a different recursive base type for each chain
-						var inflatedImplProvider = impl.TypeWithInterfaceImplementation.InflateFrom (originalType.BaseType);
+						var inflatedImplProvider = impl.TypeWithInterfaceImplementation.InflateFrom (originalType.BaseType as IGenericInstance);
 						interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations));
 					}
 				}

From 626934c2dff0cc15d0e0ac6a6392bb173e552cbe Mon Sep 17 00:00:00 2001
From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
Date: Tue, 20 Aug 2024 10:47:34 -0700
Subject: [PATCH 27/27] Add generated analyzer tests

---
 ...nterfaces.DefaultInterfaceMethodsTests.g.cs |  6 ++++++
 ...ce.Interfaces.RecursiveInterfacesTests.g.cs | 18 ++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
index 19189bfcd01705..dc2440a552f56b 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs
@@ -63,6 +63,12 @@ public Task StaticDefaultInterfaceMethodOnStruct ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task StaticDimOnDerivedInterface ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task StaticDimProvidedByUnreferencedIfaceInHierarchy ()
 		{
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
index 35cc0b65c0417b..1dbdf7bd5c9ac5 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs
@@ -9,6 +9,12 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase
 
 		protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces";
 
+		[Fact]
+		public Task BaseTypeMarkedInterfaceDerivedNotKept ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task GenericInterfaceImplementedRecursively ()
 		{
@@ -27,6 +33,18 @@ public Task OverrideOfRecursiveInterfaceIsRemoved ()
 			return RunTest (allowMissingWarnings: true);
 		}
 
+		[Fact]
+		public Task RecursiveGenericInterfaces ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
+		[Fact]
+		public Task RecursiveGenericInterfacesStatic ()
+		{
+			return RunTest (allowMissingWarnings: true);
+		}
+
 		[Fact]
 		public Task RecursiveInterfaceKept ()
 		{
