From f1df660eaefe0fd7b506bb7037c2e0a26768737c Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 6 Oct 2020 01:14:52 +0000 Subject: [PATCH 01/38] Add async modifier on await completion --- .../AwaitCompletionProvider.cs | 79 +++++++++++++++++++ .../AwaitKeywordRecommender.cs | 40 +--------- .../ContextQuery/CSharpSyntaxContext.cs | 38 +++++++++ 3 files changed, 118 insertions(+), 39 deletions(-) create mode 100644 src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs new file mode 100644 index 0000000000000..ce3239906eaef --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -0,0 +1,79 @@ +// 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.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers +{ + [ExportCompletionProvider(nameof(AwaitCompletionProvider), LanguageNames.CSharp)] + [Shared] + internal class AwaitCompletionProvider : LSPCompletionProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public AwaitCompletionProvider() + { + } + + internal override ImmutableHashSet TriggerCharacters => CompletionUtilities.CommonTriggerCharactersWithArgumentList; + + internal override async Task GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, bool disallowAddingImports, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var declaration = root?.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + if (root is null || declaration is null) + { + return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); + } + + // MethodDeclarationSyntax, LocalFunctionStatementSyntax, AnonymousMethodExpressionSyntax, ParenthesizedLambdaExpressionSyntax, SimpleLambdaExpressionSyntax. + + var newDeclaration = AddAsyncModifier(declaration, SyntaxGenerator.GetGenerator(document)); + var newDocument = document.WithSyntaxRoot(root.ReplaceNode(declaration, newDeclaration)); + var changes = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + return CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, changes.ToImmutableArray())); + } + + private static SyntaxNode AddAsyncModifier(SyntaxNode declaration, SyntaxGenerator generator) + { + var modifiers = generator.GetModifiers(declaration); + return generator.WithModifiers(declaration, modifiers.WithAsync(true)); + } + + public override async Task ProvideCompletionsAsync(CompletionContext context) + { + var document = context.Document; + var position = context.Position; + var cancellationToken = context.CancellationToken; + + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); + + var workspace = document.Project.Solution.Workspace; + var syntaxContext = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken); + + if (!syntaxContext.IsAwaitKeywordContext(position)) + { + return; + } + + var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + if (method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword)) + { + context.AddItem(CommonCompletionItem.Create("await", string.Empty, CompletionItemRules.Default, inlineDescription: "Make container async")); + } + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs index 361530d72fa76..46b3ae6604f28 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs @@ -5,10 +5,7 @@ #nullable disable using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders { @@ -20,41 +17,6 @@ public AwaitKeywordRecommender() } protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) - { - if (context.IsGlobalStatementContext) - { - return true; - } - - if (context.IsAnyExpressionContext || context.IsStatementContext) - { - foreach (var node in context.LeftToken.GetAncestors()) - { - if (node.IsAnyLambdaOrAnonymousMethod()) - { - return true; - } - - if (node.IsKind(SyntaxKind.QueryExpression)) - { - return false; - } - - if (node.IsKind(SyntaxKind.LockStatement, out LockStatementSyntax lockStatement)) - { - if (lockStatement.Statement != null && - !lockStatement.Statement.IsMissing && - lockStatement.Statement.Span.Contains(position)) - { - return false; - } - } - } - - return true; - } - - return false; - } + => context.IsAwaitKeywordContext(position); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index c2e776b00ed15..ba31d6844b856 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -402,5 +402,43 @@ internal bool IsAwaitStatementContext(int position, CancellationToken cancellati return false; } + + internal bool IsAwaitKeywordContext(int position) + { + if (IsGlobalStatementContext) + { + return true; + } + + if (IsAnyExpressionContext || IsStatementContext) + { + foreach (var node in LeftToken.GetAncestors()) + { + if (node.IsAnyLambdaOrAnonymousMethod()) + { + return true; + } + + if (node.IsKind(SyntaxKind.QueryExpression)) + { + return false; + } + + if (node.IsKind(SyntaxKind.LockStatement, out LockStatementSyntax lockStatement)) + { + if (lockStatement.Statement != null && + !lockStatement.Statement.IsMissing && + lockStatement.Statement.Span.Contains(position)) + { + return false; + } + } + } + + return true; + } + + return false; + } } } From 60f88502256157120099b7a5faf85b991a8f3738 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 6 Mar 2021 12:25:20 +0000 Subject: [PATCH 02/38] Localize and sealed --- .../Portable/CSharpFeaturesResources.resx | 4 ++ .../AwaitCompletionProvider.cs | 71 +++++++++++-------- .../xlf/CSharpFeaturesResources.cs.xlf | 5 ++ .../xlf/CSharpFeaturesResources.de.xlf | 5 ++ .../xlf/CSharpFeaturesResources.es.xlf | 5 ++ .../xlf/CSharpFeaturesResources.fr.xlf | 5 ++ .../xlf/CSharpFeaturesResources.it.xlf | 5 ++ .../xlf/CSharpFeaturesResources.ja.xlf | 5 ++ .../xlf/CSharpFeaturesResources.ko.xlf | 5 ++ .../xlf/CSharpFeaturesResources.pl.xlf | 5 ++ .../xlf/CSharpFeaturesResources.pt-BR.xlf | 5 ++ .../xlf/CSharpFeaturesResources.ru.xlf | 5 ++ .../xlf/CSharpFeaturesResources.tr.xlf | 5 ++ .../xlf/CSharpFeaturesResources.zh-Hans.xlf | 5 ++ .../xlf/CSharpFeaturesResources.zh-Hant.xlf | 5 ++ 15 files changed, 112 insertions(+), 28 deletions(-) diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 4938de097aeef..ae5fdc00bbf52 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -630,4 +630,8 @@ Change to cast + + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + \ No newline at end of file diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index ce3239906eaef..fba5a0d67cbcc 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -11,15 +11,18 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { [ExportCompletionProvider(nameof(AwaitCompletionProvider), LanguageNames.CSharp)] + [ExtensionOrder(After = nameof(KeywordCompletionProvider))] [Shared] - internal class AwaitCompletionProvider : LSPCompletionProvider + internal sealed class AwaitCompletionProvider : LSPCompletionProvider { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -27,38 +30,13 @@ public AwaitCompletionProvider() { } - internal override ImmutableHashSet TriggerCharacters => CompletionUtilities.CommonTriggerCharactersWithArgumentList; - - internal override async Task GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, bool disallowAddingImports, CancellationToken cancellationToken) - { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var declaration = root?.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); - if (root is null || declaration is null) - { - return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); - } - - // MethodDeclarationSyntax, LocalFunctionStatementSyntax, AnonymousMethodExpressionSyntax, ParenthesizedLambdaExpressionSyntax, SimpleLambdaExpressionSyntax. - - var newDeclaration = AddAsyncModifier(declaration, SyntaxGenerator.GetGenerator(document)); - var newDocument = document.WithSyntaxRoot(root.ReplaceNode(declaration, newDeclaration)); - var changes = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); - var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); - return CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, changes.ToImmutableArray())); - } - - private static SyntaxNode AddAsyncModifier(SyntaxNode declaration, SyntaxGenerator generator) - { - var modifiers = generator.GetModifiers(declaration); - return generator.WithModifiers(declaration, modifiers.WithAsync(true)); - } + public override ImmutableHashSet TriggerCharacters => CompletionUtilities.CommonTriggerCharactersWithArgumentList; public override async Task ProvideCompletionsAsync(CompletionContext context) { var document = context.Document; var position = context.Position; var cancellationToken = context.CancellationToken; - var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); var workspace = document.Project.Solution.Workspace; @@ -72,7 +50,44 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); if (method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword)) { - context.AddItem(CommonCompletionItem.Create("await", string.Empty, CompletionItemRules.Default, inlineDescription: "Make container async")); + var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; + if (symbol is not null && IsTask(symbol.ReturnType)) + { + context.AddItem(CommonCompletionItem.Create("await", string.Empty, CompletionItemRules.Default, inlineDescription: "Make container async")); + } + } + + static bool IsTask(ITypeSymbol returnType) + => returnType.Name is "Task" or "ValueTask"; + } + + internal override async Task GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, bool disallowAddingImports, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var declaration = root?.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + if (root is null || declaration is null) + { + return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); + } + + // MethodDeclarationSyntax, LocalFunctionStatementSyntax, AnonymousMethodExpressionSyntax, ParenthesizedLambdaExpressionSyntax, SimpleLambdaExpressionSyntax. + + var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(document, declaration))); + var formattedDocument = await Formatter.FormatAsync(documentWithAsyncModifier, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + + var builder = ArrayBuilder.GetInstance(); + builder.AddRange(await formattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false)); + builder.Add(new TextChange(completionListSpan, completionItem.DisplayText)); + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newText = text.WithChanges(builder); + return CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, builder.ToImmutableArray())); + + static SyntaxNode AddAsyncModifier(Document document, SyntaxNode declaration) + { + var generator = SyntaxGenerator.GetGenerator(document); + var modifiers = generator.GetModifiers(declaration); + return generator.WithModifiers(declaration, modifiers.WithAsync(true)).WithAdditionalAnnotations(Formatter.Annotation); } } } diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 676540b280384..418856b3bce9f 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -147,6 +147,11 @@ Zjistily se konflikty. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Nastavit privátní pole jako jenom pro čtení, kde je to možné diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index fa1f06e8701a1..fcc443f6dc85b 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -147,6 +147,11 @@ Konflikt(e) erkannt. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Private Felder nach Möglichkeit als schreibgeschützt festlegen diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index 0970ebe68b5af..ffdf10d3a5afe 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -147,6 +147,11 @@ Conflicto(s) detectado(s). + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Cuando sea posible, hacer que los cambios privados sean solo de lectura diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index b75e2f94405c0..6a78cd9c5216d 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -147,6 +147,11 @@ Conflit(s) détecté(s). + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Configurer les champs privés en lecture seule quand cela est possible diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index f749e2dc547c4..05214c603fe6f 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -147,6 +147,11 @@ Sono stati rilevati conflitti. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Imposta i campi privati come di sola lettura quando possibile diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index 7c4a02707ccbf..ebde760822de0 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -147,6 +147,11 @@ 競合が検出されました。 + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 可能な場合、private フィールドを読み取り専用にする diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 511cf32e62a99..9ad11d56897f3 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -147,6 +147,11 @@ 충돌이 감지되었습니다. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 가능한 경우 프라이빗 필드를 읽기 전용으로 만들기 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index e3661326a5832..1c4c4d11af504 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -147,6 +147,11 @@ Wykryto konflikty. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Ustawiaj pola prywatne jako tylko do odczytu, gdy to możliwe diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index 9ed5bd0d1812d..b8e015b8b0729 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -147,6 +147,11 @@ Conflito(s) detectado(s). + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Tornar os campos privados somente leitura quando possível diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index 912550118cf24..f51d3d18bf487 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -147,6 +147,11 @@ Обнаружены конфликты. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible При возможности делать частные поля доступными только для чтения diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 68a44463aac56..609097e57f8a3 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -147,6 +147,11 @@ Çakışmalar algılandı. + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible Özel alanları mümkün olduğunda salt okunur yap diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index 14174db73cb63..4981c93a4d2d1 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -147,6 +147,11 @@ 检测到冲突。 + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 尽可能将私有字段设置为“只读” diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index f2b5e7fadfeae..d7811626a0a64 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -147,6 +147,11 @@ 偵測到衝突。 + + Make container 'async' + Make container 'async' + {Locked="async"} "async" is a C# keyword and should not be localized. + Make private fields readonly when possible 若可能的話,請將私用欄位設定為唯讀 From 447f01fa3cf880e2cdefe1514058b3c6e5132010 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 6 Mar 2021 12:40:17 +0000 Subject: [PATCH 03/38] Fixes --- .../Completion/CompletionProviders/AwaitCompletionProvider.cs | 2 +- .../CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index fba5a0d67cbcc..cab69098cb3bf 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -53,7 +53,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; if (symbol is not null && IsTask(symbol.ReturnType)) { - context.AddItem(CommonCompletionItem.Create("await", string.Empty, CompletionItemRules.Default, inlineDescription: "Make container async")); + context.AddItem(CommonCompletionItem.Create("await", string.Empty, CompletionItemRules.Default, glyph: Glyph.Keyword, inlineDescription: CSharpFeaturesResources.Make_container_async)); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index ba31d6844b856..d8422c64a0714 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -424,7 +424,7 @@ internal bool IsAwaitKeywordContext(int position) return false; } - if (node.IsKind(SyntaxKind.LockStatement, out LockStatementSyntax lockStatement)) + if (node.IsKind(SyntaxKind.LockStatement, out LockStatementSyntax? lockStatement)) { if (lockStatement.Statement != null && !lockStatement.Statement.IsMissing && From 5938d3d9736e50bdd2cff92ac2592790643524c3 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 6 Mar 2021 12:50:06 +0000 Subject: [PATCH 04/38] Use GetRequiredSemanticModelAsync instead --- .../Completion/CompletionProviders/AwaitCompletionProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index cab69098cb3bf..6d8d6c85a841c 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -37,8 +37,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var document = context.Document; var position = context.Position; var cancellationToken = context.CancellationToken; - var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); - + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var workspace = document.Project.Solution.Workspace; var syntaxContext = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken); From 73f43bb4d16613b1ec23568a25f3d238a79c3881 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 6 Mar 2021 15:42:48 +0200 Subject: [PATCH 05/38] Update CompletionProviderOrderTests.cs --- .../CompletionProviders/CompletionProviderOrderTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs index 1e9290fe68113..326eee4c12f32 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs @@ -38,6 +38,7 @@ public void TestCompletionProviderOrder() typeof(AttributeNamedParameterCompletionProvider), typeof(NamedParameterCompletionProvider), typeof(KeywordCompletionProvider), + typeof(AwaitCompletionProvider), typeof(SpeculativeTCompletionProvider), typeof(SymbolCompletionProvider), typeof(ExplicitInterfaceMemberCompletionProvider), From 994932faefd768e8b824ce97fb1b278135c7a4ae Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 25 Mar 2021 11:26:12 +0000 Subject: [PATCH 06/38] Make single completion provider for await --- .../AbstractCSharpCompletionProviderTests.cs | 10 ++ .../DeclarationNameCompletionProviderTests.cs | 9 -- .../AwaitKeywordRecommenderTests.cs | 116 ------------------ .../AwaitCompletionProvider.cs | 37 +++++- .../KeywordCompletionProvider.cs | 1 - .../AwaitKeywordRecommender.cs | 22 ---- 6 files changed, 43 insertions(+), 152 deletions(-) delete mode 100644 src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs delete mode 100644 src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs index bb8bfd3db76ec..184128d08c85c 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Completion; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; using Microsoft.CodeAnalysis.Editor.UnitTests.Completion; @@ -27,6 +28,15 @@ public abstract class AbstractCSharpCompletionProviderTests : { protected const string NonBreakingSpaceString = "\x00A0"; + protected static string GetMarkup(string source, LanguageVersion languageVersion) + => $@" + + +{source} + + +"; + protected override TestWorkspace CreateWorkspace(string fileContents) => TestWorkspace.CreateCSharp(fileContents, exportProvider: ExportProvider); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs index 7e943d722b0a4..c7ed13930a697 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs @@ -2712,14 +2712,5 @@ private static SerializableNamingRule CreateRule(SymbolSpecification specificati EnforcementLevel = ReportDiagnostic.Error }; } - - private static string GetMarkup(string source, LanguageVersion languageVersion) - => $@" - - -{source} - - -"; } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs deleted file mode 100644 index 2a840d609e63b..0000000000000 --- a/src/EditorFeatures/CSharpTest2/Recommendations/AwaitKeywordRecommenderTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -// 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. - -#nullable disable - -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations -{ - public class AwaitKeywordRecommenderTests : KeywordRecommenderTests - { - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotInTypeContext() - { - await VerifyAbsenceAsync(@" -class Program -{ - $$ -}"); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestStatementInMethod(bool isAsync, bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"$$", isAsync: isAsync, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestExpressionInAsyncMethod(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"var z = $$", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestUsingStatement(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"using $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestUsingDirective() - => await VerifyAbsenceAsync("using $$"); - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestForeachStatement(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"foreach $$", topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestNotInQuery(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"var z = from a in ""char"" - select $$", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestInFinally(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"try { } -finally { $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestInCatch(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"try { } -catch { $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestNotInLock(bool topLevelStatement) - { - await VerifyAbsenceAsync(AddInsideMethod( -@"lock(this) { $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestInAsyncLambdaInCatch(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"try { } -catch { var z = async () => $$ }", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - - [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - [CombinatorialData] - public async Task TestAwaitInLock(bool topLevelStatement) - { - await VerifyKeywordAsync(AddInsideMethod( -@"lock($$", isAsync: true, topLevelStatement: topLevelStatement), options: CSharp9ParseOptions); - } - } -} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 6d8d6c85a841c..931836c45c824 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Editing; @@ -19,11 +20,19 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { + /// + /// A completion provider for offering keyword. + /// This is implemented separately, not as a keyword recommender as it contains extra logic for making container method async. + /// + /// + /// The container is made async if and only if the containing method is returning a Task-like type. + /// [ExportCompletionProvider(nameof(AwaitCompletionProvider), LanguageNames.CSharp)] - [ExtensionOrder(After = nameof(KeywordCompletionProvider))] [Shared] internal sealed class AwaitCompletionProvider : LSPCompletionProvider { + private bool _shouldMakeContainerAsync = false; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public AwaitCompletionProvider() @@ -52,17 +61,36 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; if (symbol is not null && IsTask(symbol.ReturnType)) { - context.AddItem(CommonCompletionItem.Create("await", string.Empty, CompletionItemRules.Default, glyph: Glyph.Keyword, inlineDescription: CSharpFeaturesResources.Make_container_async)); + _shouldMakeContainerAsync = true; + context.AddItem(GetCompletionItem()); + return; } } + context.AddItem(GetCompletionItem()); + + // Local functions. static bool IsTask(ITypeSymbol returnType) => returnType.Name is "Task" or "ValueTask"; + + CompletionItem GetCompletionItem() => CommonCompletionItem.Create( + displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), + displayTextSuffix: "", + rules: CompletionItemRules.Default, + Glyph.Keyword, + description: RecommendedKeyword.CreateDisplayParts(SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), string.Empty), + inlineDescription: _shouldMakeContainerAsync ? CSharpFeaturesResources.Make_container_async : null, + isComplexTextEdit: _shouldMakeContainerAsync); } internal override async Task GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, bool disallowAddingImports, CancellationToken cancellationToken) { - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (!_shouldMakeContainerAsync) + { + return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); + } + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var declaration = root?.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); if (root is null || declaration is null) { @@ -74,7 +102,8 @@ internal override async Task GetChangeAsync(Document document, var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(document, declaration))); var formattedDocument = await Formatter.FormatAsync(documentWithAsyncModifier, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - var builder = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var builder); + builder.AddRange(await formattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false)); builder.Add(new TextChange(completionListSpan, completionItem.DisplayText)); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs index 20cec3f82ed49..e55496ace8686 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs @@ -34,7 +34,6 @@ public KeywordCompletionProvider() new AsKeywordRecommender(), new AssemblyKeywordRecommender(), new AsyncKeywordRecommender(), - new AwaitKeywordRecommender(), new BaseKeywordRecommender(), new BoolKeywordRecommender(), new BreakKeywordRecommender(), diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs deleted file mode 100644 index 46b3ae6604f28..0000000000000 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/AwaitKeywordRecommender.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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. - -#nullable disable - -using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; - -namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders -{ - internal class AwaitKeywordRecommender : AbstractSyntacticSingleKeywordRecommender - { - public AwaitKeywordRecommender() - : base(SyntaxKind.AwaitKeyword) - { - } - - protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) - => context.IsAwaitKeywordContext(position); - } -} From 1902c949d4d190d138aa4b175295e4d8f54d9cd1 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 25 Mar 2021 11:27:10 +0000 Subject: [PATCH 07/38] Tests --- .../AwaitCompletionProviderTests.cs | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs new file mode 100644 index 0000000000000..733da344c00bb --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -0,0 +1,184 @@ +// 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. + +#nullable disable + +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations +{ + [Trait(Traits.Feature, Traits.Features.Completion)] + public class AwaitKeywordRecommenderTests : AbstractCSharpCompletionProviderTests + { + internal override Type GetCompletionProviderType() => typeof(AwaitCompletionProvider); + + // Copy from: + // https://github.com/dotnet/roslyn/blob/71130c8d2c59c36b8c098bc77b1b281efdd40071/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs#L198 + private static string AddInsideMethod(string text, bool isAsync = false, string returnType = "void", bool topLevelStatement = false) + { + if (topLevelStatement) + { + return returnType switch + { + "void" => text, + "int" => text, + _ => throw new ArgumentException("Unsupported return type", nameof(returnType)), + }; + } + var builder = new StringBuilder(); + if (isAsync && returnType != "void") + { + builder.AppendLine("using System.Threading.Tasks;"); + } + builder.AppendLine("class C"); + builder.AppendLine("{"); + builder.Append(" "); + if (isAsync) + { + builder.Append("async "); + if (returnType == "void") + { + builder.Append("Task"); + } + else + { + builder.Append($"Task<{returnType}>"); + } + } + else + { + builder.Append(returnType); + } + builder.AppendLine(" F()"); + builder.AppendLine(" {"); + builder.Append(" ").Append(text); + builder.AppendLine(" }"); + builder.Append("}"); + return builder.ToString(); + } + + private async Task VerifyAbsenceAsync(string code) + { + await VerifyItemIsAbsentAsync(code, "await"); + } + + private async Task VerifyAbsenceAsync(string code, LanguageVersion languageVersion) + { + await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), "await"); + } + + private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion) + { + await VerifyItemExistsAsync(GetMarkup(code, languageVersion), "await"); + } + + [Fact] + public async Task TestNotInTypeContext() + { + await VerifyAbsenceAsync(@" +class Program +{ + $$ +}"); + } + + [Theory] + [CombinatorialData] + public async Task TestStatementInMethod(bool isAsync, bool topLevelStatement) + { + await VerifyKeywordAsync(AddInsideMethod( +@"$$", isAsync: isAsync, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Theory] + [CombinatorialData] + public async Task TestExpressionInAsyncMethod(bool topLevelStatement) + { + await VerifyKeywordAsync(AddInsideMethod( +@"var z = $$", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Theory] + [CombinatorialData] + public async Task TestUsingStatement(bool topLevelStatement) + { + await VerifyAbsenceAsync(AddInsideMethod( +@"using $$", topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestUsingDirective() + => await VerifyAbsenceAsync("using $$"); + + [Theory] + [CombinatorialData] + public async Task TestForeachStatement(bool topLevelStatement) + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach $$", topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Theory] + [CombinatorialData] + public async Task TestNotInQuery(bool topLevelStatement) + { + await VerifyAbsenceAsync(AddInsideMethod( +@"var z = from a in ""char"" + select $$", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Theory] + [CombinatorialData] + public async Task TestInFinally(bool topLevelStatement) + { + await VerifyKeywordAsync(AddInsideMethod( +@"try { } +finally { $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Theory] + [CombinatorialData] + public async Task TestInCatch(bool topLevelStatement) + { + await VerifyKeywordAsync(AddInsideMethod( +@"try { } +catch { $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Theory] + [CombinatorialData] + public async Task TestNotInLock(bool topLevelStatement) + { + await VerifyAbsenceAsync(AddInsideMethod( +@"lock(this) { $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Theory] + [CombinatorialData] + public async Task TestInAsyncLambdaInCatch(bool topLevelStatement) + { + await VerifyKeywordAsync(AddInsideMethod( +@"try { } +catch { var z = async () => $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + + [Theory] + [CombinatorialData] + public async Task TestAwaitInLock(bool topLevelStatement) + { + await VerifyKeywordAsync(AddInsideMethod( +@"lock($$", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + } + } +} From 39a17aac8a2d20c48cd2b409f8e4f1b510408137 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 25 Mar 2021 11:29:01 +0000 Subject: [PATCH 08/38] Remove #nullable disable --- .../CompletionProviders/AwaitCompletionProviderTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index 733da344c00bb..b3db0bf52a3eb 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Text; using System.Threading.Tasks; From a39c348f4f2ecfe5545d91e8bb7e9db8245c7cd4 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 25 Mar 2021 19:45:05 +0000 Subject: [PATCH 09/38] Tests and not use field in completion provider --- .../AwaitCompletionProvider.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 931836c45c824..6110054dfce05 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -31,8 +31,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers [Shared] internal sealed class AwaitCompletionProvider : LSPCompletionProvider { - private bool _shouldMakeContainerAsync = false; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public AwaitCompletionProvider() @@ -61,31 +59,32 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; if (symbol is not null && IsTask(symbol.ReturnType)) { - _shouldMakeContainerAsync = true; - context.AddItem(GetCompletionItem()); + context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); return; } } - context.AddItem(GetCompletionItem()); + context.AddItem(GetCompletionItem(shouldMakeContainerAsync: false)); // Local functions. static bool IsTask(ITypeSymbol returnType) => returnType.Name is "Task" or "ValueTask"; - CompletionItem GetCompletionItem() => CommonCompletionItem.Create( - displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), - displayTextSuffix: "", - rules: CompletionItemRules.Default, - Glyph.Keyword, - description: RecommendedKeyword.CreateDisplayParts(SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), string.Empty), - inlineDescription: _shouldMakeContainerAsync ? CSharpFeaturesResources.Make_container_async : null, - isComplexTextEdit: _shouldMakeContainerAsync); + static CompletionItem GetCompletionItem(bool shouldMakeContainerAsync) + => CommonCompletionItem.Create( + displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), + displayTextSuffix: "", + rules: CompletionItemRules.Default, + Glyph.Keyword, + description: RecommendedKeyword.CreateDisplayParts(SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), string.Empty), + inlineDescription: shouldMakeContainerAsync ? CSharpFeaturesResources.Make_container_async : null, + isComplexTextEdit: shouldMakeContainerAsync); } internal override async Task GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, bool disallowAddingImports, CancellationToken cancellationToken) { - if (!_shouldMakeContainerAsync) + // IsComplexTextEdit is true when we want to add async to the container. + if (!completionItem.IsComplexTextEdit) { return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); } From 50ca76efefc883439017de4b2e99d436f69ac846 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 26 Mar 2021 09:00:09 +0000 Subject: [PATCH 10/38] Introduce a language service to share common code --- ...tionCommandHandlerTests_AwaitCompletion.vb | 248 ++++++++++++++++++ .../AwaitCompletionProvider.cs | 8 +- ...rpMakeMethodAsynchronousCodeFixProvider.cs | 54 ++-- ...arpMakeMethodSynchronousCodeFixProvider.cs | 31 +-- ...dAsynchronousCodeFixProvider.KnownTypes.cs | 41 --- ...ctMakeMethodAsynchronousCodeFixProvider.cs | 64 ++--- ...actMakeMethodSynchronousCodeFixProvider.cs | 8 +- ...tractRemoveAsyncModifierCodeFixProvider.cs | 54 ++-- ...icMakeMethodAsynchronousCodeFixProvider.vb | 16 +- ...sicMakeMethodSynchronousCodeFixProvider.vb | 12 +- .../CSharpWorkspaceExtensions.projitems | 1 + .../CSharpMakeMethodAsynchronousService.cs | 26 ++ .../AbstractMakeMethodAsynchronousService.cs | 36 +++ .../IMakeMethodAsynchronousService.cs | 17 ++ .../KnownTaskTypes.cs | 34 +++ .../Core/WorkspaceExtensions.projitems | 3 + ...isualBasicMakeMethodAsynchronousService.vb | 23 ++ .../VisualBasicWorkspaceExtensions.projitems | 1 + 18 files changed, 488 insertions(+), 189 deletions(-) create mode 100644 src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb delete mode 100644 src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb new file mode 100644 index 0000000000000..45da8423974f4 --- /dev/null +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -0,0 +1,248 @@ +' 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. + +Imports Microsoft.CodeAnalysis.CSharp + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense + + + Public Class CSharpCompletionCommandHandlerTests_Await + + Public Async Function AwaitCompletionAddsAsync_MethodDeclaration() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public static async Task Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_LocalFunctionDeclaration() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public static Task Main() + { + async Task LocalFunc() + { + await + } + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_AnonymousMethodExpression() As Task + Using state = TestStateFactory.CreateCSharpTestState( + a = static delegate(int i) { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + + state.SendTab() + Assert.Equal(" +using System; + +public class C +{ + public void F() + { + Action a = static delegate(int i) { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_SimpleLambdaExpression() As Task + Using state = TestStateFactory.CreateCSharpTestState( + b = static a => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + + state.SendTab() + Assert.Equal(" +using System; + +public class C +{ + public void F() + { + Action b = static a => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression() As Task + Using state = TestStateFactory.CreateCSharpTestState( + c = static (a) => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + + state.SendTab() + Assert.Equal(" +using System; + +public class C +{ + public void F() + { + Action c = static (a) => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_NotTask() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public static Task Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_Trivia() As Task + Using state = TestStateFactory.CreateCSharpTestState( + + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System.Threading.Tasks; + +public class C +{ + public /*after public*/ static /*after static*/ async Task /*after task*/ Main() + { + await + } +} +", state.GetDocumentText()) + End Using + End Function + End Class +End Namespace diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 6110054dfce05..ca21df6a55ff4 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -56,8 +57,9 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); if (method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword)) { + var asyncService = document.GetRequiredLanguageService(); var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; - if (symbol is not null && IsTask(symbol.ReturnType)) + if (symbol is not null && asyncService.IsTaskLikeType(symbol.ReturnType, new KnownTaskTypes(semanticModel.Compilation))) { context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); return; @@ -66,10 +68,6 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) context.AddItem(GetCompletionItem(shouldMakeContainerAsync: false)); - // Local functions. - static bool IsTask(ITypeSymbol returnType) - => returnType.Name is "Task" or "ValueTask"; - static CompletionItem GetCompletionItem(bool shouldMakeContainerAsync) => CommonCompletionItem.Create( displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), diff --git a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 0362b897f2115..94f5990177ea1 100644 --- a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -45,20 +45,14 @@ protected override string GetMakeAsyncVoidFunctionResource() protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) => node.IsAsyncSupportingFunctionSyntax(); - protected override bool IsAsyncReturnType(ITypeSymbol type, KnownTypes knownTypes) - { - return IsIAsyncEnumerableOrEnumerator(type, knownTypes) - || IsTaskLike(type, knownTypes); - } - protected override SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, - KnownTypes knownTypes) + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) { switch (node) { - case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, knownTypes); - case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, knownTypes); + case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, makeAsyncService, knownTaskTypes); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, makeAsyncService, knownTaskTypes); case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); @@ -68,25 +62,25 @@ protected override SyntaxNode AddAsyncTokenAndFixReturnType( private static SyntaxNode FixMethod( bool keepVoid, IMethodSymbol methodSymbol, MethodDeclarationSyntax method, - KnownTypes knownTypes) + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) { - var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, knownTypes); + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, makeAsyncService, knownTaskTypes); var newModifiers = AddAsyncModifierWithCorrectedTrivia(method.Modifiers, ref newReturnType); return method.WithReturnType(newReturnType).WithModifiers(newModifiers); } private static SyntaxNode FixLocalFunction( bool keepVoid, IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, - KnownTypes knownTypes) + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) { - var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, knownTypes); + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, makeAsyncService, knownTaskTypes); var newModifiers = AddAsyncModifierWithCorrectedTrivia(localFunction.Modifiers, ref newReturnType); return localFunction.WithReturnType(newReturnType).WithModifiers(newModifiers); } private static TypeSyntax FixMethodReturnType( bool keepVoid, IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, - KnownTypes knownTypes) + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) { var newReturnType = returnTypeSyntax.WithAdditionalAnnotations(Formatter.Annotation); @@ -94,33 +88,33 @@ private static TypeSyntax FixMethodReturnType( { if (!keepVoid) { - newReturnType = knownTypes._taskType.GenerateTypeSyntax(); + newReturnType = knownTaskTypes.Task.GenerateTypeSyntax(); } } else { var returnType = methodSymbol.ReturnType; - if (IsIEnumerable(returnType, knownTypes) && IsIterator(methodSymbol)) + if (IsIEnumerable(returnType, knownTaskTypes) && IsIterator(methodSymbol)) { - newReturnType = knownTypes._iAsyncEnumerableOfTTypeOpt is null + newReturnType = knownTaskTypes.IAsyncEnumerableOfT is null ? MakeGenericType("IAsyncEnumerable", methodSymbol.ReturnType) - : knownTypes._iAsyncEnumerableOfTTypeOpt.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + : knownTaskTypes.IAsyncEnumerableOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (IsIEnumerator(returnType, knownTypes) && IsIterator(methodSymbol)) + else if (IsIEnumerator(returnType, knownTaskTypes) && IsIterator(methodSymbol)) { - newReturnType = knownTypes._iAsyncEnumeratorOfTTypeOpt is null + newReturnType = knownTaskTypes.IAsyncEnumeratorOfT is null ? MakeGenericType("IAsyncEnumerator", methodSymbol.ReturnType) - : knownTypes._iAsyncEnumeratorOfTTypeOpt.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + : knownTaskTypes.IAsyncEnumeratorOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (IsIAsyncEnumerableOrEnumerator(returnType, knownTypes)) + else if (makeAsyncService.IsIAsyncEnumerableOrEnumerator(returnType, knownTaskTypes)) { // Leave the return type alone } - else if (!IsTaskLike(returnType, knownTypes)) + else if (!makeAsyncService.IsTaskLikeType(returnType, knownTaskTypes)) { // If it's not already Task-like, then wrap the existing return type // in Task<>. - newReturnType = knownTypes._taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax(); + newReturnType = knownTaskTypes.TaskOfT.Construct(methodSymbol.ReturnType).GenerateTypeSyntax(); } } @@ -146,15 +140,11 @@ static bool IsYield(SyntaxNode node) => node.IsKind(SyntaxKind.YieldBreakStatement, SyntaxKind.YieldReturnStatement); } - private static bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType, KnownTypes knownTypes) - => returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumerableOfTTypeOpt) || - returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumeratorOfTTypeOpt); - - private static bool IsIEnumerable(ITypeSymbol returnType, KnownTypes knownTypes) - => returnType.OriginalDefinition.Equals(knownTypes._iEnumerableOfTType); + private static bool IsIEnumerable(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + => returnType.OriginalDefinition.Equals(knownTaskTypes.IEnumerableOfT); - private static bool IsIEnumerator(ITypeSymbol returnType, KnownTypes knownTypes) - => returnType.OriginalDefinition.Equals(knownTypes._iEnumeratorOfTType); + private static bool IsIEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + => returnType.OriginalDefinition.Equals(knownTaskTypes.IEnumeratorOfT); private static SyntaxTokenList AddAsyncModifierWithCorrectedTrivia(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) { diff --git a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs index e04ef2518f657..07c8487bdb085 100644 --- a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs @@ -10,9 +10,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.MakeMethodSynchronous; using Microsoft.CodeAnalysis.Shared.Extensions; -using static Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider; namespace Microsoft.CodeAnalysis.CSharp.MakeMethodSynchronous { @@ -33,54 +33,55 @@ public CSharpMakeMethodSynchronousCodeFixProvider() protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) => node.IsAsyncSupportingFunctionSyntax(); - protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTypes knownTypes) + protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTaskTypes knownTaskTypes) { switch (node) { - case MethodDeclarationSyntax method: return FixMethod(methodSymbolOpt, method, knownTypes); - case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(methodSymbolOpt, localFunction, knownTypes); + case MethodDeclarationSyntax method: return FixMethod(methodSymbolOpt, method, knownTaskTypes); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(methodSymbolOpt, localFunction, knownTaskTypes); case AnonymousMethodExpressionSyntax method: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(method); case ParenthesizedLambdaExpressionSyntax lambda: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda); case SimpleLambdaExpressionSyntax lambda: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda); default: return node; } } - private static SyntaxNode FixMethod(IMethodSymbol methodSymbol, MethodDeclarationSyntax method, KnownTypes knownTypes) + + private static SyntaxNode FixMethod(IMethodSymbol methodSymbol, MethodDeclarationSyntax method, KnownTaskTypes knownTaskTypes) { - var newReturnType = FixMethodReturnType(methodSymbol, method.ReturnType, knownTypes); + var newReturnType = FixMethodReturnType(methodSymbol, method.ReturnType, knownTaskTypes); return RemoveAsyncModifierHelpers.WithoutAsyncModifier(method, newReturnType); } - private static SyntaxNode FixLocalFunction(IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, KnownTypes knownTypes) + private static SyntaxNode FixLocalFunction(IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, KnownTaskTypes knownTaskTypes) { - var newReturnType = FixMethodReturnType(methodSymbol, localFunction.ReturnType, knownTypes); + var newReturnType = FixMethodReturnType(methodSymbol, localFunction.ReturnType, knownTaskTypes); return RemoveAsyncModifierHelpers.WithoutAsyncModifier(localFunction, newReturnType); } - private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, KnownTypes knownTypes) + private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, KnownTaskTypes knownTaskTypes) { var newReturnType = returnTypeSyntax; var returnType = methodSymbol.ReturnType; - if (returnType.OriginalDefinition.Equals(knownTypes._taskType)) + if (returnType.OriginalDefinition.Equals(knownTaskTypes.Task)) { // If the return type is Task, then make the new return type "void". newReturnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)).WithTriviaFrom(returnTypeSyntax); } - else if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) + else if (returnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfT)) { // If the return type is Task, then make the new return type "T". newReturnType = returnType.GetTypeArguments()[0].GenerateTypeSyntax().WithTriviaFrom(returnTypeSyntax); } - else if (returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumerableOfTTypeOpt)) + else if (returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumerableOfT)) { // If the return type is IAsyncEnumerable, then make the new return type IEnumerable. - newReturnType = knownTypes._iEnumerableOfTType.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + newReturnType = knownTaskTypes.IEnumerableOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumeratorOfTTypeOpt)) + else if (returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumeratorOfT)) { // If the return type is IAsyncEnumerator, then make the new return type IEnumerator. - newReturnType = knownTypes._iEnumeratorOfTType.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + newReturnType = knownTaskTypes.IEnumeratorOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } return newReturnType; diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs deleted file mode 100644 index f902bc85c9c60..0000000000000 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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. - -#nullable disable - -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous -{ - internal abstract partial class AbstractMakeMethodAsynchronousCodeFixProvider - { - internal readonly struct KnownTypes - { - public readonly INamedTypeSymbol _taskType; - public readonly INamedTypeSymbol _taskOfTType; - public readonly INamedTypeSymbol _valueTaskType; - public readonly INamedTypeSymbol _valueTaskOfTTypeOpt; - - public readonly INamedTypeSymbol _iEnumerableOfTType; - public readonly INamedTypeSymbol _iEnumeratorOfTType; - - public readonly INamedTypeSymbol _iAsyncEnumerableOfTTypeOpt; - public readonly INamedTypeSymbol _iAsyncEnumeratorOfTTypeOpt; - - internal KnownTypes(Compilation compilation) - { - _taskType = compilation.TaskType(); - _taskOfTType = compilation.TaskOfTType(); - _valueTaskType = compilation.ValueTaskType(); - _valueTaskOfTTypeOpt = compilation.ValueTaskOfTType(); - - _iEnumerableOfTType = compilation.IEnumerableOfTType(); - _iEnumeratorOfTType = compilation.IEnumeratorOfTType(); - - _iAsyncEnumerableOfTTypeOpt = compilation.IAsyncEnumerableOfTType(); - _iAsyncEnumeratorOfTTypeOpt = compilation.IAsyncEnumeratorOfTType(); - } - } - } -} diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs index 75a120ea05a01..b708d19aadb5d 100644 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs @@ -17,15 +17,13 @@ namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous { - internal abstract partial class AbstractMakeMethodAsynchronousCodeFixProvider : CodeFixProvider + internal abstract class AbstractMakeMethodAsynchronousCodeFixProvider : CodeFixProvider { protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); - protected abstract bool IsAsyncReturnType(ITypeSymbol type, KnownTypes knownTypes); - protected abstract SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, - KnownTypes knownTypes); + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -48,8 +46,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // method if we convert it. The last is optional. It is only needed to know // if our member is already Task-Like, and that functionality recognizes // ValueTask if it is available, but does not care if it is not. - var knownTypes = new KnownTypes(compilation); - if (knownTypes._taskType == null || knownTypes._taskOfTType == null) + var knownTypes = new KnownTaskTypes(compilation); + if (knownTypes.Task == null || knownTypes.TaskOfT == null) { return; } @@ -100,20 +98,23 @@ private async Task FixNodeAsync( var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbolOpt = semanticModel.GetDeclaredSymbol(node, cancellationToken) as IMethodSymbol; var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var knownTypes = new KnownTypes(compilation); - if (NeedsRename(this, methodSymbolOpt, keepVoid, isEntryPoint, in knownTypes)) + var makeAsyncService = document.GetRequiredLanguageService(); + var knownTaskTypes = new KnownTaskTypes(compilation); + var isAsyncReturnType = makeAsyncService.IsAsyncReturnType(methodSymbolOpt.ReturnType, knownTaskTypes); + + if (NeedsRename(methodSymbolOpt, keepVoid, isEntryPoint, isAsyncReturnType)) { return await RenameThenAddAsyncTokenAsync( - keepVoid, document, node, methodSymbolOpt, knownTypes, cancellationToken).ConfigureAwait(false); + keepVoid, document, node, methodSymbolOpt, makeAsyncService, knownTaskTypes, cancellationToken).ConfigureAwait(false); } else { return await AddAsyncTokenAsync( - keepVoid, document, methodSymbolOpt, knownTypes, node, cancellationToken).ConfigureAwait(false); + keepVoid, document, methodSymbolOpt, makeAsyncService, knownTaskTypes, node, cancellationToken).ConfigureAwait(false); } - static bool NeedsRename(AbstractMakeMethodAsynchronousCodeFixProvider @this, IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, in KnownTypes knownTypes) + static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, bool isAsyncReturnType) { if (!methodSymbol.IsOrdinaryMethodOrLocalFunction()) { @@ -140,7 +141,7 @@ static bool NeedsRename(AbstractMakeMethodAsynchronousCodeFixProvider @this, IMe } else { - return !@this.IsAsyncReturnType(methodSymbol.ReturnType, knownTypes); + return !isAsyncReturnType; } } } @@ -157,7 +158,7 @@ private async Task RenameThenAddAsyncTokenAsync( Document document, SyntaxNode node, IMethodSymbol methodSymbol, - KnownTypes knownTypes, + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes, CancellationToken cancellationToken) { var name = methodSymbol.Name; @@ -176,7 +177,7 @@ private async Task RenameThenAddAsyncTokenAsync( { var semanticModel = await newDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var newMethod = (IMethodSymbol)semanticModel.GetDeclaredSymbol(newNode, cancellationToken); - return await AddAsyncTokenAsync(keepVoid, newDocument, newMethod, knownTypes, newNode, cancellationToken).ConfigureAwait(false); + return await AddAsyncTokenAsync(keepVoid, newDocument, newMethod, makeAsyncService, knownTaskTypes, newNode, cancellationToken).ConfigureAwait(false); } return newSolution; @@ -186,11 +187,11 @@ private async Task AddAsyncTokenAsync( bool keepVoid, Document document, IMethodSymbol methodSymbolOpt, - KnownTypes knownTypes, + IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes, SyntaxNode node, CancellationToken cancellationToken) { - var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, knownTypes); + var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, makeAsyncService, knownTaskTypes); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(node, newNode); @@ -199,37 +200,6 @@ private async Task AddAsyncTokenAsync( return newDocument.Project.Solution; } - protected static bool IsTaskLike(ITypeSymbol returnType, KnownTypes knownTypes) - { - if (returnType.Equals(knownTypes._taskType)) - { - return true; - } - - if (returnType.Equals(knownTypes._valueTaskType)) - { - return true; - } - - if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) - { - return true; - } - - if (returnType.OriginalDefinition.Equals(knownTypes._valueTaskOfTTypeOpt)) - { - return true; - } - - if (returnType.IsErrorType()) - { - return returnType.Name.Equals("Task") || - returnType.Name.Equals("ValueTask"); - } - - return false; - } - private class MyCodeAction : CodeAction.SolutionChangeAction { public MyCodeAction(string title, Func> createChangedSolution) diff --git a/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs index 1d05148dcdcbe..437826a4b8e77 100644 --- a/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs @@ -16,10 +16,10 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider; namespace Microsoft.CodeAnalysis.MakeMethodSynchronous { @@ -28,7 +28,7 @@ internal abstract class AbstractMakeMethodSynchronousCodeFixProvider : CodeFixPr public static readonly string EquivalenceKey = FeaturesResources.Make_method_synchronous; protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); - protected abstract SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTypes knownTypes); + protected abstract SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTaskTypes knownTaskTypes); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -94,10 +94,10 @@ private async Task RemoveAsyncTokenAsync( Document document, IMethodSymbol methodSymbolOpt, SyntaxNode node, CancellationToken cancellationToken) { var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var knownTypes = new KnownTypes(compilation); + var knownTaskTypes = new KnownTaskTypes(compilation); var annotation = new SyntaxAnnotation(); - var newNode = RemoveAsyncTokenAndFixReturnType(methodSymbolOpt, node, knownTypes) + var newNode = RemoveAsyncTokenAndFixReturnType(methodSymbolOpt, node, knownTaskTypes) .WithAdditionalAnnotations(Formatter.Annotation, annotation); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs index 0a40a44e153cd..ed56d77aebbb7 100644 --- a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs +++ b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs @@ -12,9 +12,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using KnownTypes = Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes; namespace Microsoft.CodeAnalysis.RemoveAsyncModifier { @@ -32,8 +32,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var cancellationToken = context.CancellationToken; - var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var knownTypes = new KnownTypes(compilation); + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var knownTaskTypes = new KnownTaskTypes(compilation); var diagnostic = context.Diagnostics.First(); var token = diagnostic.Location.FindToken(cancellationToken); @@ -44,6 +44,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var makeAsyncService = document.GetRequiredLanguageService(); var methodSymbol = GetMethodSymbol(node, semanticModel, cancellationToken); if (methodSymbol == null) @@ -51,7 +52,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - if (ShouldOfferFix(methodSymbol.ReturnType, knownTypes)) + if (makeAsyncService.IsTaskLikeType(methodSymbol.ReturnType, knownTaskTypes)) { context.RegisterCodeFix( new MyCodeAction(c => FixAsync(document, diagnostic, c)), @@ -66,7 +67,7 @@ protected sealed override async Task FixAllAsync( var generator = editor.Generator; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var compilation = semanticModel.Compilation; - var knownTypes = new KnownTypes(compilation); + var knownTaskTypes = new KnownTaskTypes(compilation); // For fix all we need to do nested locals or lambdas first, so order the diagnostics by location descending foreach (var diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start)) @@ -93,7 +94,7 @@ protected sealed override async Task FixAllAsync( var needsReturnStatementAdded = controlFlow == null || controlFlow.EndPointIsReachable; editor.ReplaceNode(node, - (updatedNode, generator) => RemoveAsyncModifier(generator, updatedNode, methodSymbol.ReturnType, knownTypes, needsReturnStatementAdded)); + (updatedNode, generator) => RemoveAsyncModifier(generator, updatedNode, methodSymbol.ReturnType, knownTaskTypes, needsReturnStatementAdded)); } } @@ -101,23 +102,18 @@ protected sealed override async Task FixAllAsync( => semanticModel.GetSymbolInfo(node, cancellationToken).Symbol as IMethodSymbol ?? semanticModel.GetDeclaredSymbol(node, cancellationToken) as IMethodSymbol; - private static bool ShouldOfferFix(ITypeSymbol returnType, KnownTypes knownTypes) - => IsTaskType(returnType, knownTypes) - || returnType.OriginalDefinition.Equals(knownTypes._taskOfTType) - || returnType.OriginalDefinition.Equals(knownTypes._valueTaskOfTTypeOpt); + private static bool IsTaskType(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + => returnType.OriginalDefinition.Equals(knownTaskTypes.Task) + || returnType.OriginalDefinition.Equals(knownTaskTypes.ValueTask); - private static bool IsTaskType(ITypeSymbol returnType, KnownTypes knownTypes) - => returnType.OriginalDefinition.Equals(knownTypes._taskType) - || returnType.OriginalDefinition.Equals(knownTypes._valueTaskType); - - private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTypes knownTypes, bool needsReturnStatementAdded) + private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes, bool needsReturnStatementAdded) { node = RemoveAsyncModifier(generator, node); var expression = generator.GetExpression(node); if (expression is TExpressionSyntax expressionBody) { - if (IsTaskType(returnType, knownTypes)) + if (IsTaskType(returnType, knownTaskTypes)) { // We need to add a `return Task.CompletedTask;` so we have to convert to a block body var blockBodiedNode = ConvertToBlockBody(node, expressionBody); @@ -132,13 +128,13 @@ private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode nod else { // For Task returning expression bodied methods we can just wrap the whole expression - var newExpressionBody = WrapExpressionWithTaskFromResult(generator, expressionBody, returnType, knownTypes); + var newExpressionBody = WrapExpressionWithTaskFromResult(generator, expressionBody, returnType, knownTaskTypes); node = generator.WithExpression(node, newExpressionBody); } } else { - if (IsTaskType(returnType, knownTypes)) + if (IsTaskType(returnType, knownTaskTypes)) { // If the end of the method isn't reachable, or there were no statements to analyze, then we // need to add an explicit return @@ -149,7 +145,7 @@ private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode nod } } - node = ChangeReturnStatements(generator, node, returnType, knownTypes); + node = ChangeReturnStatements(generator, node, returnType, knownTaskTypes); return node; } @@ -168,7 +164,7 @@ private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode nod private static SyntaxNode AddReturnStatement(SyntaxGenerator generator, SyntaxNode node) => generator.WithStatements(node, generator.GetStatements(node).Concat(generator.ReturnStatement())); - private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTypes knownTypes) + private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) { var editor = new SyntaxEditor(node, generator); @@ -182,13 +178,13 @@ private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode if (returnExpression is null) { // Convert return; into return Task.CompletedTask; - var returnTaskCompletedTask = GetReturnTaskCompletedTaskStatement(generator, returnType, knownTypes); + var returnTaskCompletedTask = GetReturnTaskCompletedTaskStatement(generator, returnType, knownTaskTypes); editor.ReplaceNode(returnSyntax, returnTaskCompletedTask); } else { // Convert return ; into return Task.FromResult(); - var newExpression = WrapExpressionWithTaskFromResult(generator, returnExpression, returnType, knownTypes); + var newExpression = WrapExpressionWithTaskFromResult(generator, returnExpression, returnType, knownTaskTypes); editor.ReplaceNode(returnExpression, newExpression); } } @@ -196,28 +192,28 @@ private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode return editor.GetChangedRoot(); } - private static SyntaxNode GetReturnTaskCompletedTaskStatement(SyntaxGenerator generator, ITypeSymbol returnType, KnownTypes knownTypes) + private static SyntaxNode GetReturnTaskCompletedTaskStatement(SyntaxGenerator generator, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) { SyntaxNode invocation; - if (returnType.OriginalDefinition.Equals(knownTypes._taskType)) + if (returnType.OriginalDefinition.Equals(knownTaskTypes.Task)) { - var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTypes._taskType); + var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTaskTypes.Task); invocation = generator.MemberAccessExpression(taskTypeExpression, nameof(Task.CompletedTask)); } else { - invocation = generator.ObjectCreationExpression(knownTypes._valueTaskType); + invocation = generator.ObjectCreationExpression(knownTaskTypes.ValueTask); } var statement = generator.ReturnStatement(invocation); return statement; } - private static SyntaxNode WrapExpressionWithTaskFromResult(SyntaxGenerator generator, SyntaxNode expression, ITypeSymbol returnType, KnownTypes knownTypes) + private static SyntaxNode WrapExpressionWithTaskFromResult(SyntaxGenerator generator, SyntaxNode expression, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) { - if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) + if (returnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfT)) { - var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTypes._taskType); + var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTaskTypes.Task!); var taskFromResult = generator.MemberAccessExpression(taskTypeExpression, nameof(Task.FromResult)); return generator.InvocationExpression(taskFromResult, expression.WithoutTrivia()).WithTriviaFrom(expression); } diff --git a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb index 097ae9ed190e0..ceed14ad9185b 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb @@ -48,13 +48,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Return node.IsAsyncSupportedFunctionSyntax() End Function - Protected Overrides Function IsAsyncReturnType(type As ITypeSymbol, knownTypes As KnownTypes) As Boolean - Return IsTaskLike(type, knownTypes) - End Function - Protected Overrides Function AddAsyncTokenAndFixReturnType( keepVoid As Boolean, methodSymbolOpt As IMethodSymbol, node As SyntaxNode, - knownTypes As KnownTypes) As SyntaxNode + makeAsyncService As IMakeMethodAsynchronousService, knownTaskTypes As KnownTaskTypes) As SyntaxNode If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) Then @@ -65,21 +61,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Return FixMultiLineLambdaExpression(DirectCast(node, MultiLineLambdaExpressionSyntax)) ElseIf node.IsKind(SyntaxKind.SubBlock) Then - Return FixSubBlock(keepVoid, DirectCast(node, MethodBlockSyntax), knownTypes._taskType) + Return FixSubBlock(keepVoid, DirectCast(node, MethodBlockSyntax), knownTaskTypes.Task) Else Return FixFunctionBlock( - methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTypes) + methodSymbolOpt, DirectCast(node, MethodBlockSyntax), makeAsyncService, knownTaskTypes) End If End Function - Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTypes As KnownTypes) As SyntaxNode + Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, makeAsyncService As IMakeMethodAsynchronousService, knownTaskTypes As KnownTaskTypes) As SyntaxNode Dim functionStatement = node.SubOrFunctionStatement Dim newFunctionStatement = AddAsyncKeyword(functionStatement) - If Not IsTaskLike(methodSymbol.ReturnType, knownTypes) Then + If Not makeAsyncService.IsTaskLikeType(methodSymbol.ReturnType, knownTaskTypes) Then ' if the current return type is not already task-list, then wrap it in Task(of ...) - Dim returnType = knownTypes._taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) + Dim returnType = knownTaskTypes.TaskOfT.Construct(methodSymbol.ReturnType).GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) newFunctionStatement = newFunctionStatement.WithAsClause( newFunctionStatement.AsClause.WithType(returnType)) End If diff --git a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb index 8995cb486e405..38015f3efe160 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb @@ -6,7 +6,7 @@ Imports System.Collections.Immutable Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider +Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous Imports Microsoft.CodeAnalysis.MakeMethodSynchronous Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -35,7 +35,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous Return node.IsAsyncSupportedFunctionSyntax() End Function - Protected Overrides Function RemoveAsyncTokenAndFixReturnType(methodSymbolOpt As IMethodSymbol, node As SyntaxNode, knownTypes As KnownTypes) As SyntaxNode + Protected Overrides Function RemoveAsyncTokenAndFixReturnType(methodSymbolOpt As IMethodSymbol, node As SyntaxNode, knownTaskTypes As KnownTaskTypes) As SyntaxNode If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) Then @@ -47,21 +47,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous ElseIf node.IsKind(SyntaxKind.SubBlock) Then Return FixSubBlock(DirectCast(node, MethodBlockSyntax)) Else - Return FixFunctionBlock(methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTypes) + Return FixFunctionBlock(methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTaskTypes) End If End Function - Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTypes As KnownTypes) As SyntaxNode + Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTaskTypes As KnownTaskTypes) As SyntaxNode Dim functionStatement = node.SubOrFunctionStatement ' if this returns Task(of T), then we want to convert this to a T returning function. ' if this returns Task, then we want to convert it to a Sub method. - If methodSymbol.ReturnType.OriginalDefinition.Equals(knownTypes._taskOfTType) Then + If methodSymbol.ReturnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfT) Then Dim newAsClause = functionStatement.AsClause.WithType(methodSymbol.ReturnType.GetTypeArguments()(0).GenerateTypeSyntax()) Dim newFunctionStatement = functionStatement.WithAsClause(newAsClause) newFunctionStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(newFunctionStatement) Return node.WithSubOrFunctionStatement(newFunctionStatement) - ElseIf Equals(methodSymbol.ReturnType.OriginalDefinition, knownTypes._taskType) Then + ElseIf Equals(methodSymbol.ReturnType.OriginalDefinition, knownTaskTypes.Task) Then ' Convert this to a 'Sub' method. Dim subStatement = SyntaxFactory.SubStatement( functionStatement.AttributeLists, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems index b0f5b302e7181..98d47c82d32bc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems @@ -40,6 +40,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs new file mode 100644 index 0000000000000..54c461af6f976 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.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; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.MakeMethodAsynchronous; + +namespace Microsoft.CodeAnalysis.CSharp.MakeMethodAsynchronous +{ + [ExportLanguageService(typeof(IMakeMethodAsynchronousService), LanguageNames.CSharp), Shared] + internal sealed class CSharpMakeMethodAsynchronousService : AbstractMakeMethodAsynchronousService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpMakeMethodAsynchronousService() + { + } + + public override bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes) + { + return IsIAsyncEnumerableOrEnumerator(type, knownTaskTypes) || IsTaskLikeType(type, knownTaskTypes); + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs new file mode 100644 index 0000000000000..9a6b7696b9230 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs @@ -0,0 +1,36 @@ +// 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 Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous +{ + internal abstract class AbstractMakeMethodAsynchronousService : IMakeMethodAsynchronousService + { + public abstract bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); + + public bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + => returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumerableOfT, SymbolEqualityComparer.Default) || + returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumeratorOfT, SymbolEqualityComparer.Default); + + public bool IsTaskLikeType(ITypeSymbol type, KnownTaskTypes knownTaskTypes) + { + if (type.Equals(knownTaskTypes.Task, SymbolEqualityComparer.Default) || + type.Equals(knownTaskTypes.ValueTask, SymbolEqualityComparer.Default) || + type.OriginalDefinition.Equals(knownTaskTypes.TaskOfT, SymbolEqualityComparer.Default) || + type.OriginalDefinition.Equals(knownTaskTypes.ValueTaskOfT, SymbolEqualityComparer.Default)) + { + return true; + } + + if (type.IsErrorType()) + { + return type.Name.Equals("Task") || + type.Name.Equals("ValueTask"); + } + + return false; + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs new file mode 100644 index 0000000000000..d32f1ba69bdd8 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs @@ -0,0 +1,17 @@ +// 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 Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous +{ + internal interface IMakeMethodAsynchronousService : ILanguageService + { + bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes); + + bool IsTaskLikeType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); + + bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs new file mode 100644 index 0000000000000..073a0fe529a62 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs @@ -0,0 +1,34 @@ +// 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 Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous +{ + internal readonly struct KnownTaskTypes + { + public INamedTypeSymbol? Task { get; } + public INamedTypeSymbol? TaskOfT { get; } + public INamedTypeSymbol? ValueTask { get; } + public INamedTypeSymbol? ValueTaskOfT { get; } + + public INamedTypeSymbol? IEnumerableOfT { get; } + public INamedTypeSymbol? IEnumeratorOfT { get; } + public INamedTypeSymbol? IAsyncEnumerableOfT { get; } + public INamedTypeSymbol? IAsyncEnumeratorOfT { get; } + + public KnownTaskTypes(Compilation compilation) + { + Task = compilation.TaskType(); + TaskOfT = compilation.TaskOfTType(); + ValueTask = compilation.ValueTaskType(); + ValueTaskOfT = compilation.ValueTaskOfTType(); + + IEnumerableOfT = compilation.IEnumerableOfTType(); + IEnumeratorOfT = compilation.IEnumeratorOfTType(); + IAsyncEnumerableOfT = compilation.IAsyncEnumerableOfTType(); + IAsyncEnumeratorOfT = compilation.IAsyncEnumeratorOfTType(); + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems index 433f11d995fd4..10554fb493e95 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems @@ -34,6 +34,9 @@ + + + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb new file mode 100644 index 0000000000000..81e44976d7376 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb @@ -0,0 +1,23 @@ +' 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. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous + +Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous + + Friend Class VisualBasicMakeMethodAsynchronousService + Inherits AbstractMakeMethodAsynchronousService + + + + Public Sub New() + End Sub + + Public Overrides Function IsAsyncReturnType(type As ITypeSymbol, knownTaskTypes As KnownTaskTypes) As Boolean + Return IsTaskLikeType(type, knownTaskTypes) + End Function + End Class +End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems index 82a991fe0937e..7656a57668db7 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems @@ -38,6 +38,7 @@ + From 36b1ac8b2f354494b9a406f468c32968a1fded01 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 26 Mar 2021 09:21:13 +0000 Subject: [PATCH 11/38] Comments --- .../CompletionProviders/AwaitCompletionProviderTests.cs | 6 ++++++ .../CompletionProviders/AwaitCompletionProvider.cs | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index b3db0bf52a3eb..a1b496b0c05bc 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -14,6 +14,12 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations { + /// + /// The adds async modifier if the return type is Task or ValueTask. + /// The tests here are only when it does NOT add async modifier. + /// Tests when it adds async modifier are in: + /// src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb + /// [Trait(Traits.Feature, Traits.Features.Completion)] public class AwaitKeywordRecommenderTests : AbstractCSharpCompletionProviderTests { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index ca21df6a55ff4..805bd569ad521 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -94,8 +94,6 @@ internal override async Task GetChangeAsync(Document document, return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); } - // MethodDeclarationSyntax, LocalFunctionStatementSyntax, AnonymousMethodExpressionSyntax, ParenthesizedLambdaExpressionSyntax, SimpleLambdaExpressionSyntax. - var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(document, declaration))); var formattedDocument = await Formatter.FormatAsync(documentWithAsyncModifier, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); From a5eeb677d217131134e0e1ca544bb82ced711c24 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 26 Mar 2021 10:03:26 +0000 Subject: [PATCH 12/38] Clean tests --- .../AwaitCompletionProviderTests.cs | 273 +++++++++++------- 1 file changed, 176 insertions(+), 97 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index a1b496b0c05bc..93bbd3e9a4283 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -16,8 +16,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations { /// /// The adds async modifier if the return type is Task or ValueTask. - /// The tests here are only when it does NOT add async modifier. - /// Tests when it adds async modifier are in: + /// The tests here are only checking whether the completion item is provided or not. + /// Tests for checking adding async modifier are in: /// src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb /// [Trait(Traits.Feature, Traits.Features.Completion)] @@ -25,51 +25,6 @@ public class AwaitKeywordRecommenderTests : AbstractCSharpCompletionProviderTest { internal override Type GetCompletionProviderType() => typeof(AwaitCompletionProvider); - // Copy from: - // https://github.com/dotnet/roslyn/blob/71130c8d2c59c36b8c098bc77b1b281efdd40071/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs#L198 - private static string AddInsideMethod(string text, bool isAsync = false, string returnType = "void", bool topLevelStatement = false) - { - if (topLevelStatement) - { - return returnType switch - { - "void" => text, - "int" => text, - _ => throw new ArgumentException("Unsupported return type", nameof(returnType)), - }; - } - var builder = new StringBuilder(); - if (isAsync && returnType != "void") - { - builder.AppendLine("using System.Threading.Tasks;"); - } - builder.AppendLine("class C"); - builder.AppendLine("{"); - builder.Append(" "); - if (isAsync) - { - builder.Append("async "); - if (returnType == "void") - { - builder.Append("Task"); - } - else - { - builder.Append($"Task<{returnType}>"); - } - } - else - { - builder.Append(returnType); - } - builder.AppendLine(" F()"); - builder.AppendLine(" {"); - builder.Append(" ").Append(text); - builder.AppendLine(" }"); - builder.Append("}"); - return builder.ToString(); - } - private async Task VerifyAbsenceAsync(string code) { await VerifyItemIsAbsentAsync(code, "await"); @@ -95,94 +50,218 @@ class Program }"); } - [Theory] - [CombinatorialData] - public async Task TestStatementInMethod(bool isAsync, bool topLevelStatement) + [Fact] + public async Task TestStatementInMethod() + { + await VerifyKeywordAsync(@" +class C +{ + void F() + { + $$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestStatementInMethod_Async() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + $$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestStatementInMethod_TopLevel() + { + await VerifyKeywordAsync("$$", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestExpressionInAsyncMethod() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + var z = $$ } +} +", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestExpressionInAsyncMethod_TopLevel() { - await VerifyKeywordAsync(AddInsideMethod( -@"$$", isAsync: isAsync, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + await VerifyKeywordAsync("var z = $$", LanguageVersion.CSharp9); } - [Theory] - [CombinatorialData] - public async Task TestExpressionInAsyncMethod(bool topLevelStatement) + [Fact] + public async Task TestUsingStatement() { - await VerifyKeywordAsync(AddInsideMethod( -@"var z = $$", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + using $$ } +}", LanguageVersion.CSharp9); } - [Theory] - [CombinatorialData] - public async Task TestUsingStatement(bool topLevelStatement) + [Fact] + public async Task TestUsingStatement_TopLevel() { - await VerifyAbsenceAsync(AddInsideMethod( -@"using $$", topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + await VerifyAbsenceAsync("using $$", LanguageVersion.CSharp9); } [Fact] public async Task TestUsingDirective() => await VerifyAbsenceAsync("using $$"); - [Theory] - [CombinatorialData] - public async Task TestForeachStatement(bool topLevelStatement) + [Fact] + public async Task TestForeachStatement() { - await VerifyAbsenceAsync(AddInsideMethod( -@"foreach $$", topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + foreach $$ } +}", LanguageVersion.CSharp9); } - [Theory] - [CombinatorialData] - public async Task TestNotInQuery(bool topLevelStatement) + [Fact] + public async Task TestForeachStatement_TopLevel() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyAbsenceAsync("foreach $$", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInQuery() + { + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + var z = from a in ""char"" + select $$ } + } +", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInQuery_TopLevel() + { + await VerifyAbsenceAsync( @"var z = from a in ""char"" - select $$", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + select $$", LanguageVersion.CSharp9); } [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] - [Theory] - [CombinatorialData] - public async Task TestInFinally(bool topLevelStatement) + [Fact] + public async Task TestInFinally() { - await VerifyKeywordAsync(AddInsideMethod( + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + try { } +finally { $$ } } +}", LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Fact] + public async Task TestInFinally_TopLevel() + { + await VerifyKeywordAsync( @"try { } -finally { $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); +finally { $$ }", LanguageVersion.CSharp9); } [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] - [Theory] - [CombinatorialData] - public async Task TestInCatch(bool topLevelStatement) + [Fact] + public async Task TestInCatch() { - await VerifyKeywordAsync(AddInsideMethod( + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + try { } +catch { $$ } } +}", LanguageVersion.CSharp9); + } + + [WorkItem(907052, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")] + [Fact] + public async Task TestInCatch_TopLevel() + { + await VerifyKeywordAsync( @"try { } -catch { $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); +catch { $$ }", LanguageVersion.CSharp9); } - [Theory] - [CombinatorialData] - public async Task TestNotInLock(bool topLevelStatement) + [Fact] + public async Task TestNotInLock() { - await VerifyAbsenceAsync(AddInsideMethod( -@"lock(this) { $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + await VerifyAbsenceAsync(@" +class C +{ + async Task F() + { + lock(this) { $$ } } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestNotInLock_TopLevel() + { + await VerifyAbsenceAsync("lock (this) { $$ }", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestInAsyncLambdaInCatch() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + try { } +catch { var z = async () => $$ } } +}", LanguageVersion.CSharp9); } - [Theory] - [CombinatorialData] - public async Task TestInAsyncLambdaInCatch(bool topLevelStatement) + [Fact] + public async Task TestInAsyncLambdaInCatch_TopLevel() { - await VerifyKeywordAsync(AddInsideMethod( + await VerifyKeywordAsync( @"try { } -catch { var z = async () => $$ }", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); +catch { var z = async () => $$ }", LanguageVersion.CSharp9); } - [Theory] - [CombinatorialData] - public async Task TestAwaitInLock(bool topLevelStatement) + [Fact] + public async Task TestAwaitInLock() + { + await VerifyKeywordAsync(@" +class C +{ + async Task F() + { + lock($$ } +}", LanguageVersion.CSharp9); + } + + [Fact] + public async Task TestAwaitInLock_TopLevel() { - await VerifyKeywordAsync(AddInsideMethod( -@"lock($$", isAsync: true, topLevelStatement: topLevelStatement), LanguageVersion.CSharp9); + await VerifyKeywordAsync("lock($$", LanguageVersion.CSharp9); } } } From 0431d4ac548d98bd06263a3d413adedffd0a42af Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 26 Mar 2021 15:55:03 +0200 Subject: [PATCH 13/38] Fix NRE --- .../AbstractMakeMethodAsynchronousCodeFixProvider.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs index b708d19aadb5d..d00dd7a60e076 100644 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs @@ -101,9 +101,8 @@ private async Task FixNodeAsync( var makeAsyncService = document.GetRequiredLanguageService(); var knownTaskTypes = new KnownTaskTypes(compilation); - var isAsyncReturnType = makeAsyncService.IsAsyncReturnType(methodSymbolOpt.ReturnType, knownTaskTypes); - if (NeedsRename(methodSymbolOpt, keepVoid, isEntryPoint, isAsyncReturnType)) + if (NeedsRename(methodSymbolOpt, keepVoid, isEntryPoint, makeAsyncService, knownTaskTypes)) { return await RenameThenAddAsyncTokenAsync( keepVoid, document, node, methodSymbolOpt, makeAsyncService, knownTaskTypes, cancellationToken).ConfigureAwait(false); @@ -114,7 +113,7 @@ private async Task FixNodeAsync( keepVoid, document, methodSymbolOpt, makeAsyncService, knownTaskTypes, node, cancellationToken).ConfigureAwait(false); } - static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, bool isAsyncReturnType) + static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) { if (!methodSymbol.IsOrdinaryMethodOrLocalFunction()) { @@ -141,7 +140,7 @@ static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryP } else { - return !isAsyncReturnType; + return !makeAsyncService.IsAsyncReturnType(methodSymbolOpt.ReturnType, knownTaskTypes);; } } } From 71cce7f834787de9434a6d3d2bca7e58302134ec Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 30 Mar 2021 17:03:45 +0200 Subject: [PATCH 14/38] Fix --- .../AbstractMakeMethodAsynchronousCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs index d00dd7a60e076..d906c75d29f0c 100644 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs @@ -140,7 +140,7 @@ static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryP } else { - return !makeAsyncService.IsAsyncReturnType(methodSymbolOpt.ReturnType, knownTaskTypes);; + return !makeAsyncService.IsAsyncReturnType(methodSymbol.ReturnType, knownTaskTypes); } } } From c4aa2bbd7f6bf93c8d396d9f309b3c704600112f Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 08:05:33 +0000 Subject: [PATCH 15/38] Address feedback --- .../CSharpCompletionCommandHandlerTests_AwaitCompletion.vb | 4 ++-- .../CompletionProviders/AwaitCompletionProvider.cs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index 45da8423974f4..f25764a20fc42 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -180,7 +180,7 @@ public class C End Function - Public Async Function AwaitCompletionAddsAsync_NotTask() As Task + Public Async Function AwaitCompletionDoesNotAddAsync_NotTask() As Task Using state = TestStateFactory.CreateCSharpTestState( [ExportCompletionProvider(nameof(AwaitCompletionProvider), LanguageNames.CSharp)] + [ExtensionOrder(After = nameof(KeywordCompletionProvider))] [Shared] internal sealed class AwaitCompletionProvider : LSPCompletionProvider { @@ -88,8 +89,8 @@ internal override async Task GetChangeAsync(Document document, } var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var declaration = root?.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); - if (root is null || declaration is null) + var declaration = root.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + if (declaration is null) { return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); } From 8cfc8f9b5e7432879a933fa0c4130c5b871c83d9 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 08:39:08 +0000 Subject: [PATCH 16/38] Test glyph and inline description in unit tests --- .../AwaitCompletionProviderTests.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index 93bbd3e9a4283..2093c15329c87 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; @@ -35,9 +34,9 @@ private async Task VerifyAbsenceAsync(string code, LanguageVersion languageVersi await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), "await"); } - private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion) + private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion, string inlineDescription = null) { - await VerifyItemExistsAsync(GetMarkup(code, languageVersion), "await"); + await VerifyItemExistsAsync(GetMarkup(code, languageVersion), "await", glyph: (int)Glyph.Keyword, inlineDescription: inlineDescription); } [Fact] @@ -93,6 +92,19 @@ async Task F() ", LanguageVersion.CSharp9); } + [Fact] + public async Task TestExpressionInNonAsyncMethodWithTaskReturn() + { + await VerifyKeywordAsync(@" +class C +{ + Task F() + { + var z = $$ } +} +", LanguageVersion.CSharp9, CSharpFeaturesResources.Make_container_async); + } + [Fact] public async Task TestExpressionInAsyncMethod_TopLevel() { From 45a9f6b2bf975d76159c60455804712649db1d2b Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 09:38:29 +0000 Subject: [PATCH 17/38] Support anonymous functions --- .../AwaitCompletionProviderTests.cs | 2 +- ...tionCommandHandlerTests_AwaitCompletion.vb | 113 +++++++++++++++++- .../AwaitCompletionProvider.cs | 1 + .../CodeGeneration/CSharpSyntaxGenerator.cs | 5 +- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 1 + 5 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index 2093c15329c87..453f1b1276f9a 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -34,7 +34,7 @@ private async Task VerifyAbsenceAsync(string code, LanguageVersion languageVersi await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), "await"); } - private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion, string inlineDescription = null) + private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion, string? inlineDescription = null) { await VerifyItemExistsAsync(GetMarkup(code, languageVersion), "await", glyph: (int)Glyph.Keyword, inlineDescription: inlineDescription); } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index f25764a20fc42..6cc342004774f 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -81,7 +81,7 @@ public class C End Function - Public Async Function AwaitCompletionAddsAsync_AnonymousMethodExpression() As Task + Public Async Function AwaitCompletionAddsAsync_AnonymousMethodExpression_Void() As Task Using state = TestStateFactory.CreateCSharpTestState( a = static delegate(int i) { await }; + Action a = static delegate (int i) { await }; } } ", state.GetDocumentText()) @@ -114,7 +114,42 @@ public class C End Function - Public Async Function AwaitCompletionAddsAsync_SimpleLambdaExpression() As Task + Public Async Function AwaitCompletionAddsAsync_AnonymousMethodExpression_Task() As Task + Using state = TestStateFactory.CreateCSharpTestState( + a = static delegate(int i) { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func a = static async delegate(int i) { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_SimpleLambdaExpression_Void() As Task Using state = TestStateFactory.CreateCSharpTestState( - Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression() As Task + Public Async Function AwaitCompletionAddsAsync_SimpleLambdaExpression_Task() As Task + Using state = TestStateFactory.CreateCSharpTestState( + b = static a => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func b = static async a => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + + + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression_Void() As Task Using state = TestStateFactory.CreateCSharpTestState( + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression_Task() As Task + Using state = TestStateFactory.CreateCSharpTestState( + c = static (a) => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func c = static async (a) => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + Public Async Function AwaitCompletionDoesNotAddAsync_NotTask() As Task Using state = TestStateFactory.CreateCSharpTestState( diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 08127e19f4bb9..8fd805c13d0cc 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -60,6 +60,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) { var asyncService = document.GetRequiredLanguageService(); var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; + symbol ??= semanticModel.GetSymbolInfo(method, cancellationToken).Symbol as IMethodSymbol; if (symbol is not null && asyncService.IsTaskLikeType(symbol.ReturnType, new KnownTaskTypes(semanticModel.Compilation))) { context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 36f009884ee5c..59256158bd858 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -1452,7 +1452,8 @@ public override SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibili DeclarationModifiers.Extern; private static readonly DeclarationModifiers s_lambdaModifiers = - DeclarationModifiers.Async; + DeclarationModifiers.Async | + DeclarationModifiers.Static; private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) { @@ -1562,7 +1563,7 @@ private static SyntaxNode SetModifierTokens(SyntaxNode declaration, SyntaxTokenL LocalDeclarationStatementSyntax localDecl => localDecl.WithModifiers(modifiers), LocalFunctionStatementSyntax localFunc => localFunc.WithModifiers(modifiers), AccessorDeclarationSyntax accessor => accessor.WithModifiers(modifiers), - LambdaExpressionSyntax lambda => lambda.WithModifiers(modifiers), + AnonymousFunctionExpressionSyntax anonymous => anonymous.WithModifiers(modifiers), _ => declaration, }; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index abf316d7a1e17..c00e92b4fdc91 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1901,6 +1901,7 @@ public override SyntaxTokenList GetModifierTokens(SyntaxNode? declaration) AccessorDeclarationSyntax accessor => accessor.Modifiers, VariableDeclarationSyntax varDecl => GetModifierTokens(varDecl.Parent), VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.Parent), + AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, _ => default, }; From 77bf329bbd637bc039ba5078730f63aebcbabdd5 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 09:40:36 +0000 Subject: [PATCH 18/38] Fix --- .../CSharpCompletionCommandHandlerTests_AwaitCompletion.vb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index 6cc342004774f..5f5a166756987 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -106,7 +106,7 @@ public class C { public void F() { - Action a = static delegate (int i) { await }; + Action a = static delegate(int i) { await }; } } ", state.GetDocumentText()) @@ -141,7 +141,7 @@ public class C { public void F() { - Func a = static async delegate(int i) { await }; + Func a = static async delegate (int i) { await }; } } ", state.GetDocumentText()) From 0caf239e7c683b6724077d3a9c66d557d25b28f0 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 12:46:45 +0200 Subject: [PATCH 19/38] Rename test class --- .../CompletionProviders/AwaitCompletionProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index 453f1b1276f9a..d0adf7cd941ce 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations /// src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb /// [Trait(Traits.Feature, Traits.Features.Completion)] - public class AwaitKeywordRecommenderTests : AbstractCSharpCompletionProviderTests + public class AwaitCompletionProviderTests : AbstractCSharpCompletionProviderTests { internal override Type GetCompletionProviderType() => typeof(AwaitCompletionProvider); From 61a0823aa57a7dcb27b86d08fa8566930d5708fc Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 11:49:28 +0000 Subject: [PATCH 20/38] Fix failing test --- .../Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 16926dcb5bc7c..c0e288bf54b54 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -941,6 +941,7 @@ public static SyntaxTokenList GetModifiers(this SyntaxNode? member) { case MemberDeclarationSyntax memberDecl: return memberDecl.Modifiers; case AccessorDeclarationSyntax accessor: return accessor.Modifiers; + case AnonymousFunctionExpressionSyntax anonymous: return anonymous.Modifiers; } return default; From 2f68771a5db539b50ef862302f94b42ffbaaebbb Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 31 Mar 2021 12:42:20 +0000 Subject: [PATCH 21/38] Fix TestCompletionProviderOrderMetadata --- .../CompletionProviders/SpeculativeTCompletionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs index 192ca16d3576b..522dea0daf69f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SpeculativeTCompletionProvider.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { [ExportCompletionProvider(nameof(SpeculativeTCompletionProvider), LanguageNames.CSharp)] - [ExtensionOrder(After = nameof(KeywordCompletionProvider))] + [ExtensionOrder(After = nameof(AwaitCompletionProvider))] [Shared] internal class SpeculativeTCompletionProvider : LSPCompletionProvider { From 408eed79602b93954306fec4f821c4c650ecf7ed Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Apr 2021 14:50:05 +0000 Subject: [PATCH 22/38] Move to KnownTaskTypes --- .../AwaitCompletionProvider.cs | 2 +- ...rpMakeMethodAsynchronousCodeFixProvider.cs | 20 +++++------ ...ctMakeMethodAsynchronousCodeFixProvider.cs | 14 ++++---- ...tractRemoveAsyncModifierCodeFixProvider.cs | 3 +- ...icMakeMethodAsynchronousCodeFixProvider.vb | 8 ++--- .../ContextQuery/CSharpSyntaxContext.cs | 3 ++ .../CSharpMakeMethodAsynchronousService.cs | 6 ++-- .../AbstractMakeMethodAsynchronousService.cs | 36 ------------------- .../IMakeMethodAsynchronousService.cs | 4 --- .../KnownTaskTypes.cs | 23 ++++++++++++ .../Core/WorkspaceExtensions.projitems | 1 - ...isualBasicMakeMethodAsynchronousService.vb | 6 ++-- 12 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 8fd805c13d0cc..e232e71edf21b 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -61,7 +61,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var asyncService = document.GetRequiredLanguageService(); var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; symbol ??= semanticModel.GetSymbolInfo(method, cancellationToken).Symbol as IMethodSymbol; - if (symbol is not null && asyncService.IsTaskLikeType(symbol.ReturnType, new KnownTaskTypes(semanticModel.Compilation))) + if (symbol is not null && asyncService.IsAsyncReturnType(symbol.ReturnType, new KnownTaskTypes(semanticModel.Compilation))) { context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); return; diff --git a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 94f5990177ea1..63f23019a172b 100644 --- a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -47,12 +47,12 @@ protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) protected override SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) + KnownTaskTypes knownTaskTypes) { switch (node) { - case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, makeAsyncService, knownTaskTypes); - case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, makeAsyncService, knownTaskTypes); + case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, knownTaskTypes); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, knownTaskTypes); case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); @@ -62,25 +62,25 @@ protected override SyntaxNode AddAsyncTokenAndFixReturnType( private static SyntaxNode FixMethod( bool keepVoid, IMethodSymbol methodSymbol, MethodDeclarationSyntax method, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) + KnownTaskTypes knownTaskTypes) { - var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, makeAsyncService, knownTaskTypes); + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, knownTaskTypes); var newModifiers = AddAsyncModifierWithCorrectedTrivia(method.Modifiers, ref newReturnType); return method.WithReturnType(newReturnType).WithModifiers(newModifiers); } private static SyntaxNode FixLocalFunction( bool keepVoid, IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) + KnownTaskTypes knownTaskTypes) { - var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, makeAsyncService, knownTaskTypes); + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, knownTaskTypes); var newModifiers = AddAsyncModifierWithCorrectedTrivia(localFunction.Modifiers, ref newReturnType); return localFunction.WithReturnType(newReturnType).WithModifiers(newModifiers); } private static TypeSyntax FixMethodReturnType( bool keepVoid, IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) + KnownTaskTypes knownTaskTypes) { var newReturnType = returnTypeSyntax.WithAdditionalAnnotations(Formatter.Annotation); @@ -106,11 +106,11 @@ private static TypeSyntax FixMethodReturnType( ? MakeGenericType("IAsyncEnumerator", methodSymbol.ReturnType) : knownTaskTypes.IAsyncEnumeratorOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (makeAsyncService.IsIAsyncEnumerableOrEnumerator(returnType, knownTaskTypes)) + else if (knownTaskTypes.IsIAsyncEnumerableOrEnumerator(returnType)) { // Leave the return type alone } - else if (!makeAsyncService.IsTaskLikeType(returnType, knownTaskTypes)) + else if (!knownTaskTypes.IsTaskLikeType(returnType)) { // If it's not already Task-like, then wrap the existing return type // in Task<>. diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs index d906c75d29f0c..bb4bc3b0bf8f5 100644 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs @@ -23,7 +23,7 @@ internal abstract class AbstractMakeMethodAsynchronousCodeFixProvider : CodeFixP protected abstract SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes); + KnownTaskTypes knownTaskTypes); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -105,12 +105,12 @@ private async Task FixNodeAsync( if (NeedsRename(methodSymbolOpt, keepVoid, isEntryPoint, makeAsyncService, knownTaskTypes)) { return await RenameThenAddAsyncTokenAsync( - keepVoid, document, node, methodSymbolOpt, makeAsyncService, knownTaskTypes, cancellationToken).ConfigureAwait(false); + keepVoid, document, node, methodSymbolOpt, knownTaskTypes, cancellationToken).ConfigureAwait(false); } else { return await AddAsyncTokenAsync( - keepVoid, document, methodSymbolOpt, makeAsyncService, knownTaskTypes, node, cancellationToken).ConfigureAwait(false); + keepVoid, document, methodSymbolOpt, knownTaskTypes, node, cancellationToken).ConfigureAwait(false); } static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) @@ -157,7 +157,7 @@ private async Task RenameThenAddAsyncTokenAsync( Document document, SyntaxNode node, IMethodSymbol methodSymbol, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes, + KnownTaskTypes knownTaskTypes, CancellationToken cancellationToken) { var name = methodSymbol.Name; @@ -176,7 +176,7 @@ private async Task RenameThenAddAsyncTokenAsync( { var semanticModel = await newDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var newMethod = (IMethodSymbol)semanticModel.GetDeclaredSymbol(newNode, cancellationToken); - return await AddAsyncTokenAsync(keepVoid, newDocument, newMethod, makeAsyncService, knownTaskTypes, newNode, cancellationToken).ConfigureAwait(false); + return await AddAsyncTokenAsync(keepVoid, newDocument, newMethod, knownTaskTypes, newNode, cancellationToken).ConfigureAwait(false); } return newSolution; @@ -186,11 +186,11 @@ private async Task AddAsyncTokenAsync( bool keepVoid, Document document, IMethodSymbol methodSymbolOpt, - IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes, + KnownTaskTypes knownTaskTypes, SyntaxNode node, CancellationToken cancellationToken) { - var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, makeAsyncService, knownTaskTypes); + var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, knownTaskTypes); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(node, newNode); diff --git a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs index ed56d77aebbb7..cc8798ef40a34 100644 --- a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs +++ b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs @@ -44,7 +44,6 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var makeAsyncService = document.GetRequiredLanguageService(); var methodSymbol = GetMethodSymbol(node, semanticModel, cancellationToken); if (methodSymbol == null) @@ -52,7 +51,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - if (makeAsyncService.IsTaskLikeType(methodSymbol.ReturnType, knownTaskTypes)) + if (knownTaskTypes.IsTaskLikeType(methodSymbol.ReturnType)) { context.RegisterCodeFix( new MyCodeAction(c => FixAsync(document, diagnostic, c)), diff --git a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb index ceed14ad9185b..7ccef585543e4 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb @@ -50,7 +50,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Protected Overrides Function AddAsyncTokenAndFixReturnType( keepVoid As Boolean, methodSymbolOpt As IMethodSymbol, node As SyntaxNode, - makeAsyncService As IMakeMethodAsynchronousService, knownTaskTypes As KnownTaskTypes) As SyntaxNode + knownTaskTypes As KnownTaskTypes) As SyntaxNode If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) Then @@ -64,16 +64,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Return FixSubBlock(keepVoid, DirectCast(node, MethodBlockSyntax), knownTaskTypes.Task) Else Return FixFunctionBlock( - methodSymbolOpt, DirectCast(node, MethodBlockSyntax), makeAsyncService, knownTaskTypes) + methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTaskTypes) End If End Function - Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, makeAsyncService As IMakeMethodAsynchronousService, knownTaskTypes As KnownTaskTypes) As SyntaxNode + Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTaskTypes As KnownTaskTypes) As SyntaxNode Dim functionStatement = node.SubOrFunctionStatement Dim newFunctionStatement = AddAsyncKeyword(functionStatement) - If Not makeAsyncService.IsTaskLikeType(methodSymbol.ReturnType, knownTaskTypes) Then + If Not knownTaskTypes.IsTaskLikeType(methodSymbol.ReturnType) Then ' if the current return type is not already task-list, then wrap it in Task(of ...) Dim returnType = knownTaskTypes.TaskOfT.Construct(methodSymbol.ReturnType).GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) newFunctionStatement = newFunctionStatement.WithAsClause( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index d8422c64a0714..024e7c6a6fd1b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -403,6 +403,9 @@ internal bool IsAwaitStatementContext(int position, CancellationToken cancellati return false; } + /// + /// Determines whether await should be suggested in a given position. + /// internal bool IsAwaitKeywordContext(int position) { if (IsGlobalStatementContext) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs index 54c461af6f976..a3a6f608a7bf4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.CSharp.MakeMethodAsynchronous { [ExportLanguageService(typeof(IMakeMethodAsynchronousService), LanguageNames.CSharp), Shared] - internal sealed class CSharpMakeMethodAsynchronousService : AbstractMakeMethodAsynchronousService + internal sealed class CSharpMakeMethodAsynchronousService : IMakeMethodAsynchronousService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -18,9 +18,9 @@ public CSharpMakeMethodAsynchronousService() { } - public override bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes) + public bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes) { - return IsIAsyncEnumerableOrEnumerator(type, knownTaskTypes) || IsTaskLikeType(type, knownTaskTypes); + return knownTaskTypes.IsIAsyncEnumerableOrEnumerator(type) || knownTaskTypes.IsTaskLikeType(type); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs deleted file mode 100644 index 9a6b7696b9230..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/AbstractMakeMethodAsynchronousService.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous -{ - internal abstract class AbstractMakeMethodAsynchronousService : IMakeMethodAsynchronousService - { - public abstract bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); - - public bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) - => returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumerableOfT, SymbolEqualityComparer.Default) || - returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumeratorOfT, SymbolEqualityComparer.Default); - - public bool IsTaskLikeType(ITypeSymbol type, KnownTaskTypes knownTaskTypes) - { - if (type.Equals(knownTaskTypes.Task, SymbolEqualityComparer.Default) || - type.Equals(knownTaskTypes.ValueTask, SymbolEqualityComparer.Default) || - type.OriginalDefinition.Equals(knownTaskTypes.TaskOfT, SymbolEqualityComparer.Default) || - type.OriginalDefinition.Equals(knownTaskTypes.ValueTaskOfT, SymbolEqualityComparer.Default)) - { - return true; - } - - if (type.IsErrorType()) - { - return type.Name.Equals("Task") || - type.Name.Equals("ValueTask"); - } - - return false; - } - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs index d32f1ba69bdd8..94e438701ea35 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs @@ -8,10 +8,6 @@ namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous { internal interface IMakeMethodAsynchronousService : ILanguageService { - bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes); - - bool IsTaskLikeType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); - bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs index 073a0fe529a62..f8d1be072947b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs @@ -30,5 +30,28 @@ public KnownTaskTypes(Compilation compilation) IAsyncEnumerableOfT = compilation.IAsyncEnumerableOfTType(); IAsyncEnumeratorOfT = compilation.IAsyncEnumeratorOfTType(); } + + public bool IsTaskLikeType(ITypeSymbol type) + { + if (type.Equals(Task, SymbolEqualityComparer.Default) || + type.Equals(ValueTask, SymbolEqualityComparer.Default) || + type.OriginalDefinition.Equals(TaskOfT, SymbolEqualityComparer.Default) || + type.OriginalDefinition.Equals(ValueTaskOfT, SymbolEqualityComparer.Default)) + { + return true; + } + + if (type.IsErrorType()) + { + return type.Name.Equals("Task") || + type.Name.Equals("ValueTask"); + } + + return false; + } + + public bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType) + => returnType.OriginalDefinition.Equals(IAsyncEnumerableOfT, SymbolEqualityComparer.Default) || + returnType.OriginalDefinition.Equals(IAsyncEnumeratorOfT, SymbolEqualityComparer.Default); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems index 10554fb493e95..e364d51096e31 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems @@ -34,7 +34,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb index 81e44976d7376..dce5485363782 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb @@ -9,15 +9,15 @@ Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Friend Class VisualBasicMakeMethodAsynchronousService - Inherits AbstractMakeMethodAsynchronousService + Implements IMakeMethodAsynchronousService Public Sub New() End Sub - Public Overrides Function IsAsyncReturnType(type As ITypeSymbol, knownTaskTypes As KnownTaskTypes) As Boolean - Return IsTaskLikeType(type, knownTaskTypes) + Public Function IsAsyncReturnType(type As ITypeSymbol, knownTaskTypes As KnownTaskTypes) As Boolean Implements IMakeMethodAsynchronousService.IsAsyncReturnType + Return knownTaskTypes.IsTaskLikeType(type) End Function End Class End Namespace From bf2ccad0d816a156636ec6b1739b0690275a466b Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 4 May 2021 16:41:01 +0200 Subject: [PATCH 23/38] Fix --- .../Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 735f12936d3f4..9ea1a69b1af4c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -966,7 +966,7 @@ public static SyntaxTokenList GetModifiers(this SyntaxNode? member) { case MemberDeclarationSyntax memberDecl: return memberDecl.WithModifiers(modifiers); case AccessorDeclarationSyntax accessor: return accessor.WithModifiers(modifiers); - case AnonymousFunctionExpressionSyntax anonymous: anonymous.WithModifiers(modifiers); + case AnonymousFunctionExpressionSyntax anonymous: return anonymous.WithModifiers(modifiers); case LocalFunctionStatementSyntax localFunction: return localFunction.WithModifiers(modifiers); case LocalDeclarationStatementSyntax localDeclaration: return localDeclaration.WithModifiers(modifiers); } From b5811857a60eceb1e96d5d507ec24e6218784c60 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 6 May 2021 06:49:51 +0000 Subject: [PATCH 24/38] Address feedback: override the public overload --- .../CompletionProviders/AwaitCompletionProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index e232e71edf21b..854dd80589b5d 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -81,19 +81,19 @@ static CompletionItem GetCompletionItem(bool shouldMakeContainerAsync) isComplexTextEdit: shouldMakeContainerAsync); } - internal override async Task GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, bool disallowAddingImports, CancellationToken cancellationToken) + public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) { // IsComplexTextEdit is true when we want to add async to the container. - if (!completionItem.IsComplexTextEdit) + if (!item.IsComplexTextEdit) { - return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); + return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); } var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var declaration = root.FindToken(completionListSpan.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); + var declaration = root.FindToken(item.Span.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); if (declaration is null) { - return await base.GetChangeAsync(document, completionItem, completionListSpan, commitKey, disallowAddingImports, cancellationToken).ConfigureAwait(false); + return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); } var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(document, declaration))); @@ -102,7 +102,7 @@ internal override async Task GetChangeAsync(Document document, using var _ = ArrayBuilder.GetInstance(out var builder); builder.AddRange(await formattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false)); - builder.Add(new TextChange(completionListSpan, completionItem.DisplayText)); + builder.Add(new TextChange(item.Span, item.DisplayText)); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newText = text.WithChanges(builder); From 2f921c3a31eab6176ba55f23e9fd0a0ef2fa789d Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 6 May 2021 07:39:09 +0000 Subject: [PATCH 25/38] Add async regardless return type --- .../AwaitCompletionProvider.cs | 9 +-- ...rpMakeMethodAsynchronousCodeFixProvider.cs | 54 +++++++++------- ...arpMakeMethodSynchronousCodeFixProvider.cs | 31 +++++---- ...dAsynchronousCodeFixProvider.KnownTypes.cs | 41 ++++++++++++ ...ctMakeMethodAsynchronousCodeFixProvider.cs | 63 ++++++++++++++----- ...actMakeMethodSynchronousCodeFixProvider.cs | 8 +-- ...tractRemoveAsyncModifierCodeFixProvider.cs | 53 +++++++++------- ...icMakeMethodAsynchronousCodeFixProvider.vb | 18 +++--- ...sicMakeMethodSynchronousCodeFixProvider.vb | 14 ++--- .../CSharpWorkspaceExtensions.projitems | 1 - .../CSharpMakeMethodAsynchronousService.cs | 26 -------- .../IMakeMethodAsynchronousService.cs | 13 ---- .../KnownTaskTypes.cs | 57 ----------------- .../Core/WorkspaceExtensions.projitems | 2 - ...isualBasicMakeMethodAsynchronousService.vb | 23 ------- .../VisualBasicWorkspaceExtensions.projitems | 1 - 16 files changed, 187 insertions(+), 227 deletions(-) create mode 100644 src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 854dd80589b5d..fcccb4d83878e 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -58,14 +58,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); if (method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword)) { - var asyncService = document.GetRequiredLanguageService(); - var symbol = semanticModel.GetDeclaredSymbol(method, cancellationToken) as IMethodSymbol; - symbol ??= semanticModel.GetSymbolInfo(method, cancellationToken).Symbol as IMethodSymbol; - if (symbol is not null && asyncService.IsAsyncReturnType(symbol.ReturnType, new KnownTaskTypes(semanticModel.Compilation))) - { - context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); - return; - } + context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); } context.AddItem(GetCompletionItem(shouldMakeContainerAsync: false)); diff --git a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 93284ff06f219..79c2085b9d102 100644 --- a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -45,14 +45,20 @@ protected override string GetMakeAsyncVoidFunctionResource() protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) => node.IsAsyncSupportingFunctionSyntax(); + protected override bool IsAsyncReturnType(ITypeSymbol type, KnownTypes knownTypes) + { + return IsIAsyncEnumerableOrEnumerator(type, knownTypes) + || IsTaskLike(type, knownTypes); + } + protected override SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, - KnownTaskTypes knownTaskTypes) + KnownTypes knownTypes) { switch (node) { - case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, knownTaskTypes); - case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, knownTaskTypes); + case MethodDeclarationSyntax method: return FixMethod(keepVoid, methodSymbolOpt, method, knownTypes); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(keepVoid, methodSymbolOpt, localFunction, knownTypes); case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); @@ -62,25 +68,25 @@ protected override SyntaxNode AddAsyncTokenAndFixReturnType( private static SyntaxNode FixMethod( bool keepVoid, IMethodSymbol methodSymbol, MethodDeclarationSyntax method, - KnownTaskTypes knownTaskTypes) + KnownTypes knownTypes) { - var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, knownTaskTypes); + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, method.ReturnType, knownTypes); var newModifiers = AddAsyncModifierWithCorrectedTrivia(method.Modifiers, ref newReturnType); return method.WithReturnType(newReturnType).WithModifiers(newModifiers); } private static SyntaxNode FixLocalFunction( bool keepVoid, IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, - KnownTaskTypes knownTaskTypes) + KnownTypes knownTypes) { - var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, knownTaskTypes); + var newReturnType = FixMethodReturnType(keepVoid, methodSymbol, localFunction.ReturnType, knownTypes); var newModifiers = AddAsyncModifierWithCorrectedTrivia(localFunction.Modifiers, ref newReturnType); return localFunction.WithReturnType(newReturnType).WithModifiers(newModifiers); } private static TypeSyntax FixMethodReturnType( bool keepVoid, IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, - KnownTaskTypes knownTaskTypes) + KnownTypes knownTypes) { var newReturnType = returnTypeSyntax.WithAdditionalAnnotations(Formatter.Annotation); @@ -88,33 +94,33 @@ private static TypeSyntax FixMethodReturnType( { if (!keepVoid) { - newReturnType = knownTaskTypes.Task.GenerateTypeSyntax(); + newReturnType = knownTypes._taskType.GenerateTypeSyntax(); } } else { var returnType = methodSymbol.ReturnType; - if (IsIEnumerable(returnType, knownTaskTypes) && IsIterator(methodSymbol)) + if (IsIEnumerable(returnType, knownTypes) && IsIterator(methodSymbol)) { - newReturnType = knownTaskTypes.IAsyncEnumerableOfT is null + newReturnType = knownTypes._iAsyncEnumerableOfTTypeOpt is null ? MakeGenericType("IAsyncEnumerable", methodSymbol.ReturnType) - : knownTaskTypes.IAsyncEnumerableOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + : knownTypes._iAsyncEnumerableOfTTypeOpt.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (IsIEnumerator(returnType, knownTaskTypes) && IsIterator(methodSymbol)) + else if (IsIEnumerator(returnType, knownTypes) && IsIterator(methodSymbol)) { - newReturnType = knownTaskTypes.IAsyncEnumeratorOfT is null + newReturnType = knownTypes._iAsyncEnumeratorOfTTypeOpt is null ? MakeGenericType("IAsyncEnumerator", methodSymbol.ReturnType) - : knownTaskTypes.IAsyncEnumeratorOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + : knownTypes._iAsyncEnumeratorOfTTypeOpt.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (knownTaskTypes.IsIAsyncEnumerableOrEnumerator(returnType)) + else if (IsIAsyncEnumerableOrEnumerator(returnType, knownTypes)) { // Leave the return type alone } - else if (!knownTaskTypes.IsTaskLikeType(returnType)) + else if (!IsTaskLike(returnType, knownTypes)) { // If it's not already Task-like, then wrap the existing return type // in Task<>. - newReturnType = knownTaskTypes.TaskOfT.Construct(methodSymbol.ReturnType).GenerateTypeSyntax(); + newReturnType = knownTypes._taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax(); } } @@ -140,11 +146,15 @@ static bool IsYield(SyntaxNode node) => node.IsKind(SyntaxKind.YieldBreakStatement, SyntaxKind.YieldReturnStatement); } - private static bool IsIEnumerable(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) - => returnType.OriginalDefinition.Equals(knownTaskTypes.IEnumerableOfT); + private static bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType, KnownTypes knownTypes) + => returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumerableOfTTypeOpt) || + returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumeratorOfTTypeOpt); + + private static bool IsIEnumerable(ITypeSymbol returnType, KnownTypes knownTypes) + => returnType.OriginalDefinition.Equals(knownTypes._iEnumerableOfTType); - private static bool IsIEnumerator(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) - => returnType.OriginalDefinition.Equals(knownTaskTypes.IEnumeratorOfT); + private static bool IsIEnumerator(ITypeSymbol returnType, KnownTypes knownTypes) + => returnType.OriginalDefinition.Equals(knownTypes._iEnumeratorOfTType); private static SyntaxTokenList AddAsyncModifierWithCorrectedTrivia(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) { diff --git a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs index 07c8487bdb085..e04ef2518f657 100644 --- a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs @@ -10,9 +10,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.MakeMethodSynchronous; using Microsoft.CodeAnalysis.Shared.Extensions; +using static Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider; namespace Microsoft.CodeAnalysis.CSharp.MakeMethodSynchronous { @@ -33,55 +33,54 @@ public CSharpMakeMethodSynchronousCodeFixProvider() protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) => node.IsAsyncSupportingFunctionSyntax(); - protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTaskTypes knownTaskTypes) + protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTypes knownTypes) { switch (node) { - case MethodDeclarationSyntax method: return FixMethod(methodSymbolOpt, method, knownTaskTypes); - case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(methodSymbolOpt, localFunction, knownTaskTypes); + case MethodDeclarationSyntax method: return FixMethod(methodSymbolOpt, method, knownTypes); + case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(methodSymbolOpt, localFunction, knownTypes); case AnonymousMethodExpressionSyntax method: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(method); case ParenthesizedLambdaExpressionSyntax lambda: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda); case SimpleLambdaExpressionSyntax lambda: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda); default: return node; } } - - private static SyntaxNode FixMethod(IMethodSymbol methodSymbol, MethodDeclarationSyntax method, KnownTaskTypes knownTaskTypes) + private static SyntaxNode FixMethod(IMethodSymbol methodSymbol, MethodDeclarationSyntax method, KnownTypes knownTypes) { - var newReturnType = FixMethodReturnType(methodSymbol, method.ReturnType, knownTaskTypes); + var newReturnType = FixMethodReturnType(methodSymbol, method.ReturnType, knownTypes); return RemoveAsyncModifierHelpers.WithoutAsyncModifier(method, newReturnType); } - private static SyntaxNode FixLocalFunction(IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, KnownTaskTypes knownTaskTypes) + private static SyntaxNode FixLocalFunction(IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, KnownTypes knownTypes) { - var newReturnType = FixMethodReturnType(methodSymbol, localFunction.ReturnType, knownTaskTypes); + var newReturnType = FixMethodReturnType(methodSymbol, localFunction.ReturnType, knownTypes); return RemoveAsyncModifierHelpers.WithoutAsyncModifier(localFunction, newReturnType); } - private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, KnownTaskTypes knownTaskTypes) + private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, KnownTypes knownTypes) { var newReturnType = returnTypeSyntax; var returnType = methodSymbol.ReturnType; - if (returnType.OriginalDefinition.Equals(knownTaskTypes.Task)) + if (returnType.OriginalDefinition.Equals(knownTypes._taskType)) { // If the return type is Task, then make the new return type "void". newReturnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)).WithTriviaFrom(returnTypeSyntax); } - else if (returnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfT)) + else if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) { // If the return type is Task, then make the new return type "T". newReturnType = returnType.GetTypeArguments()[0].GenerateTypeSyntax().WithTriviaFrom(returnTypeSyntax); } - else if (returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumerableOfT)) + else if (returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumerableOfTTypeOpt)) { // If the return type is IAsyncEnumerable, then make the new return type IEnumerable. - newReturnType = knownTaskTypes.IEnumerableOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + newReturnType = knownTypes._iEnumerableOfTType.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } - else if (returnType.OriginalDefinition.Equals(knownTaskTypes.IAsyncEnumeratorOfT)) + else if (returnType.OriginalDefinition.Equals(knownTypes._iAsyncEnumeratorOfTTypeOpt)) { // If the return type is IAsyncEnumerator, then make the new return type IEnumerator. - newReturnType = knownTaskTypes.IEnumeratorOfT.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); + newReturnType = knownTypes._iEnumeratorOfTType.Construct(methodSymbol.ReturnType.GetTypeArguments()[0]).GenerateTypeSyntax(); } return newReturnType; diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs new file mode 100644 index 0000000000000..f902bc85c9c60 --- /dev/null +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes.cs @@ -0,0 +1,41 @@ +// 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. + +#nullable disable + +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous +{ + internal abstract partial class AbstractMakeMethodAsynchronousCodeFixProvider + { + internal readonly struct KnownTypes + { + public readonly INamedTypeSymbol _taskType; + public readonly INamedTypeSymbol _taskOfTType; + public readonly INamedTypeSymbol _valueTaskType; + public readonly INamedTypeSymbol _valueTaskOfTTypeOpt; + + public readonly INamedTypeSymbol _iEnumerableOfTType; + public readonly INamedTypeSymbol _iEnumeratorOfTType; + + public readonly INamedTypeSymbol _iAsyncEnumerableOfTTypeOpt; + public readonly INamedTypeSymbol _iAsyncEnumeratorOfTTypeOpt; + + internal KnownTypes(Compilation compilation) + { + _taskType = compilation.TaskType(); + _taskOfTType = compilation.TaskOfTType(); + _valueTaskType = compilation.ValueTaskType(); + _valueTaskOfTTypeOpt = compilation.ValueTaskOfTType(); + + _iEnumerableOfTType = compilation.IEnumerableOfTType(); + _iEnumeratorOfTType = compilation.IEnumeratorOfTType(); + + _iAsyncEnumerableOfTTypeOpt = compilation.IAsyncEnumerableOfTType(); + _iAsyncEnumeratorOfTTypeOpt = compilation.IAsyncEnumeratorOfTType(); + } + } + } +} diff --git a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs index bb4bc3b0bf8f5..75a120ea05a01 100644 --- a/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodAsynchronous/AbstractMakeMethodAsynchronousCodeFixProvider.cs @@ -17,13 +17,15 @@ namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous { - internal abstract class AbstractMakeMethodAsynchronousCodeFixProvider : CodeFixProvider + internal abstract partial class AbstractMakeMethodAsynchronousCodeFixProvider : CodeFixProvider { protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); + protected abstract bool IsAsyncReturnType(ITypeSymbol type, KnownTypes knownTypes); + protected abstract SyntaxNode AddAsyncTokenAndFixReturnType( bool keepVoid, IMethodSymbol methodSymbolOpt, SyntaxNode node, - KnownTaskTypes knownTaskTypes); + KnownTypes knownTypes); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -46,8 +48,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // method if we convert it. The last is optional. It is only needed to know // if our member is already Task-Like, and that functionality recognizes // ValueTask if it is available, but does not care if it is not. - var knownTypes = new KnownTaskTypes(compilation); - if (knownTypes.Task == null || knownTypes.TaskOfT == null) + var knownTypes = new KnownTypes(compilation); + if (knownTypes._taskType == null || knownTypes._taskOfTType == null) { return; } @@ -98,22 +100,20 @@ private async Task FixNodeAsync( var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var methodSymbolOpt = semanticModel.GetDeclaredSymbol(node, cancellationToken) as IMethodSymbol; var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var knownTypes = new KnownTypes(compilation); - var makeAsyncService = document.GetRequiredLanguageService(); - var knownTaskTypes = new KnownTaskTypes(compilation); - - if (NeedsRename(methodSymbolOpt, keepVoid, isEntryPoint, makeAsyncService, knownTaskTypes)) + if (NeedsRename(this, methodSymbolOpt, keepVoid, isEntryPoint, in knownTypes)) { return await RenameThenAddAsyncTokenAsync( - keepVoid, document, node, methodSymbolOpt, knownTaskTypes, cancellationToken).ConfigureAwait(false); + keepVoid, document, node, methodSymbolOpt, knownTypes, cancellationToken).ConfigureAwait(false); } else { return await AddAsyncTokenAsync( - keepVoid, document, methodSymbolOpt, knownTaskTypes, node, cancellationToken).ConfigureAwait(false); + keepVoid, document, methodSymbolOpt, knownTypes, node, cancellationToken).ConfigureAwait(false); } - static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, IMakeMethodAsynchronousService makeAsyncService, KnownTaskTypes knownTaskTypes) + static bool NeedsRename(AbstractMakeMethodAsynchronousCodeFixProvider @this, IMethodSymbol methodSymbol, bool keepVoid, bool isEntryPoint, in KnownTypes knownTypes) { if (!methodSymbol.IsOrdinaryMethodOrLocalFunction()) { @@ -140,7 +140,7 @@ static bool NeedsRename(IMethodSymbol methodSymbol, bool keepVoid, bool isEntryP } else { - return !makeAsyncService.IsAsyncReturnType(methodSymbol.ReturnType, knownTaskTypes); + return !@this.IsAsyncReturnType(methodSymbol.ReturnType, knownTypes); } } } @@ -157,7 +157,7 @@ private async Task RenameThenAddAsyncTokenAsync( Document document, SyntaxNode node, IMethodSymbol methodSymbol, - KnownTaskTypes knownTaskTypes, + KnownTypes knownTypes, CancellationToken cancellationToken) { var name = methodSymbol.Name; @@ -176,7 +176,7 @@ private async Task RenameThenAddAsyncTokenAsync( { var semanticModel = await newDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var newMethod = (IMethodSymbol)semanticModel.GetDeclaredSymbol(newNode, cancellationToken); - return await AddAsyncTokenAsync(keepVoid, newDocument, newMethod, knownTaskTypes, newNode, cancellationToken).ConfigureAwait(false); + return await AddAsyncTokenAsync(keepVoid, newDocument, newMethod, knownTypes, newNode, cancellationToken).ConfigureAwait(false); } return newSolution; @@ -186,11 +186,11 @@ private async Task AddAsyncTokenAsync( bool keepVoid, Document document, IMethodSymbol methodSymbolOpt, - KnownTaskTypes knownTaskTypes, + KnownTypes knownTypes, SyntaxNode node, CancellationToken cancellationToken) { - var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, knownTaskTypes); + var newNode = AddAsyncTokenAndFixReturnType(keepVoid, methodSymbolOpt, node, knownTypes); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(node, newNode); @@ -199,6 +199,37 @@ private async Task AddAsyncTokenAsync( return newDocument.Project.Solution; } + protected static bool IsTaskLike(ITypeSymbol returnType, KnownTypes knownTypes) + { + if (returnType.Equals(knownTypes._taskType)) + { + return true; + } + + if (returnType.Equals(knownTypes._valueTaskType)) + { + return true; + } + + if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) + { + return true; + } + + if (returnType.OriginalDefinition.Equals(knownTypes._valueTaskOfTTypeOpt)) + { + return true; + } + + if (returnType.IsErrorType()) + { + return returnType.Name.Equals("Task") || + returnType.Name.Equals("ValueTask"); + } + + return false; + } + private class MyCodeAction : CodeAction.SolutionChangeAction { public MyCodeAction(string title, Func> createChangedSolution) diff --git a/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs b/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs index 437826a4b8e77..1d05148dcdcbe 100644 --- a/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/Core/Portable/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs @@ -16,10 +16,10 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider; namespace Microsoft.CodeAnalysis.MakeMethodSynchronous { @@ -28,7 +28,7 @@ internal abstract class AbstractMakeMethodSynchronousCodeFixProvider : CodeFixPr public static readonly string EquivalenceKey = FeaturesResources.Make_method_synchronous; protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); - protected abstract SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTaskTypes knownTaskTypes); + protected abstract SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol methodSymbolOpt, SyntaxNode node, KnownTypes knownTypes); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -94,10 +94,10 @@ private async Task RemoveAsyncTokenAsync( Document document, IMethodSymbol methodSymbolOpt, SyntaxNode node, CancellationToken cancellationToken) { var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var knownTaskTypes = new KnownTaskTypes(compilation); + var knownTypes = new KnownTypes(compilation); var annotation = new SyntaxAnnotation(); - var newNode = RemoveAsyncTokenAndFixReturnType(methodSymbolOpt, node, knownTaskTypes) + var newNode = RemoveAsyncTokenAndFixReturnType(methodSymbolOpt, node, knownTypes) .WithAdditionalAnnotations(Formatter.Annotation, annotation); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs index cc8798ef40a34..0a40a44e153cd 100644 --- a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs +++ b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs @@ -12,9 +12,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; +using KnownTypes = Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes; namespace Microsoft.CodeAnalysis.RemoveAsyncModifier { @@ -32,8 +32,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var cancellationToken = context.CancellationToken; - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var knownTaskTypes = new KnownTaskTypes(compilation); + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var knownTypes = new KnownTypes(compilation); var diagnostic = context.Diagnostics.First(); var token = diagnostic.Location.FindToken(cancellationToken); @@ -51,7 +51,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - if (knownTaskTypes.IsTaskLikeType(methodSymbol.ReturnType)) + if (ShouldOfferFix(methodSymbol.ReturnType, knownTypes)) { context.RegisterCodeFix( new MyCodeAction(c => FixAsync(document, diagnostic, c)), @@ -66,7 +66,7 @@ protected sealed override async Task FixAllAsync( var generator = editor.Generator; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var compilation = semanticModel.Compilation; - var knownTaskTypes = new KnownTaskTypes(compilation); + var knownTypes = new KnownTypes(compilation); // For fix all we need to do nested locals or lambdas first, so order the diagnostics by location descending foreach (var diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start)) @@ -93,7 +93,7 @@ protected sealed override async Task FixAllAsync( var needsReturnStatementAdded = controlFlow == null || controlFlow.EndPointIsReachable; editor.ReplaceNode(node, - (updatedNode, generator) => RemoveAsyncModifier(generator, updatedNode, methodSymbol.ReturnType, knownTaskTypes, needsReturnStatementAdded)); + (updatedNode, generator) => RemoveAsyncModifier(generator, updatedNode, methodSymbol.ReturnType, knownTypes, needsReturnStatementAdded)); } } @@ -101,18 +101,23 @@ protected sealed override async Task FixAllAsync( => semanticModel.GetSymbolInfo(node, cancellationToken).Symbol as IMethodSymbol ?? semanticModel.GetDeclaredSymbol(node, cancellationToken) as IMethodSymbol; - private static bool IsTaskType(ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) - => returnType.OriginalDefinition.Equals(knownTaskTypes.Task) - || returnType.OriginalDefinition.Equals(knownTaskTypes.ValueTask); + private static bool ShouldOfferFix(ITypeSymbol returnType, KnownTypes knownTypes) + => IsTaskType(returnType, knownTypes) + || returnType.OriginalDefinition.Equals(knownTypes._taskOfTType) + || returnType.OriginalDefinition.Equals(knownTypes._valueTaskOfTTypeOpt); - private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes, bool needsReturnStatementAdded) + private static bool IsTaskType(ITypeSymbol returnType, KnownTypes knownTypes) + => returnType.OriginalDefinition.Equals(knownTypes._taskType) + || returnType.OriginalDefinition.Equals(knownTypes._valueTaskType); + + private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTypes knownTypes, bool needsReturnStatementAdded) { node = RemoveAsyncModifier(generator, node); var expression = generator.GetExpression(node); if (expression is TExpressionSyntax expressionBody) { - if (IsTaskType(returnType, knownTaskTypes)) + if (IsTaskType(returnType, knownTypes)) { // We need to add a `return Task.CompletedTask;` so we have to convert to a block body var blockBodiedNode = ConvertToBlockBody(node, expressionBody); @@ -127,13 +132,13 @@ private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode nod else { // For Task returning expression bodied methods we can just wrap the whole expression - var newExpressionBody = WrapExpressionWithTaskFromResult(generator, expressionBody, returnType, knownTaskTypes); + var newExpressionBody = WrapExpressionWithTaskFromResult(generator, expressionBody, returnType, knownTypes); node = generator.WithExpression(node, newExpressionBody); } } else { - if (IsTaskType(returnType, knownTaskTypes)) + if (IsTaskType(returnType, knownTypes)) { // If the end of the method isn't reachable, or there were no statements to analyze, then we // need to add an explicit return @@ -144,7 +149,7 @@ private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode nod } } - node = ChangeReturnStatements(generator, node, returnType, knownTaskTypes); + node = ChangeReturnStatements(generator, node, returnType, knownTypes); return node; } @@ -163,7 +168,7 @@ private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode nod private static SyntaxNode AddReturnStatement(SyntaxGenerator generator, SyntaxNode node) => generator.WithStatements(node, generator.GetStatements(node).Concat(generator.ReturnStatement())); - private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTypes knownTypes) { var editor = new SyntaxEditor(node, generator); @@ -177,13 +182,13 @@ private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode if (returnExpression is null) { // Convert return; into return Task.CompletedTask; - var returnTaskCompletedTask = GetReturnTaskCompletedTaskStatement(generator, returnType, knownTaskTypes); + var returnTaskCompletedTask = GetReturnTaskCompletedTaskStatement(generator, returnType, knownTypes); editor.ReplaceNode(returnSyntax, returnTaskCompletedTask); } else { // Convert return ; into return Task.FromResult(); - var newExpression = WrapExpressionWithTaskFromResult(generator, returnExpression, returnType, knownTaskTypes); + var newExpression = WrapExpressionWithTaskFromResult(generator, returnExpression, returnType, knownTypes); editor.ReplaceNode(returnExpression, newExpression); } } @@ -191,28 +196,28 @@ private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode return editor.GetChangedRoot(); } - private static SyntaxNode GetReturnTaskCompletedTaskStatement(SyntaxGenerator generator, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + private static SyntaxNode GetReturnTaskCompletedTaskStatement(SyntaxGenerator generator, ITypeSymbol returnType, KnownTypes knownTypes) { SyntaxNode invocation; - if (returnType.OriginalDefinition.Equals(knownTaskTypes.Task)) + if (returnType.OriginalDefinition.Equals(knownTypes._taskType)) { - var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTaskTypes.Task); + var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTypes._taskType); invocation = generator.MemberAccessExpression(taskTypeExpression, nameof(Task.CompletedTask)); } else { - invocation = generator.ObjectCreationExpression(knownTaskTypes.ValueTask); + invocation = generator.ObjectCreationExpression(knownTypes._valueTaskType); } var statement = generator.ReturnStatement(invocation); return statement; } - private static SyntaxNode WrapExpressionWithTaskFromResult(SyntaxGenerator generator, SyntaxNode expression, ITypeSymbol returnType, KnownTaskTypes knownTaskTypes) + private static SyntaxNode WrapExpressionWithTaskFromResult(SyntaxGenerator generator, SyntaxNode expression, ITypeSymbol returnType, KnownTypes knownTypes) { - if (returnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfT)) + if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) { - var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTaskTypes.Task!); + var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTypes._taskType); var taskFromResult = generator.MemberAccessExpression(taskTypeExpression, nameof(Task.FromResult)); return generator.InvocationExpression(taskFromResult, expression.WithoutTrivia()).WithTriviaFrom(expression); } diff --git a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb index 8d5c5456c3a32..c2110772b34f6 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb @@ -1,4 +1,4 @@ -' Licensed to the .NET Foundation under one or more agreements. +' 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. @@ -48,9 +48,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Return node.IsAsyncSupportedFunctionSyntax() End Function + Protected Overrides Function IsAsyncReturnType(type As ITypeSymbol, knownTypes As KnownTypes) As Boolean + Return IsTaskLike(type, knownTypes) + End Function + Protected Overrides Function AddAsyncTokenAndFixReturnType( keepVoid As Boolean, methodSymbolOpt As IMethodSymbol, node As SyntaxNode, - knownTaskTypes As KnownTaskTypes) As SyntaxNode + knownTypes As KnownTypes) As SyntaxNode If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) Then @@ -61,21 +65,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous Return FixMultiLineLambdaExpression(DirectCast(node, MultiLineLambdaExpressionSyntax)) ElseIf node.IsKind(SyntaxKind.SubBlock) Then - Return FixSubBlock(keepVoid, DirectCast(node, MethodBlockSyntax), knownTaskTypes.Task) + Return FixSubBlock(keepVoid, DirectCast(node, MethodBlockSyntax), knownTypes._taskType) Else Return FixFunctionBlock( - methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTaskTypes) + methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTypes) End If End Function - Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTaskTypes As KnownTaskTypes) As SyntaxNode + Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTypes As KnownTypes) As SyntaxNode Dim functionStatement = node.SubOrFunctionStatement Dim newFunctionStatement = AddAsyncKeyword(functionStatement) - If Not knownTaskTypes.IsTaskLikeType(methodSymbol.ReturnType) Then + If Not IsTaskLike(methodSymbol.ReturnType, knownTypes) Then ' if the current return type is not already task-list, then wrap it in Task(of ...) - Dim returnType = knownTaskTypes.TaskOfT.Construct(methodSymbol.ReturnType).GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) + Dim returnType = knownTypes._taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) newFunctionStatement = newFunctionStatement.WithAsClause( newFunctionStatement.AsClause.WithType(returnType)) End If diff --git a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb index 38015f3efe160..3b0694de39444 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb @@ -1,4 +1,4 @@ -' Licensed to the .NET Foundation under one or more agreements. +' 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. @@ -6,7 +6,7 @@ Imports System.Collections.Immutable Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous +Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider Imports Microsoft.CodeAnalysis.MakeMethodSynchronous Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -35,7 +35,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous Return node.IsAsyncSupportedFunctionSyntax() End Function - Protected Overrides Function RemoveAsyncTokenAndFixReturnType(methodSymbolOpt As IMethodSymbol, node As SyntaxNode, knownTaskTypes As KnownTaskTypes) As SyntaxNode + Protected Overrides Function RemoveAsyncTokenAndFixReturnType(methodSymbolOpt As IMethodSymbol, node As SyntaxNode, knownTypes As KnownTypes) As SyntaxNode If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) Then @@ -47,21 +47,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous ElseIf node.IsKind(SyntaxKind.SubBlock) Then Return FixSubBlock(DirectCast(node, MethodBlockSyntax)) Else - Return FixFunctionBlock(methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTaskTypes) + Return FixFunctionBlock(methodSymbolOpt, DirectCast(node, MethodBlockSyntax), knownTypes) End If End Function - Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTaskTypes As KnownTaskTypes) As SyntaxNode + Private Shared Function FixFunctionBlock(methodSymbol As IMethodSymbol, node As MethodBlockSyntax, knownTypes As KnownTypes) As SyntaxNode Dim functionStatement = node.SubOrFunctionStatement ' if this returns Task(of T), then we want to convert this to a T returning function. ' if this returns Task, then we want to convert it to a Sub method. - If methodSymbol.ReturnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfT) Then + If methodSymbol.ReturnType.OriginalDefinition.Equals(knownTypes._taskOfTType) Then Dim newAsClause = functionStatement.AsClause.WithType(methodSymbol.ReturnType.GetTypeArguments()(0).GenerateTypeSyntax()) Dim newFunctionStatement = functionStatement.WithAsClause(newAsClause) newFunctionStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(newFunctionStatement) Return node.WithSubOrFunctionStatement(newFunctionStatement) - ElseIf Equals(methodSymbol.ReturnType.OriginalDefinition, knownTaskTypes.Task) Then + ElseIf Equals(methodSymbol.ReturnType.OriginalDefinition, knownTypes._taskType) Then ' Convert this to a 'Sub' method. Dim subStatement = SyntaxFactory.SubStatement( functionStatement.AttributeLists, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems index 98d47c82d32bc..b0f5b302e7181 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems @@ -40,7 +40,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs deleted file mode 100644 index a3a6f608a7bf4..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpMakeMethodAsynchronousService.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.MakeMethodAsynchronous; - -namespace Microsoft.CodeAnalysis.CSharp.MakeMethodAsynchronous -{ - [ExportLanguageService(typeof(IMakeMethodAsynchronousService), LanguageNames.CSharp), Shared] - internal sealed class CSharpMakeMethodAsynchronousService : IMakeMethodAsynchronousService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpMakeMethodAsynchronousService() - { - } - - public bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes) - { - return knownTaskTypes.IsIAsyncEnumerableOrEnumerator(type) || knownTaskTypes.IsTaskLikeType(type); - } - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs deleted file mode 100644 index 94e438701ea35..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/IMakeMethodAsynchronousService.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous -{ - internal interface IMakeMethodAsynchronousService : ILanguageService - { - bool IsAsyncReturnType(ITypeSymbol type, KnownTaskTypes knownTaskTypes); - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs deleted file mode 100644 index f8d1be072947b..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/MakeMethodAsynchronousService/KnownTaskTypes.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.MakeMethodAsynchronous -{ - internal readonly struct KnownTaskTypes - { - public INamedTypeSymbol? Task { get; } - public INamedTypeSymbol? TaskOfT { get; } - public INamedTypeSymbol? ValueTask { get; } - public INamedTypeSymbol? ValueTaskOfT { get; } - - public INamedTypeSymbol? IEnumerableOfT { get; } - public INamedTypeSymbol? IEnumeratorOfT { get; } - public INamedTypeSymbol? IAsyncEnumerableOfT { get; } - public INamedTypeSymbol? IAsyncEnumeratorOfT { get; } - - public KnownTaskTypes(Compilation compilation) - { - Task = compilation.TaskType(); - TaskOfT = compilation.TaskOfTType(); - ValueTask = compilation.ValueTaskType(); - ValueTaskOfT = compilation.ValueTaskOfTType(); - - IEnumerableOfT = compilation.IEnumerableOfTType(); - IEnumeratorOfT = compilation.IEnumeratorOfTType(); - IAsyncEnumerableOfT = compilation.IAsyncEnumerableOfTType(); - IAsyncEnumeratorOfT = compilation.IAsyncEnumeratorOfTType(); - } - - public bool IsTaskLikeType(ITypeSymbol type) - { - if (type.Equals(Task, SymbolEqualityComparer.Default) || - type.Equals(ValueTask, SymbolEqualityComparer.Default) || - type.OriginalDefinition.Equals(TaskOfT, SymbolEqualityComparer.Default) || - type.OriginalDefinition.Equals(ValueTaskOfT, SymbolEqualityComparer.Default)) - { - return true; - } - - if (type.IsErrorType()) - { - return type.Name.Equals("Task") || - type.Name.Equals("ValueTask"); - } - - return false; - } - - public bool IsIAsyncEnumerableOrEnumerator(ITypeSymbol returnType) - => returnType.OriginalDefinition.Equals(IAsyncEnumerableOfT, SymbolEqualityComparer.Default) || - returnType.OriginalDefinition.Equals(IAsyncEnumeratorOfT, SymbolEqualityComparer.Default); - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems index e364d51096e31..433f11d995fd4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems @@ -34,8 +34,6 @@ - - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb deleted file mode 100644 index dce5485363782..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicMakeMethodAsynchronousService.vb +++ /dev/null @@ -1,23 +0,0 @@ -' 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. - -Imports System.Composition -Imports Microsoft.CodeAnalysis.Host.Mef -Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous - -Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous - - Friend Class VisualBasicMakeMethodAsynchronousService - Implements IMakeMethodAsynchronousService - - - - Public Sub New() - End Sub - - Public Function IsAsyncReturnType(type As ITypeSymbol, knownTaskTypes As KnownTaskTypes) As Boolean Implements IMakeMethodAsynchronousService.IsAsyncReturnType - Return knownTaskTypes.IsTaskLikeType(type) - End Function - End Class -End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems index 7656a57668db7..82a991fe0937e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/VisualBasicWorkspaceExtensions.projitems @@ -38,7 +38,6 @@ - From a4b7a018997d53b562f766b26a678da83d274744 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 6 May 2021 09:41:37 +0200 Subject: [PATCH 26/38] Update VisualBasicMakeMethodSynchronousCodeFixProvider.vb --- .../VisualBasicMakeMethodSynchronousCodeFixProvider.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb index 3b0694de39444..8995cb486e405 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb @@ -1,4 +1,4 @@ -' Licensed to the .NET Foundation under one or more agreements. +' 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. From cf21c76b235994ccea80cbc50c30327ee4c999d5 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 6 May 2021 09:41:49 +0200 Subject: [PATCH 27/38] Update VisualBasicMakeMethodAsynchronousCodeFixProvider.vb --- .../VisualBasicMakeMethodAsynchronousCodeFixProvider.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb index c2110772b34f6..53578e7bf174b 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb @@ -1,4 +1,4 @@ -' Licensed to the .NET Foundation under one or more agreements. +' 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. From f82655c3935830bbd1d79294ef5fd4fe41e91e89 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 6 May 2021 08:48:38 +0000 Subject: [PATCH 28/38] Fix --- .../CompletionProviders/AwaitCompletionProvider.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index fcccb4d83878e..5b8d7172b186f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -56,12 +56,10 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) } var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); - if (method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword)) - { - context.AddItem(GetCompletionItem(shouldMakeContainerAsync: true)); - } + var shouldMakeContainerAsync = method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword); + context.AddItem(GetCompletionItem(shouldMakeContainerAsync)); - context.AddItem(GetCompletionItem(shouldMakeContainerAsync: false)); + return; static CompletionItem GetCompletionItem(bool shouldMakeContainerAsync) => CommonCompletionItem.Create( From 6324abae7b1e3bf10e0e0d45b4cbe274d025fc99 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 6 May 2021 11:52:08 +0200 Subject: [PATCH 29/38] Fix test --- .../CompletionProviders/AwaitCompletionProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs index ac09720857e95..18a76261af6fc 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs @@ -58,7 +58,7 @@ class C void F() { $$ } -}", LanguageVersion.CSharp9); +}", LanguageVersion.CSharp9, CSharpFeaturesResources.Make_container_async); } [Fact] From d501feef262bb5c9df7b60e37b30debb451dcacd Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 6 May 2021 11:54:29 +0200 Subject: [PATCH 30/38] Fix tests --- ...pletionCommandHandlerTests_AwaitCompletion.vb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index 5f5a166756987..b38b540989a4e 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -96,7 +96,7 @@ public class C ]]> ) state.SendTypeChars("aw") - Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) state.SendTab() Assert.Equal(" @@ -106,7 +106,7 @@ public class C { public void F() { - Action a = static delegate(int i) { await }; + Action a = static async delegate(int i) { await }; } } ", state.GetDocumentText()) @@ -164,7 +164,7 @@ public class C ]]> ) state.SendTypeChars("aw") - Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) state.SendTab() Assert.Equal(" @@ -174,7 +174,7 @@ public class C { public void F() { - Action b = static a => { await }; + Action b = static async a => { await }; } } ", state.GetDocumentText()) @@ -232,7 +232,7 @@ public class C ]]> ) state.SendTypeChars("aw") - Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) state.SendTab() Assert.Equal(" @@ -242,7 +242,7 @@ public class C { public void F() { - Action c = static (a) => { await }; + Action c = static async (a) => { await }; } } ", state.GetDocumentText()) @@ -300,7 +300,7 @@ public class C ]]> ) state.SendTypeChars("aw") - Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=String.Empty) + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) state.SendTab() Assert.Equal(" @@ -308,7 +308,7 @@ using System.Threading.Tasks; public class C { - public static void Main() + public static async void Main() { await } From 92043415217eaac77a60ccaaba766508a9942c98 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 6 May 2021 12:46:01 +0200 Subject: [PATCH 31/38] Fix test --- .../CSharpCompletionCommandHandlerTests_AwaitCompletion.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index b38b540989a4e..5f92491646a77 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -106,7 +106,7 @@ public class C { public void F() { - Action a = static async delegate(int i) { await }; + Action a = static async delegate (int i) { await }; } } ", state.GetDocumentText()) From c5f219ac5866f4792048ad624f62843cfea0e8cf Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 7 May 2021 06:53:22 +0000 Subject: [PATCH 32/38] Handle trivia --- .../AwaitCompletionProvider.cs | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 5b8d7172b186f..75c12e5b68780 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -5,12 +5,14 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; @@ -18,6 +20,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { @@ -84,27 +87,54 @@ public override async Task GetChangeAsync(Document document, C var declaration = root.FindToken(item.Span.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); if (declaration is null) { + // We already check that in ProvideCompletionsAsync above. + Debug.Assert(false, "Expected non-null value for declaration."); return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); } - var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(document, declaration))); - var formattedDocument = await Formatter.FormatAsync(documentWithAsyncModifier, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - + var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(declaration))); using var _ = ArrayBuilder.GetInstance(out var builder); - builder.AddRange(await formattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false)); + builder.AddRange(await documentWithAsyncModifier.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false)); builder.Add(new TextChange(item.Span, item.DisplayText)); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newText = text.WithChanges(builder); return CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, builder.ToImmutableArray())); + } - static SyntaxNode AddAsyncModifier(Document document, SyntaxNode declaration) + private static SyntaxNode AddAsyncModifier(SyntaxNode declaration) + { + var asyncToken = SyntaxFactory.Token(SyntaxKind.AsyncKeyword).WithTrailingTrivia(SyntaxFactory.Space); + return declaration switch { - var generator = SyntaxGenerator.GetGenerator(document); - var modifiers = generator.GetModifiers(declaration); - return generator.WithModifiers(declaration, modifiers.WithAsync(true)).WithAdditionalAnnotations(Formatter.Annotation); - } + MethodDeclarationSyntax method => AddAsyncModifier(method, asyncToken), + LocalFunctionStatementSyntax local => AddAsyncModifier(local, asyncToken), + AnonymousFunctionExpressionSyntax anonymous => AddAsyncModifier(anonymous), + _ => throw ExceptionUtilities.UnexpectedValue(declaration.Kind()) + }; + } + + private static SyntaxNode AddAsyncModifier(MethodDeclarationSyntax method, SyntaxToken asyncToken) + { + if (method.Modifiers.Any()) + return method.WithModifiers(method.Modifiers.Add(asyncToken)); + + var modifiers = SyntaxFactory.TokenList(asyncToken.WithLeadingTrivia(method.ReturnType.GetLeadingTrivia())); + return method.WithModifiers(result).WithReturnType(method.ReturnType.WithoutLeadingTrivia()); + } + + private static SyntaxNode AddAsyncModifier(LocalFunctionStatementSyntax local, SyntaxToken asyncToken) + { + if (local.Modifiers.Any()) + return local.WithModifiers(local.Modifiers.Add(asyncToken)); + + var modifiers = SyntaxFactory.TokenList(asyncToken.WithLeadingTrivia(local.ReturnType.GetLeadingTrivia())); + return local.WithModifiers(modifiers).WithReturnType(local.ReturnType.WithoutLeadingTrivia()); + } + + private static SyntaxNode AddAsyncModifier(AnonymousFunctionExpressionSyntax anonymous, SyntaxToken asyncToken) + => anonymous.WithoutLeadingTrivia().WithAsyncKeyword(asyncToken.WithPrependedLeadingTrivia(anonymous.GetLeadingTrivia())); } } } From de412033a2d7c80cb8d3c296cc0f74a2908d8db4 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 7 May 2021 07:04:15 +0000 Subject: [PATCH 33/38] Fix --- .../Completion/CompletionProviders/AwaitCompletionProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 75c12e5b68780..1ae20d6f279af 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -135,6 +135,5 @@ private static SyntaxNode AddAsyncModifier(LocalFunctionStatementSyntax local, S private static SyntaxNode AddAsyncModifier(AnonymousFunctionExpressionSyntax anonymous, SyntaxToken asyncToken) => anonymous.WithoutLeadingTrivia().WithAsyncKeyword(asyncToken.WithPrependedLeadingTrivia(anonymous.GetLeadingTrivia())); - } } } From f4bcff7bc57f29f8577c4f2cda570c28ef9121ee Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 7 May 2021 07:10:33 +0000 Subject: [PATCH 34/38] Fix --- .../Completion/CompletionProviders/AwaitCompletionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 1ae20d6f279af..4f5e0ef827892 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -121,7 +121,7 @@ private static SyntaxNode AddAsyncModifier(MethodDeclarationSyntax method, Synta return method.WithModifiers(method.Modifiers.Add(asyncToken)); var modifiers = SyntaxFactory.TokenList(asyncToken.WithLeadingTrivia(method.ReturnType.GetLeadingTrivia())); - return method.WithModifiers(result).WithReturnType(method.ReturnType.WithoutLeadingTrivia()); + return method.WithModifiers(modifiers).WithReturnType(method.ReturnType.WithoutLeadingTrivia()); } private static SyntaxNode AddAsyncModifier(LocalFunctionStatementSyntax local, SyntaxToken asyncToken) From 7546e523ffcb624b738fdfb93e179a4c47da267e Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 7 May 2021 09:11:08 +0000 Subject: [PATCH 35/38] Fix --- .../Completion/CompletionProviders/AwaitCompletionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index 4f5e0ef827892..f63a9e4fa9ddc 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -110,7 +110,7 @@ private static SyntaxNode AddAsyncModifier(SyntaxNode declaration) { MethodDeclarationSyntax method => AddAsyncModifier(method, asyncToken), LocalFunctionStatementSyntax local => AddAsyncModifier(local, asyncToken), - AnonymousFunctionExpressionSyntax anonymous => AddAsyncModifier(anonymous), + AnonymousFunctionExpressionSyntax anonymous => AddAsyncModifier(anonymous, asyncToken), _ => throw ExceptionUtilities.UnexpectedValue(declaration.Kind()) }; } From 2e2f0411351df95df3d2512d2d191c42cc907458 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 30 Jun 2021 21:02:30 +0200 Subject: [PATCH 36/38] Address feedback --- .../AwaitCompletionProvider.cs | 67 ++++++------------- 1 file changed, 20 insertions(+), 47 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index f63a9e4fa9ddc..be15e3b664a0c 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -13,10 +13,7 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.MakeMethodAsynchronous; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -49,10 +46,9 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var document = context.Document; var position = context.Position; var cancellationToken = context.CancellationToken; - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); var workspace = document.Project.Solution.Workspace; var syntaxContext = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken); - if (!syntaxContext.IsAwaitKeywordContext(position)) { return; @@ -60,19 +56,15 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var method = syntaxContext.TargetToken.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax()); var shouldMakeContainerAsync = method is not null && !method.GetModifiers().Any(SyntaxKind.AsyncKeyword); - context.AddItem(GetCompletionItem(shouldMakeContainerAsync)); - - return; - - static CompletionItem GetCompletionItem(bool shouldMakeContainerAsync) - => CommonCompletionItem.Create( - displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), - displayTextSuffix: "", - rules: CompletionItemRules.Default, - Glyph.Keyword, - description: RecommendedKeyword.CreateDisplayParts(SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), string.Empty), - inlineDescription: shouldMakeContainerAsync ? CSharpFeaturesResources.Make_container_async : null, - isComplexTextEdit: shouldMakeContainerAsync); + var completionItem = CommonCompletionItem.Create( + displayText: SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), + displayTextSuffix: "", + rules: CompletionItemRules.Default, + Glyph.Keyword, + description: RecommendedKeyword.CreateDisplayParts(SyntaxFacts.GetText(SyntaxKind.AwaitKeyword), string.Empty), + inlineDescription: shouldMakeContainerAsync ? CSharpFeaturesResources.Make_container_async : null, + isComplexTextEdit: shouldMakeContainerAsync); + context.AddItem(completionItem); } public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) @@ -92,10 +84,8 @@ public override async Task GetChangeAsync(Document document, C return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false); } - var documentWithAsyncModifier = document.WithSyntaxRoot(root.ReplaceNode(declaration, AddAsyncModifier(declaration))); using var _ = ArrayBuilder.GetInstance(out var builder); - - builder.AddRange(await documentWithAsyncModifier.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false)); + builder.Add(new TextChange(new TextSpan(GetSpanStart(declaration), 0), "async ")); builder.Add(new TextChange(item.Span, item.DisplayText)); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -103,37 +93,20 @@ public override async Task GetChangeAsync(Document document, C return CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, builder.ToImmutableArray())); } - private static SyntaxNode AddAsyncModifier(SyntaxNode declaration) + /// + /// Gets the span start where async keyword should go. + /// + private static int GetSpanStart(SyntaxNode declaration) { - var asyncToken = SyntaxFactory.Token(SyntaxKind.AsyncKeyword).WithTrailingTrivia(SyntaxFactory.Space); return declaration switch { - MethodDeclarationSyntax method => AddAsyncModifier(method, asyncToken), - LocalFunctionStatementSyntax local => AddAsyncModifier(local, asyncToken), - AnonymousFunctionExpressionSyntax anonymous => AddAsyncModifier(anonymous, asyncToken), + MethodDeclarationSyntax method => method.ReturnType.SpanStart, + LocalFunctionStatementSyntax local => local.ReturnType.SpanStart, + AnonymousMethodExpressionSyntax anonymous => anonymous.DelegateKeyword.SpanStart, + ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList.SpanStart, + SimpleLambdaExpressionSyntax simpleLambda => simpleLambda.Parameter.SpanStart, _ => throw ExceptionUtilities.UnexpectedValue(declaration.Kind()) }; } - - private static SyntaxNode AddAsyncModifier(MethodDeclarationSyntax method, SyntaxToken asyncToken) - { - if (method.Modifiers.Any()) - return method.WithModifiers(method.Modifiers.Add(asyncToken)); - - var modifiers = SyntaxFactory.TokenList(asyncToken.WithLeadingTrivia(method.ReturnType.GetLeadingTrivia())); - return method.WithModifiers(modifiers).WithReturnType(method.ReturnType.WithoutLeadingTrivia()); - } - - private static SyntaxNode AddAsyncModifier(LocalFunctionStatementSyntax local, SyntaxToken asyncToken) - { - if (local.Modifiers.Any()) - return local.WithModifiers(local.Modifiers.Add(asyncToken)); - - var modifiers = SyntaxFactory.TokenList(asyncToken.WithLeadingTrivia(local.ReturnType.GetLeadingTrivia())); - return local.WithModifiers(modifiers).WithReturnType(local.ReturnType.WithoutLeadingTrivia()); - } - - private static SyntaxNode AddAsyncModifier(AnonymousFunctionExpressionSyntax anonymous, SyntaxToken asyncToken) - => anonymous.WithoutLeadingTrivia().WithAsyncKeyword(asyncToken.WithPrependedLeadingTrivia(anonymous.GetLeadingTrivia())); } } From a376f7c47c405658efa10379417a646aef729bd5 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 30 Jun 2021 21:28:26 +0200 Subject: [PATCH 37/38] Handle explicit lambda return type --- ...tionCommandHandlerTests_AwaitCompletion.vb | 35 +++++++++++++++++++ .../AwaitCompletionProvider.cs | 5 ++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index 5f92491646a77..cb9ffae218742 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -284,6 +284,41 @@ public class C End Using End Function + + Public Async Function AwaitCompletionAddsAsync_ParenthesizedLambdaExpression_ExplicitType() As Task + Using state = TestStateFactory.CreateCSharpTestState( + c = static Task (a) => { $$ }; + } +} +]]> + ) + state.SendTypeChars("aw") + Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=CSharpFeaturesResources.Make_container_async) + + state.SendTab() + Assert.Equal(" +using System; +using System.Threading.Tasks; + +public class C +{ + public void F() + { + Func c = static async Task (a) => { await }; + } +} +", state.GetDocumentText()) + End Using + End Function + Public Async Function AwaitCompletionDoesNotAddAsync_NotTask() As Task Using state = TestStateFactory.CreateCSharpTestState( diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs index be15e3b664a0c..8d09d671a80a0 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs @@ -103,7 +103,10 @@ private static int GetSpanStart(SyntaxNode declaration) MethodDeclarationSyntax method => method.ReturnType.SpanStart, LocalFunctionStatementSyntax local => local.ReturnType.SpanStart, AnonymousMethodExpressionSyntax anonymous => anonymous.DelegateKeyword.SpanStart, - ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList.SpanStart, + // If we have an explicit lambda return type, async should go just before it. Otherwise, it should go before parameter list. + // static [|async|] (a) => .... + // static [|async|] ExplicitReturnType (a) => .... + ParenthesizedLambdaExpressionSyntax parenthesizedLambda => (parenthesizedLambda.ReturnType as SyntaxNode ?? parenthesizedLambda.ParameterList).SpanStart, SimpleLambdaExpressionSyntax simpleLambda => simpleLambda.Parameter.SpanStart, _ => throw ExceptionUtilities.UnexpectedValue(declaration.Kind()) }; From 8f85b5765e9ece64319e83de6bd8d3a689ac44da Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 9 Jul 2021 12:27:19 +0200 Subject: [PATCH 38/38] Fix tests --- .../CSharpCompletionCommandHandlerTests_AwaitCompletion.vb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb index cb9ffae218742..98fa65e7ca75e 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb @@ -106,7 +106,7 @@ public class C { public void F() { - Action a = static async delegate (int i) { await }; + Action a = static async delegate(int i) { await }; } } ", state.GetDocumentText()) @@ -141,7 +141,7 @@ public class C { public void F() { - Func a = static async delegate (int i) { await }; + Func a = static async delegate(int i) { await }; } } ", state.GetDocumentText())