Add support for static virtual methods (#66084)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Thu, 3 Mar 2022 07:30:18 +0000 (08:30 +0100)
committerGitHub <noreply@github.com>
Thu, 3 Mar 2022 07:30:18 +0000 (08:30 +0100)
Took the type system changes from #54063 and cleaned them up, added unit tests.

Hooked it up into JitInterface/ResolveConstraintMethodApprox. Using the pre-existing `ConstrainedMethodUseLookupResult` that wasn't currently getting emitted. We'll want to use it for its original purpose at some point, but I think we can make this work for both instance and static constrained calls.

Missing things:

* Support creating delegates to static virtual methods. This will need a RyuJIT/JitInterface change.
* Type loader support. If `MakeGeneric` needs static virtuals at runtime, it will throw.

But this is enough to get HttpClient working again. Fixes #65613. Contributes to dotnet/runtimelab#1665.

21 files changed:
src/coreclr/tools/Common/Compiler/TypeExtensions.cs
src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs
src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs
src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs
src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/VirtualStaticInterfaceMethods.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj
src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/VirtualStaticInterfaceMethodTests.cs [new file with mode: 0644]
src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs

index df3282e..3eb9849 100644 (file)
@@ -421,9 +421,11 @@ namespace ILCompiler
         {
             forceRuntimeLookup = false;
 
+            bool isStaticVirtualMethod = interfaceMethod.Signature.IsStatic;
+
             // We can't resolve constraint calls effectively for reference types, and there's
             // not a lot of perf. benefit in doing it anyway.
-            if (!constrainedType.IsValueType)
+            if (!constrainedType.IsValueType && (!isStaticVirtualMethod || constrainedType.IsCanonicalDefinitionType(CanonicalFormKind.Any)))
             {
                 return null;
             }
@@ -466,10 +468,17 @@ namespace ILCompiler
                                 potentialInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)potentialInterfaceType);
                         }
 
-                        method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
+                        if (isStaticVirtualMethod)
+                        {
+                            method = canonType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(potentialInterfaceMethod);
+                        }
+                        else
+                        {
+                            method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
+                        }
 
                         // See code:#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
-                        if (method != null && !method.OwningType.IsValueType)
+                        if (!isStaticVirtualMethod && method != null && !method.OwningType.IsValueType)
                         {
                             // We explicitly wouldn't want to abort if we found a default implementation.
                             // The above resolution doesn't consider the default methods.
@@ -500,7 +509,14 @@ namespace ILCompiler
                             // We can resolve to exact method
                             MethodDesc exactInterfaceMethod = context.GetMethodForInstantiatedType(
                                 genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
-                            method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
+                            if (isStaticVirtualMethod)
+                            {
+                                method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
+                            }
+                            else
+                            {
+                                method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
+                            }
                             isExactMethodResolved = method != null;
                         }
                     }
@@ -523,7 +539,14 @@ namespace ILCompiler
                         if (genInterfaceMethod.OwningType != interfaceType)
                             exactInterfaceMethod = context.GetMethodForInstantiatedType(
                                 genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
-                        method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
+                        if (isStaticVirtualMethod)
+                        {
+                            method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
+                        }
+                        else
+                        {
+                            method = constrainedType.ResolveVariantInterfaceMethodToVirtualMethodOnType(exactInterfaceMethod);
+                        }
                     }
                 }
             }
@@ -548,7 +571,7 @@ namespace ILCompiler
             //#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
             // Only return a method if the value type itself declares the method,
             // otherwise we might get a method from Object or System.ValueType
-            if (!method.OwningType.IsValueType)
+            if (!isStaticVirtualMethod && !method.OwningType.IsValueType)
             {
                 // Fall back to VSD
                 return null;
index 8dbd9a2..eb55228 100644 (file)
@@ -572,6 +572,16 @@ namespace Internal.TypeSystem
             return ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
         }
 
+        public override MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
+        {
+            return ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
+        }
+
+        public override MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType)
+        {
+            return ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, (MetadataType)currentType);
+        }
+
         //////////////////////// INTERFACE RESOLUTION
         //Interface function resolution
         //    Interface function resolution follows the following rules
@@ -588,6 +598,8 @@ namespace Internal.TypeSystem
         //    See current interface call resolution for details on how that happens.
         private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
         {
+            Debug.Assert(!interfaceMethod.Signature.IsStatic);
+
             if (currentType.IsInterface)
                 return null;
 
@@ -657,6 +669,8 @@ namespace Internal.TypeSystem
 
         public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
         {
+            Debug.Assert(!interfaceMethod.Signature.IsStatic);
+
             MetadataType interfaceType = (MetadataType)interfaceMethod.OwningType;
             bool foundInterface = IsInterfaceImplementedOnType(currentType, interfaceType);
             MethodDesc implMethod;
@@ -841,5 +855,108 @@ namespace Internal.TypeSystem
                 } while (type != null);
             }
         }
+
+        /// <summary>
+        /// Try to resolve a given virtual static interface method on a given constrained type and its base types.
+        /// </summary>
+        /// <param name="interfaceMethod">Interface method to resolve</param>
+        /// <param name="currentType">Type to attempt virtual static method resolution on</param>
+        /// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
+        public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
+        {
+            TypeDesc interfaceType = interfaceMethod.OwningType;
+
+            // Search for match on a per-level in the type hierarchy
+            for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
+            {
+                MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod);
+                if (resolvedMethodOnType != null)
+                {
+                    return resolvedMethodOnType;
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Try to resolve a given virtual static interface method on a given constrained type and its base types.
+        /// </summary>
+        /// <param name="interfaceMethod">Interface method to resolve</param>
+        /// <param name="currentType">Type to attempt virtual static method resolution on</param>
+        /// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
+        public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
+        {
+            TypeDesc interfaceType = interfaceMethod.OwningType;
+
+            // Search for match on a per-level in the type hierarchy
+            for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
+            {
+                MethodDesc resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, interfaceMethod);
+                if (resolvedMethodOnType != null)
+                {
+                    return resolvedMethodOnType;
+                }
+
+                // Variant interface dispatch
+                foreach (DefType runtimeInterfaceType in typeToCheck.RuntimeInterfaces)
+                {
+                    if (runtimeInterfaceType == interfaceType)
+                    {
+                        // This is the variant interface check logic, skip this
+                        continue;
+                    }
+
+                    if (!runtimeInterfaceType.HasSameTypeDefinition(interfaceType))
+                    {
+                        // Variance matches require a typedef match
+                        // Equivalence isn't sufficient, and is uninteresting as equivalent interfaces cannot have static virtuals.
+                        continue;
+                    }
+
+                    if (runtimeInterfaceType.CanCastTo(interfaceType))
+                    {
+                        // Attempt to resolve on variance matched interface
+                        MethodDesc runtimeInterfaceMethod = runtimeInterfaceType.FindMethodOnExactTypeWithMatchingTypicalMethod(interfaceMethod);
+                        resolvedMethodOnType = TryResolveVirtualStaticMethodOnThisType(typeToCheck, runtimeInterfaceMethod);
+                        if (resolvedMethodOnType != null)
+                        {
+                            return resolvedMethodOnType;
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Try to resolve a given virtual static interface method on a given constrained type and return the resolved method or null when not found.
+        /// </summary>
+        /// <param name="constrainedType">Type to attempt method resolution on</param>
+        /// <param name="interfaceMethod">Method to resolve</param>
+        /// <returns>MethodDesc of the resolved method or null when not found (runtime lookup must be used)</returns>
+        private static MethodDesc TryResolveVirtualStaticMethodOnThisType(MetadataType constrainedType, MethodDesc interfaceMethod)
+        {
+            Debug.Assert(interfaceMethod.Signature.IsStatic);
+
+            MethodImplRecord[] possibleImpls = constrainedType.FindMethodsImplWithMatchingDeclName(interfaceMethod.Name);
+            if (possibleImpls == null)
+                return null;
+
+            MethodDesc interfaceMethodDefinition = interfaceMethod.GetMethodDefinition();
+            foreach (MethodImplRecord methodImpl in possibleImpls)
+            {
+                if (methodImpl.Decl == interfaceMethodDefinition)
+                {
+                    MethodDesc resolvedMethodImpl = methodImpl.Body;
+                    if (interfaceMethod != interfaceMethodDefinition)
+                    {
+                        resolvedMethodImpl = resolvedMethodImpl.MakeInstantiatedMethod(interfaceMethod.Instantiation);
+                    }
+                    return resolvedMethodImpl;
+                }
+            }
+
+            return null;
+        }
     }
 }
index f36f45f..ccddbb3 100644 (file)
@@ -205,6 +205,16 @@ namespace Internal.TypeSystem
             return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, type);
         }
 
+        public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod)
+        {
+            return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type);
+        }
+
+        public static MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(this TypeDesc type, MethodDesc interfaceMethod)
+        {
+            return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, type);
+        }
+
         public static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(this TypeDesc type, MethodDesc interfaceMethod, out MethodDesc implMethod)
         {
             return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, type, out implMethod);
index e13e61d..804ab39 100644 (file)
@@ -24,6 +24,10 @@ namespace Internal.TypeSystem
 
         public abstract MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);
 
+        public abstract MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);
+
+        public abstract MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, TypeDesc currentType);
+
         public abstract DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl);
 
         /// <summary>
index d19b60b..b1a75cc 100644 (file)
@@ -252,6 +252,10 @@ namespace ILCompiler
                 case ReadyToRunHelperId.FieldHandle:
                     return ((FieldDesc)targetOfLookup).OwningType.IsRuntimeDeterminedSubtype;
 
+                case ReadyToRunHelperId.ConstrainedDirectCall:
+                    return ((ConstrainedCallInfo)targetOfLookup).Method.IsRuntimeDeterminedExactMethod
+                        || ((ConstrainedCallInfo)targetOfLookup).ConstrainedType.IsRuntimeDeterminedSubtype;
+
                 default:
                     throw new NotImplementedException();
             }
@@ -643,4 +647,12 @@ namespace ILCompiler
             }
         }
     }
+
+    public sealed class ConstrainedCallInfo
+    {
+        public readonly TypeDesc ConstrainedType;
+        public readonly MethodDesc Method;
+        public ConstrainedCallInfo(TypeDesc constrainedType, MethodDesc method)
+            => (ConstrainedType, Method) = (constrainedType, method);
+    }
 }
index 6aef1df..9b3975c 100644 (file)
@@ -162,6 +162,10 @@ namespace ILCompiler.DependencyAnalysis
                         {
                             Debug.Assert(method.IsVirtual);
 
+                            // Static interface methods don't participate in GVM analysis
+                            if (method.Signature.IsStatic)
+                                continue;
+
                             if (method.HasInstantiation)
                             {
                                 // We found a GVM on one of the implemented interfaces. Find if the type implements this method. 
index 754621f..3da65b1 100644 (file)
@@ -1431,20 +1431,36 @@ namespace ILCompiler.DependencyAnalysis
         {
             MethodDesc instantiatedConstrainedMethod = _constrainedMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
             TypeDesc instantiatedConstraintType = _constraintType.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
-            MethodDesc implMethod = instantiatedConstrainedMethod;
+            MethodDesc implMethod;
 
-            if (implMethod.OwningType.IsInterface)
+            if (instantiatedConstrainedMethod.OwningType.IsInterface)
             {
-                implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToVirtualMethodOnType(implMethod);
+                if (instantiatedConstrainedMethod.Signature.IsStatic)
+                {
+                    implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(instantiatedConstrainedMethod);
+                }
+                else
+                {
+                    throw new NotImplementedException();
+                }
+            }
+            else
+            {
+                implMethod = instantiatedConstraintType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(instantiatedConstrainedMethod);
             }
-
-            implMethod = instantiatedConstraintType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(implMethod);
 
             // AOT use of this generic lookup is restricted to finding methods on valuetypes (runtime usage of this slot in universal generics is more flexible)
-            Debug.Assert(instantiatedConstraintType.IsValueType);
-            Debug.Assert(implMethod.OwningType == instantiatedConstraintType);
+            Debug.Assert(instantiatedConstraintType.IsValueType || (instantiatedConstrainedMethod.OwningType.IsInterface && instantiatedConstrainedMethod.Signature.IsStatic));
+            Debug.Assert(!instantiatedConstraintType.IsValueType || implMethod.OwningType == instantiatedConstraintType);
 
-            if (implMethod.HasInstantiation)
+            if (implMethod.Signature.IsStatic)
+            {
+                if (implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).IsSharedByGenericInstantiations)
+                    return factory.ExactCallableAddress(implMethod);
+                else
+                    return factory.MethodEntrypoint(implMethod);
+            }
+            else if (implMethod.HasInstantiation)
             {
                 return factory.ExactCallableAddress(implMethod);
             }
@@ -1467,7 +1483,8 @@ namespace ILCompiler.DependencyAnalysis
 
         public override NativeLayoutVertexNode TemplateDictionaryNode(NodeFactory factory)
         {
-            return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall);
+            return factory.NativeLayout.NotSupportedDictionarySlot;
+            //return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall);
         }
 
         public override void WriteDictionaryTocData(NodeFactory factory, IGenericLookupResultTocWriter writer)
index 201fe5a..151ab64 100644 (file)
@@ -111,6 +111,10 @@ namespace ILCompiler.DependencyAnalysis
 
                 foreach (MethodDesc slotMethod in slots)
                 {
+                    // Static interface methods don't go in the dispatch map
+                    if (slotMethod.Signature.IsStatic)
+                        continue;
+
                     MethodDesc declMethod = slotMethod;
 
                     Debug.Assert(!declMethod.Signature.IsStatic && declMethod.IsVirtual);
index 829a26e..a12f2db 100644 (file)
@@ -81,6 +81,11 @@ namespace ILCompiler.DependencyAnalysis
                     return factory.GenericLookup.DefaultCtorLookupResult((TypeDesc)target);
                 case ReadyToRunHelperId.ObjectAllocator:
                     return factory.GenericLookup.ObjectAllocator((TypeDesc)target);
+                case ReadyToRunHelperId.ConstrainedDirectCall:
+                    return factory.GenericLookup.ConstrainedMethodUse(
+                        ((ConstrainedCallInfo)target).Method,
+                        ((ConstrainedCallInfo)target).ConstrainedType,
+                        directCall: !((ConstrainedCallInfo)target).Method.HasInstantiation);
                 default:
                     throw new NotImplementedException();
             }
index 77c17f1..62d120b 100644 (file)
@@ -40,6 +40,7 @@ namespace ILCompiler.DependencyAnalysis
         DefaultConstructor,
         TypeHandleForCasting,
         ObjectAllocator,
+        ConstrainedDirectCall,
     }
 
     public partial class ReadyToRunHelperNode : AssemblyStubNode, INodeWithDebugInfo
index bb12fb1..2cee3ec 100644 (file)
@@ -197,6 +197,7 @@ namespace ILCompiler.DependencyAnalysis
                 case ReadyToRunHelperId.DefaultConstructor:
                 case ReadyToRunHelperId.ObjectAllocator:
                 case ReadyToRunHelperId.TypeHandleForCasting:
+                case ReadyToRunHelperId.ConstrainedDirectCall:
                     {
                         EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly);
                         encoder.EmitRET();
index 8451b67..5857cee 100644 (file)
@@ -198,6 +198,7 @@ namespace ILCompiler.DependencyAnalysis
                 case ReadyToRunHelperId.DefaultConstructor:
                 case ReadyToRunHelperId.ObjectAllocator:
                 case ReadyToRunHelperId.TypeHandleForCasting:
+                case ReadyToRunHelperId.ConstrainedDirectCall:
                     {
                         EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly);
                         encoder.EmitRET();
index 2ec2b99..7f9d064 100644 (file)
@@ -221,6 +221,7 @@ namespace ILCompiler.DependencyAnalysis
                 case ReadyToRunHelperId.DefaultConstructor:
                 case ReadyToRunHelperId.ObjectAllocator:
                 case ReadyToRunHelperId.TypeHandleForCasting:
+                case ReadyToRunHelperId.ConstrainedDirectCall:
                     {
                         EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly);
                         encoder.EmitRET();
index 54c1fb2..0e91c5b 100644 (file)
@@ -225,6 +225,10 @@ namespace ILCompiler.DependencyAnalysis
                 if (method.HasInstantiation)
                     continue;
 
+                // Static interface methods don't go into vtables
+                if (method.Signature.IsStatic)
+                    continue;
+
                 // Current type doesn't define this slot. Another VTableSlice will take care of this.
                 if (method.OwningType != defType)
                     continue;
index f662d76..2799d45 100644 (file)
@@ -456,6 +456,11 @@ namespace Internal.IL
 
                     exactType = constrained;
                 }
+                else if (method.Signature.IsStatic)
+                {
+                    Debug.Assert(method.OwningType.IsInterface);
+                    exactType = constrained;
+                }
                 else if (constrained.IsValueType)
                 {
                     // We'll need to box `this`. Note we use _constrained here, because the other one is canonical.
@@ -483,8 +488,16 @@ namespace Internal.IL
 
             if (targetMethod.Signature.IsStatic)
             {
-                // Static methods are always direct calls
-                directCall = true;
+                if (_constrained != null && (!resolvedConstraint || forceUseRuntimeLookup))
+                {
+                    // Constrained call to static virtual interface method we didn't resolve statically
+                    Debug.Assert(targetMethod.IsVirtual && targetMethod.OwningType.IsInterface);
+                }
+                else
+                {
+                    // Static methods are always direct calls
+                    directCall = true;
+                }
             }
             else if ((opcode != ILOpcode.callvirt && opcode != ILOpcode.ldvirtftn) || resolvedConstraint)
             {
@@ -533,8 +546,10 @@ namespace Internal.IL
                 MethodDesc targetOfLookup;
                 if (_constrained.IsRuntimeDeterminedType)
                     targetOfLookup = _compilation.TypeSystemContext.GetMethodForRuntimeDeterminedType(targetMethod.GetTypicalMethodDefinition(), (RuntimeDeterminedType)_constrained);
-                else
+                else if (_constrained.HasInstantiation)
                     targetOfLookup = _compilation.TypeSystemContext.GetMethodForInstantiatedType(targetMethod.GetTypicalMethodDefinition(), (InstantiatedType)_constrained);
+                else
+                    targetOfLookup = targetMethod.GetMethodDefinition();
                 if (targetOfLookup.HasInstantiation)
                 {
                     targetOfLookup = targetOfLookup.MakeInstantiatedMethod(runtimeDeterminedMethod.Instantiation);
@@ -690,6 +705,15 @@ namespace Internal.IL
                     _dependencies.Add(GetMethodEntrypoint(targetMethod), reason);
                 }
             }
+            else if (method.Signature.IsStatic)
+            {
+                // This should be an unresolved static virtual interface method call. Other static methods should
+                // have been handled as a directCall above.
+                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && _constrained != null);
+
+                var constrainedCallInfo = new ConstrainedCallInfo(_constrained, runtimeDeterminedMethod);
+                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ConstrainedDirectCall, constrainedCallInfo), reason);
+            }
             else if (method.HasInstantiation)
             {
                 // Generic virtual method call
index 00c3ff2..6c231ef 100644 (file)
@@ -1122,7 +1122,7 @@ namespace Internal.JitInterface
             TypeDesc exactType = HandleToObject(pResolvedToken.hClass);
 
             TypeDesc constrainedType = null;
-            if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0 && pConstrainedResolvedToken != null)
+            if (pConstrainedResolvedToken != null)
             {
                 constrainedType = HandleToObject(pConstrainedResolvedToken->hClass);
             }
@@ -1168,6 +1168,11 @@ namespace Internal.JitInterface
 
                     exactType = constrainedType;
                 }
+                else if (method.Signature.IsStatic)
+                {
+                    Debug.Assert(method.OwningType.IsInterface);
+                    exactType = constrainedType;
+                }
                 else if (constrainedType.IsValueType)
                 {
                     pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS;
@@ -1222,8 +1227,16 @@ namespace Internal.JitInterface
 
             if (targetMethod.Signature.IsStatic)
             {
-                // Static methods are always direct calls
-                directCall = true;
+                if (constrainedType != null && (!resolvedConstraint || forceUseRuntimeLookup))
+                {
+                    // Constrained call to static virtual interface method we didn't resolve statically
+                    Debug.Assert(targetMethod.IsVirtual && targetMethod.OwningType.IsInterface);
+                }
+                else
+                {
+                    // Static methods are always direct calls
+                    directCall = true;
+                }
             }
             else if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) == 0 || resolvedConstraint)
             {
@@ -1294,7 +1307,7 @@ namespace Internal.JitInterface
 
                 pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER;
                 pResult->codePointerOrStubLookup.constLookup.accessType = InfoAccessType.IAT_VALUE;
-                pResult->nullInstanceCheck = true;
+                pResult->nullInstanceCheck = !targetMethod.Signature.IsStatic;
 
                 // We have the canonical version of the method - find the runtime determined version.
                 // This is simplified because we know the method is on a valuetype.
@@ -1315,8 +1328,10 @@ namespace Internal.JitInterface
                 MethodDesc targetOfLookup;
                 if (runtimeDeterminedConstrainedType.IsRuntimeDeterminedType)
                     targetOfLookup = _compilation.TypeSystemContext.GetMethodForRuntimeDeterminedType(targetMethod.GetTypicalMethodDefinition(), (RuntimeDeterminedType)runtimeDeterminedConstrainedType);
-                else
+                else if (runtimeDeterminedConstrainedType.HasInstantiation)
                     targetOfLookup = _compilation.TypeSystemContext.GetMethodForInstantiatedType(targetMethod.GetTypicalMethodDefinition(), (InstantiatedType)runtimeDeterminedConstrainedType);
+                else
+                    targetOfLookup = targetMethod.GetMethodDefinition();
                 if (targetOfLookup.HasInstantiation)
                 {
                     var methodToGetInstantiation = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
@@ -1413,6 +1428,26 @@ namespace Internal.JitInterface
 
                 pResult->nullInstanceCheck = resolvedCallVirt;
             }
+            else if (targetMethod.Signature.IsStatic)
+            {
+                // This should be an unresolved static virtual interface method call. Other static methods should
+                // have been handled as a directCall above.
+                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && constrainedType != null);
+
+                pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER;
+
+                TypeDesc runtimeDeterminedConstrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *pConstrainedResolvedToken);
+                MethodDesc runtimeDeterminedInterfaceMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
+
+                ComputeLookup(ref pResolvedToken,
+                    new ConstrainedCallInfo(runtimeDeterminedConstrainedType, runtimeDeterminedInterfaceMethod),
+                    ReadyToRunHelperId.ConstrainedDirectCall,
+                    ref pResult->codePointerOrStubLookup);
+
+                targetIsFatFunctionPointer = true;
+                useFatCallTransform = true;
+                pResult->nullInstanceCheck = false;
+            }
             else if (targetMethod.HasInstantiation)
             {
                 // Generic virtual method call support
index e5f3502..ac8b380 100644 (file)
@@ -132,4 +132,9 @@ namespace System.Runtime.CompilerServices
     public sealed class IsByRefLikeAttribute : Attribute
     {
     }
+
+    public static class RuntimeFeature
+    {
+        public const string VirtualStaticsInInterfaces = nameof(VirtualStaticsInInterfaces);
+    }
 }
diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/VirtualStaticInterfaceMethods.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/VirtualStaticInterfaceMethods.cs
new file mode 100644 (file)
index 0000000..55f104b
--- /dev/null
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace VirtualStaticInterfaceMethods
+{
+    interface ISimple
+    {
+        static abstract int WhichMethod();
+    }
+
+    class Simple : ISimple
+    {
+        public static int WhichMethod() => throw null;
+    }
+
+    interface IVariant<in T>
+    {
+        static abstract string WhichMethod(T param);
+    }
+
+    class SimpleVariant : IVariant<Base>
+    {
+        public static string WhichMethod(Base b) => throw null;
+    }
+
+    class SimpleVariantTwice : IVariant<Base>, IVariant<Mid>
+    {
+        public static string WhichMethod(Base b) => throw null;
+        public static string WhichMethod(Mid b) => throw null;
+    }
+
+    class VariantWithInheritanceBase : IVariant<Mid>
+    {
+        public static string WhichMethod(Mid b) => throw null;
+    }
+
+    class VariantWithInheritanceDerived : VariantWithInheritanceBase, IVariant<Base>
+    {
+        public static string WhichMethod(Base b) => throw null;
+    }
+
+    class GenericVariantWithInheritanceBase<T> : IVariant<T>
+    {
+        public static string WhichMethod(T b) => throw null;
+    }
+
+    class GenericVariantWithInheritanceDerived<T> : GenericVariantWithInheritanceBase<T>, IVariant<T>
+    {
+        public static new string WhichMethod(T b) => throw null;
+    }
+
+    class GenericVariantWithHiddenBase : IVariant<Mid>
+    {
+        public static string WhichMethod(Mid b) => throw null;
+    }
+
+    class GenericVariantWithHiddenDerived<T> : GenericVariantWithHiddenBase, IVariant<T>
+    {
+        public static string WhichMethod(T b) => throw null;
+    }
+
+    class Base { }
+    class Mid : Base { }
+    class Derived : Mid { }
+}
index 767680f..d23f31c 100644 (file)
@@ -69,6 +69,7 @@
     <Compile Include="InstanceFieldLayoutTests.cs" />
     <Compile Include="StaticFieldLayoutTests.cs" />
     <Compile Include="TestTypeSystemContext.cs" />
+    <Compile Include="VirtualStaticInterfaceMethodTests.cs" />
     <Compile Include="WellKnownTypeTests.cs" />
     <Compile Include="ExceptionStringTests.cs" />
     <Compile Include="MarshalUtilsTests.cs" />
diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/VirtualStaticInterfaceMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/VirtualStaticInterfaceMethodTests.cs
new file mode 100644 (file)
index 0000000..3553f8a
--- /dev/null
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+using Internal.TypeSystem;
+
+using Xunit;
+
+namespace TypeSystemTests
+{
+    public class VirtualStaticInterfaceMethodTests
+    {
+        public static IEnumerable<object[]> VariantTestData()
+        {
+            var context = new TestTypeSystemContext(TargetArchitecture.Unknown);
+            ModuleDesc testModule = context.CreateModuleForSimpleName("CoreTestAssembly");
+            context.SetSystemModule(testModule);
+
+            MetadataType simple = testModule.GetType("VirtualStaticInterfaceMethods", "Simple");
+            MetadataType iSimple = testModule.GetType("VirtualStaticInterfaceMethods", "ISimple");
+            MetadataType iVariant = testModule.GetType("VirtualStaticInterfaceMethods", "IVariant`1");
+            MetadataType @base = testModule.GetType("VirtualStaticInterfaceMethods", "Base");
+            MetadataType mid = testModule.GetType("VirtualStaticInterfaceMethods", "Mid");
+            MetadataType derived = testModule.GetType("VirtualStaticInterfaceMethods", "Derived");
+            MetadataType simpleVariant = testModule.GetType("VirtualStaticInterfaceMethods", "SimpleVariant");
+            MetadataType simpleVariantTwice = testModule.GetType("VirtualStaticInterfaceMethods", "SimpleVariantTwice");
+            MetadataType variantWithInheritanceDerived = testModule.GetType("VirtualStaticInterfaceMethods", "VariantWithInheritanceDerived");
+            MetadataType genericVariantWithInheritanceDerived = testModule.GetType("VirtualStaticInterfaceMethods", "GenericVariantWithInheritanceDerived`1");
+            MetadataType genericVariantWithHiddenBase = testModule.GetType("VirtualStaticInterfaceMethods", "GenericVariantWithHiddenBase");
+            MetadataType genericVariantWithHiddenDerived = testModule.GetType("VirtualStaticInterfaceMethods", "GenericVariantWithHiddenDerived`1");
+
+            MethodDesc iSimpleMethod = iSimple.GetMethod("WhichMethod", null);
+            MethodDesc iVariantBaseMethod = iVariant.MakeInstantiatedType(@base).GetMethod("WhichMethod", null);
+            MethodDesc iVariantMidMethod = iVariant.MakeInstantiatedType(mid).GetMethod("WhichMethod", null);
+            MethodDesc iVariantDerivedMethod = iVariant.MakeInstantiatedType(derived).GetMethod("WhichMethod", null);
+
+            yield return new object[] { simple, iSimpleMethod, simple.GetMethod("WhichMethod", null) };
+
+            yield return new object[] { simpleVariant, iVariantBaseMethod, simpleVariant.GetMethod("WhichMethod", null) };
+            yield return new object[] { simpleVariant, iVariantDerivedMethod, simpleVariant.GetMethod("WhichMethod", null) };
+
+            yield return new object[] { simpleVariantTwice, iVariantBaseMethod, simpleVariantTwice.GetMethod("WhichMethod", new MethodSignature(MethodSignatureFlags.Static, 0, context.GetWellKnownType(WellKnownType.String), new TypeDesc[] { @base })) };
+            yield return new object[] { simpleVariantTwice, iVariantMidMethod, simpleVariantTwice.GetMethod("WhichMethod", new MethodSignature(MethodSignatureFlags.Static, 0, context.GetWellKnownType(WellKnownType.String), new TypeDesc[] { mid })) };
+            yield return new object[] { simpleVariantTwice, iVariantDerivedMethod, simpleVariantTwice.GetMethod("WhichMethod", new MethodSignature(MethodSignatureFlags.Static, 0, context.GetWellKnownType(WellKnownType.String), new TypeDesc[] { @base })) };
+
+            yield return new object[] { variantWithInheritanceDerived, iVariantBaseMethod, variantWithInheritanceDerived.GetMethod("WhichMethod", null) };
+            yield return new object[] { variantWithInheritanceDerived, iVariantMidMethod, variantWithInheritanceDerived.GetMethod("WhichMethod", null) };
+            yield return new object[] { variantWithInheritanceDerived, iVariantDerivedMethod, variantWithInheritanceDerived.GetMethod("WhichMethod", null) };
+
+            yield return new object[] { genericVariantWithInheritanceDerived.MakeInstantiatedType(@base), iVariantBaseMethod, genericVariantWithInheritanceDerived.MakeInstantiatedType(@base).GetMethod("WhichMethod", null) };
+            yield return new object[] { genericVariantWithInheritanceDerived.MakeInstantiatedType(@base), iVariantMidMethod, genericVariantWithInheritanceDerived.MakeInstantiatedType(@base).GetMethod("WhichMethod", null) };
+            yield return new object[] { genericVariantWithInheritanceDerived.MakeInstantiatedType(mid), iVariantMidMethod, genericVariantWithInheritanceDerived.MakeInstantiatedType(mid).GetMethod("WhichMethod", null) };
+
+            yield return new object[] { genericVariantWithHiddenDerived.MakeInstantiatedType(@base), iVariantBaseMethod, genericVariantWithHiddenDerived.MakeInstantiatedType(@base).GetMethod("WhichMethod", null) };
+            yield return new object[] { genericVariantWithHiddenDerived.MakeInstantiatedType(@base), iVariantMidMethod, genericVariantWithHiddenDerived.MakeInstantiatedType(@base).GetMethod("WhichMethod", null) };
+            yield return new object[] { genericVariantWithHiddenDerived.MakeInstantiatedType(mid), iVariantMidMethod, genericVariantWithHiddenDerived.MakeInstantiatedType(mid).GetMethod("WhichMethod", null) };
+            yield return new object[] { genericVariantWithHiddenDerived.MakeInstantiatedType(derived), iVariantMidMethod, genericVariantWithHiddenBase.GetMethod("WhichMethod", null) };
+        }
+
+        [Theory]
+        [MemberData(nameof(VariantTestData))]
+        public void Test(MetadataType theClass, MethodDesc intfMethod, MethodDesc expected)
+        {
+            MethodDesc result = theClass.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(intfMethod);
+            Assert.Equal(expected, result);
+        }
+    }
+}
index 8ae4a36..a6aa3b9 100644 (file)
@@ -41,6 +41,8 @@ public class Interfaces
         TestSharedIntefaceMethods.Run();
         TestCovariantReturns.Run();
         TestDynamicInterfaceCastable.Run();
+        TestStaticInterfaceMethodsAnalysis.Run();
+        TestStaticInterfaceMethods.Run();
 
         return Pass;
     }
@@ -808,4 +810,183 @@ public class Interfaces
             }
         }
     }
+
+    class TestStaticInterfaceMethodsAnalysis
+    {
+        interface IFoo
+        {
+            static abstract object Frob();
+        }
+
+        class Foo<T> : IFoo
+        {
+            static object IFoo.Frob() => new Gen<T>();
+        }
+
+        static object CallFrob<T>() where T : IFoo => T.Frob();
+
+        class Gen<T> { }
+        struct Struct1 { }
+        struct Struct2 { }
+
+        public static void Run()
+        {
+            CallFrob<Foo<object>>();
+            Console.WriteLine(typeof(Foo<string>));
+
+            CallFrob<Foo<Struct1>>();
+            Console.WriteLine(typeof(Foo<Struct2>));
+        }
+    }
+
+    class TestStaticInterfaceMethods
+    {
+        interface ISimple
+        {
+            static abstract string GetCookie();
+            static abstract string GetCookieGeneric<T>();
+        }
+
+        class SimpleClass : ISimple
+        {
+            public static string GetCookie() => "SimpleClass";
+            public static string GetCookieGeneric<T>() => $"SimpleClass.GetCookieGeneric<{typeof(T).Name}>";
+        }
+
+        struct SimpleStruct : ISimple
+        {
+            public static string GetCookie() => "SimpleStruct";
+            public static string GetCookieGeneric<T>() => $"SimpleStruct.GetCookieGeneric<{typeof(T).Name}>";
+        }
+
+        struct SimpleGenericStruct<T> : ISimple
+        {
+            public static string GetCookie() => $"SimpleGenericStruct<{typeof(T).Name}>";
+            public static string GetCookieGeneric<U>() => $"SimpleGenericStruct<{typeof(T).Name}>.GetCookieGeneric<{typeof(U).Name}>";
+        }
+
+        class SimpleGenericClass<T> : ISimple
+        {
+            public static string GetCookie() => $"SimpleGenericClass<{typeof(T).Name}>";
+            public static string GetCookieGeneric<U>() => $"SimpleGenericClass<{typeof(T).Name}>.GetCookieGeneric<{typeof(U).Name}>";
+        }
+
+        interface IVariant<in T>
+        {
+            static abstract string WhichMethod(T param);
+        }
+
+        class SimpleVariant : IVariant<Base>
+        {
+            public static string WhichMethod(Base b) => "SimpleVariant.WhichMethod(Base)";
+        }
+
+        class SimpleVariantTwice : IVariant<Base>, IVariant<Mid>
+        {
+            public static string WhichMethod(Base b) => "SimpleVariantTwice.WhichMethod(Base)";
+            public static string WhichMethod(Mid b) => "SimpleVariantTwice.WhichMethod(Mid)";
+        }
+
+        class VariantWithInheritanceBase : IVariant<Mid>
+        {
+            public static string WhichMethod(Mid b) => "VariantWithInheritanceBase.WhichMethod(Mid)";
+        }
+
+        class VariantWithInheritanceDerived : VariantWithInheritanceBase, IVariant<Base>
+        {
+            public static string WhichMethod(Base b) => "VariantWithInheritanceDerived.WhichMethod(Base)";
+        }
+
+        class GenericVariantWithInheritanceBase<T> : IVariant<T>
+        {
+            public static string WhichMethod(T b) => "GenericVariantWithInheritanceBase.WhichMethod(T)";
+        }
+
+        class GenericVariantWithInheritanceDerived<T> : GenericVariantWithInheritanceBase<T>, IVariant<T>
+        {
+            public static new string WhichMethod(T b) => $"GenericVariantWithInheritanceDerived.WhichMethod({typeof(T).Name})";
+        }
+
+        class GenericVariantWithHiddenBase : IVariant<Mid>
+        {
+            public static string WhichMethod(Mid b) => "GenericVariantWithHiddenBase.WhichMethod(Mid)";
+        }
+
+        class GenericVariantWithHiddenDerived<T> : GenericVariantWithHiddenBase, IVariant<T>
+        {
+            public static string WhichMethod(T b) => $"GenericVariantWithHiddenDerived.WhichMethod({typeof(T).Name})";
+        }
+
+        struct Struct { }
+        class Base { }
+        class Mid : Base { }
+        class Derived : Mid { }
+
+
+        static void TestSimpleInterface<T>(string expected) where T : ISimple
+        {
+            string actual = T.GetCookie();
+            if (actual != expected)
+            {
+                throw new Exception($"{actual} != {expected}");
+            }
+        }
+
+        static void TestSimpleInterfaceWithGenericMethod<T, U>(string expected) where T : ISimple
+        {
+            string actual = T.GetCookieGeneric<U>();
+            if (actual != expected)
+            {
+                throw new Exception($"{actual} != {expected}");
+            }
+        }
+
+        static void TestVariantInterface<T, U>(string expected) where T : IVariant<U>
+        {
+            string actual = T.WhichMethod(default);
+            if (actual != expected)
+            {
+                throw new Exception($"{actual} != {expected}");
+            }
+        }
+
+        public static void Run()
+        {
+            TestSimpleInterface<SimpleClass>("SimpleClass");
+            TestSimpleInterface<SimpleStruct>("SimpleStruct");
+
+            TestSimpleInterface<SimpleGenericClass<Base>>("SimpleGenericClass<Base>");
+            TestSimpleInterface<SimpleGenericStruct<Base>>("SimpleGenericStruct<Base>");
+
+            TestSimpleInterfaceWithGenericMethod<SimpleClass, Base>("SimpleClass.GetCookieGeneric<Base>");
+            TestSimpleInterfaceWithGenericMethod<SimpleStruct, Base>("SimpleStruct.GetCookieGeneric<Base>");
+            TestSimpleInterfaceWithGenericMethod<SimpleClass, Struct>("SimpleClass.GetCookieGeneric<Struct>");
+            TestSimpleInterfaceWithGenericMethod<SimpleStruct, Struct>("SimpleStruct.GetCookieGeneric<Struct>");
+
+            TestSimpleInterfaceWithGenericMethod<SimpleGenericClass<Base>, Base>("SimpleGenericClass<Base>.GetCookieGeneric<Base>");
+            TestSimpleInterfaceWithGenericMethod<SimpleGenericStruct<Base>, Base>("SimpleGenericStruct<Base>.GetCookieGeneric<Base>");
+            TestSimpleInterfaceWithGenericMethod<SimpleGenericClass<Base>, Struct>("SimpleGenericClass<Base>.GetCookieGeneric<Struct>");
+            TestSimpleInterfaceWithGenericMethod<SimpleGenericStruct<Base>, Struct>("SimpleGenericStruct<Base>.GetCookieGeneric<Struct>");
+
+            TestVariantInterface<SimpleVariant, Base>("SimpleVariant.WhichMethod(Base)");
+            TestVariantInterface<SimpleVariant, Derived>("SimpleVariant.WhichMethod(Base)");
+
+            TestVariantInterface<SimpleVariantTwice, Base>("SimpleVariantTwice.WhichMethod(Base)");
+            TestVariantInterface<SimpleVariantTwice, Mid>("SimpleVariantTwice.WhichMethod(Mid)");
+            TestVariantInterface<SimpleVariantTwice, Derived>("SimpleVariantTwice.WhichMethod(Base)");
+
+            TestVariantInterface<VariantWithInheritanceDerived, Base>("VariantWithInheritanceDerived.WhichMethod(Base)");
+            TestVariantInterface<VariantWithInheritanceDerived, Mid>("VariantWithInheritanceDerived.WhichMethod(Base)");
+            TestVariantInterface<VariantWithInheritanceDerived, Derived>("VariantWithInheritanceDerived.WhichMethod(Base)");
+
+            TestVariantInterface<GenericVariantWithInheritanceDerived<Base>, Base>("GenericVariantWithInheritanceDerived.WhichMethod(Base)");
+            TestVariantInterface<GenericVariantWithInheritanceDerived<Base>, Mid>("GenericVariantWithInheritanceDerived.WhichMethod(Base)");
+            TestVariantInterface<GenericVariantWithInheritanceDerived<Mid>, Mid>("GenericVariantWithInheritanceDerived.WhichMethod(Mid)");
+
+            TestVariantInterface<GenericVariantWithHiddenDerived<Base>, Base>("GenericVariantWithHiddenDerived.WhichMethod(Base)");
+            TestVariantInterface<GenericVariantWithHiddenDerived<Base>, Mid>("GenericVariantWithHiddenDerived.WhichMethod(Base)");
+            TestVariantInterface<GenericVariantWithHiddenDerived<Mid>, Mid>("GenericVariantWithHiddenDerived.WhichMethod(Mid)");
+            TestVariantInterface<GenericVariantWithHiddenDerived<Derived>, Mid>("GenericVariantWithHiddenBase.WhichMethod(Mid)");
+        }
+    }
 }