diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index e04d7b0dccb28..4c141a2d75f5a 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -316,9 +316,7 @@ internal bool IsNullableAnalysisEnabledAlways /// Returns true if this method should be processed with runtime async handling instead /// of compiler async state machine generation. /// -#pragma warning disable IDE0060 // Remove unused parameter internal bool IsRuntimeAsyncEnabledIn(MethodSymbol method) -#pragma warning restore IDE0060 // Remove unused parameter { // PROTOTYPE: EE tests fail this assert, handle and test //Debug.Assert(ReferenceEquals(method.ContainingAssembly, Assembly)); @@ -327,8 +325,12 @@ internal bool IsRuntimeAsyncEnabledIn(MethodSymbol method) return false; } - // PROTOTYPE: Check for attributes that turn on/off the feature member-by-member - return true; + return method switch + { + SourceMethodSymbol { IsRuntimeAsyncEnabledInMethod: ThreeState.True } => true, + SourceMethodSymbol { IsRuntimeAsyncEnabledInMethod: ThreeState.False } => false, + _ => Feature("runtime-async") == "on" + }; } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs index e95f295b21e87..c9410bc2f831c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs @@ -149,5 +149,21 @@ public UnmanagedCallersOnlyAttributeData? UnmanagedCallersOnlyAttributeData SetDataStored(); } } + + private ThreeState _runtimeAsyncMethodGenerationSetting; + public ThreeState RuntimeAsyncMethodGenerationSetting + { + get + { + VerifySealed(expected: true); + return _runtimeAsyncMethodGenerationSetting; + } + set + { + VerifySealed(expected: false); + _runtimeAsyncMethodGenerationSetting = value; + SetDataStored(); + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 9a50eea9844f5..dbd6e7f2a2f6a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -651,6 +651,14 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut arguments.AttributeSyntaxOpt); } } + else if (attribute.IsTargetAttribute(AttributeDescription.RuntimeAsyncMethodGenerationAttribute)) + { + // PROTOTYPE: Validate langversion? Validate previewness of the runtime feature flag? + arguments.GetOrCreateData().RuntimeAsyncMethodGenerationSetting = + attribute.CommonConstructorArguments[0].DecodeValue(SpecialType.System_Boolean) + ? ThreeState.True + : ThreeState.False; + } else { var compilation = this.DeclaringCompilation; @@ -661,6 +669,9 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut } } + internal ThreeState IsRuntimeAsyncEnabledInMethod + => GetDecodedWellKnownAttributeData()?.RuntimeAsyncMethodGenerationSetting ?? ThreeState.Unknown; + internal override ImmutableArray NotNullMembers => GetDecodedWellKnownAttributeData()?.NotNullMembers ?? ImmutableArray.Empty; diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs index 66126dc35c666..2807e211f1817 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs @@ -25,6 +25,7 @@ public class CodeGenAsyncTests : EmitMetadataTestBase { // PROTOTYPE: Use the real value when possible private const MethodImplAttributes MethodImplOptionsAsync = (MethodImplAttributes)1024; + private static CSharpParseOptions WithRuntimeAsync(CSharpParseOptions options) => options.WithFeature("runtime-async", "on"); internal static string ExpectedOutput(string output) { @@ -173,7 +174,7 @@ public static void Main() "; CompileAndVerify(source, expectedOutput: expected); - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var verifier = CompileAndVerify(comp, verify: Verification.Fails with @@ -240,7 +241,7 @@ public static async Task Main() }"; //var expected = "42"; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var verifier = CompileAndVerify(comp, verify: Verification.Fails with @@ -302,7 +303,7 @@ public static void Main() "; CompileAndVerify(source, expectedOutput: expected); - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var verifier = CompileAndVerify(comp, verify: Verification.Fails with @@ -363,7 +364,7 @@ public static async Task Main() } }"; //var expected = @"O brave new world..."; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var verifier = CompileAndVerify(comp, verify: Verification.Fails with @@ -421,7 +422,7 @@ public static async Task Main() } } """; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var ilVerifyMessage = (useValueTask, useGeneric) switch @@ -561,7 +562,7 @@ public static async Task Main() } } """; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var ilVerifyMessage = (useValueTask, useGeneric) switch @@ -686,7 +687,7 @@ public static async Task Main() } } """; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var ilVerifyMessage = (useValueTask, useGeneric) switch @@ -800,7 +801,7 @@ public static async Task Main() } } """; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var ilVerifyMessage = (useValueTask, useGeneric) switch @@ -932,7 +933,7 @@ public static async Task Main() } } """; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var ilVerifyMessage = (useValueTask, useGeneric) switch @@ -1046,7 +1047,7 @@ public static async Task Main() public static {{retType}} Prop => {{baseExpr}}; } """; - var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90); + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); var ilVerifyMessage = (useValueTask, useGeneric) switch @@ -7282,5 +7283,437 @@ static void validate(ModuleSymbol m) m.GlobalNamespace.GetMember("Test1.d__0.x").GetAttributes().Select(a => a.ToString())); } } + + [Fact] + public void RuntimeAsync_CompilerFeatureFlag_EnabledWithRuntimeAsync() + { + var source = """ + using System.Threading.Tasks; + + await Task.CompletedTask; + """; + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails with { ILVerifyMessage = ReturnValueMissing("
$", "0xa") }); + + verifier.VerifyIL("", """ + { + // Code size 11 (0xb) + .maxstack 1 + IL_0000: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0005: call "void System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task)" + IL_000a: ret + } + """); + } + + [Fact] + public void RuntimeAsync_CompilerFeatureFlag_EnabledWithoutRuntimeAsync() + { + var source = """ + using System.Threading.Tasks; + + await Task.CompletedTask; + """; + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); + + var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify); + + verifier.VerifyIL("", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.<
$>d__0 V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.<
$>d__0.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start$>d__0>(ref Program.<
$>d__0)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void RuntimeAsync_CompilerFeatureFlag_DisabledWithRuntimeAsync(bool explicitDisable) + { + var source = """ + using System.Threading.Tasks; + + await Task.CompletedTask; + """; + + var parseOptions = TestOptions.RegularPreview; + if (explicitDisable) + { + parseOptions = parseOptions.WithFeature("runtime-async", "off"); + } + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers], targetFramework: TargetFramework.Net90, parseOptions: parseOptions); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify); + + verifier.VerifyIL("", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.<
$>d__0 V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.<
$>d__0.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start$>d__0>(ref Program.<
$>d__0)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void RuntimeAsync_CompilerFeatureFlag_DisabledWithRuntimeAsync_EnabledOnMethod(bool explicitDisable) + { + var source = """ + using System.Threading.Tasks; + + class Program + { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(true)] + static async Task Main() + { + await Task.CompletedTask; + } + } + """; + + var parseOptions = TestOptions.RegularPreview; + if (explicitDisable) + { + parseOptions = parseOptions.WithFeature("runtime-async", "off"); + } + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers, RuntimeAsyncMethodGenerationAttributeDefinition], targetFramework: TargetFramework.Net90, parseOptions: parseOptions); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails with { ILVerifyMessage = ReturnValueMissing("Main", "0xa") }); + + verifier.VerifyIL("Program.Main()", """ + { + // Code size 11 (0xb) + .maxstack 1 + IL_0000: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0005: call "void System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task)" + IL_000a: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void RuntimeAsync_CompilerFeatureFlag_DisabledWithRuntimeAsync_EnabledOnMethod_NoPreferenceOnNestedLocalFunction(bool explicitDisable) + { + var source = """ + using System.Threading.Tasks; + + class Program + { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(true)] + static async Task Main() + { + await Task.CompletedTask; + + async Task LocalFunc() => await Task.CompletedTask; + } + } + """; + + var parseOptions = TestOptions.RegularPreview; + if (explicitDisable) + { + parseOptions = parseOptions.WithFeature("runtime-async", "off"); + } + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers, RuntimeAsyncMethodGenerationAttributeDefinition], targetFramework: TargetFramework.Net90, parseOptions: parseOptions); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails with { ILVerifyMessage = ReturnValueMissing("Main", "0xa") }); + + verifier.VerifyIL("Program.Main()", """ + { + // Code size 11 (0xb) + .maxstack 1 + IL_0000: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0005: call "void System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task)" + IL_000a: ret + } + """); + + verifier.VerifyIL("Program.
g__LocalFunc|0_0()", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.<
g__LocalFunc|0_0>d V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
g__LocalFunc|0_0>d.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.<
g__LocalFunc|0_0>d.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
g__LocalFunc|0_0>d.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startg__LocalFunc|0_0>d>(ref Program.<
g__LocalFunc|0_0>d)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
g__LocalFunc|0_0>d.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void RuntimeAsync_CompilerFeatureFlag_DisabledWithRuntimeAsync_EnabledOnMethod_NoPreferenceOnNestedLambda(bool explicitDisable) + { + var source = """ + using System.Threading.Tasks; + + class Program + { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(true)] + static async Task Main() + { + await Task.CompletedTask; + + var a = async () => await Task.CompletedTask; + } + } + """; + + var parseOptions = TestOptions.RegularPreview; + if (explicitDisable) + { + parseOptions = parseOptions.WithFeature("runtime-async", "off"); + } + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers, RuntimeAsyncMethodGenerationAttributeDefinition], targetFramework: TargetFramework.Net90, parseOptions: parseOptions); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails with { ILVerifyMessage = ReturnValueMissing("Main", "0x26") }); + + verifier.VerifyIL("Program.Main()", """ + { + // Code size 39 (0x27) + .maxstack 2 + IL_0000: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0005: call "void System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task)" + IL_000a: ldsfld "System.Func Program.<>c.<>9__0_0" + IL_000f: brtrue.s IL_0026 + IL_0011: ldsfld "Program.<>c Program.<>c.<>9" + IL_0016: ldftn "System.Threading.Tasks.Task Program.<>c.
b__0_0()" + IL_001c: newobj "System.Func..ctor(object, nint)" + IL_0021: stsfld "System.Func Program.<>c.<>9__0_0" + IL_0026: ret + } + """); + + verifier.VerifyIL("Program.<>c.
b__0_0()", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.<>c.<
b__0_0>d V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<>c.<
b__0_0>d.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.<>c.<
b__0_0>d.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<>c.<
b__0_0>d.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startc.<
b__0_0>d>(ref Program.<>c.<
b__0_0>d)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<>c.<
b__0_0>d.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + } + + [Fact] + public void RuntimeAsync_CompilerFeatureFlag_EnabledWithRuntimeAsync_DisabledOnMethod() + { + var source = """ + using System.Threading.Tasks; + + class Program + { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(false)] + static async Task Main() + { + await Task.CompletedTask; + } + } + """; + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers, RuntimeAsyncMethodGenerationAttributeDefinition], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify); + + verifier.VerifyIL("Program.Main()", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.
d__0 V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.
d__0.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startd__0>(ref Program.
d__0)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + } + + [Fact] + public void RuntimeAsync_CompilerFeatureFlag_EnabledWithRuntimeAsync_DisabledOnMethod_NoPreferenceOnNestedLocalFunction() + { + var source = """ + using System.Threading.Tasks; + + class Program + { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(false)] + static async Task Main() + { + await Task.CompletedTask; + + async Task LocalFunc() => await Task.CompletedTask; + } + } + """; + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers, RuntimeAsyncMethodGenerationAttributeDefinition], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails with { ILVerifyMessage = ReturnValueMissing("
g__LocalFunc|0_0", "0xa") }); + + verifier.VerifyIL("Program.Main()", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.
d__0 V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.
d__0.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startd__0>(ref Program.
d__0)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + + verifier.VerifyIL("Program.
g__LocalFunc|0_0()", """ + { + // Code size 11 (0xb) + .maxstack 1 + IL_0000: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0005: call "void System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task)" + IL_000a: ret + } + """); + } + + [Fact] + public void RuntimeAsync_CompilerFeatureFlag_EnabledWithRuntimeAsync_DisabledOnMethod_NoPreferenceOnNestedLambda() + { + var source = """ + using System.Threading.Tasks; + + class Program + { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(false)] + static async Task Main() + { + await Task.CompletedTask; + + var a = async () => await Task.CompletedTask; + } + } + """; + + var comp = CreateCompilation([source, RuntimeAsyncAwaitHelpers, RuntimeAsyncMethodGenerationAttributeDefinition], targetFramework: TargetFramework.Net90, parseOptions: WithRuntimeAsync(TestOptions.RegularPreview)); + comp.Assembly.SetOverrideRuntimeSupportsAsyncMethods(); + + var verifier = CompileAndVerify(comp, verify: Verification.Fails with { ILVerifyMessage = ReturnValueMissing("
b__0_0", "0xa") }); + + verifier.VerifyIL("Program.Main()", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (Program.
d__0 V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int Program.
d__0.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startd__0>(ref Program.
d__0)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.
d__0.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); + + verifier.VerifyIL("Program.<>c.
b__0_0()", """ + { + // Code size 11 (0xb) + .maxstack 1 + IL_0000: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0005: call "void System.Runtime.CompilerServices.RuntimeHelpers.Await(System.Threading.Tasks.Task)" + IL_000a: ret + } + """); + } } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index 6b16eac1db4e2..606b084ca8c40 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -491,5 +491,6 @@ static AttributeDescription() internal static readonly AttributeDescription CollectionBuilderAttribute = new AttributeDescription("System.Runtime.CompilerServices", "CollectionBuilderAttribute", s_signaturesOfCollectionBuilderAttribute); internal static readonly AttributeDescription OverloadResolutionPriorityAttribute = new AttributeDescription("System.Runtime.CompilerServices", "OverloadResolutionPriorityAttribute", s_signatures_HasThis_Void_Int32_Only); internal static readonly AttributeDescription CompilerLoweringPreserveAttribute = new AttributeDescription("System.Runtime.CompilerServices", "CompilerLoweringPreserveAttribute", s_signatures_HasThis_Void_Only); + internal static readonly AttributeDescription RuntimeAsyncMethodGenerationAttribute = new AttributeDescription("System.Runtime.CompilerServices", "RuntimeAsyncMethodGenerationAttribute", s_signatures_HasThis_Void_Boolean_Only); } } diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 97f3a40347ec0..a5d7b845c62f1 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -813,6 +813,13 @@ public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter } """; + internal const string RuntimeAsyncMethodGenerationAttributeDefinition = """ + namespace System.Runtime.CompilerServices; + + [AttributeUsage(AttributeTargets.Method)] + public class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute(); + """; + protected static T GetSyntax(SyntaxTree tree, string text) { return GetSyntaxes(tree, text).Single();