Add support for static default interface methods (#72661)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Fri, 22 Jul 2022 21:32:44 +0000 (06:32 +0900)
committerGitHub <noreply@github.com>
Fri, 22 Jul 2022 21:32:44 +0000 (06:32 +0900)
We were only supporting default interface methods in the sense of non-abstract virtuals. We now also support overrides.

What's missing is support for diamond/reabstraction. I thin we need to make throwing stubs that we can dispatch to - it doesn't look like the strategy used in the VM (`callsiteCalloutHelper`) can be used for prejit. I don't want to increase the scope of this pull request, so we invalidate the entire method that has such dispatch.

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/DependencyAnalysis/EETypeNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
src/tests/issues.targets
src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs

index c6b028a..47cdc09 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Runtime.CompilerServices;
+
 using Internal.IL;
 using Internal.TypeSystem;
 
@@ -410,6 +412,11 @@ namespace ILCompiler
             return false;
         }
 
+        public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrainedType, TypeDesc interfaceType, MethodDesc interfaceMethod, out bool forceRuntimeLookup)
+        {
+            return TryResolveConstraintMethodApprox(constrainedType, interfaceType, interfaceMethod, out forceRuntimeLookup, ref Unsafe.NullRef<DefaultInterfaceMethodResolution>());
+        }
+
         /// <summary>
         /// Attempts to resolve constrained call to <paramref name="interfaceMethod"/> into a concrete non-unboxing
         /// method on <paramref name="constrainedType"/>.
@@ -417,7 +424,7 @@ namespace ILCompiler
         /// for generic code.
         /// </summary>
         /// <returns>The resolved method or null if the constraint couldn't be resolved.</returns>
-        public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrainedType, TypeDesc interfaceType, MethodDesc interfaceMethod, out bool forceRuntimeLookup)
+        public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrainedType, TypeDesc interfaceType, MethodDesc interfaceMethod, out bool forceRuntimeLookup, ref DefaultInterfaceMethodResolution staticResolution)
         {
             forceRuntimeLookup = false;
 
@@ -461,6 +468,12 @@ namespace ILCompiler
                         interfaceType.ConvertToCanonForm(CanonicalFormKind.Specific))
                     {
                         potentialMatchingInterfaces++;
+
+                        // The below code is just trying to prevent one of the matches from requiring boxing
+                        // It doesn't apply to static virtual methods.
+                        if (isStaticVirtualMethod)
+                            continue;
+
                         MethodDesc potentialInterfaceMethod = genInterfaceMethod;
                         if (potentialInterfaceMethod.OwningType != potentialInterfaceType)
                         {
@@ -468,17 +481,10 @@ namespace ILCompiler
                                 potentialInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)potentialInterfaceType);
                         }
 
-                        if (isStaticVirtualMethod)
-                        {
-                            method = canonType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(potentialInterfaceMethod);
-                        }
-                        else
-                        {
-                            method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
-                        }
+                        method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod);
 
                         // See code:#TryResolveConstraintMethodApprox_DoNotReturnParentMethod
-                        if (!isStaticVirtualMethod && method != null && !method.OwningType.IsValueType)
+                        if (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.
@@ -512,6 +518,12 @@ namespace ILCompiler
                             if (isStaticVirtualMethod)
                             {
                                 method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
+                                if (method == null)
+                                {
+                                    staticResolution = constrainedType.ResolveVariantInterfaceMethodToDefaultImplementationOnType(exactInterfaceMethod, out method);
+                                    if (staticResolution != DefaultInterfaceMethodResolution.DefaultImplementation)
+                                        method = null;
+                                }
                             }
                             else
                             {
@@ -542,6 +554,12 @@ namespace ILCompiler
                         if (isStaticVirtualMethod)
                         {
                             method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
+                            if (method == null)
+                            {
+                                staticResolution = constrainedType.ResolveVariantInterfaceMethodToDefaultImplementationOnType(exactInterfaceMethod, out method);
+                                if (staticResolution != DefaultInterfaceMethodResolution.DefaultImplementation)
+                                    method = null;
+                            }
                         }
                         else
                         {
@@ -562,16 +580,6 @@ namespace ILCompiler
                 method = null;
             }
 
-            // Default implementation logic, which only kicks in for default implementations when looking up on an exact interface target
-            if (isStaticVirtualMethod && method == null && !genInterfaceMethod.IsAbstract && !constrainedType.IsCanonicalSubtype(CanonicalFormKind.Any))
-            {
-                MethodDesc exactInterfaceMethod = genInterfaceMethod;
-                if (genInterfaceMethod.OwningType != interfaceType)
-                    exactInterfaceMethod = context.GetMethodForInstantiatedType(
-                        genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType);
-                method = exactInterfaceMethod;
-            }
-
             if (method == null)
             {
                 // Fall back to VSD
@@ -594,6 +602,16 @@ namespace ILCompiler
                 method = method.MakeInstantiatedMethod(methodInstantiation);
             }
 
+            // It's difficult to discern what runtime determined form the interface method
+            // is on later so fail the resolution if this would be that.
+            // This is pretty conservative and can be narrowed down.
+            if (method.IsCanonicalMethod(CanonicalFormKind.Any)
+                && !method.OwningType.IsValueType)
+            {
+                Debug.Assert(method.Signature.IsStatic);
+                return null;
+            }
+
             Debug.Assert(method != null);
 
             return method;
index cec25e3..564da8c 100644 (file)
@@ -754,6 +754,7 @@ namespace Internal.TypeSystem
             bool diamondCase = false;
             impl = null;
 
+            MethodDesc interfaceMethodDefinition = interfaceMethod.GetMethodDefinition();
             DefType[] consideredInterfaces;
             if (!currentType.IsInterface)
             {
@@ -778,7 +779,7 @@ namespace Internal.TypeSystem
                     if (mostSpecificInterface == null && !interfaceMethod.IsAbstract)
                     {
                         mostSpecificInterface = runtimeInterface;
-                        impl = interfaceMethod;
+                        impl = interfaceMethodDefinition;
                     }
                 }
                 else if (Array.IndexOf(runtimeInterface.RuntimeInterfaces, interfaceMethodOwningType) != -1)
@@ -789,7 +790,7 @@ namespace Internal.TypeSystem
                     {
                         foreach (MethodImplRecord implRecord in possibleImpls)
                         {
-                            if (implRecord.Decl == interfaceMethod)
+                            if (implRecord.Decl == interfaceMethodDefinition)
                             {
                                 // This interface provides a default implementation.
                                 // Is it also most specific?
@@ -822,12 +823,54 @@ namespace Internal.TypeSystem
             }
             else if (impl.IsAbstract)
             {
+                impl = null;
                 return DefaultInterfaceMethodResolution.Reabstraction;
             }
 
+            if (interfaceMethod != interfaceMethodDefinition)
+                impl = impl.MakeInstantiatedMethod(interfaceMethod.Instantiation);
+
             return DefaultInterfaceMethodResolution.DefaultImplementation;
         }
 
+        public override DefaultInterfaceMethodResolution ResolveVariantInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl)
+        {
+            return ResolveVariantInterfaceMethodToDefaultImplementationOnType(interfaceMethod, (MetadataType)currentType, out impl);
+        }
+
+        public static DefaultInterfaceMethodResolution ResolveVariantInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, MetadataType currentType, out MethodDesc impl)
+        {
+            Debug.Assert(interfaceMethod.Signature.IsStatic);
+
+            MetadataType interfaceType = (MetadataType)interfaceMethod.OwningType;
+            bool foundInterface = IsInterfaceImplementedOnType(currentType, interfaceType);
+
+            if (foundInterface)
+            {
+                DefaultInterfaceMethodResolution resolution = ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, currentType, out impl);
+                if (resolution != DefaultInterfaceMethodResolution.None)
+                    return resolution;
+            }
+
+            MethodDesc interfaceMethodDefinition = interfaceMethod.GetMethodDefinition();
+            foreach (TypeDesc iface in currentType.RuntimeInterfaces)
+            {
+                if (iface.HasSameTypeDefinition(interfaceType) && iface.CanCastTo(interfaceType))
+                {
+                    MethodDesc variantMethod = iface.FindMethodOnTypeWithMatchingTypicalMethod(interfaceMethodDefinition);
+                    Debug.Assert(variantMethod != null);
+                    if (interfaceMethod != interfaceMethodDefinition)
+                        variantMethod = variantMethod.MakeInstantiatedMethod(interfaceMethod.Instantiation);
+                    DefaultInterfaceMethodResolution resolution = ResolveInterfaceMethodToDefaultImplementationOnType(variantMethod, currentType, out impl);
+                    if (resolution != DefaultInterfaceMethodResolution.None)
+                        return resolution;
+                }
+            }
+
+            impl = null;
+            return DefaultInterfaceMethodResolution.None;
+        }
+
         public override IEnumerable<MethodDesc> ComputeAllVirtualSlots(TypeDesc type)
         {
             return EnumAllVirtualSlots((MetadataType)type);
index a10e358..7c84144 100644 (file)
@@ -220,6 +220,11 @@ namespace Internal.TypeSystem
             return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, type, out implMethod);
         }
 
+        public static DefaultInterfaceMethodResolution ResolveVariantInterfaceMethodToDefaultImplementationOnType(this TypeDesc type, MethodDesc interfaceMethod, out MethodDesc implMethod)
+        {
+            return type.Context.GetVirtualMethodAlgorithmForType(type).ResolveVariantInterfaceMethodToDefaultImplementationOnType(interfaceMethod, type, out implMethod);
+        }
+
         /// <summary>
         /// Resolves a virtual method call.
         /// </summary>
index d021afc..55062e0 100644 (file)
@@ -30,6 +30,8 @@ namespace Internal.TypeSystem
 
         public abstract DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl);
 
+        public abstract DefaultInterfaceMethodResolution ResolveVariantInterfaceMethodToDefaultImplementationOnType(MethodDesc interfaceMethod, TypeDesc currentType, out MethodDesc impl);
+
         /// <summary>
         /// Resolves a virtual method call.
         /// </summary>
index da90de0..c5da7a6 100644 (file)
@@ -382,8 +382,10 @@ namespace ILCompiler.DependencyAnalysis
                             {
                                 Debug.Assert(!implMethod.IsVirtual);
 
+                                MethodDesc defaultIntfMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
+
                                 // If the interface method is used virtually, the implementation body is used
-                                result.Add(new CombinedDependencyListEntry(factory.CanonicalEntrypoint(implMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method"));
+                                result.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(defaultIntfMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method"));
                             }
                             else
                             {
index 46d70a1..cba6ebe 100644 (file)
@@ -1454,9 +1454,15 @@ namespace ILCompiler.DependencyAnalysis
                 if (instantiatedConstrainedMethod.Signature.IsStatic)
                 {
                     implMethod = instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(instantiatedConstrainedMethod);
-                    if (implMethod == null && !instantiatedConstrainedMethod.IsAbstract)
+                    if (implMethod == null)
                     {
-                        implMethod = instantiatedConstrainedMethod;
+                        DefaultInterfaceMethodResolution resolution =
+                            instantiatedConstraintType.GetClosestDefType().ResolveVariantInterfaceMethodToDefaultImplementationOnType(instantiatedConstrainedMethod, out implMethod);
+                        if (resolution != DefaultInterfaceMethodResolution.DefaultImplementation)
+                        {
+                            // TODO: diamond/reabstraction
+                            ThrowHelper.ThrowInvalidProgramException();
+                        }
                     }
                 }
                 else
index 597e42d..3f561a8 100644 (file)
@@ -384,6 +384,7 @@ namespace Internal.IL
 
             bool resolvedConstraint = false;
             bool forceUseRuntimeLookup = false;
+            DefaultInterfaceMethodResolution staticResolution = default;
 
             MethodDesc methodAfterConstraintResolution = method;
             if (_constrained != null)
@@ -398,7 +399,7 @@ namespace Internal.IL
                 if (constrained.IsRuntimeDeterminedSubtype)
                     constrained = constrained.ConvertToCanonForm(CanonicalFormKind.Specific);
 
-                MethodDesc directMethod = constrained.GetClosestDefType().TryResolveConstraintMethodApprox(method.OwningType, method, out forceUseRuntimeLookup);
+                MethodDesc directMethod = constrained.GetClosestDefType().TryResolveConstraintMethodApprox(method.OwningType, method, out forceUseRuntimeLookup, ref staticResolution);
                 if (directMethod == null && constrained.IsEnum)
                 {
                     // Constrained calls to methods on enum methods resolve to System.Enum's methods. System.Enum is a reference
@@ -419,7 +420,7 @@ namespace Internal.IL
                         || methodAfterConstraintResolution.Signature.IsStatic);
                     resolvedConstraint = true;
 
-                    exactType = constrained;
+                    exactType = directMethod.OwningType;
                 }
                 else if (method.Signature.IsStatic)
                 {
@@ -525,7 +526,7 @@ namespace Internal.IL
                 }
                 else
                 {
-                    _dependencies.Add(_factory.FatFunctionPointer(runtimeDeterminedMethod), reason);
+                    _dependencies.Add(_factory.FatFunctionPointer(targetMethod), reason);
                 }
             }
             else if (directCall)
@@ -676,6 +677,12 @@ namespace Internal.IL
                     _dependencies.Add(GetMethodEntrypoint(targetMethod), reason);
                 }
             }
+            else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction)
+            {
+                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && _constrained != null);
+
+                ThrowHelper.ThrowBadImageFormatException();
+            }
             else if (method.Signature.IsStatic)
             {
                 // This should be an unresolved static virtual interface method call. Other static methods should
@@ -683,7 +690,14 @@ namespace Internal.IL
                 Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && _constrained != null);
 
                 var constrainedCallInfo = new ConstrainedCallInfo(_constrained, runtimeDeterminedMethod);
-                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ConstrainedDirectCall, constrainedCallInfo), reason);
+                var constrainedHelperId = ReadyToRunHelperId.ConstrainedDirectCall;
+
+                // Constant lookup doesn't make sense and we don't implement it. If we need constant lookup,
+                // the method wasn't implemented. Don't crash on it.
+                if (!_compilation.NeedsRuntimeLookup(constrainedHelperId, constrainedCallInfo))
+                    ThrowHelper.ThrowTypeLoadException(_constrained);
+
+                _dependencies.Add(GetGenericLookupHelper(constrainedHelperId, constrainedCallInfo), reason);
             }
             else if (method.HasInstantiation)
             {
index a979b8f..84a8956 100644 (file)
@@ -33,6 +33,7 @@ namespace ILCompiler.DependencyAnalysis
         public MethodCodeNode(MethodDesc method)
         {
             Debug.Assert(!method.IsAbstract);
+            Debug.Assert(!method.IsGenericMethodDefinition && !method.OwningType.IsGenericDefinition);
             Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method);
             _method = method;
         }
index bfdae78..2458aef 100644 (file)
@@ -382,8 +382,16 @@ namespace Internal.JitInterface
                 var interfaceMethod = (MethodDesc)ResolveTokenInScope(methodIL, typeOrMethodContext, pTargetMethod.token);
                 constrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(methodIL, typeOrMethodContext, targetConstraint);
 
-                MethodDesc directMethod = canonConstrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(interfaceType, interfaceMethod, out bool forceRuntimeLookup);
-                if (directMethod != null)
+                DefaultInterfaceMethodResolution staticResolution = default;
+                MethodDesc directMethod = canonConstrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(interfaceType, interfaceMethod, out bool forceRuntimeLookup, ref staticResolution);
+                if (directMethod != null && !directMethod.IsCanonicalMethod(CanonicalFormKind.Any))
+                {
+                    targetMethod = directMethod;
+
+                    // We resolved the constraint
+                    constrainedType = null;
+                }
+                else if (directMethod != null)
                 {
                     // We resolved on a canonical form of the valuetype. Now find the method on the runtime determined form.
                     Debug.Assert(directMethod.OwningType.IsValueType);
@@ -405,6 +413,11 @@ namespace Internal.JitInterface
                     // We resolved the constraint
                     constrainedType = null;
                 }
+                else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction)
+                {
+                    // TODO
+                    ThrowHelper.ThrowInvalidProgramException();
+                }
             }
 
             // We better come up with the same method that getCallInfo came up with, with the only difference being
@@ -1172,6 +1185,7 @@ namespace Internal.JitInterface
             bool forceUseRuntimeLookup = false;
             bool targetIsFatFunctionPointer = false;
             bool useFatCallTransform = false;
+            DefaultInterfaceMethodResolution staticResolution = default;
 
             MethodDesc methodAfterConstraintResolution = method;
             if (constrainedType == null)
@@ -1186,7 +1200,7 @@ namespace Internal.JitInterface
                 // JIT compilation, and require a runtime lookup for the actual code pointer
                 // to call.
 
-                MethodDesc directMethod = constrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(exactType, method, out forceUseRuntimeLookup);
+                MethodDesc directMethod = constrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(exactType, method, out forceUseRuntimeLookup, ref staticResolution);
                 if (directMethod == null && constrainedType.IsEnum)
                 {
                     // Constrained calls to methods on enum methods resolve to System.Enum's methods. System.Enum is a reference
@@ -1208,7 +1222,7 @@ namespace Internal.JitInterface
                     resolvedConstraint = true;
                     pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM;
 
-                    exactType = constrainedType;
+                    exactType = directMethod.OwningType;
                 }
                 else if (method.Signature.IsStatic)
                 {
@@ -1470,6 +1484,12 @@ namespace Internal.JitInterface
 
                 pResult->nullInstanceCheck = resolvedCallVirt;
             }
+            else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction)
+            {
+                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && constrainedType != null);
+
+                ThrowHelper.ThrowBadImageFormatException();
+            }
             else if (targetMethod.Signature.IsStatic)
             {
                 // This should be an unresolved static virtual interface method call. Other static methods should
@@ -1481,9 +1501,17 @@ namespace Internal.JitInterface
                 TypeDesc runtimeDeterminedConstrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *pConstrainedResolvedToken);
                 MethodDesc runtimeDeterminedInterfaceMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
 
+                var constrainedCallInfo = new ConstrainedCallInfo(runtimeDeterminedConstrainedType, runtimeDeterminedInterfaceMethod);
+                var constrainedHelperId = ReadyToRunHelperId.ConstrainedDirectCall;
+
+                // Constant lookup doesn't make sense and we don't implement it. If we need constant lookup,
+                // the method wasn't implemented. Don't crash on it.
+                if (!_compilation.NeedsRuntimeLookup(constrainedHelperId, constrainedCallInfo))
+                    ThrowHelper.ThrowTypeLoadException(constrainedType);
+
                 ComputeLookup(ref pResolvedToken,
-                    new ConstrainedCallInfo(runtimeDeterminedConstrainedType, runtimeDeterminedInterfaceMethod),
-                    ReadyToRunHelperId.ConstrainedDirectCall,
+                    constrainedCallInfo,
+                    constrainedHelperId,
                     ref pResult->codePointerOrStubLookup);
 
                 targetIsFatFunctionPointer = true;
index a284d2b..d826d3f 100644 (file)
 
       <!-- NativeAOT specific -->
     <ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(TestBuildMode)' == 'nativeaot' and '$(RuntimeFlavor)' == 'coreclr'">
+        <ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/StaticVirtualMethods/NegativeTestCases/**">
+            <Issue>https://github.com/dotnet/runtimelab/issues/155: Compatible TypeLoadException for invalid inputs</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/StaticVirtualMethods/DiamondShape/svm_diamondshape_d/*">
+            <Issue>https://github.com/dotnet/runtime/issues/72589</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/StaticVirtualMethods/DiamondShape/svm_diamondshape_r/*">
+            <Issue>https://github.com/dotnet/runtime/issues/72589</Issue>
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/baseservices/callconvs/TestCallingConventions/*">
             <Issue>https://github.com/dotnet/runtimelab/issues/153</Issue>
         </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/Loader\classloader\MethodImpl\CovariantReturns\UnitTest\UnitTest_GVM\*" />
         <ExcludeList Include="$(XunitTestBinBase)/Loader\classloader\MethodImpl\CovariantReturns\UnitTest\UnitTestDelegates\*" />
         <ExcludeList Include="$(XunitTestBinBase)/Loader\classloader\MethodImpl\CovariantReturns\UnitTest\OverrideMoreDerivedReturn\*" />
-
-        <!-- Static virtual methods -->
-        <ExcludeList Include="$(XunitTestBinBase)/Loader\classloader\StaticVirtualMethods\**" />
     </ItemGroup>
 
     <!-- run.proj finds all the *.cmd/*.sh scripts in a test folder and creates corresponding test methods.
index 0d7db3e..736d93a 100644 (file)
@@ -47,6 +47,10 @@ public class Interfaces
         TestSimpleDynamicStaticVirtualMethods.Run();
         TestGenericDynamicStaticVirtualMethods.Run();
         TestVariantGenericDynamicStaticVirtualMethods.Run();
+        TestStaticDefaultMethodAmbiguity.Run();
+        TestMoreConstraints.Run();
+        TestSimpleNonGeneric.Run();
+        TestSimpleGeneric.Run();
 
         return Pass;
     }
@@ -1288,4 +1292,171 @@ public class Interfaces
         }
     }
 
+    class TestStaticDefaultMethodAmbiguity
+    {
+        class Atom1 { }
+        class Atom2 { }
+
+        interface IFoo<T>
+        {
+            static abstract (Type, Type) DisambiguateMe();
+        }
+
+        interface IBar<T, U> : IFoo<U>
+        {
+            static (Type, Type) IFoo<U>.DisambiguateMe() => (typeof(T), typeof(U));
+        }
+
+        struct GenericStruct<T, U> : IBar<T[], U>
+        {
+        }
+
+        static (Type, Type) CallTheCall<T, U>() where T : IFoo<U> => T.DisambiguateMe();
+        static (Type, Type) DelegateTheCall<T, U>() where T : IFoo<U>
+        {
+            Func<(Type, Type)> d = T.DisambiguateMe;
+            return d();
+        }
+
+        public static void Run()
+        {
+            {
+                var results = CallTheCall<GenericStruct<Atom1, Atom2>, Atom2>();
+                if (results.Item1 != typeof(Atom1[]) || results.Item2 != typeof(Atom2))
+                    throw new Exception();
+            }
+
+            {
+                var results = DelegateTheCall<GenericStruct<Atom1, Atom2>, Atom2>();
+                if (results.Item1 != typeof(Atom1[]) || results.Item2 != typeof(Atom2))
+                    throw new Exception();
+            }
+        }
+    }
+
+    class TestMoreConstraints
+    {
+        interface IFoo
+        {
+            void Frob();
+        }
+
+        struct GenericStruct<T> : IFoo
+        {
+            public int State;
+            public void Frob() => State++;
+        }
+
+        class GenericClass<T> : IFoo
+        {
+            public int State;
+            public void Frob() => State++;
+        }
+
+        static void DoFrob<T>(ref T theT, ref GenericStruct<T> theGenericStruct) where T : IFoo
+        {
+            theT.Frob();
+            theGenericStruct.Frob();
+
+            Action delT = theT.Frob;
+            delT();
+
+            Action delGenericStruct = theGenericStruct.Frob;
+            delGenericStruct();
+        }
+
+        public static void Run()
+        {
+            GenericStruct<object> s1 = default;
+            GenericStruct<GenericStruct<object>> s2 = default;
+
+            DoFrob(ref s1, ref s2);
+
+            if (s1.State != 1 || s2.State != 1)
+                throw new Exception();
+
+            var c1 = new GenericClass<object>();
+            GenericStruct<GenericClass<object>> c2 = default;
+
+            DoFrob(ref c1, ref c2);
+            if (c1.State != 2 || c2.State != 1)
+                throw new Exception();
+        }
+    }
+
+    class TestSimpleNonGeneric
+    {
+        interface IFoo
+        {
+            static abstract int GetCookie(int val);
+        }
+
+        interface IBar : IFoo
+        {
+            static int IFoo.GetCookie(int val) => 1234 + val;
+        }
+
+        class SimpleClass : IBar { }
+        struct SimpleStruct : IBar { }
+
+        static int Call<T>(int val) where T : IFoo => T.GetCookie(val);
+
+        static int CallIndirect<T>(int val) where T : IFoo
+        {
+            Func<int, int> del = T.GetCookie;
+            return del(val);
+        }
+
+        public static void Run()
+        {
+            if (Call<SimpleClass>(1) != 1235)
+                throw new Exception();
+            if (Call<SimpleStruct>(2) != 1236)
+                throw new Exception();
+            if (CallIndirect<SimpleClass>(1) != 1235)
+                throw new Exception();
+            if (CallIndirect<SimpleStruct>(2) != 1236)
+                throw new Exception();
+        }
+    }
+
+    class TestSimpleGeneric
+    {
+        interface IFoo
+        {
+            static abstract (int, Type) GetCookie(int val);
+        }
+
+        interface IBar<T> : IFoo
+        {
+            static (int, Type) IFoo.GetCookie(int val) => (1234 + val, typeof(IBar<T>));
+        }
+
+        class SimpleClass : IBar<Atom1> { }
+        struct SimpleStruct : IBar<Atom2> { }
+
+        static (int, Type) Call<T>(int val) where T : IFoo => T.GetCookie(val);
+
+        static (int, Type) CallIndirect<T>(int val) where T : IFoo
+        {
+            Func<int, (int, Type)> del = T.GetCookie;
+            return del(val);
+        }
+
+        class Atom1 { }
+        class Atom2 { }
+
+        public static void Run()
+        {
+            if (Call<SimpleClass>(1) != (1235, typeof(IBar<Atom1>)))
+                throw new Exception();
+            if (Call<SimpleStruct>(2) != (1236, typeof(IBar<Atom2>)))
+                throw new Exception();
+
+            if (CallIndirect<SimpleClass>(1) != (1235, typeof(IBar<Atom1>)))
+                throw new Exception();
+            if (CallIndirect<SimpleStruct>(2) != (1236, typeof(IBar<Atom2>)))
+                throw new Exception();
+        }
+    }
 }