提交 7248e2cf 编写于 作者: A Andy Gocke 提交者: GitHub

Implement async and void keyword completion for local functions (#15211)

This PR implements completion for the simple cases of async and void in
a local function context. What it misses cases where parsing incorrectly
considers bare modifiers to be local variable declaration statements
rather than local function declaration statements. This is tracked by
bug #14525.

Fixes #8616
Fixes #8617
上级 6ca7da43
......@@ -128,6 +128,120 @@ public async Task TestNotAfterPartialInClass()
class Foo
{
partial $$
}");
}
[Fact]
[WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
public async Task TestLocalFunction()
{
await VerifyKeywordAsync(@"
class Foo
{
public void M()
{
$$
}
}");
}
[Fact]
[WorkItem(14525, "https://github.com/dotnet/roslyn/issues/14525")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction2()
{
await VerifyKeywordAsync(@"
class Foo
{
public void M()
{
unsafe $$
}
}");
}
[Fact]
[WorkItem(14525, "https://github.com/dotnet/roslyn/issues/14525")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction3()
{
await VerifyKeywordAsync(@"
class Foo
{
public void M()
{
unsafe $$ void L() { }
}
}");
}
[Fact]
[WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction4()
{
await VerifyKeywordAsync(@"
class Foo
{
public void M()
{
$$ void L() { }
}
}");
}
[Fact]
[WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction5()
{
await VerifyKeywordAsync(@"
class Foo
{
public void M(Action<int> a)
{
M(async () =>
{
$$
});
}
}");
}
[Fact]
[WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction6()
{
await VerifyAbsenceAsync(@"
class Foo
{
public void M()
{
int $$
}
}");
}
[Fact]
[WorkItem(8616, "https://github.com/dotnet/roslyn/issues/8616")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction7()
{
await VerifyAbsenceAsync(@"
class Foo
{
public void M()
{
static $$
}
}");
}
}
......
......@@ -83,13 +83,6 @@ public async Task TestNotInCastType2()
@"var str = (($$)items) as string;"));
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestNotInEmptyStatement()
{
await VerifyAbsenceAsync(AddInsideMethod(
@"$$"));
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestInTypeOf()
{
......@@ -642,5 +635,123 @@ public async Task TestNotAfterAsyncAsType()
{
await VerifyAbsenceAsync(@"class c { async async $$ }");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction()
{
await VerifyKeywordAsync(@"
class C
{
void M()
{
$$
}
}");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[WorkItem(14525, "https://github.com/dotnet/roslyn/issues/14525")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction2()
{
await VerifyKeywordAsync(@"
class C
{
void M()
{
async $$
}
}");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction3()
{
await VerifyAbsenceAsync(@"
class C
{
void M()
{
async async $$
}
}");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction4()
{
await VerifyAbsenceAsync(@"
class C
{
void M()
{
var async $$
}
}");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction5()
{
await VerifyAbsenceAsync(@"
using System;
class C
{
void M(Action<int> a)
{
M(async $$ () =>
}
}");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction6()
{
await VerifyKeywordAsync(@"
class C
{
void M()
{
unsafe async $$
}
}");
}
[Fact]
[WorkItem(8617, "https://github.com/dotnet/roslyn/issues/8617")]
[Test.Utilities.CompilerTrait(Test.Utilities.CompilerFeature.LocalFunctions)]
[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public async Task TestLocalFunction7()
{
await VerifyKeywordAsync(@"
using System;
class C
{
void M(Action<int> a)
{
M(async () =>
{
async $$
})
}
}");
}
}
}
......@@ -21,8 +21,13 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context
return true;
}
return !context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword)
&& InMemberDeclarationContext(position, context, cancellationToken);
if (context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword))
{
return false;
}
return InMemberDeclarationContext(position, context, cancellationToken)
|| context.SyntaxTree.IsLocalFunctionDeclarationContext(position, cancellationToken);
}
private static bool InMemberDeclarationContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken)
......
......@@ -45,7 +45,8 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context
IsUnsafeParameterTypeContext(context) ||
IsUnsafeCastTypeContext(context) ||
IsUnsafeDefaultExpressionContext(context, cancellationToken) ||
context.IsFixedVariableDeclarationContext;
context.IsFixedVariableDeclarationContext ||
context.SyntaxTree.IsLocalFunctionDeclarationContext(position, cancellationToken);
}
private bool IsUnsafeDefaultExpressionContext(CSharpSyntaxContext context, CancellationToken cancellationToken)
......
......@@ -292,6 +292,46 @@ public static bool IsAttributeNameContext(this SyntaxTree syntaxTree, int positi
return false;
}
public static bool IsLocalFunctionDeclarationContext(
this SyntaxTree syntaxTree,
int position,
CancellationToken cancellationToken)
{
var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
var token = leftToken.GetPreviousTokenIfTouchingWord(position);
// Local functions are always valid in a statement context
if (syntaxTree.IsStatementContext(position, leftToken, cancellationToken))
{
return true;
}
// Also valid after certain modifiers
var validModifiers = SyntaxKindSet.LocalFunctionModifiers;
var modifierTokens = syntaxTree.GetPrecedingModifiers(
position, token, out int beforeModifiersPosition);
if (modifierTokens.IsSubsetOf(validModifiers))
{
if (token.HasMatchingText(SyntaxKind.AsyncKeyword))
{
// second appearance of "async" not followed by modifier: treat as type
if (syntaxTree.GetPrecedingModifiers(token.SpanStart, token, cancellationToken)
.Contains(SyntaxKind.AsyncKeyword))
{
return false;
}
}
leftToken = syntaxTree.FindTokenOnLeftOfPosition(beforeModifiersPosition, cancellationToken);
token = leftToken.GetPreviousTokenIfTouchingWord(beforeModifiersPosition);
return syntaxTree.IsStatementContext(beforeModifiersPosition, token, cancellationToken);
}
return false;
}
public static bool IsTypeDeclarationContext(
this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
{
......
......@@ -16,7 +16,17 @@ namespace Microsoft.CodeAnalysis.CSharp.Extensions
internal static partial class SyntaxTreeExtensions
{
public static ISet<SyntaxKind> GetPrecedingModifiers(
this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
this SyntaxTree syntaxTree,
int position,
SyntaxToken tokenOnLeftOfPosition,
CancellationToken cancellationToken)
=> syntaxTree.GetPrecedingModifiers(position, tokenOnLeftOfPosition, out var _);
public static ISet<SyntaxKind> GetPrecedingModifiers(
this SyntaxTree syntaxTree,
int position,
SyntaxToken tokenOnLeftOfPosition,
out int positionBeforeModifiers)
{
var token = tokenOnLeftOfPosition;
token = token.GetPreviousTokenIfTouchingWord(position);
......@@ -58,6 +68,7 @@ internal static partial class SyntaxTreeExtensions
break;
}
positionBeforeModifiers = token.FullSpan.End;
return result;
}
......
......@@ -52,6 +52,12 @@ internal class SyntaxKindSet
SyntaxKind.VolatileKeyword,
};
public static readonly ISet<SyntaxKind> LocalFunctionModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer)
{
SyntaxKind.AsyncKeyword,
SyntaxKind.UnsafeKeyword
};
public static readonly ISet<SyntaxKind> AllTypeDeclarations = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer)
{
SyntaxKind.InterfaceDeclaration,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册