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.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Runtime.CompilerServices;
+
using Internal.IL;
using Internal.TypeSystem;
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"/>.
/// 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;
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)
{
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.
if (isStaticVirtualMethod)
{
method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
+ if (method == null)
+ {
+ staticResolution = constrainedType.ResolveVariantInterfaceMethodToDefaultImplementationOnType(exactInterfaceMethod, out method);
+ if (staticResolution != DefaultInterfaceMethodResolution.DefaultImplementation)
+ method = null;
+ }
}
else
{
if (isStaticVirtualMethod)
{
method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod);
+ if (method == null)
+ {
+ staticResolution = constrainedType.ResolveVariantInterfaceMethodToDefaultImplementationOnType(exactInterfaceMethod, out method);
+ if (staticResolution != DefaultInterfaceMethodResolution.DefaultImplementation)
+ method = null;
+ }
}
else
{
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
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;
bool diamondCase = false;
impl = null;
+ MethodDesc interfaceMethodDefinition = interfaceMethod.GetMethodDefinition();
DefType[] consideredInterfaces;
if (!currentType.IsInterface)
{
if (mostSpecificInterface == null && !interfaceMethod.IsAbstract)
{
mostSpecificInterface = runtimeInterface;
- impl = interfaceMethod;
+ impl = interfaceMethodDefinition;
}
}
else if (Array.IndexOf(runtimeInterface.RuntimeInterfaces, interfaceMethodOwningType) != -1)
{
foreach (MethodImplRecord implRecord in possibleImpls)
{
- if (implRecord.Decl == interfaceMethod)
+ if (implRecord.Decl == interfaceMethodDefinition)
{
// This interface provides a default implementation.
// Is it also most specific?
}
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);
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>
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>
{
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
{
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
bool resolvedConstraint = false;
bool forceUseRuntimeLookup = false;
+ DefaultInterfaceMethodResolution staticResolution = default;
MethodDesc methodAfterConstraintResolution = method;
if (_constrained != null)
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
|| methodAfterConstraintResolution.Signature.IsStatic);
resolvedConstraint = true;
- exactType = constrained;
+ exactType = directMethod.OwningType;
}
else if (method.Signature.IsStatic)
{
}
else
{
- _dependencies.Add(_factory.FatFunctionPointer(runtimeDeterminedMethod), reason);
+ _dependencies.Add(_factory.FatFunctionPointer(targetMethod), reason);
}
}
else if (directCall)
_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
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)
{
public MethodCodeNode(MethodDesc method)
{
Debug.Assert(!method.IsAbstract);
+ Debug.Assert(!method.IsGenericMethodDefinition && !method.OwningType.IsGenericDefinition);
Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method);
_method = method;
}
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);
// 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
bool forceUseRuntimeLookup = false;
bool targetIsFatFunctionPointer = false;
bool useFatCallTransform = false;
+ DefaultInterfaceMethodResolution staticResolution = default;
MethodDesc methodAfterConstraintResolution = method;
if (constrainedType == null)
// 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
resolvedConstraint = true;
pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM;
- exactType = constrainedType;
+ exactType = directMethod.OwningType;
}
else if (method.Signature.IsStatic)
{
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
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;
<!-- 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.
TestSimpleDynamicStaticVirtualMethods.Run();
TestGenericDynamicStaticVirtualMethods.Run();
TestVariantGenericDynamicStaticVirtualMethods.Run();
+ TestStaticDefaultMethodAmbiguity.Run();
+ TestMoreConstraints.Run();
+ TestSimpleNonGeneric.Run();
+ TestSimpleGeneric.Run();
return Pass;
}
}
}
+ 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();
+ }
+ }
}