-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Add support for static default interface methods #72661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ | |
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| using Internal.IL; | ||
| using Internal.TypeSystem; | ||
|
|
||
|
|
@@ -410,14 +412,19 @@ private static bool ImplementsEquivalentInterface(this TypeDesc type, TypeDesc i | |
| 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"/>. | ||
| /// The ability to resolve constraint methods is affected by the degree of code sharing we are performing | ||
| /// for generic code. | ||
| /// </summary> | ||
| /// <returns>The resolved method or null if the constraint couldn't be resolved.</returns> | ||
| public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrainedType, TypeDesc interfaceType, MethodDesc interfaceMethod, out bool forceRuntimeLookup) | ||
| public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrainedType, TypeDesc interfaceType, MethodDesc interfaceMethod, out bool forceRuntimeLookup, ref DefaultInterfaceMethodResolution staticResolution) | ||
| { | ||
| forceRuntimeLookup = false; | ||
|
|
||
|
|
@@ -461,24 +468,23 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai | |
| 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 = context.GetMethodForInstantiatedType( | ||
| potentialInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)potentialInterfaceType); | ||
| } | ||
|
|
||
| if (isStaticVirtualMethod) | ||
| { | ||
| method = canonType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(potentialInterfaceMethod); | ||
| } | ||
| else | ||
| { | ||
| method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod); | ||
| } | ||
| method = canonType.ResolveInterfaceMethodToVirtualMethodOnType(potentialInterfaceMethod); | ||
|
|
||
| // See code:#TryResolveConstraintMethodApprox_DoNotReturnParentMethod | ||
| if (!isStaticVirtualMethod && method != null && !method.OwningType.IsValueType) | ||
| if (method != null && !method.OwningType.IsValueType) | ||
| { | ||
| // We explicitly wouldn't want to abort if we found a default implementation. | ||
| // The above resolution doesn't consider the default methods. | ||
|
|
@@ -512,6 +518,12 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai | |
| if (isStaticVirtualMethod) | ||
| { | ||
| method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod); | ||
| if (method == null) | ||
| { | ||
| staticResolution = constrainedType.ResolveVariantInterfaceMethodToDefaultImplementationOnType(exactInterfaceMethod, out method); | ||
| if (staticResolution != DefaultInterfaceMethodResolution.DefaultImplementation) | ||
| method = null; | ||
| } | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -542,6 +554,12 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai | |
| if (isStaticVirtualMethod) | ||
| { | ||
| method = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(exactInterfaceMethod); | ||
| if (method == null) | ||
| { | ||
| staticResolution = constrainedType.ResolveVariantInterfaceMethodToDefaultImplementationOnType(exactInterfaceMethod, out method); | ||
| if (staticResolution != DefaultInterfaceMethodResolution.DefaultImplementation) | ||
| method = null; | ||
| } | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -562,16 +580,6 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai | |
| method = null; | ||
| } | ||
|
|
||
| // Default implementation logic, which only kicks in for default implementations when looking up on an exact interface target | ||
| if (isStaticVirtualMethod && method == null && !genInterfaceMethod.IsAbstract && !constrainedType.IsCanonicalSubtype(CanonicalFormKind.Any)) | ||
| { | ||
| MethodDesc exactInterfaceMethod = genInterfaceMethod; | ||
| if (genInterfaceMethod.OwningType != interfaceType) | ||
| exactInterfaceMethod = context.GetMethodForInstantiatedType( | ||
| genInterfaceMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceType); | ||
| method = exactInterfaceMethod; | ||
| } | ||
|
|
||
| if (method == null) | ||
| { | ||
| // Fall back to VSD | ||
|
|
@@ -594,6 +602,16 @@ public static MethodDesc TryResolveConstraintMethodApprox(this TypeDesc constrai | |
| 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. | ||
|
Comment on lines
+605
to
+607
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went down this rabbit hole and it lead to stuff like: I really didn't like it and had trouble getting it to work. The problem is that RyuJIT doesn't operate on runtime-determined things and we can't even properly resolve such constrained calls (the support for that was never needed in the type system because only universal shared code needs to resolve constrained calls on runtime determined types otherwise and that was always supported by the STS type system in NUTC). |
||
| if (method.IsCanonicalMethod(CanonicalFormKind.Any) | ||
| && !method.OwningType.IsValueType) | ||
| { | ||
| Debug.Assert(method.Signature.IsStatic); | ||
| return null; | ||
| } | ||
|
|
||
| Debug.Assert(method != null); | ||
|
|
||
| return method; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this
usingreally needed?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, Unsafe.NullRef so that users who don't need static method support av if they accidentally pass static methods.