From d525b85f6377ddffdd7dbfbacd9a28a9b80bc084 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 11:26:06 +0200 Subject: [PATCH 01/15] Work some 'scoped' issues on IDE --- .../Classification/TotalClassifierTests.cs | 39 +++++++++++++++++++ .../CSharpReassignedVariableTests.cs | 28 +++++++++++-- .../NameSyntaxClassifier.cs | 1 + .../Extensions/ExpressionSyntaxExtensions.cs | 13 +++++-- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index 3cf18cd926eb6..e12dfb69b21de 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -2616,5 +2616,44 @@ static void staticLocalFunction() { }|] Punctuation.OpenCurly, Punctuation.CloseCurly); } + + [Theory] + [CombinatorialData] + public async Task TestScopedVar(TestHost testHost) + { + await TestAsync(""" + static void method(scoped in S s) + { + scoped var rs1 = s; + } + + file readonly ref struct S { } + """, testHost, + Keyword("static"), + Keyword("void"), + Method("method"), + Static("method"), + Punctuation.OpenParen, + Keyword("scoped"), + Keyword("in"), + Struct("S"), + Parameter("s"), + Punctuation.CloseParen, + Punctuation.OpenCurly, + Keyword("scoped"), + Keyword("var"), + Local("rs1"), + Operators.Equals, + Parameter("s"), + Punctuation.Semicolon, + Punctuation.CloseCurly, + Keyword("file"), + Keyword("readonly"), + Keyword("ref"), + Keyword("struct"), + Struct("S"), + Punctuation.OpenCurly, + Punctuation.CloseCurly); + } } } diff --git a/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs b/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs index f7fe02f04df99..cbecabbaeb05f 100644 --- a/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs @@ -500,13 +500,21 @@ await TestAsync( using System; class C { - void M(ref int [|p|]) + void M1(ref int [|p|]) { ref var [|local|] = ref [|p|]; [|local|] = 0; [|local|] = 1; Console.WriteLine([|local|]); } + + void M2(ref int [|p|]) + { + scoped ref var [|local|] = ref [|p|]; + [|local|] = 0; + [|local|] = 1; + Console.WriteLine([|local|]); + } }"); } @@ -644,12 +652,19 @@ await TestAsync( using System; class C { - void M() + void M1() { int p = 0; ref readonly int refP = ref p; Console.WriteLine(p); } + + void M2() + { + int p = 0; + scoped ref readonly int refP = ref p; + Console.WriteLine(p); + } }"); } @@ -661,12 +676,19 @@ await TestAsync( using System; class C { - void M() + void M1() { int p = 0; ref readonly int refP = ref p!; Console.WriteLine(p); } + + void M2() + { + int p = 0; + scoped ref readonly int refP = ref p!; + Console.WriteLine(p); + } }"); } diff --git a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs index b918f11ed2b5d..1f5269be22c56 100644 --- a/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs +++ b/src/Workspaces/CSharp/Portable/Classification/SyntaxClassification/NameSyntaxClassifier.cs @@ -302,6 +302,7 @@ private static bool IsInVarContext(NameSyntax name) { return name.CheckParent(v => v.Type == name) || + name.CheckParent(v => v.Type == name) || name.CheckParent(f => f.Type == name) || name.CheckParent(v => v.Type == name) || name.CheckParent(v => v.Type == name) || diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs index b39a1a079b4e8..57bf7fb8472ce 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs @@ -334,10 +334,17 @@ public static bool IsWrittenTo( // most cases of `ref x` will count as a potential write of `x`. An important exception is: // `ref readonly y = ref x`. In that case, because 'y' can't be written to, this would not // be a write of 'x'. - if (refParent.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: RefTypeSyntax refType } } } - && refType.ReadOnlyKeyword != default) + if (refParent.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: { } variableDeclarationType } } }) { - return false; + if (variableDeclarationType is ScopedTypeSyntax scopedType) + { + variableDeclarationType = scopedType.Type; + } + + if (variableDeclarationType is RefTypeSyntax refType && refType.ReadOnlyKeyword != default) + { + return false; + } } return true; From fbc36c5edb3e33a142962bcc2dbc6a3b81ba02e7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 13:40:44 +0200 Subject: [PATCH 02/15] Add auto completion for 'scoped' --- .../ScopedKeywordRecommenderTests.cs | 424 ++++++++++++++++++ .../ScopedKeywordRecommender.cs | 56 +++ 2 files changed, 480 insertions(+) create mode 100644 src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs create mode 100644 src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs new file mode 100644 index 0000000000000..46baf9d76b288 --- /dev/null +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs @@ -0,0 +1,424 @@ +// 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.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations +{ + public class ScopedKeywordRecommenderTests : RecommenderTests + { + protected override string KeywordText => "scoped"; + + private readonly ScopedKeywordRecommender _recommender = new(); + + public ScopedKeywordRecommenderTests() + { + this.RecommendKeywordsAsync = (position, context) => Task.FromResult(_recommender.RecommendKeywords(position, context, CancellationToken.None)); + } + + [Fact] + public async Task TestAtRoot_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, +@"$$"); + } + + [Fact] + public async Task TestAfterClass_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, +@"class C { } +$$"); + } + + [Fact] + public async Task TestAfterGlobalStatement_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, +@"System.Console.WriteLine(); +$$"); + } + + [Fact] + public async Task TestAfterGlobalVariableDeclaration_Interactive() + { + await VerifyKeywordAsync(SourceCodeKind.Script, +@"int i = 0; +$$"); + } + + [Fact] + public async Task TestNotInUsingAlias() + { + await VerifyAbsenceAsync( +@"using Goo = $$"); + } + + [Fact] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( +@"global using Goo = $$"); + } + + [Fact] + public async Task TestNotAfterStackAlloc() + { + await VerifyAbsenceAsync( +@"class C { + int* goo = stackalloc $$"); + } + + [Fact] + public async Task TestNotInFixedStatement() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"fixed ($$")); + } + + [Fact] + public async Task TestNotInDelegateReturnType() + { + await VerifyAbsenceAsync( +@"public delegate $$"); + } + + [Fact] + public async Task TestPossibleLambda() + { + // Could be `var x = ((scoped ref int x) => x);` + await VerifyKeywordAsync(AddInsideMethod( +@"var x = (($$")); + } + + [Fact] + public async Task TestEmptyStatement() + { + await VerifyKeywordAsync(AddInsideMethod( +@"$$")); + } + + [Fact] + public async Task TestBeforeStatement() + { + await VerifyKeywordAsync(AddInsideMethod( +@"$$ +return true;")); + } + + [Fact] + public async Task TestAfterStatement() + { + await VerifyKeywordAsync(AddInsideMethod( +@"return true; +$$")); + } + + [Fact] + public async Task TestNotInClass() + { + await VerifyAbsenceAsync(@"class C +{ + $$ +}"); + } + + [Fact] + public async Task TestInFor() + { + await VerifyKeywordAsync(AddInsideMethod( +@"for ($$")); + } + + [Fact] + public async Task TestInFor2() + { + await VerifyKeywordAsync(AddInsideMethod( +@"for ($$;")); + } + + [Fact] + public async Task TestInFor3() + { + await VerifyKeywordAsync(AddInsideMethod( +@"for ($$;;")); + } + + [Fact] + public async Task TestNotInFor() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"for (var $$")); + } + + [Fact] + public async Task TestNotAfterVar() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"var $$")); + } + + [Fact] + public async Task TestInForEach() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach ($$")); + } + + [Fact] + public async Task TestNotInForEach() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach (var $$")); + } + + [Fact] + public async Task TestNotInAwaitForEach() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"await foreach ($$")); + } + + [Fact] + public async Task TestNotInAwaitForEach2() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"await foreach (var $$")); + } + + [Fact] + public async Task TestNotInForEachRefLoop0() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach (ref $$")); + } + + [Fact] + public async Task TestNotInForEachRefLoop1() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach (ref $$ x")); + } + + [Fact] + public async Task TestNotInForEachRefLoop2() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach (ref v$$ x")); + } + + [Fact] + public async Task TestNotInForEachRefReadonlyLoop0() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"foreach (ref readonly $$ x")); + } + + [Fact] + public async Task TestNotInForRefLoop0() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"for (ref $$")); + } + + [Fact] + public async Task TestNotInForRefLoop1() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"for (ref v$$")); + } + + [Fact] + public async Task TestNotInForRefReadonlyLoop0() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"for (ref readonly $$")); + } + + [Fact] + public async Task TestNotInForRefReadonlyLoop1() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"for (ref readonly v$$")); + } + + [Fact] + public async Task TestNotInUsing() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"using ($$")); + } + + [Fact] + public async Task TestNotInUsing2() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"using (var $$")); + } + + [Fact] + public async Task TestNotInAwaitUsing() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"await using ($$")); + } + + [Fact] + public async Task TestNotInAwaitUsing2() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"await using (var $$")); + } + + [Fact] + public async Task TestNotAfterConstLocal() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"const $$")); + } + + [Fact] + public async Task TestNotAfterConstField() + { + await VerifyAbsenceAsync( +@"class C { + const $$"); + } + + [Fact] + public async Task TestAfterOutKeywordInArgument() + { + await VerifyKeywordAsync(AddInsideMethod( +@"M(out $$")); + } + + [Fact] + public async Task TestNotAfterRefInMemberContext() + { + await VerifyAbsenceAsync( +@"class C { + ref $$"); + } + + [Fact] + public async Task TestNotAfterRefReadonlyInMemberContext() + { + await VerifyAbsenceAsync( +@"class C { + ref readonly $$"); + } + + [Fact] + public async Task TestNotAfterRefInStatementContext() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"ref $$")); + } + + [Fact] + public async Task TestNotAfterRefReadonlyInStatementContext() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"ref readonly $$")); + } + + [Fact] + public async Task TestNotAfterRefLocalDeclaration() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"ref $$ int local;")); + } + + [Fact] + public async Task TestNotAfterRefReadonlyLocalDeclaration() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"ref readonly $$ int local;")); + } + + [Fact] + public async Task TestNotAfterRefExpression() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"ref int x = ref $$")); + } + + [Fact] + public async Task TestInParameter1() + { + await VerifyKeywordAsync(""" + class C + { + public void M($$) + } + """); + } + + [Fact] + public async Task TestInParameter2() + { + await VerifyKeywordAsync(""" + class C + { + public void M($$ ref) + } + """); + } + + [Fact] + public async Task TestInParameter3() + { + await VerifyKeywordAsync(""" + class C + { + public void M($$ ref int i) + } + """); + } + + [Fact] + public async Task TestInParameter4() + { + await VerifyKeywordAsync(""" + class C + { + public void M($$ ref int i) + } + """); + } + + [Fact] + public async Task TestInOperatorParameter() + { + await VerifyKeywordAsync(""" + class C + { + public static C operator +($$ in C c) + } + """); + } + + [Fact] + public async Task TestInAnonymousMethodParameter() + { + await VerifyKeywordAsync(""" + class C + { + void M() + { + var x = delegate ($$) { }; + } + } + """); + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs new file mode 100644 index 0000000000000..8011a51fb92e3 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs @@ -0,0 +1,56 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders +{ + internal class ScopedKeywordRecommender : AbstractSyntacticSingleKeywordRecommender + { + public ScopedKeywordRecommender() + : base(SyntaxKind.ScopedKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + var syntaxTree = context.SyntaxTree; + return + syntaxTree.IsParameterModifierContext(position, context.LeftToken, includeOperators: true, out _, out _) || + syntaxTree.IsAnonymousMethodParameterModifierContext(position, context.LeftToken) || + syntaxTree.IsPossibleLambdaParameterModifierContext(position, context.LeftToken, cancellationToken) || + IsValidScopedLocalContext(context); + } + + private static bool IsValidScopedLocalContext(CSharpSyntaxContext context) + { + // scoped ref var x ... + if (context.IsStatementContext || context.IsGlobalStatementContext) + { + return true; + } + + var token = context.TargetToken; + switch (token.Kind()) + { + // for (scoped ref var x ... + case SyntaxKind.OpenParenToken: + var previous = token.GetPreviousToken(includeSkipped: true); + return previous.IsKind(SyntaxKind.ForKeyword); + + // M(out scoped ..) + case SyntaxKind.OutKeyword: + return token.Parent is ArgumentSyntax; + } + + return false; + } + + } +} From f97b613a0835d0ff038d255ebcec8b0a3e09e54d Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 15:15:11 +0200 Subject: [PATCH 03/15] Adjust ref/in/out completion to work after scoped --- .../InKeywordRecommenderTests.cs | 186 ++++++++++-------- .../OutKeywordRecommenderTests.cs | 25 +++ .../RefKeywordRecommenderTests.cs | 38 ++++ .../InKeywordRecommender.cs | 2 +- .../OutKeywordRecommender.cs | 2 +- .../RefKeywordRecommender.cs | 2 +- .../ContextQuery/SyntaxTreeExtensions.cs | 40 +++- 7 files changed, 202 insertions(+), 93 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs index 904bdca1b9f33..0ffc9f4b54825 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs @@ -9,16 +9,17 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations { + [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public class InKeywordRecommenderTests : KeywordRecommenderTests { - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotAtRoot_Interactive() { await VerifyAbsenceAsync(SourceCodeKind.Script, @"$$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotAfterClass_Interactive() { await VerifyAbsenceAsync(SourceCodeKind.Script, @@ -26,7 +27,7 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotAfterGlobalStatement_Interactive() { await VerifyAbsenceAsync(SourceCodeKind.Script, @@ -34,7 +35,7 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotAfterGlobalVariableDeclaration_Interactive() { await VerifyAbsenceAsync(SourceCodeKind.Script, @@ -42,49 +43,49 @@ await VerifyAbsenceAsync(SourceCodeKind.Script, $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInUsingAlias() { await VerifyAbsenceAsync( @"using Goo = $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInGlobalUsingAlias() { await VerifyAbsenceAsync( @"global using Goo = $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInEmptyStatement() { await VerifyAbsenceAsync(AddInsideMethod( @"$$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotAfterFrom() { await VerifyAbsenceAsync(AddInsideMethod( @"var q = from $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestAfterFromIdentifier() { await VerifyKeywordAsync(AddInsideMethod( @"var q = from x $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestAfterFromAndTypeAndIdentifier() { await VerifyKeywordAsync(AddInsideMethod( @"var q = from int x $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotAfterJoin() { await VerifyAbsenceAsync(AddInsideMethod( @@ -92,7 +93,7 @@ await VerifyAbsenceAsync(AddInsideMethod( join $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestAfterJoinIdentifier() { await VerifyKeywordAsync(AddInsideMethod( @@ -100,7 +101,7 @@ await VerifyKeywordAsync(AddInsideMethod( join z $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestAfterJoinAndTypeAndIdentifier() { await VerifyKeywordAsync(AddInsideMethod( @@ -108,7 +109,7 @@ await VerifyKeywordAsync(AddInsideMethod( join int z $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestAfterJoinNotAfterIn() { await VerifyAbsenceAsync(AddInsideMethod( @@ -116,7 +117,7 @@ await VerifyAbsenceAsync(AddInsideMethod( join z in $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] [WorkItem(544158, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544158")] public async Task TestNotAfterJoinPredefinedType() { @@ -130,7 +131,7 @@ void M() join int $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] [WorkItem(544158, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544158")] public async Task TestNotAfterJoinType() { @@ -144,126 +145,126 @@ void M() join Int32 $$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestInForEach() { await VerifyKeywordAsync(AddInsideMethod( @"foreach (var v $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestInForEach1() { await VerifyKeywordAsync(AddInsideMethod( @"foreach (var v $$ c")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestInForEach2() { await VerifyKeywordAsync(AddInsideMethod( @"foreach (var v $$ c")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInForEach() { await VerifyAbsenceAsync(AddInsideMethod( @"foreach ($$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInForEach1() { await VerifyAbsenceAsync(AddInsideMethod( @"foreach (var $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInForEach2() { await VerifyAbsenceAsync(AddInsideMethod( @"foreach (var v in $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestNotInForEach3() { await VerifyAbsenceAsync(AddInsideMethod( @"foreach (var v in c $$")); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestInterfaceTypeVarianceAfterAngle() { await VerifyKeywordAsync( @"interface IGoo<$$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact] public async Task TestInterfaceTypeVarianceNotAfterIn() { await VerifyAbsenceAsync( @"interface IGoo Date: Mon, 14 Nov 2022 15:42:34 +0200 Subject: [PATCH 04/15] Fix build --- .../CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index 9cc1c614cb3c7..e0e2d8e1c8784 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -1164,7 +1164,7 @@ public static bool IsParameterTypeContext(this SyntaxTree syntaxTree, int positi { var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position); - if (syntaxTree.IsParameterModifierContext(position, tokenOnLeftOfPosition, includeOperators: true, out _, out var previousModifier)) + if (syntaxTree.IsParameterModifierContext(position, tokenOnLeftOfPosition, includeOperators: true, out _, out _)) { return true; } From 1d08fde43fd4dd8a2b902db77f1f3880ff4cd8a9 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 16:19:13 +0200 Subject: [PATCH 05/15] Fix build --- .../Completion/KeywordRecommenders/ScopedKeywordRecommender.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs index 8011a51fb92e3..4825983c39a41 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs @@ -51,6 +51,5 @@ private static bool IsValidScopedLocalContext(CSharpSyntaxContext context) return false; } - } } From 1f40ee65616b25879c21d0dd05390a687ab7a744 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 18:59:20 +0200 Subject: [PATCH 06/15] Fix ref completion --- .../Completion/KeywordRecommenders/RefKeywordRecommender.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RefKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RefKeywordRecommender.cs index 187bd6e71aadf..b54dff97bf265 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RefKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/RefKeywordRecommender.cs @@ -150,6 +150,11 @@ private static bool IsValidRefExpressionContext(CSharpSyntaxContext context) case SyntaxKind.ReturnKeyword: return true; + // scoped ref ... + case SyntaxKind.ScopedKeyword: + case SyntaxKind.IdentifierToken when token.Text == "scoped": + return true; + // { // () => ref ... // From eb265d35d0a10b8e90a85352dca9d6b109e0f4bb Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 19:49:17 +0200 Subject: [PATCH 07/15] Fix scoped completion in foreach --- .../Recommendations/ScopedKeywordRecommenderTests.cs | 8 ++++---- .../KeywordRecommenders/ScopedKeywordRecommender.cs | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs index 46baf9d76b288..51f628c6cd2a5 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs @@ -170,7 +170,7 @@ await VerifyAbsenceAsync(AddInsideMethod( [Fact] public async Task TestInForEach() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyKeywordAsync(AddInsideMethod( @"foreach ($$")); } @@ -182,14 +182,14 @@ await VerifyAbsenceAsync(AddInsideMethod( } [Fact] - public async Task TestNotInAwaitForEach() + public async Task TestInAwaitForEach() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyKeywordAsync(AddInsideMethod( @"await foreach ($$")); } [Fact] - public async Task TestNotInAwaitForEach2() + public async Task TestNotInAwaitForEach() { await VerifyAbsenceAsync(AddInsideMethod( @"await foreach (var $$")); diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs index 4825983c39a41..ded0f3e44fcb0 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ScopedKeywordRecommender.cs @@ -39,10 +39,11 @@ private static bool IsValidScopedLocalContext(CSharpSyntaxContext context) var token = context.TargetToken; switch (token.Kind()) { - // for (scoped ref var x ... + // for (scoped ref var x ... + // foreach (scoped ... case SyntaxKind.OpenParenToken: var previous = token.GetPreviousToken(includeSkipped: true); - return previous.IsKind(SyntaxKind.ForKeyword); + return previous.Kind() is SyntaxKind.ForKeyword or SyntaxKind.ForEachKeyword; // M(out scoped ..) case SyntaxKind.OutKeyword: From c417ddd1ffc54cf625216f5f48a3eba9b7ab19e7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 20:26:41 +0200 Subject: [PATCH 08/15] Completion for 'var' after scoped --- .../Recommendations/VarKeywordRecommenderTests.cs | 7 +++++++ .../Extensions/ContextQuery/SyntaxTreeExtensions.cs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs index 9db736fd230b7..cbb86f9066ffb 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs @@ -450,5 +450,12 @@ public async Task TestInMixedDeclarationAndAssignmentInDeconstruction() await VerifyKeywordAsync(AddInsideMethod( @"(x, $$) = (0, 0);")); } + + [Fact] + public async Task TestAfterScoped() + { + await VerifyKeywordAsync(AddInsideMethod("scoped $$")); + await VerifyKeywordAsync("scoped $$"); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index e0e2d8e1c8784..a76594e538227 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -1776,6 +1776,7 @@ public static bool IsLocalVariableDeclarationContext( // join var // using var // await using var + // scoped var var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position); @@ -1858,6 +1859,13 @@ token.Parent is ArgumentSyntax argument && return true; } + // scoped | + if ((token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.Parent!.Kind() is SyntaxKind.VariableDeclaration or SyntaxKind.ExpressionStatement or SyntaxKind.IncompleteMember) || + token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent.IsKind(SyntaxKind.ScopedType)) + { + return true; + } + return false; } From 8b84ab76e2b9c4cf99b0f43e205ab247f07edede Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 21:14:37 +0200 Subject: [PATCH 09/15] More completion fixes --- .../SymbolCompletionProviderTests.cs | 44 ++++++++++++ .../TypeImportCompletionProviderTests.cs | 68 +++++++++++++++++++ .../GlobalKeywordRecommenderTests.cs | 7 ++ .../GlobalKeywordRecommender.cs | 1 + .../ContextQuery/SyntaxTreeExtensions.cs | 2 +- 5 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index ebbda2ca97cdf..7ed9639f321c1 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -11896,6 +11896,50 @@ class C await VerifyAnyItemExistsAsync(source); } + [Fact] + public async Task AfterScopedInsideMethod() + { + var source = @" +class C +{ + void M() + { + scoped $$ + } +} + +ref struct MyRefStruct { } +"; + await VerifyItemExistsAsync(MakeMarkup(source), "MyRefStruct"); + } + + [Fact] + public async Task AfterScopedGlobalStatement() + { + var source = @" +scoped $$ + +ref struct MyRefStruct { } +"; + await VerifyItemExistsAsync(MakeMarkup(source), "MyRefStruct"); + } + + [Fact] + public async Task AfterScopedInParameter() + { + var source = @" +class C +{ + void M(scoped $$) + { + } +} + +ref struct MyRefStruct { } +"; + await VerifyItemExistsAsync(MakeMarkup(source), "MyRefStruct"); + } + private static string MakeMarkup(string source, string languageVersion = "Preview") { return $$""" diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs index 537ca2ba2d015..5db2d641b4819 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs @@ -1798,6 +1798,74 @@ await VerifyTypeImportItemIsAbsentAsync( inlineDescription: "Foo"); } + [WpfFact] + public async Task TestAfterScoped() + { + var markup = @" +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} + +namespace Test +{ + class Program + { + public static void Main() + { + scoped $$ + } + } +}"; + + var expectedCodeAfterCommit = @" +using MyNamespace; + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} + +namespace Test +{ + class Program + { + public static void Main() + { + scoped MyRefStruct$$ + } + } +}"; + + await VerifyCustomCommitProviderAsync(markup, "MyRefStruct", expectedCodeAfterCommit); + } + + [WpfFact] + public async Task TestAfterScopedInTopLevel() + { + var markup = @" +scoped $$ + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} +"; + + var expectedCodeAfterCommit = @" +using MyNamespace; + +scoped MyRefStruct$$ + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} +"; + + await VerifyCustomCommitProviderAsync(markup, "MyRefStruct", expectedCodeAfterCommit); + } + private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null, CompletionItemFlags? flags = null) => VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, isComplexTextEdit: true, flags: flags); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs index e7f2b6d073a5e..f3c046bc6a1e8 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/GlobalKeywordRecommenderTests.cs @@ -349,5 +349,12 @@ await VerifyKeywordAsync( @"$$ [assembly: Call()]"); } + + [Fact] + public async Task TestAfterScoped() + { + await VerifyKeywordAsync("scoped $$"); + await VerifyKeywordAsync(AddInsideMethod("scoped $$")); + } } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs index 2f87981cf6899..43e010a42bbf1 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs @@ -34,6 +34,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context return context.IsStatementContext || context.IsGlobalStatementContext || + context.IsTypeContext || UsingKeywordRecommender.IsUsingDirectiveContext(context, forGlobalKeyword: true, cancellationToken) || context.IsAnyExpressionContext || context.IsObjectCreationTypeContext || diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index a76594e538227..36c32313143fe 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -1861,7 +1861,7 @@ token.Parent is ArgumentSyntax argument && // scoped | if ((token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.Parent!.Kind() is SyntaxKind.VariableDeclaration or SyntaxKind.ExpressionStatement or SyntaxKind.IncompleteMember) || - token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent.IsKind(SyntaxKind.ScopedType)) + token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent.Kind() is SyntaxKind.ScopedType or SyntaxKind.IncompleteMember) { return true; } From d03433165e754cf2d421bc5f58b2ee21bb5ecdbd Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 22:00:10 +0200 Subject: [PATCH 10/15] Move to CSharpCompletionCommandHandlerTests --- .../TypeImportCompletionProviderTests.cs | 68 ----------- .../CSharpCompletionCommandHandlerTests.vb | 106 ++++++++++++++++++ .../ContextQuery/SyntaxTreeExtensions.cs | 2 +- 3 files changed, 107 insertions(+), 69 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs index 5db2d641b4819..537ca2ba2d015 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs @@ -1798,74 +1798,6 @@ await VerifyTypeImportItemIsAbsentAsync( inlineDescription: "Foo"); } - [WpfFact] - public async Task TestAfterScoped() - { - var markup = @" -namespace MyNamespace -{ - public ref struct MyRefStruct { } -} - -namespace Test -{ - class Program - { - public static void Main() - { - scoped $$ - } - } -}"; - - var expectedCodeAfterCommit = @" -using MyNamespace; - -namespace MyNamespace -{ - public ref struct MyRefStruct { } -} - -namespace Test -{ - class Program - { - public static void Main() - { - scoped MyRefStruct$$ - } - } -}"; - - await VerifyCustomCommitProviderAsync(markup, "MyRefStruct", expectedCodeAfterCommit); - } - - [WpfFact] - public async Task TestAfterScopedInTopLevel() - { - var markup = @" -scoped $$ - -namespace MyNamespace -{ - public ref struct MyRefStruct { } -} -"; - - var expectedCodeAfterCommit = @" -using MyNamespace; - -scoped MyRefStruct$$ - -namespace MyNamespace -{ - public ref struct MyRefStruct { } -} -"; - - await VerifyCustomCommitProviderAsync(markup, "MyRefStruct", expectedCodeAfterCommit); - } - private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null, CompletionItemFlags? flags = null) => VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, isComplexTextEdit: true, flags: flags); diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 1cfceffdd1d26..d114dec922ea7 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -8938,6 +8938,112 @@ public class AA End Using End Function + + Public Async Function TestTypeImportCompletionAfterScoped(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} + +namespace Test +{ + class Program + { + public static void Main() + { + scoped $$ + } + } +} +, + showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) + state.Workspace.GlobalOptions.SetGlobalOption( + New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + + state.SendInvokeCompletionList() + Await state.WaitForAsynchronousOperationsAsync() + Await state.WaitForUIRenderedAsync() + + ' Make sure expander is selected + state.SetCompletionItemExpanderState(isSelected:=True) + Await state.WaitForAsynchronousOperationsAsync() + Await state.WaitForUIRenderedAsync() + + Dim expectedText = " +using MyNamespace; + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} + +namespace Test +{ + class Program + { + public static void Main() + { + scoped MyRefStruct + } + } +} +" + state.SendTypeChars("MyR") + state.SendSelectCompletionItem("MyRefStruct") + state.SendTypeChars(" ") + Assert.Equal(expectedText, state.GetDocumentText()) + Await state.AssertLineTextAroundCaret(expectedTextBeforeCaret:=" scoped MyRefStruct ", expectedTextAfterCaret:="") + End Using + End Function + + + Public Async Function TestTypeImportCompletionAfterScopedInTopLevel(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + +scoped $$ + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} +, + showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) + state.Workspace.GlobalOptions.SetGlobalOption( + New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + + state.SendInvokeCompletionList() + Await state.WaitForAsynchronousOperationsAsync() + Await state.WaitForUIRenderedAsync() + + ' Make sure expander is selected + state.SetCompletionItemExpanderState(isSelected:=True) + Await state.WaitForAsynchronousOperationsAsync() + Await state.WaitForUIRenderedAsync() + + Dim expectedText = " +using MyNamespace; + +scoped MyRefStruct + +namespace MyNamespace +{ + public ref struct MyRefStruct { } +} +" + state.SendTypeChars("MyR") + state.SendSelectCompletionItem("MyRefStruct") + state.SendTypeChars(" ") + Assert.Equal(expectedText, state.GetDocumentText()) + Await state.AssertLineTextAroundCaret(expectedTextBeforeCaret:="scoped MyRefStruct ", expectedTextAfterCaret:="") + End Using + End Function + Public Async Function TestCompleteParenthesisForMethodUnderNameofContext(showCompletionInArgumentLists As Boolean) As Task Using state = TestStateFactory.CreateCSharpTestState( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index 36c32313143fe..9cc6e74610d0e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -1861,7 +1861,7 @@ token.Parent is ArgumentSyntax argument && // scoped | if ((token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.Parent!.Kind() is SyntaxKind.VariableDeclaration or SyntaxKind.ExpressionStatement or SyntaxKind.IncompleteMember) || - token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent.Kind() is SyntaxKind.ScopedType or SyntaxKind.IncompleteMember) + token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent!.Kind() is SyntaxKind.ScopedType or SyntaxKind.IncompleteMember) { return true; } From b060564c51100a182339fa3da96f98b7477cf784 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 22:06:40 +0200 Subject: [PATCH 11/15] Simplify GlobalKeywordRecommender --- .../KeywordRecommenders/GlobalKeywordRecommender.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs index 43e010a42bbf1..430822733b537 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/GlobalKeywordRecommender.cs @@ -32,14 +32,9 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context } return - context.IsStatementContext || - context.IsGlobalStatementContext || context.IsTypeContext || UsingKeywordRecommender.IsUsingDirectiveContext(context, forGlobalKeyword: true, cancellationToken) || context.IsAnyExpressionContext || - context.IsObjectCreationTypeContext || - context.IsIsOrAsTypeContext || - context.IsFunctionPointerTypeArgumentContext || syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) || syntaxTree.IsAfterKeyword(position, SyntaxKind.RefKeyword, cancellationToken) || syntaxTree.IsAfterKeyword(position, SyntaxKind.ReadOnlyKeyword, cancellationToken) || From 772159c963fdf529d07e7b05e0214238eda97531 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 14 Nov 2022 22:28:37 +0200 Subject: [PATCH 12/15] Address review comments --- .../Recommendations/ScopedKeywordRecommenderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs index 51f628c6cd2a5..fd0daf6c241db 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs @@ -213,7 +213,7 @@ await VerifyAbsenceAsync(AddInsideMethod( public async Task TestNotInForEachRefLoop2() { await VerifyAbsenceAsync(AddInsideMethod( -@"foreach (ref v$$ x")); +@"foreach (ref s$$ x")); } [Fact] @@ -234,7 +234,7 @@ await VerifyAbsenceAsync(AddInsideMethod( public async Task TestNotInForRefLoop1() { await VerifyAbsenceAsync(AddInsideMethod( -@"for (ref v$$")); +@"for (ref s$$")); } [Fact] @@ -248,7 +248,7 @@ await VerifyAbsenceAsync(AddInsideMethod( public async Task TestNotInForRefReadonlyLoop1() { await VerifyAbsenceAsync(AddInsideMethod( -@"for (ref readonly v$$")); +@"for (ref readonly s$$")); } [Fact] From 53f4568dda3df3b2e2dd0285fe00ed4e11e79af9 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 17 Nov 2022 14:51:41 +0200 Subject: [PATCH 13/15] Add tests, split condition --- .../SymbolCompletionProviderTests.cs | 17 +++++++- .../CSharpReassignedVariableTests.cs | 42 ++++++++++++++++--- .../InKeywordRecommenderTests.cs | 11 +++++ .../OutKeywordRecommenderTests.cs | 11 +++++ .../RefKeywordRecommenderTests.cs | 11 +++++ .../ScopedKeywordRecommenderTests.cs | 11 +++++ .../ContextQuery/SyntaxTreeExtensions.cs | 14 ++++++- 7 files changed, 107 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index 7ed9639f321c1..6aee6df79b097 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp; @@ -11914,7 +11915,7 @@ ref struct MyRefStruct { } } [Fact] - public async Task AfterScopedGlobalStatement() + public async Task AfterScopedGlobalStatement_FollowedByType() { var source = @" scoped $$ @@ -11924,6 +11925,18 @@ ref struct MyRefStruct { } await VerifyItemExistsAsync(MakeMarkup(source), "MyRefStruct"); } + [Fact] + public async Task AfterScopedGlobalStatement_NotFollowedByType() + { + var source = """ + using System; + + scoped $$ + """; + + await VerifyItemExistsAsync(MakeMarkup(source), "ReadOnlySpan", displayTextSuffix: "<>"); + } + [Fact] public async Task AfterScopedInParameter() { @@ -11944,7 +11957,7 @@ private static string MakeMarkup(string source, string languageVersion = "Previe { return $$""" - + {{source}} diff --git a/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs b/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs index cbecabbaeb05f..5bac067b28fb6 100644 --- a/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/ReassignedVariable/CSharpReassignedVariableTests.cs @@ -500,15 +500,25 @@ await TestAsync( using System; class C { - void M1(ref int [|p|]) + void M(ref int [|p|]) { ref var [|local|] = ref [|p|]; [|local|] = 0; [|local|] = 1; Console.WriteLine([|local|]); } +}"); + } - void M2(ref int [|p|]) + [Fact] + public async Task AssignmentThroughScopedRefLocal() + { + await TestAsync( +@" +using System; +class C +{ + void M(ref int [|p|]) { scoped ref var [|local|] = ref [|p|]; [|local|] = 0; @@ -652,14 +662,24 @@ await TestAsync( using System; class C { - void M1() + void M() { int p = 0; ref readonly int refP = ref p; Console.WriteLine(p); } +}"); + } - void M2() + [Fact] + public async Task TestScopedReadonlyRefLocalWithNoReassignment() + { + await TestAsync( +@" +using System; +class C +{ + void M() { int p = 0; scoped ref readonly int refP = ref p; @@ -676,14 +696,24 @@ await TestAsync( using System; class C { - void M1() + void M() { int p = 0; ref readonly int refP = ref p!; Console.WriteLine(p); } +}"); + } - void M2() + [Fact] + public async Task TestScopedReadonlyRefLocalWithNoReassignment1() + { + await TestAsync( +@" +using System; +class C +{ + void M1() { int p = 0; scoped ref readonly int refP = ref p!; diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs index 0ffc9f4b54825..0b953a1717a2f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/InKeywordRecommenderTests.cs @@ -739,6 +739,17 @@ void M(scoped $$) """); } + [Fact] + public async Task TestInParameterAfterThisScoped() + { + await VerifyKeywordAsync(""" + static class C + { + static void M(this scoped $$) + } + """); + } + [Fact] public async Task TestInAnonymousMethodParameterAfterScoped() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs index d9dc00bb4e6bf..036521bb29e7e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/OutKeywordRecommenderTests.cs @@ -484,6 +484,17 @@ void M(scoped $$) """); } + [Fact] + public async Task TestInParameterAfterThisScoped() + { + await VerifyKeywordAsync(""" + static class C + { + static void M(this scoped $$) + } + """); + } + [Fact] public async Task TestInAnonymousMethodParameterAfterScoped() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs index ee168a9888f38..b1f306d8faf06 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RefKeywordRecommenderTests.cs @@ -1021,6 +1021,17 @@ void M(scoped $$) """); } + [Fact] + public async Task TestInParameterAfterThisScoped() + { + await VerifyKeywordAsync(""" + static class C + { + static void M(this scoped $$) + } + """); + } + [Fact] public async Task TestInAnonymousMethodParameterAfterScoped() { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs index fd0daf6c241db..e41b289601c50 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ScopedKeywordRecommenderTests.cs @@ -420,5 +420,16 @@ void M() } """); } + + [Fact] + public async Task TestInParameterAfterThisScoped() + { + await VerifyKeywordAsync(""" + static class C + { + static void M(this $$) + } + """); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs index 454a153b10b95..5ce100d79990e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/SyntaxTreeExtensions.cs @@ -1866,8 +1866,18 @@ token.Parent is ArgumentSyntax argument && } // scoped | - if ((token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.Parent!.Kind() is SyntaxKind.VariableDeclaration or SyntaxKind.ExpressionStatement or SyntaxKind.IncompleteMember) || - token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent!.Kind() is SyntaxKind.ScopedType or SyntaxKind.IncompleteMember) + // The compiler parses this as an identifier whose parent is: + // - ExpressionStatementSyntax when in method declaration. + // - IncompleteMemberSyntax when in top-level code and there are no class declarations after it. + // - BaseTypeDeclarationSyntax if it comes after scoped + // - VariableDeclarationSyntax for `scoped X` inside method declaration + if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.Parent is VariableDeclarationSyntax or ExpressionStatementSyntax or IncompleteMemberSyntax) + { + return true; + } + + // scoped v| + if (token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent is IncompleteMemberSyntax) { return true; } From 322cb8c9ed31e7211218f9c4fbea3272a8184e4c Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 17 Nov 2022 17:07:22 +0200 Subject: [PATCH 14/15] Update CSharpCompletionCommandHandlerTests.vb --- .../IntelliSense/CSharpCompletionCommandHandlerTests.vb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 5ba6638660d24..6fb3b78399c18 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -8916,9 +8916,7 @@ namespace Test Await state.WaitForUIRenderedAsync() ' Make sure expander is selected - state.SetCompletionItemExpanderState(isSelected:=True) - Await state.WaitForAsynchronousOperationsAsync() - Await state.WaitForUIRenderedAsync() + state.SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected:=True) Dim expectedText = " using MyNamespace; @@ -8969,9 +8967,7 @@ namespace MyNamespace Await state.WaitForUIRenderedAsync() ' Make sure expander is selected - state.SetCompletionItemExpanderState(isSelected:=True) - Await state.WaitForAsynchronousOperationsAsync() - Await state.WaitForUIRenderedAsync() + state.SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected:=True) Dim expectedText = " using MyNamespace; From 72c49aa6866204292d734156e7bbab489367ac8f Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 17 Nov 2022 18:01:09 +0200 Subject: [PATCH 15/15] add missing Await --- .../Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 6fb3b78399c18..ff543c0efbd38 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -8916,7 +8916,7 @@ namespace Test Await state.WaitForUIRenderedAsync() ' Make sure expander is selected - state.SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected:=True) + Await state.SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected:=True) Dim expectedText = " using MyNamespace; @@ -8967,7 +8967,7 @@ namespace MyNamespace Await state.WaitForUIRenderedAsync() ' Make sure expander is selected - state.SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected:=True) + Await state.SetCompletionItemExpanderStateAndWaitForUiRenderAsync(isSelected:=True) Dim expectedText = " using MyNamespace;