Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/coreclr/vm/method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,16 @@ DWORD MethodDesc::GetAttrs() const
_ASSERTE(!"If this ever fires, then this method should return HRESULT");
return 0;
}

if (IsReturnDroppingThunk())
{
// A return-dropping thunk is synthesized by the runtime and always has an implementation -
// it calls the ordinary async variant virtually and drops the result.
// The metadata method that the thunk is derived from may be abstract (i.e. when the covariant
// override that needs the thunk is abstract), but the thunk itself never is.
dwAttributes &= ~mdAbstract;
Comment thread
VSadov marked this conversation as resolved.
}

return dwAttributes;
}

Expand Down
16 changes: 15 additions & 1 deletion src/coreclr/vm/methodtablebuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,21 @@ class MethodTableBuilder
// Returns the metadata declaration attributes for this method.
DWORD
GetDeclAttrs() const
{ LIMITED_METHOD_CONTRACT; return m_dwDeclAttrs; }
{
LIMITED_METHOD_CONTRACT;

DWORD dwDeclAttrs = m_dwDeclAttrs;
if (hasAsyncFlags(m_asyncMethodFlags, AsyncMethodFlags::ReturnDroppingThunk))
{
// A return-dropping thunk is synthesized by the runtime and always has an implementation -
// it calls the ordinary async variant virtually and drops the result.
// The metadata method that the thunk is derived from may be abstract (i.e. when the covariant
// override that needs the thunk is abstract), but the thunk itself never is.
dwDeclAttrs &= ~mdAbstract;
}

return dwDeclAttrs;
}

//-----------------------------------------------------------------------------------------
// Returns the metadata implementation attributes for this method.
Expand Down
78 changes: 78 additions & 0 deletions src/tests/async/covariant-return/covariant-returns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
Expand Down Expand Up @@ -294,3 +295,80 @@ public static void TestAsyncInterfaceGenericMethod()
}
}
}

namespace AbstractCovariantReturn
{
// A covariant Task -> Task<T> override may be abstract.
// The runtime still has to provide an async variant that matches the void-returning
// async variant of the base, otherwise concrete derived types cannot be loaded.
public class Program
{
internal static string Trace;

[Fact]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "This test intentionally exercises Assembly.GetTypes().")]
public static void TestAssemblyGetTypes()
{
_ = typeof(Program).Assembly.GetTypes();
}

[Fact]
public static void TestAbstractCovariantOverride()
{
Trace = null;
Base b = new Derived();
CallBase(b).GetAwaiter().GetResult();
Assert.Equal("Derived.M1;", Trace);

Trace = null;
Assert.Equal(42, CallMid(new Derived()).GetAwaiter().GetResult());
Assert.Equal("Derived.M1;", Trace);
}

[Fact]
public static void TestAbstractCovariantOverrideProperty()
{
Base b = new Derived();
Assert.Equal(42, CallBaseProperty(b).GetAwaiter().GetResult());
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static async Task CallBase(Base b) => await b.M1();

[MethodImpl(MethodImplOptions.NoInlining)]
private static async Task<int> CallMid(Mid<int> m) => await m.M1();

[MethodImpl(MethodImplOptions.NoInlining)]
private static async Task<int> CallBaseProperty(Base b)
{
await b.Task;
return await ((Mid<int>)b).Task;
}

public abstract class Base
{
public abstract Task M1();

public abstract Task Task { get; }
}

public abstract class Mid<T> : Base
{
public abstract override Task<T> M1();

public abstract override Task<T> Task { get; }
}

public sealed class Derived : Mid<int>
{
public override Task<int> M1()
{
Trace += "Derived.M1;";
return System.Threading.Tasks.Task.FromResult(42);
}

public override Task<int> Task => System.Threading.Tasks.Task.FromResult(42);
}
}
}
Loading