From 93bac1eafe8afd99ff8ca3122549cb963ec08792 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 2 Apr 2024 15:09:44 +0300 Subject: [PATCH 1/4] Fix #72821 --- .../CSharp/Portable/PublicAPI.Unshipped.txt | 1 + .../Syntax/LocalFunctionStatementSyntax.cs | 8 ++++ .../WhereKeywordRecommenderTests.cs | 41 +++++++++++++++++++ .../WhereKeywordRecommender.cs | 21 +++++++--- 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index a7eede3cf18d3..a7fb654a25541 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -3,6 +3,7 @@ Microsoft.CodeAnalysis.CSharp.Conversion.IsCollectionExpression.get -> bool Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! +Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Arity.get -> int Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Dispose() -> void Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseNextToken() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result diff --git a/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs index 4cb9a352a0052..6d9d2dcc1b188 100644 --- a/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs @@ -8,6 +8,14 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax { public partial class LocalFunctionStatementSyntax { + public int Arity + { + get + { + return this.TypeParameterList == null ? 0 : this.TypeParameterList.Parameters.Count; + } + } + // Preserved as shipped public API for binary compatibility public LocalFunctionStatementSyntax Update(SyntaxTokenList modifiers, TypeSyntax returnType, SyntaxToken identifier, TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, SyntaxList constraintClauses, BlockSyntax body, ArrowExpressionClauseSyntax expressionBody, SyntaxToken semicolonToken) { diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs index cc300bf7dd65f..fed82dd236dff 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs @@ -631,5 +631,46 @@ public class C where T : List<(int, string)> $$ } """); } + + [Fact] + public async Task TestNotAfterLocalFunction() + { + await VerifyAbsenceAsync( + """ + class C + { + void M() + { + void Inner() $$ + """); + } + + [Fact] + public async Task TestAfterGenericLocalFunction() + { + await VerifyKeywordAsync( + """ + class C + { + void M() + { + void Inner() $$ + """); + } + + [Fact] + public async Task TestAfterFirstValidConstraintInGenericLocalFunction() + { + await VerifyKeywordAsync( + """ + class C + { + void M() + { + void Inner() + where T1 : C + $$ + """); + } } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs index 288947677d05c..330649245f8df 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs @@ -68,13 +68,24 @@ private static bool IsTypeParameterConstraintContext(CSharpSyntaxContext context // void Goo() | if (token.Kind() == SyntaxKind.CloseParenToken && - token.Parent.IsKind(SyntaxKind.ParameterList) && - token.Parent.IsParentKind(SyntaxKind.MethodDeclaration)) + token.Parent.IsKind(SyntaxKind.ParameterList)) { - var decl = token.GetAncestor(); - if (decl != null && decl.Arity > 0) + var tokenParent = token.Parent; + if (tokenParent.IsParentKind(SyntaxKind.MethodDeclaration)) { - return true; + var decl = token.GetAncestor(); + if (decl != null && decl.Arity > 0) + { + return true; + } + } + else if (tokenParent.IsParentKind(SyntaxKind.LocalFunctionStatement)) + { + var decl = token.GetAncestor(); + if (decl != null && decl.Arity > 0) + { + return true; + } } } From b90617b874704578bdafe88211eb75a2cfc01be5 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 2 Apr 2024 15:45:07 +0300 Subject: [PATCH 2/4] Remove public API --- src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt | 1 - .../Portable/Syntax/LocalFunctionStatementSyntax.cs | 8 -------- .../KeywordRecommenders/WhereKeywordRecommender.cs | 2 +- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index a7fb654a25541..a7eede3cf18d3 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -3,7 +3,6 @@ Microsoft.CodeAnalysis.CSharp.Conversion.IsCollectionExpression.get -> bool Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! -Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Arity.get -> int Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Dispose() -> void Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseNextToken() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result diff --git a/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs index 6d9d2dcc1b188..4cb9a352a0052 100644 --- a/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/LocalFunctionStatementSyntax.cs @@ -8,14 +8,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax { public partial class LocalFunctionStatementSyntax { - public int Arity - { - get - { - return this.TypeParameterList == null ? 0 : this.TypeParameterList.Parameters.Count; - } - } - // Preserved as shipped public API for binary compatibility public LocalFunctionStatementSyntax Update(SyntaxTokenList modifiers, TypeSyntax returnType, SyntaxToken identifier, TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, SyntaxList constraintClauses, BlockSyntax body, ArrowExpressionClauseSyntax expressionBody, SyntaxToken semicolonToken) { diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs index 330649245f8df..099e10b21c660 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs @@ -82,7 +82,7 @@ private static bool IsTypeParameterConstraintContext(CSharpSyntaxContext context else if (tokenParent.IsParentKind(SyntaxKind.LocalFunctionStatement)) { var decl = token.GetAncestor(); - if (decl != null && decl.Arity > 0) + if (decl is { TypeParameterList.Parameters.Count: > 0 }) { return true; } From 29e1888161d1ae79abe7929290abd9aa9920526b Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 2 Apr 2024 15:59:35 +0300 Subject: [PATCH 3/4] Inline parent declaration --- .../KeywordRecommenders/WhereKeywordRecommender.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs index 099e10b21c660..9d4db952f2d63 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs @@ -71,18 +71,16 @@ private static bool IsTypeParameterConstraintContext(CSharpSyntaxContext context token.Parent.IsKind(SyntaxKind.ParameterList)) { var tokenParent = token.Parent; - if (tokenParent.IsParentKind(SyntaxKind.MethodDeclaration)) + if (tokenParent.IsParentKind(SyntaxKind.MethodDeclaration, out var methodDeclaration)) { - var decl = token.GetAncestor(); - if (decl != null && decl.Arity > 0) + if (methodDeclaration.Arity > 0) { return true; } } - else if (tokenParent.IsParentKind(SyntaxKind.LocalFunctionStatement)) + else if (tokenParent.IsParentKind(SyntaxKind.LocalFunctionStatement, out var localFunctionDeclaration)) { - var decl = token.GetAncestor(); - if (decl is { TypeParameterList.Parameters.Count: > 0 }) + if (localFunctionDeclaration is { TypeParameterList.Parameters.Count: > 0 }) { return true; } From 712d9f2390940cacc6669940abd41385cc7c7a7b Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 2 Apr 2024 19:00:39 +0300 Subject: [PATCH 4/4] Suggestions Co-Authored-By: Cyrus Najmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- .../Recommendations/WhereKeywordRecommenderTests.cs | 6 +++--- .../KeywordRecommenders/WhereKeywordRecommender.cs | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs index fed82dd236dff..05abad2c2c64d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WhereKeywordRecommenderTests.cs @@ -632,7 +632,7 @@ public class C where T : List<(int, string)> $$ """); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72821")] public async Task TestNotAfterLocalFunction() { await VerifyAbsenceAsync( @@ -645,7 +645,7 @@ void Inner() $$ """); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72821")] public async Task TestAfterGenericLocalFunction() { await VerifyKeywordAsync( @@ -658,7 +658,7 @@ void Inner() $$ """); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72821")] public async Task TestAfterFirstValidConstraintInGenericLocalFunction() { await VerifyKeywordAsync( diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs index 9d4db952f2d63..54e09fcf7e941 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhereKeywordRecommender.cs @@ -78,12 +78,9 @@ private static bool IsTypeParameterConstraintContext(CSharpSyntaxContext context return true; } } - else if (tokenParent.IsParentKind(SyntaxKind.LocalFunctionStatement, out var localFunctionDeclaration)) + else if (tokenParent.Parent is LocalFunctionStatementSyntax { TypeParameterList.Parameters.Count: > 0 }) { - if (localFunctionDeclaration is { TypeParameterList.Parameters.Count: > 0 }) - { - return true; - } + return true; } }