Fix for runtime constraint checking for default interface methods (#34889)
authorManish Godse <61718172+mangod9@users.noreply.github.com>
Tue, 14 Apr 2020 22:04:30 +0000 (15:04 -0700)
committerGitHub <noreply@github.com>
Tue, 14 Apr 2020 22:04:30 +0000 (15:04 -0700)
* pass in InstantiationContext to check constraints.

* add a test

src/coreclr/src/vm/genmeth.cpp
src/coreclr/tests/src/Regressions/coreclr/GitHub_1674/GitHub_1674.csproj [new file with mode: 0644]
src/coreclr/tests/src/Regressions/coreclr/GitHub_1674/TestConstraintOnDefaultInterfaceMethod.cs [new file with mode: 0644]

index 2b1e19a..1e6ca14 100644 (file)
@@ -1676,6 +1676,7 @@ BOOL MethodDesc::SatisfiesMethodConstraints(TypeHandle thParent, BOOL fThrowIfNo
     //NB: according to the constructor's signature, thParent should be the declaring type,
     // but the code appears to admit derived types too.
     SigTypeContext typeContext(this,thParent);
+    InstantiationContext instContext(&typeContext, NULL);
 
     for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
     {
@@ -1688,7 +1689,8 @@ BOOL MethodDesc::SatisfiesMethodConstraints(TypeHandle thParent, BOOL fThrowIfNo
 
         tyvar->LoadConstraints(); //TODO: is this necessary for anything but the typical method?
 
-        if (!tyvar->SatisfiesConstraints(&typeContext,thArg))
+        // Pass in the InstatiationContext so contraints can be correctly evaluated
+        if (!tyvar->SatisfiesConstraints(&typeContext,thArg, &instContext))
         {
             if (fThrowIfNotSatisfied)
             {
diff --git a/src/coreclr/tests/src/Regressions/coreclr/GitHub_1674/GitHub_1674.csproj b/src/coreclr/tests/src/Regressions/coreclr/GitHub_1674/GitHub_1674.csproj
new file mode 100644 (file)
index 0000000..a1ab093
--- /dev/null
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="TestConstraintOnDefaultInterfaceMethod.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/coreclr/tests/src/Regressions/coreclr/GitHub_1674/TestConstraintOnDefaultInterfaceMethod.cs b/src/coreclr/tests/src/Regressions/coreclr/GitHub_1674/TestConstraintOnDefaultInterfaceMethod.cs
new file mode 100644 (file)
index 0000000..39ce162
--- /dev/null
@@ -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.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace TestConstraint
+{
+    class TestConstraintOnDefaultInterfaceMethod
+    {
+        static int Main()
+        {
+            IAuditTrail<IRaftLogEntry> auditTrail = new AuditTrail();
+            // This should not fail per C# specs here:
+            // https://github.com/dotnet/csharplang/blob/master/spec/classes.md#type-parameter-constraints
+            auditTrail.AppendAsync<EmptyLogEntry>(new EmptyLogEntry(), CancellationToken.None);
+
+            // This should work since int always meets the constraint of IBuggy<T1>.Foo<T2> where T2: T1
+            ((IBuggy<int>)new Worky()).Foo<int>();
+            // This should work since Object always meets the constraint of IBuggy<T1>.Foo<T2> where T2: T1
+            ((IBuggy<object>)new Worky2()).Foo<string>();
+            // This should not throw since Open meets the constraint of IBuggy<T1>.Foo<T2> where T2: T1
+            ((IBuggy<Open>)new Buggy()).Foo<Open>();
+
+            return 100;
+
+        }
+
+        interface IBuggy<T1>
+        {
+            public void Foo<T2>() where T2 : T1 => Console.WriteLine($"Works for type: {typeof(T1)}");
+        }
+        public class Worky : IBuggy<int> { }
+        public class Worky2 : IBuggy<object> { }
+        public class Buggy : IBuggy<Open> { }
+        public class Open { }
+
+
+        private interface ILogEntry
+        {
+            long Index { get; }
+        }
+
+        private interface IAuditTrail<TRecord>
+            where TRecord : class, ILogEntry
+        {
+            ValueTask AppendAsync<TRecordImpl>(TRecordImpl impl, CancellationToken token)
+                where TRecordImpl : notnull, TRecord {Console.WriteLine("works.."); return new ValueTask();}
+        }
+
+        private interface IRaftLogEntry : ILogEntry
+        {
+            long Term { get; }
+        }
+
+        private sealed class AuditTrail : IAuditTrail<IRaftLogEntry>
+        {
+        }
+
+        private readonly struct EmptyLogEntry : IRaftLogEntry
+        {
+            long IRaftLogEntry.Term => 0L;
+
+            long ILogEntry.Index => 0L;
+        }
+    }
+}