From 44f91892f666f74f5937f50a3f3bb7605a487ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 2 Mar 2022 15:32:15 +0100 Subject: [PATCH] Add support for static virtual methods 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. --- .../tools/Common/Compiler/TypeExtensions.cs | 35 +++- .../Common/MetadataVirtualMethodAlgorithm.cs | 117 +++++++++++ .../TypeSystem/Common/TypeSystemHelpers.cs | 10 + .../Common/VirtualMethodAlgorithm.cs | 4 + .../Compiler/Compilation.cs | 12 ++ .../Compiler/DependencyAnalysis/EETypeNode.cs | 4 + .../DependencyAnalysis/GenericLookupResult.cs | 35 +++- .../InterfaceDispatchMapNode.cs | 4 + .../ReadyToRunGenericHelperNode.cs | 5 + .../ReadyToRunHelperNode.cs | 1 + .../ARMReadyToRunGenericHelperNode.cs | 1 + .../ARM64ReadyToRunGenericHelperNode.cs | 1 + .../X64ReadyToRunGenericHelperNode.cs | 1 + .../DependencyAnalysis/VTableSliceNode.cs | 4 + .../IL/ILImporter.Scanner.cs | 30 ++- .../JitInterface/CorInfoImpl.RyuJit.cs | 45 ++++- .../CoreTestAssembly/Platform.cs | 5 + .../VirtualStaticInterfaceMethods.cs | 65 +++++++ .../ILCompiler.TypeSystem.Tests.csproj | 1 + .../VirtualStaticInterfaceMethodTests.cs | 69 +++++++ .../SmokeTests/UnitTests/Interfaces.cs | 181 ++++++++++++++++++ 21 files changed, 607 insertions(+), 23 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/VirtualStaticInterfaceMethods.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/VirtualStaticInterfaceMethodTests.cs diff --git a/src/coreclr/tools/Common/Compiler/TypeExtensions.cs b/src/coreclr/tools/Common/Compiler/TypeExtensions.cs index df3282e24ce80..3eb98495bc0f1 100644 --- a/src/coreclr/tools/Common/Compiler/TypeExtensions.cs +++ b/src/coreclr/tools/Common/Compiler/TypeExtensions.cs @@ -421,9 +421,11 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai { 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 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai 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 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai // 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 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai 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 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai //#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; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs index 8dbd9a265b7cc..eb55228d3e5d9 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataVirtualMethodAlgorithm.cs @@ -572,6 +572,16 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me 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 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me // 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 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc 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 @@ public static IEnumerable EnumAllVirtualSlots(MetadataType type) } while (type != null); } } + + /// + /// Try to resolve a given virtual static interface method on a given constrained type and its base types. + /// + /// Interface method to resolve + /// Type to attempt virtual static method resolution on + /// MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used) + 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; + } + + /// + /// Try to resolve a given virtual static interface method on a given constrained type and its base types. + /// + /// Interface method to resolve + /// Type to attempt virtual static method resolution on + /// MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used) + 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; + } + + /// + /// Try to resolve a given virtual static interface method on a given constrained type and return the resolved method or null when not found. + /// + /// Type to attempt method resolution on + /// Method to resolve + /// MethodDesc of the resolved method or null when not found (runtime lookup must be used) + 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; + } } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs index f36f45f00f5cc..ccddbb35dd4bb 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemHelpers.cs @@ -205,6 +205,16 @@ public static MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(this 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); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs index e13e61d3862da..804ab398b3ac4 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/VirtualMethodAlgorithm.cs @@ -24,6 +24,10 @@ public abstract class VirtualMethodAlgorithm 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); /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs index d19b60b104c24..b1a75cc774be1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs @@ -252,6 +252,10 @@ public bool NeedsRuntimeLookup(ReadyToRunHelperId lookupKind, object targetOfLoo 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 @@ public IEnumerable ConstructedEETypes } } } + + public sealed class ConstrainedCallInfo + { + public readonly TypeDesc ConstrainedType; + public readonly MethodDesc Method; + public ConstrainedCallInfo(TypeDesc constrainedType, MethodDesc method) + => (ConstrainedType, Method) = (constrainedType, method); + } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index 6aef1df4d46ff..9b3975c51e7d9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -162,6 +162,10 @@ public override bool InterestingForDynamicDependencyAnalysis { 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. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index 754621f41bd7b..3da65b1530ed2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -1431,20 +1431,36 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo { 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 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde 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) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs index 201fe5a74a051..151ab641a091f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs @@ -111,6 +111,10 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact 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); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index 829a26e212a0c..a12f2db56ce2c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -81,6 +81,11 @@ public static GenericLookupResult GetLookupSignature(NodeFactory factory, ReadyT 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(); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs index 77c17f1804ca5..62d120bf46c47 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs @@ -40,6 +40,7 @@ public enum ReadyToRunHelperId DefaultConstructor, TypeHandleForCasting, ObjectAllocator, + ConstrainedDirectCall, } public partial class ReadyToRunHelperNode : AssemblyStubNode, INodeWithDebugInfo diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs index bb12fb1696371..2cee3ec3cf97e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs @@ -197,6 +197,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARMEmitter enco case ReadyToRunHelperId.DefaultConstructor: case ReadyToRunHelperId.ObjectAllocator: case ReadyToRunHelperId.TypeHandleForCasting: + case ReadyToRunHelperId.ConstrainedDirectCall: { EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); encoder.EmitRET(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs index 8451b672dffce..5857cee6588eb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs @@ -198,6 +198,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARM64Emitter en case ReadyToRunHelperId.DefaultConstructor: case ReadyToRunHelperId.ObjectAllocator: case ReadyToRunHelperId.TypeHandleForCasting: + case ReadyToRunHelperId.ConstrainedDirectCall: { EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); encoder.EmitRET(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs index 2ec2b998afe76..7f9d0645799f9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs @@ -221,6 +221,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref X64Emitter enco case ReadyToRunHelperId.DefaultConstructor: case ReadyToRunHelperId.ObjectAllocator: case ReadyToRunHelperId.TypeHandleForCasting: + case ReadyToRunHelperId.ConstrainedDirectCall: { EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); encoder.EmitRET(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs index 54c1fb215ddc8..0e91c5bd552de 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs @@ -225,6 +225,10 @@ public override IEnumerable GetConditionalStaticDep 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; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index f662d76c3e41f..2799d455f0cdd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -456,6 +456,11 @@ private void ImportCall(ILOpcode opcode, int token) 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 @@ private void ImportCall(ILOpcode opcode, int token) 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 @@ private void ImportCall(ILOpcode opcode, int token) 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 @@ private void ImportCall(ILOpcode opcode, int token) _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 diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 00c3ff26cf076..6c231eff007cd 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -1122,7 +1122,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO 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 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO 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 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO 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 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO 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 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO 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 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO 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 diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs index e5f35029d882d..ac8b380d19416 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs @@ -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 index 0000000000000..55f104b5fbdda --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/VirtualStaticInterfaceMethods.cs @@ -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 + { + static abstract string WhichMethod(T param); + } + + class SimpleVariant : IVariant + { + public static string WhichMethod(Base b) => throw null; + } + + class SimpleVariantTwice : IVariant, IVariant + { + public static string WhichMethod(Base b) => throw null; + public static string WhichMethod(Mid b) => throw null; + } + + class VariantWithInheritanceBase : IVariant + { + public static string WhichMethod(Mid b) => throw null; + } + + class VariantWithInheritanceDerived : VariantWithInheritanceBase, IVariant + { + public static string WhichMethod(Base b) => throw null; + } + + class GenericVariantWithInheritanceBase : IVariant + { + public static string WhichMethod(T b) => throw null; + } + + class GenericVariantWithInheritanceDerived : GenericVariantWithInheritanceBase, IVariant + { + public static new string WhichMethod(T b) => throw null; + } + + class GenericVariantWithHiddenBase : IVariant + { + public static string WhichMethod(Mid b) => throw null; + } + + class GenericVariantWithHiddenDerived : GenericVariantWithHiddenBase, IVariant + { + public static string WhichMethod(T b) => throw null; + } + + class Base { } + class Mid : Base { } + class Derived : Mid { } +} diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj index 767680f651aac..d23f31ce87aa7 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj @@ -69,6 +69,7 @@ + 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 index 0000000000000..3553f8adb332e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/VirtualStaticInterfaceMethodTests.cs @@ -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 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); + } + } +} diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs index 8ae4a36800b3e..a6aa3b9327e62 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs @@ -41,6 +41,8 @@ public static int Run() TestSharedIntefaceMethods.Run(); TestCovariantReturns.Run(); TestDynamicInterfaceCastable.Run(); + TestStaticInterfaceMethodsAnalysis.Run(); + TestStaticInterfaceMethods.Run(); return Pass; } @@ -808,4 +810,183 @@ public static void Run() } } } + + class TestStaticInterfaceMethodsAnalysis + { + interface IFoo + { + static abstract object Frob(); + } + + class Foo : IFoo + { + static object IFoo.Frob() => new Gen(); + } + + static object CallFrob() where T : IFoo => T.Frob(); + + class Gen { } + struct Struct1 { } + struct Struct2 { } + + public static void Run() + { + CallFrob>(); + Console.WriteLine(typeof(Foo)); + + CallFrob>(); + Console.WriteLine(typeof(Foo)); + } + } + + class TestStaticInterfaceMethods + { + interface ISimple + { + static abstract string GetCookie(); + static abstract string GetCookieGeneric(); + } + + class SimpleClass : ISimple + { + public static string GetCookie() => "SimpleClass"; + public static string GetCookieGeneric() => $"SimpleClass.GetCookieGeneric<{typeof(T).Name}>"; + } + + struct SimpleStruct : ISimple + { + public static string GetCookie() => "SimpleStruct"; + public static string GetCookieGeneric() => $"SimpleStruct.GetCookieGeneric<{typeof(T).Name}>"; + } + + struct SimpleGenericStruct : ISimple + { + public static string GetCookie() => $"SimpleGenericStruct<{typeof(T).Name}>"; + public static string GetCookieGeneric() => $"SimpleGenericStruct<{typeof(T).Name}>.GetCookieGeneric<{typeof(U).Name}>"; + } + + class SimpleGenericClass : ISimple + { + public static string GetCookie() => $"SimpleGenericClass<{typeof(T).Name}>"; + public static string GetCookieGeneric() => $"SimpleGenericClass<{typeof(T).Name}>.GetCookieGeneric<{typeof(U).Name}>"; + } + + interface IVariant + { + static abstract string WhichMethod(T param); + } + + class SimpleVariant : IVariant + { + public static string WhichMethod(Base b) => "SimpleVariant.WhichMethod(Base)"; + } + + class SimpleVariantTwice : IVariant, IVariant + { + public static string WhichMethod(Base b) => "SimpleVariantTwice.WhichMethod(Base)"; + public static string WhichMethod(Mid b) => "SimpleVariantTwice.WhichMethod(Mid)"; + } + + class VariantWithInheritanceBase : IVariant + { + public static string WhichMethod(Mid b) => "VariantWithInheritanceBase.WhichMethod(Mid)"; + } + + class VariantWithInheritanceDerived : VariantWithInheritanceBase, IVariant + { + public static string WhichMethod(Base b) => "VariantWithInheritanceDerived.WhichMethod(Base)"; + } + + class GenericVariantWithInheritanceBase : IVariant + { + public static string WhichMethod(T b) => "GenericVariantWithInheritanceBase.WhichMethod(T)"; + } + + class GenericVariantWithInheritanceDerived : GenericVariantWithInheritanceBase, IVariant + { + public static new string WhichMethod(T b) => $"GenericVariantWithInheritanceDerived.WhichMethod({typeof(T).Name})"; + } + + class GenericVariantWithHiddenBase : IVariant + { + public static string WhichMethod(Mid b) => "GenericVariantWithHiddenBase.WhichMethod(Mid)"; + } + + class GenericVariantWithHiddenDerived : GenericVariantWithHiddenBase, IVariant + { + public static string WhichMethod(T b) => $"GenericVariantWithHiddenDerived.WhichMethod({typeof(T).Name})"; + } + + struct Struct { } + class Base { } + class Mid : Base { } + class Derived : Mid { } + + + static void TestSimpleInterface(string expected) where T : ISimple + { + string actual = T.GetCookie(); + if (actual != expected) + { + throw new Exception($"{actual} != {expected}"); + } + } + + static void TestSimpleInterfaceWithGenericMethod(string expected) where T : ISimple + { + string actual = T.GetCookieGeneric(); + if (actual != expected) + { + throw new Exception($"{actual} != {expected}"); + } + } + + static void TestVariantInterface(string expected) where T : IVariant + { + string actual = T.WhichMethod(default); + if (actual != expected) + { + throw new Exception($"{actual} != {expected}"); + } + } + + public static void Run() + { + TestSimpleInterface("SimpleClass"); + TestSimpleInterface("SimpleStruct"); + + TestSimpleInterface>("SimpleGenericClass"); + TestSimpleInterface>("SimpleGenericStruct"); + + TestSimpleInterfaceWithGenericMethod("SimpleClass.GetCookieGeneric"); + TestSimpleInterfaceWithGenericMethod("SimpleStruct.GetCookieGeneric"); + TestSimpleInterfaceWithGenericMethod("SimpleClass.GetCookieGeneric"); + TestSimpleInterfaceWithGenericMethod("SimpleStruct.GetCookieGeneric"); + + TestSimpleInterfaceWithGenericMethod, Base>("SimpleGenericClass.GetCookieGeneric"); + TestSimpleInterfaceWithGenericMethod, Base>("SimpleGenericStruct.GetCookieGeneric"); + TestSimpleInterfaceWithGenericMethod, Struct>("SimpleGenericClass.GetCookieGeneric"); + TestSimpleInterfaceWithGenericMethod, Struct>("SimpleGenericStruct.GetCookieGeneric"); + + TestVariantInterface("SimpleVariant.WhichMethod(Base)"); + TestVariantInterface("SimpleVariant.WhichMethod(Base)"); + + TestVariantInterface("SimpleVariantTwice.WhichMethod(Base)"); + TestVariantInterface("SimpleVariantTwice.WhichMethod(Mid)"); + TestVariantInterface("SimpleVariantTwice.WhichMethod(Base)"); + + TestVariantInterface("VariantWithInheritanceDerived.WhichMethod(Base)"); + TestVariantInterface("VariantWithInheritanceDerived.WhichMethod(Base)"); + TestVariantInterface("VariantWithInheritanceDerived.WhichMethod(Base)"); + + TestVariantInterface, Base>("GenericVariantWithInheritanceDerived.WhichMethod(Base)"); + TestVariantInterface, Mid>("GenericVariantWithInheritanceDerived.WhichMethod(Base)"); + TestVariantInterface, Mid>("GenericVariantWithInheritanceDerived.WhichMethod(Mid)"); + + TestVariantInterface, Base>("GenericVariantWithHiddenDerived.WhichMethod(Base)"); + TestVariantInterface, Mid>("GenericVariantWithHiddenDerived.WhichMethod(Base)"); + TestVariantInterface, Mid>("GenericVariantWithHiddenDerived.WhichMethod(Mid)"); + TestVariantInterface, Mid>("GenericVariantWithHiddenBase.WhichMethod(Mid)"); + } + } }