diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index e3d9b05248fcc..239f79f174564 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -1756,18 +1756,22 @@ internal override System.Reflection.MethodImplAttributes ImplementationAttribute result |= (System.Reflection.MethodImplAttributes.Runtime | System.Reflection.MethodImplAttributes.InternalCall); } - if (this.IsAsync && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this)) - { - // https://github.com/dotnet/roslyn/issues/79792: Use real value from MethodImplAttributes when available - // When a method is emitted using runtime async, we add MethodImplAttributes.Async to indicate to the - // runtime to generate the state machine - result |= (System.Reflection.MethodImplAttributes)0x2000; - } + AddAsyncImplAttributeIfNeeded(ref result); return result; } } + protected void AddAsyncImplAttributeIfNeeded(ref System.Reflection.MethodImplAttributes result) + { + if (this.IsAsync && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this)) + { + // When a method is emitted using runtime async, we add MethodImplAttributes.Async to indicate to the + // runtime to generate the state machine + result |= System.Reflection.MethodImplAttributes.Async; + } + } + internal override int TryGetOverloadResolutionPriority() => GetEarlyDecodedWellKnownAttributeData()?.OverloadResolutionPriority ?? 0; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs index 971dea49f7b2d..7c2bba9f9b815 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs @@ -125,7 +125,12 @@ public override string Name internal override System.Reflection.MethodImplAttributes ImplementationAttributes { - get { return default(System.Reflection.MethodImplAttributes); } + get + { + var attributes = default(System.Reflection.MethodImplAttributes); + AddAsyncImplAttributeIfNeeded(ref attributes); + return attributes; + } } public override ImmutableArray TypeParameters diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs index 8e6c997573354..aae7c293df18c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs @@ -23,9 +23,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen [CompilerTrait(CompilerFeature.Async)] public class CodeGenAsyncTests : EmitMetadataTestBase { - // https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible - private const MethodImplAttributes MethodImplOptionsAsync = (MethodImplAttributes)0x2000; - internal static string ExpectedOutput(string output, bool isRuntimeAsync = false) { return ExecutionConditionUtil.IsMonoOrCoreClr @@ -332,7 +329,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Equal(["<>c"], test.GetTypeMembers().SelectAsArray(t => t.Name)); } } @@ -396,7 +393,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); Assert.Empty(test.GetTypeMembers()); } } @@ -459,7 +456,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Equal(["<>c"], test.GetTypeMembers().SelectAsArray(t => t.Name)); } } @@ -512,7 +509,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Empty(test.GetTypeMembers()); } } @@ -632,7 +629,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); if (!useValueTask && useGeneric) { AssertEx.Equal(["<>c"], test.GetTypeMembers().SelectAsArray(t => t.Name)); @@ -767,7 +764,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Empty(test.GetTypeMembers()); } } @@ -887,7 +884,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Empty(test.GetTypeMembers()); } } @@ -1019,7 +1016,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Empty(test.GetTypeMembers()); } } @@ -1130,7 +1127,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Empty(test.GetTypeMembers()); } } @@ -1238,7 +1235,7 @@ void verify(ModuleSymbol module) { var test = module.ContainingAssembly.GetTypeByMetadataName("Test"); var f = test.GetMethod("F"); - Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); AssertEx.Empty(test.GetTypeMembers()); } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs index 39ce17e008cbb..4d399fc429e0d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs @@ -1104,7 +1104,7 @@ public void LocalDeclarationStatement_17() CompileAndVerify(comp, expectedOutput: "Hi!"); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80313")] public void LocalDeclarationStatement_18() { var text = @" @@ -1133,6 +1133,50 @@ public void LocalDeclarationStatement_18() comp = CreateCompilation(text, options: TestOptions.DebugExe); comp.VerifyEmitDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(text); + // https://github.com/dotnet/roslyn/issues/79791: Verify runtime async output + var verifier = CompileAndVerify(comp, expectedOutput: null, verify: Verification.Fails with + { + ILVerifyMessage = "[
$]: Return value missing on the stack. { Offset = 0x2f }" + }, sourceSymbolValidator: validator); + verifier.VerifyIL("", """ + { + // Code size 48 (0x30) + .maxstack 1 + .locals init (int V_0, //c + System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter V_1, + System.Runtime.CompilerServices.YieldAwaitable V_2) + IL_0000: ldc.i4.s -100 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: ldind.i4 + IL_0006: call "void System.Console.Write(int)" + IL_000b: call "System.Runtime.CompilerServices.YieldAwaitable System.Threading.Tasks.Task.Yield()" + IL_0010: stloc.2 + IL_0011: ldloca.s V_2 + IL_0013: call "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter System.Runtime.CompilerServices.YieldAwaitable.GetAwaiter()" + IL_0018: stloc.1 + IL_0019: ldloca.s V_1 + IL_001b: call "bool System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.IsCompleted.get" + IL_0020: brtrue.s IL_0028 + IL_0022: ldloc.1 + IL_0023: call "void System.Runtime.CompilerServices.AsyncHelpers.UnsafeAwaitAwaiter(System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter)" + IL_0028: ldloca.s V_1 + IL_002a: call "void System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.GetResult()" + IL_002f: ret + } + """); + + static void validator(ModuleSymbol module) + { + var program = module.GlobalNamespace.GetTypeMember("Program"); + Assert.NotNull(program); + + var main = program.GetMethod("
$"); + Assert.NotNull(main); + Assert.Equal(MethodImplAttributes.Async, main.ImplementationAttributes & MethodImplAttributes.Async); + } } [Fact] @@ -7825,7 +7869,7 @@ public void Return_03() } } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80313")] public void Return_04() { var text = @" @@ -7893,6 +7937,51 @@ public void Return_04() ", options: PdbValidationOptions.SkipConversionValidation); } + + comp = CreateRuntimeAsyncCompilation(text); + // https://github.com/dotnet/roslyn/issues/79791: Verify runtime async output + var verifier = CompileAndVerify(comp, expectedOutput: null, verify: Verification.Fails with + { + ILVerifyMessage = "[
$]: Unexpected type on the stack. { Offset = 0x43, Found = Int32, Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1' }" + }, sourceSymbolValidator: validator); + + verifier.VerifyIL("", """ + { + // Code size 68 (0x44) + .maxstack 3 + IL_0000: ldstr "hello " + IL_0005: call "void System.Console.Write(string)" + IL_000a: call "System.Threading.Tasks.TaskFactory System.Threading.Tasks.Task.Factory.get" + IL_000f: ldsfld "System.Func Program.<>c.<>9__0_0" + IL_0014: dup + IL_0015: brtrue.s IL_002e + IL_0017: pop + IL_0018: ldsfld "Program.<>c Program.<>c.<>9" + IL_001d: ldftn "int Program.<>c.<
$>b__0_0()" + IL_0023: newobj "System.Func..ctor(object, System.IntPtr)" + IL_0028: dup + IL_0029: stsfld "System.Func Program.<>c.<>9__0_0" + IL_002e: callvirt "System.Threading.Tasks.Task System.Threading.Tasks.TaskFactory.StartNew(System.Func)" + IL_0033: call "int System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_0038: pop + IL_0039: ldarg.0 + IL_003a: ldc.i4.0 + IL_003b: ldelem.ref + IL_003c: call "void System.Console.Write(string)" + IL_0041: ldc.i4.s 11 + IL_0043: ret + } + """); + + static void validator(ModuleSymbol module) + { + var program = module.GlobalNamespace.GetTypeMember("Program"); + Assert.NotNull(program); + + var main = program.GetMethod("
$"); + Assert.NotNull(main); + Assert.Equal(MethodImplAttributes.Async, main.ImplementationAttributes & MethodImplAttributes.Async); + } } [Fact] diff --git a/src/Compilers/Core/Portable/MethodImplExtensions.cs b/src/Compilers/Core/Portable/MethodImplExtensions.cs new file mode 100644 index 0000000000000..5d4b62f001890 --- /dev/null +++ b/src/Compilers/Core/Portable/MethodImplExtensions.cs @@ -0,0 +1,26 @@ +// 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.Reflection; +using System.Runtime.CompilerServices; + +namespace Microsoft.CodeAnalysis; + +internal static class MethodImplAttributeExtensions +{ + extension(MethodImplAttributes) + { + // https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible + public static MethodImplAttributes Async => (MethodImplAttributes)0x2000; + } +} + +internal static class MethodImplOptionsExtensions +{ + extension(MethodImplOptions) + { + // https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible + public static MethodImplOptions Async => (MethodImplOptions)0x2000; + } +} diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs index b4f4afda3d9d4..b83974f744d5c 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs @@ -427,9 +427,7 @@ internal static void DecodeMethodImplAttribute