diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs index 3b7f050fb8d81ffff4dd7ed1ae2ad78985d9f75f..a006900dbfacfdc92c364bdcd022236435e8a61f 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/StackAllocKeywordRecommenderTests.cs @@ -55,9 +55,9 @@ public async Task TestNotInEmptyStatement() } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestNotInEmptySpace() + public async Task TestInEmptySpaceAfterAssignment() { - await VerifyAbsenceAsync(AddInsideMethod( + await VerifyKeywordAsync(AddInsideMethod( @"var v = $$")); } @@ -71,9 +71,10 @@ public async Task TestInUnsafeEmptySpace() } [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestInUnsafeEmptySpace_NotAfterNonPointer() + public async Task TestInUnsafeEmptySpace_AfterNonPointer() { - await VerifyAbsenceAsync( + // There can be an implicit conversion to int + await VerifyKeywordAsync( @"unsafe class C { void Goo() { int v = $$"); @@ -124,12 +125,146 @@ unsafe static void Main(string[] args) [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestInsideForStatementVarDecl3() { - await VerifyAbsenceAsync( + await VerifyKeywordAsync( @"class C { unsafe static void Main(string[] args) { for (string i = $$"); } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSOfAssignment_Span() + { + await VerifyKeywordAsync(AddInsideMethod(@" +Span s = $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSOfAssignment_Pointer() + { + await VerifyKeywordAsync(AddInsideMethod( +@"int* v = $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSOfAssignment_ReAssignment() + { + await VerifyKeywordAsync(AddInsideMethod( +@"v = $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithCast() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = (Span)$$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = (Span)$$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_True() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value ? $$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value ? $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_True_WithCast() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value ? (Span)$$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value ? (Span)$$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_False() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value ? stackalloc int[10] : $$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value ? stackalloc int[10] : $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_False_WithCast() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value ? stackalloc int[10] : (Span)$$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value ? stackalloc int[10] : (Span)$$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_NestedConditional_True() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value1 ? value2 ? $$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value1 ? value2 ? $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_NestedConditional_WithCast_True() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value1 ? value2 ? (Span)$$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value1 ? value2 ? (Span)$$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_NestedConditional_False() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value1 ? value2 ? stackalloc int [10] : $$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value1 ? value2 ? stackalloc int [10] : $$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestOnRHSWithConditionalExpression_NestedConditional_WithCast_False() + { + await VerifyKeywordAsync(AddInsideMethod(@" +var s = value1 ? value2 ? stackalloc int [10] : (Span)$$")); + + await VerifyKeywordAsync(AddInsideMethod(@" +s = value1 ? value2 ? stackalloc int [10] : (Span)$$")); + } + + [WorkItem(23584, "https://github.com/dotnet/roslyn/issues/23584")] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInLHSOfAssignment() + { + await VerifyAbsenceAsync(AddInsideMethod(@" +var x $$ =")); + + await VerifyAbsenceAsync(AddInsideMethod(@" +x $$ =")); + } } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StackAllocKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StackAllocKeywordRecommender.cs index 5a9cc3be3da999877a4fd45a49c8621a5e70a33f..a09924f881384d21364a8c5f9833b8789fd48687 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StackAllocKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/StackAllocKeywordRecommender.cs @@ -16,20 +16,47 @@ public StackAllocKeywordRecommender() protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) { - // type t = | - var token = context.TargetToken; - if (token.IsUnsafeContext()) + var node = context.TargetToken.Parent; + + // At start of a file + if (node == null) + { + return false; + } + + // After a cast or parenthesized expression: (Span)stackalloc + if (context.TargetToken.IsAfterPossibleCast()) + { + node = node.Parent; + } + + // Inside a conditional expression: value ? stackalloc : stackalloc + while (node.IsKind(SyntaxKind.ConditionalExpression) && + (context.TargetToken.IsKind(SyntaxKind.QuestionToken, SyntaxKind.ColonToken) || context.TargetToken.IsAfterPossibleCast())) + { + node = node.Parent; + } + + // assignment: x = stackalloc + if (node.IsKind(SyntaxKind.SimpleAssignmentExpression)) + { + return node.Parent.IsKind(SyntaxKind.ExpressionStatement); + } + + // declaration: var x = stackalloc + if (node.IsKind(SyntaxKind.EqualsValueClause)) { - if (token.Kind() == SyntaxKind.EqualsToken && - token.Parent.IsKind(SyntaxKind.EqualsValueClause) && - token.Parent.IsParentKind(SyntaxKind.VariableDeclarator) && - token.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclaration)) + node = node.Parent; + + if (node.IsKind(SyntaxKind.VariableDeclarator)) { - var variableDeclaration = (VariableDeclarationSyntax)token.Parent.Parent.Parent; - if (variableDeclaration.IsParentKind(SyntaxKind.LocalDeclarationStatement) || - variableDeclaration.IsParentKind(SyntaxKind.ForStatement)) + node = node.Parent; + + if (node.IsKind(SyntaxKind.VariableDeclaration)) { - return variableDeclaration.Type.IsVar || variableDeclaration.Type.IsKind(SyntaxKind.PointerType); + node = node.Parent; + + return node.IsKind(SyntaxKind.LocalDeclarationStatement, SyntaxKind.ForStatement); } } }