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.
{
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;
}
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.
// 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;
}
}
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);
+ }
}
}
}
//#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;
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
// 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;
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;
} 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;
+ }
}
}
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);
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>
case ReadyToRunHelperId.FieldHandle:
return ((FieldDesc)targetOfLookup).OwningType.IsRuntimeDeterminedSubtype;
+ case ReadyToRunHelperId.ConstrainedDirectCall:
+ return ((ConstrainedCallInfo)targetOfLookup).Method.IsRuntimeDeterminedExactMethod
+ || ((ConstrainedCallInfo)targetOfLookup).ConstrainedType.IsRuntimeDeterminedSubtype;
+
default:
throw new NotImplementedException();
}
}
}
}
+
+ public sealed class ConstrainedCallInfo
+ {
+ public readonly TypeDesc ConstrainedType;
+ public readonly MethodDesc Method;
+ public ConstrainedCallInfo(TypeDesc constrainedType, MethodDesc method)
+ => (ConstrainedType, Method) = (constrainedType, method);
+ }
}
{
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.
{
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);
}
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)
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);
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();
}
DefaultConstructor,
TypeHandleForCasting,
ObjectAllocator,
+ ConstrainedDirectCall,
}
public partial class ReadyToRunHelperNode : AssemblyStubNode, INodeWithDebugInfo
case ReadyToRunHelperId.DefaultConstructor:
case ReadyToRunHelperId.ObjectAllocator:
case ReadyToRunHelperId.TypeHandleForCasting:
+ case ReadyToRunHelperId.ConstrainedDirectCall:
{
EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly);
encoder.EmitRET();
case ReadyToRunHelperId.DefaultConstructor:
case ReadyToRunHelperId.ObjectAllocator:
case ReadyToRunHelperId.TypeHandleForCasting:
+ case ReadyToRunHelperId.ConstrainedDirectCall:
{
EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly);
encoder.EmitRET();
case ReadyToRunHelperId.DefaultConstructor:
case ReadyToRunHelperId.ObjectAllocator:
case ReadyToRunHelperId.TypeHandleForCasting:
+ case ReadyToRunHelperId.ConstrainedDirectCall:
{
EmitDictionaryLookup(factory, ref encoder, contextRegister, encoder.TargetRegister.Result, _lookupSignature, relocsOnly);
encoder.EmitRET();
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;
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.
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)
{
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);
_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
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);
}
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;
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)
{
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.
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);
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
public sealed class IsByRefLikeAttribute : Attribute
{
}
+
+ public static class RuntimeFeature
+ {
+ public const string VirtualStaticsInInterfaces = nameof(VirtualStaticsInInterfaces);
+ }
}
--- /dev/null
+// 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 { }
+}
<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" />
--- /dev/null
+// 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);
+ }
+ }
+}
TestSharedIntefaceMethods.Run();
TestCovariantReturns.Run();
TestDynamicInterfaceCastable.Run();
+ TestStaticInterfaceMethodsAnalysis.Run();
+ TestStaticInterfaceMethods.Run();
return Pass;
}
}
}
}
+
+ 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)");
+ }
+ }
}