diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 7a4bb9bf9ce8a..e588c5d3ac09d 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -357,11 +357,12 @@ internal bool IsRuntimeAsyncEnabledIn(Symbol? symbol) Debug.Assert(ReferenceEquals(method.ContainingAssembly, Assembly)); Debug.Assert(method.IsDefinition); + Debug.Assert(method is not Symbols.Metadata.PE.PEMethodSymbol); - var runtimeAsyncEnabledInMethod = symbol switch + var runtimeAsyncEnabledInMethod = method.RuntimeAsyncMethodGenerationAttributeSetting switch { - SourceMethodSymbol { IsRuntimeAsyncEnabledInMethod: ThreeState.True } => true, - SourceMethodSymbol { IsRuntimeAsyncEnabledInMethod: ThreeState.False } => false, + ThreeState.True => true, + ThreeState.False => false, _ => Feature(CodeAnalysis.Feature.RuntimeAsync) == "on" }; diff --git a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs index d13dedd6d70f5..2a5fab97aee21 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs @@ -214,9 +214,9 @@ public sealed override bool IsImplicitlyDeclared get { return true; } } - internal sealed override ThreeState IsRuntimeAsyncEnabledInMethod => - InheritsBaseMethodAttributes && BaseMethod is SourceMethodSymbol { IsRuntimeAsyncEnabledInMethod: var value } - ? value + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => + InheritsBaseMethodAttributes + ? BaseMethod.RuntimeAsyncMethodGenerationAttributeSetting : ThreeState.Unknown; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs index bf9c36d438ec1..7a9d7fad82363 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs @@ -178,6 +178,8 @@ public override bool ReturnsVoid public override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => ThreeState.Unknown; + public override bool IsVararg { get { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs index 504e348e8514b..be787c0bb645c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs @@ -64,6 +64,10 @@ internal override bool IsIterator get { return _originalMethod.IsIterator; } } + public sealed override bool IsAsync => _originalMethod.IsAsync; + + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => _originalMethod.RuntimeAsyncMethodGenerationAttributeSetting; + internal sealed override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) { return _originalMethod.CalculateLocalSyntaxOffset(localPosition, localTree); diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs index 4c6cbbdb45816..38310a8aacd06 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs @@ -845,6 +845,7 @@ public override bool IsVararg public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; public override ImmutableHashSet ReturnNotNullIfParameterNotNull => ImmutableHashSet.Empty; public override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); internal override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) => false; internal override bool IsMetadataVirtual(IsMetadataVirtualOption option = IsMetadataVirtualOption.None) => false; internal sealed override UnmanagedCallersOnlyAttributeData? GetUnmanagedCallersOnlyAttributeData(bool forceComplete) => null; diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs index 81f824f813411..e5c89e4254491 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs @@ -702,6 +702,15 @@ public override FlowAnalysisAnnotations FlowAnalysisAnnotations } } + internal override ThreeState RuntimeAsyncMethodGenerationAttributeSetting + { + get + { + Debug.Fail("Not expecting to get here; if we end up here through ENC, add tests to verify"); + return ThreeState.Unknown; + } + } + internal override ImmutableArray NotNullMembers { get diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 1428c4574fefe..bfa116808aa81 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -98,6 +98,13 @@ public virtual bool IsGenericMethod internal abstract bool HasSpecialNameAttribute { get; } + /// + /// Returns the method-level runtime async setting from + /// RuntimeAsyncMethodGenerationAttribute, or + /// if no setting was specified. + /// + internal abstract ThreeState RuntimeAsyncMethodGenerationAttributeSetting { get; } + /// /// If a method is annotated with `[MemberNotNull(...)]` attributes, returns the list of members /// listed in those attributes. diff --git a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs index 5931850c42ffa..4c2656cfa8a53 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs @@ -360,6 +360,10 @@ internal NativeIntegerMethodSymbol(NativeIntegerTypeSymbol container, MethodSymb public override ImmutableArray TypeParameters => ImmutableArray.Empty; + public override bool IsAsync => UnderlyingMethod.IsAsync; + + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + public override ImmutableArray Parameters { get diff --git a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs index 5ed29b79ba21c..323d2d0f43098 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs @@ -503,6 +503,8 @@ public override TypeWithAnnotations ReturnTypeWithAnnotations public override FlowAnalysisAnnotations FlowAnalysisAnnotations => _reducedFrom.FlowAnalysisAnnotations; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + public override ImmutableArray RefCustomModifiers { get { return _typeMap.SubstituteCustomModifiers(_reducedFrom.RefCustomModifiers); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs index 2c8d3bca40777..2a68f3ffbf9be 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingMethodSymbol.cs @@ -126,6 +126,10 @@ public override ImmutableArray TypeArgumentsWithAnnotations } } + public override bool IsAsync => _underlyingMethod.IsAsync; + + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + public override TypeWithAnnotations ReturnTypeWithAnnotations { get diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs index 1795defaa7f7f..74ba90670dda3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs @@ -190,6 +190,8 @@ internal override bool IsMetadataFinal internal sealed override int TryGetOverloadResolutionPriority() => throw ExceptionUtilities.Unreachable(); + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + #endregion } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 24f29a248f35e..14feba964135a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -688,7 +688,7 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut } } - internal virtual ThreeState IsRuntimeAsyncEnabledInMethod + internal override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => GetDecodedWellKnownAttributeData()?.RuntimeAsyncMethodGenerationSetting ?? ThreeState.Unknown; internal override ImmutableArray NotNullMembers => diff --git a/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs index f75b0c8e57661..7e63ff81d0251 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SubstitutedMethodSymbol.cs @@ -171,6 +171,10 @@ public override TypeSymbol ReceiverType } } + public override bool IsAsync => OriginalDefinition.IsAsync; + + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + public override TypeWithAnnotations GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter) { // This will throw if API shouldn't be supported or there is a problem with the argument. diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedCollectionBuilderProjectedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedCollectionBuilderProjectedMethodSymbol.cs index d0557a9f32e91..5e525e9740f88 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedCollectionBuilderProjectedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedCollectionBuilderProjectedMethodSymbol.cs @@ -43,8 +43,10 @@ public override ImmutableArray GetAttributes() => this.UnderlyingMethod.GetAttributes(); public override Symbol ContainingSymbol => this.UnderlyingMethod.ContainingSymbol; + public override bool IsAsync => this.UnderlyingMethod.IsAsync; public override ImmutableArray RefCustomModifiers => this.UnderlyingMethod.RefCustomModifiers; public override TypeWithAnnotations ReturnTypeWithAnnotations => this.UnderlyingMethod.ReturnTypeWithAnnotations; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); /// /// The projection method itself is intentionally not obsolete. We don't want to report obsoletion errors when @@ -139,4 +141,3 @@ private sealed class SynthesizedCollectionBuilderProjectedParameterSymbol( internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) => throw ExceptionUtilities.Unreachable(); } } - diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 19516e64b273d..a049f61eafe1f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -148,6 +148,8 @@ public override bool ReturnsVoid public sealed override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => ThreeState.Unknown; + public override MethodKind MethodKind { get { return MethodKind.Ordinary; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs index eeb976077d5e7..de6cd25b135a6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs @@ -218,6 +218,8 @@ public sealed override FlowAnalysisAnnotations FlowAnalysisAnnotations get { return FlowAnalysisAnnotations.None; } } + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => ThreeState.Unknown; + public override ImmutableArray RefCustomModifiers { get { return ImmutableArray.Empty; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs index c922152f6c21a..d79ac70c608f7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs @@ -236,6 +236,8 @@ public override TypeWithAnnotations ReturnTypeWithAnnotations public override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => ThreeState.Unknown; + public override ImmutableArray TypeArgumentsWithAnnotations { get diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedMethodSymbol.cs index bbfc7ee1980c0..aa192a3aee479 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedMethodSymbol.cs @@ -91,6 +91,8 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l public sealed override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + internal override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => ThreeState.Unknown; + internal override bool IsNullableAnalysisEnabled() => false; internal sealed override bool HasUnscopedRefAttribute => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs index 3fb90e276ae58..441b94a1100dc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs @@ -146,6 +146,8 @@ public override TypeWithAnnotations ReturnTypeWithAnnotations public override FlowAnalysisAnnotations FlowAnalysisAnnotations => FlowAnalysisAnnotations.None; + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => ThreeState.Unknown; + public override ImmutableArray RefCustomModifiers { get diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs index db2fb11d4e3c8..3409dd6f15748 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs @@ -140,14 +140,6 @@ public override bool IsVirtual } } - public override bool IsAsync - { - get - { - return UnderlyingMethod.IsAsync; - } - } - public override bool IsOverride { get diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs index 6fc12c66a9357..ea05fd0818733 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs @@ -8514,10 +8514,11 @@ .locals init (S V_0) """); } - [Fact] - public void SpillAssignmentToThisStruct_02() + [Theory] + [CombinatorialData] + public void SpillAssignmentToThisStruct_02(bool disableRuntimeAsync) { - var source = """ + var source = $$""" using System; using System.Threading.Tasks; struct S : I @@ -8543,6 +8544,7 @@ static class Extensions { extension(T t) where T : I { + {{(disableRuntimeAsync ? "[System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)]" : "")}} public async Task M() { t.P = 1; @@ -8553,30 +8555,38 @@ public async Task M() """; var expectedOutput = "0"; - CompileAndVerify(source, expectedOutput: expectedOutput, options: TestOptions.ReleaseExe); - CompileAndVerify(source, expectedOutput: expectedOutput, options: TestOptions.DebugExe); + CompileAndVerify([source, RuntimeAsyncMethodGenerationAttributeDefinition], expectedOutput: expectedOutput, options: TestOptions.ReleaseExe); + CompileAndVerify([source, RuntimeAsyncMethodGenerationAttributeDefinition], expectedOutput: expectedOutput, options: TestOptions.DebugExe); - var comp = CreateRuntimeAsyncCompilation(source, TestOptions.ReleaseExe); + var comp = CreateRuntimeAsyncCompilation([source, RuntimeAsyncMethodGenerationAttributeDefinition], TestOptions.ReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Fails with { - ILVerifyMessage = """ + ILVerifyMessage = disableRuntimeAsync + ? """ + [Main]: Return value missing on the stack. { Offset = 0x1f } + """ + : """ [Main]: Return value missing on the stack. { Offset = 0x1f } [M]: Return value missing on the stack. { Offset = 0xe } """ }); verifier.VerifyDiagnostics(); - verifier.VerifyIL("Extensions.M(this T)", """ - { - // Code size 15 (0xf) - .maxstack 2 - IL_0000: ldarga.s V_0 - IL_0002: ldc.i4.1 - IL_0003: constrained. "T" - IL_0009: callvirt "void I.P.set" - IL_000e: ret - } - """); + if (!disableRuntimeAsync) + { + + verifier.VerifyIL("Extensions.M(this T)", """ + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarga.s V_0 + IL_0002: ldc.i4.1 + IL_0003: constrained. "T" + IL_0009: callvirt "void I.P.set" + IL_000e: ret + } + """); + } } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_Synthesized.cs b/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_Synthesized.cs index c92d68cf1a4e7..888dd4121642b 100644 --- a/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_Synthesized.cs +++ b/src/Compilers/CSharp/Test/Emit3/Attributes/AttributeTests_Synthesized.cs @@ -56,6 +56,20 @@ public static IEnumerable FullMatrixTheoryData } } } + + public static IEnumerable RuntimeAsyncSuppressionAndOptimizationLevelTheoryData + { + get + { + foreach (bool suppressRuntimeAsync in new[] { false, true }) + { + foreach (var level in Enum.GetValues(typeof(OptimizationLevel))) + { + yield return new object[] { level, suppressRuntimeAsync }; + } + } + } + } #endregion #region Helpers @@ -1949,16 +1963,17 @@ public void AsyncStateMachineAttribute_RuntimeAsync_TLSEntrypoint(OptimizationLe } [Theory] - [MemberData(nameof(OptimizationLevelTheoryData))] - public void AsyncStateMachineAttribute_RuntimeAsync_ExtensionBlockMember(OptimizationLevel optimizationLevel) + [MemberData(nameof(RuntimeAsyncSuppressionAndOptimizationLevelTheoryData))] + public void AsyncStateMachineAttribute_RuntimeAsync_ExtensionBlockMember(OptimizationLevel optimizationLevel, bool suppressRuntimeAsync) { - string source = """ + string source = $$""" using System.Threading.Tasks; public static class Ex { extension(object o) { + {{(suppressRuntimeAsync ? "[System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)]" : "")}} public async Task F() { await Task.Delay(0); @@ -1970,28 +1985,35 @@ public async Task F() var options = TestOptions.CreateTestOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel) .WithMetadataImportOptions(MetadataImportOptions.All); - var compilation = CreateRuntimeAsyncCompilation(source, options); - var verifier = CompileAndVerify(compilation, verify: Verification.Skipped, symbolValidator: static module => + var compilation = CreateRuntimeAsyncCompilation([source, RuntimeAsyncMethodGenerationAttributeDefinition], options); + var verifier = CompileAndVerify(compilation, verify: Verification.Skipped, symbolValidator: module => { var type = module.GlobalNamespace.GetMember("Ex"); var asyncImplMethod = type.GetMember("F"); - // When runtime async is enabled, no state machine is generated, - // so there should be no AsyncStateMachineAttribute and no DebuggerStepThroughAttribute - Assert.Empty(asyncImplMethod.GetAttributes()); + if (suppressRuntimeAsync) + { + Assert.Contains(asyncImplMethod.GetAttributes(), static a => a.AttributeClass?.Name == "AsyncStateMachineAttribute"); + } + else + { + // When runtime async is enabled, no state machine is generated, + // so there should be no AsyncStateMachineAttribute and no DebuggerStepThroughAttribute + Assert.Empty(asyncImplMethod.GetAttributes()); + } - var extension = type.GetTypeMembers().Single(); + var extension = type.GetTypeMembers().Single(static typeMember => typeMember.GetMembers("F").Any()); var asyncExtensionSignatureMethod = extension.GetMember("F"); - Assert.Empty(asyncExtensionSignatureMethod.GetAttributes()); + Assert.DoesNotContain(asyncExtensionSignatureMethod.GetAttributes(), static a => a.AttributeClass?.Name == "AsyncStateMachineAttribute"); }); verifier.VerifyDiagnostics(); } [Theory] - [MemberData(nameof(OptimizationLevelTheoryData))] - public void AsyncStateMachineAttribute_RuntimeAsync_ExtensionBlockMember_WithLambda(OptimizationLevel optimizationLevel) + [MemberData(nameof(RuntimeAsyncSuppressionAndOptimizationLevelTheoryData))] + public void AsyncStateMachineAttribute_RuntimeAsync_ExtensionBlockMember_WithLambda(OptimizationLevel optimizationLevel, bool suppressRuntimeAsync) { - string source = """ + string source = $$""" using System.Threading.Tasks; public static class Ex @@ -2000,7 +2022,7 @@ public static class Ex { public async Task F() { - var f = async () => { await Task.Delay(0); }; + var f = {{(suppressRuntimeAsync ? "[System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] " : "")}}async () => { await Task.Delay(0); }; await f(); } } @@ -2010,23 +2032,30 @@ public async Task F() var options = TestOptions.CreateTestOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel) .WithMetadataImportOptions(MetadataImportOptions.All); - var compilation = CreateRuntimeAsyncCompilation(source, options); - var verifier = CompileAndVerify(compilation, verify: Verification.Skipped, symbolValidator: static module => + var compilation = CreateRuntimeAsyncCompilation([source, RuntimeAsyncMethodGenerationAttributeDefinition], options); + var verifier = CompileAndVerify(compilation, verify: Verification.Skipped, symbolValidator: module => { var type = module.GlobalNamespace.GetMember("Ex"); var typeMembers = type.GetTypeMembers(); AssertEx.SequenceEqual(["Ex.$C43E2675C7BBF9284AF22FB8A9BF0280.$119AA281C143547563250CAF89B48A76", "Ex.<>c"], typeMembers.ToTestDisplayStrings()); var asyncLambda = typeMembers[1].GetMember("b__1_0"); - Assert.Empty(asyncLambda.GetAttributes()); + if (suppressRuntimeAsync) + { + Assert.Contains(asyncLambda.GetAttributes(), static a => a.AttributeClass?.Name == "AsyncStateMachineAttribute"); + } + else + { + Assert.Empty(asyncLambda.GetAttributes()); + } }); verifier.VerifyDiagnostics(); } [Theory] - [MemberData(nameof(OptimizationLevelTheoryData))] - public void AsyncStateMachineAttribute_RuntimeAsync_ExtensionBlockMember_WithLocalFunction(OptimizationLevel optimizationLevel) + [MemberData(nameof(RuntimeAsyncSuppressionAndOptimizationLevelTheoryData))] + public void AsyncStateMachineAttribute_RuntimeAsync_ExtensionBlockMember_WithLocalFunction(OptimizationLevel optimizationLevel, bool suppressRuntimeAsync) { - string source = """ + string source = $$""" using System.Threading.Tasks; public static class Ex @@ -2036,6 +2065,7 @@ public static class Ex public async Task F() { await LocalAsync(); + {{(suppressRuntimeAsync ? "[System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)]" : "")}} async Task LocalAsync() { await Task.Delay(0); } } } @@ -2045,13 +2075,23 @@ public async Task F() var options = TestOptions.CreateTestOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel) .WithMetadataImportOptions(MetadataImportOptions.All); - var compilation = CreateRuntimeAsyncCompilation(source, options); - var verifier = CompileAndVerify(compilation, verify: Verification.Skipped, symbolValidator: static module => + var compilation = suppressRuntimeAsync + ? CreateRuntimeAsyncCompilation([source, RuntimeAsyncMethodGenerationAttributeDefinition], options) + : CreateRuntimeAsyncCompilation(source, options); + var verifier = CompileAndVerify(compilation, verify: Verification.Skipped, symbolValidator: module => { var type = module.GlobalNamespace.GetMember("Ex"); - Assert.Single(type.GetTypeMembers()); + Assert.Equal(suppressRuntimeAsync ? 2 : 1, type.GetTypeMembers().Length); var localFunction = type.GetMember("g__LocalAsync|1_0"); - AssertEx.SequenceEqual(["System.Runtime.CompilerServices.CompilerGeneratedAttribute..ctor()"], localFunction.GetAttributes().SelectAsArray(a => a.AttributeConstructor.ToTestDisplayString())); + Assert.Contains(localFunction.GetAttributes(), static a => a.AttributeClass?.Name == "CompilerGeneratedAttribute"); + if (suppressRuntimeAsync) + { + Assert.Contains(localFunction.GetAttributes(), static a => a.AttributeClass?.Name == "AsyncStateMachineAttribute"); + } + else + { + Assert.DoesNotContain(localFunction.GetAttributes(), static a => a.AttributeClass?.Name == "AsyncStateMachineAttribute"); + } }); verifier.VerifyDiagnostics(); } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs index 6844b466db7c5..79c0cb22dd2bd 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EECompilationContextMethod.cs @@ -60,6 +60,10 @@ public override ImmutableArray Parameters get { return _parameters; } } + public override bool IsAsync => false; + + internal sealed override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + public override TypeWithAnnotations ReturnTypeWithAnnotations => _underlyingMethod.ReturnTypeWithAnnotations; public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs index 9ad9e2400449b..15efd0c2bada7 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs @@ -91,6 +91,7 @@ internal EEMethodSymbol( Debug.Assert(sourceMethod.IsDefinition); Debug.Assert(TypeSymbol.Equals((TypeSymbol)sourceMethod.ContainingSymbol, container.SubstitutedSourceType.OriginalDefinition, TypeCompareKind.ConsiderEverything2)); Debug.Assert(sourceLocals.All(l => l.ContainingSymbol == sourceMethod)); + Debug.Assert(!sourceMethod.IsAsync); _container = container; _name = name; @@ -325,6 +326,8 @@ public override bool IsAsync get { return false; } } + internal override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + public override TypeWithAnnotations ReturnTypeWithAnnotations { get diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs index 3ffbf293a6a61..5c96bc7a35fc7 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs @@ -281,6 +281,8 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l protected override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable(); + internal override ThreeState RuntimeAsyncMethodGenerationAttributeSetting => throw ExceptionUtilities.Unreachable(); + internal sealed override bool HasAsyncMethodBuilderAttribute(out TypeSymbol builderArgument) { builderArgument = null;