From 0d05f0af5bc9b6155760e18d328c4c9d3fed7f69 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 16:34:29 -0800 Subject: [PATCH 1/9] Improve completion in an expression before an 'await Task' expression. --- .../CSharp/Portable/CSharpExtensions.cs | 3 +- .../CSharpCompletionCommandHandlerTests.vb | 26 ++++- .../CSharpRecommendationService.cs | 107 ++++++++++++------ 3 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpExtensions.cs b/src/Compilers/CSharp/Portable/CSharpExtensions.cs index d2e4bf6f714b8..a9332cd949347 100644 --- a/src/Compilers/CSharp/Portable/CSharpExtensions.cs +++ b/src/Compilers/CSharp/Portable/CSharpExtensions.cs @@ -610,7 +610,6 @@ public static SymbolInfo GetSpeculativeSymbolInfo(this SemanticModel semanticMod } } - public static TypeInfo GetTypeInfo(this SemanticModel semanticModel, SelectOrGroupClauseSyntax node, CancellationToken cancellationToken = default(CancellationToken)) { var csmodel = semanticModel as CSharpSemanticModel; @@ -1339,4 +1338,4 @@ public static Conversion ClassifyConversion(this SemanticModel semanticModel, in } #endregion } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 92249062d03b8..7c1da18a15d93 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -2730,6 +2730,7 @@ class C End Function End Class + Public Async Function Filters_EmptyList1() As Task Using state = TestState.CreateCSharpTestState( Public Async Function Filters_EmptyList2() As Task Using state = TestState.CreateCSharpTestState( Public Async Function Filters_EmptyList3() As Task Using state = TestState.CreateCSharpTestState( Public Async Function Filters_EmptyList4() As Task Using state = TestState.CreateCSharpTestState( + + Public Async Function CompletionAfterDotBeforeAwaitTask() As Task + Using state = TestState.CreateCSharpTestState( + ) + + state.SendInvokeCompletionList() + Await state.AssertCompletionSession() + End Using + End Function End Class End Namespace diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs index d2c9411c3df58..cd777f0199391 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs @@ -305,56 +305,89 @@ private static ImmutableArray GetSymbolsOffOfName( if (name.IsFoundUnder(d => d.Declaration.Type) || name.IsFoundUnder(d => d.Declaration.Type)) { - var speculativeBinding = context.SemanticModel.GetSpeculativeSymbolInfo(name.SpanStart, name, SpeculativeBindingOption.BindAsExpression); - var container = context.SemanticModel.GetSpeculativeTypeInfo(name.SpanStart, name, SpeculativeBindingOption.BindAsExpression).Type; - return GetSymbolsOffOfBoundExpression(context, name, name, speculativeBinding, container, cancellationToken); - } + var speculativeBinding = context.SemanticModel.GetSpeculativeSymbolInfo( + name.SpanStart, name, SpeculativeBindingOption.BindAsExpression); - // We're in a name-only context, since if we were an expression we'd be a - // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. - var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); + var container = context.SemanticModel.GetSpeculativeTypeInfo( + name.SpanStart, name, SpeculativeBindingOption.BindAsExpression).Type; - var symbol = nameBinding.Symbol as INamespaceOrTypeSymbol; - if (symbol != null) - { - if (context.IsNameOfContext) + var speculativeResult = GetSymbolsOffOfBoundExpression( + context, name, name, speculativeBinding, container, cancellationToken); + + // If this speculative binding produced good results, then return them. + if (speculativeResult.Length > 0) { - return context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); + return speculativeResult; } - var symbols = context.SemanticModel.LookupNamespacesAndTypes( - position: name.SpanStart, - container: symbol); - - if (context.IsNamespaceDeclarationNameContext) + // we didn't get any useful speculative results. This can happen in cases like: + // + // Task. + // await Task.Delay() + // + // The problem here is the above is parsed as "Task.await Task", putting a Local + // scope called "Task". This then causes lookup of 'Task' to bind to that local. + // As the local is of type "Task.await", we get no results. + // + // So fallback to just seeing if we can at least bind 'Task' as a type to get + // members off of it here. + var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); + container = nameBinding.GetAnySymbol() as INamedTypeSymbol; + + if (container != null) { - var declarationSyntax = name.GetAncestorOrThis(); - return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); + return GetSymbolsOffOfBoundExpression( + context, name, name, nameBinding, container, cancellationToken); } + } + else + { + // We're in a name-only context, since if we were an expression we'd be a + // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. + var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); - // Filter the types when in a using directive, but not an alias. - // - // Cases: - // using | -- Show namespaces - // using A.| -- Show namespaces - // using static | -- Show namespace and types - // using A = B.| -- Show namespace and types - var usingDirective = name.GetAncestorOrThis(); - if (usingDirective != null && usingDirective.Alias == null) + var symbol = nameBinding.Symbol as INamespaceOrTypeSymbol; + if (symbol != null) { - if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) + if (context.IsNameOfContext) { - return symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()); + return context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); } - else + + var symbols = context.SemanticModel.LookupNamespacesAndTypes( + position: name.SpanStart, + container: symbol); + + if (context.IsNamespaceDeclarationNameContext) { - symbols = symbols.WhereAsArray(s => s.IsNamespace()); + var declarationSyntax = name.GetAncestorOrThis(); + return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); } - } - if (symbols.Any()) - { - return symbols; + // Filter the types when in a using directive, but not an alias. + // + // Cases: + // using | -- Show namespaces + // using A.| -- Show namespaces + // using static | -- Show namespace and types + // using A = B.| -- Show namespace and types + var usingDirective = name.GetAncestorOrThis(); + if (usingDirective != null && usingDirective.Alias == null) + { + if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) + { + return symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()); + } + else + { + symbols = symbols.WhereAsArray(s => s.IsNamespace()); + } + } + + if (symbols.Any()) + { + return symbols; + } } } @@ -436,7 +469,7 @@ private static ImmutableArray GetSymbolsOffOfBoundExpression( var useBaseReferenceAccessibility = false; var excludeInstance = false; var excludeStatic = false; - var symbol = leftHandBinding.GetBestOrAllSymbols().FirstOrDefault(); + var symbol = leftHandBinding.GetAnySymbol(); if (symbol != null) { From 426677bde848cae463ca6bd2663dc81f07d9e7b3 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 16:54:42 -0800 Subject: [PATCH 2/9] Parse incomplete code differently to better reflect user intent. --- .../CSharp/Portable/Parser/LanguageParser.cs | 47 +++++++++ .../CSharpRecommendationService.cs | 96 +++++++------------ 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 0d7876c9b4222..629291df59887 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -7010,6 +7010,53 @@ private bool IsPossibleLocalDeclarationStatement(bool allowAnyExpression) return typedIdentifier.Value; } + // It's common to have code like the following: + // + // Task. + // await Task.Delay() + // + // In this case we don't want to parse this as as a local declaration like: + // + // Task.await Task + // + // This does not represent user intent, and it causes all sorts of problems to higher + // layers. This is because both the parse tree is strage, and the symbol tables have + // entries that throw things off (like a bogus 'Task' local). + // + // Note that we explicitly do this check when we see that the code spreads over multiple + // lines. We don't want this if the user has actually written "X.Y z" + if (tk == SyntaxKind.IdentifierToken) + { + var token1 = PeekToken(1); + if (token1.Kind == SyntaxKind.DotToken && + token1.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia)) + { + if (PeekToken(2).Kind == SyntaxKind.IdentifierToken && + PeekToken(3).Kind == SyntaxKind.IdentifierToken) + { + // We have something like: + // + // X. + // Y z + // + // This is only a local declaration if we have: + // + // X.Y z; + // X.Y z = ... + // X.Y z, ... + // X.Y z( ... (local function) + // X.Y z GetSymbolsOffOfName( var speculativeResult = GetSymbolsOffOfBoundExpression( context, name, name, speculativeBinding, container, cancellationToken); - // If this speculative binding produced good results, then return them. - if (speculativeResult.Length > 0) - { - return speculativeResult; - } - - // we didn't get any useful speculative results. This can happen in cases like: - // - // Task. - // await Task.Delay() - // - // The problem here is the above is parsed as "Task.await Task", putting a Local - // scope called "Task". This then causes lookup of 'Task' to bind to that local. - // As the local is of type "Task.await", we get no results. - // - // So fallback to just seeing if we can at least bind 'Task' as a type to get - // members off of it here. - var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); - container = nameBinding.GetAnySymbol() as INamedTypeSymbol; - - if (container != null) - { - return GetSymbolsOffOfBoundExpression( - context, name, name, nameBinding, container, cancellationToken); - } + return speculativeResult; } - else - { - // We're in a name-only context, since if we were an expression we'd be a + + // We're in a name-only context, since if we were an expression we'd be a // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); var symbol = nameBinding.Symbol as INamespaceOrTypeSymbol; - if (symbol != null) + if (symbol != null) + { + if (context.IsNameOfContext) { - if (context.IsNameOfContext) - { - return context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); - } + return context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); + } - var symbols = context.SemanticModel.LookupNamespacesAndTypes( - position: name.SpanStart, - container: symbol); + var symbols = context.SemanticModel.LookupNamespacesAndTypes( + position: name.SpanStart, + container: symbol); - if (context.IsNamespaceDeclarationNameContext) - { - var declarationSyntax = name.GetAncestorOrThis(); - return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); - } + if (context.IsNamespaceDeclarationNameContext) + { + var declarationSyntax = name.GetAncestorOrThis(); + return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); + } - // Filter the types when in a using directive, but not an alias. - // - // Cases: - // using | -- Show namespaces - // using A.| -- Show namespaces - // using static | -- Show namespace and types - // using A = B.| -- Show namespace and types - var usingDirective = name.GetAncestorOrThis(); - if (usingDirective != null && usingDirective.Alias == null) + // Filter the types when in a using directive, but not an alias. + // + // Cases: + // using | -- Show namespaces + // using A.| -- Show namespaces + // using static | -- Show namespace and types + // using A = B.| -- Show namespace and types + var usingDirective = name.GetAncestorOrThis(); + if (usingDirective != null && usingDirective.Alias == null) + { + if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) { - if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) - { - return symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()); - } - else - { - symbols = symbols.WhereAsArray(s => s.IsNamespace()); - } + return symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()); } - - if (symbols.Any()) + else { - return symbols; + symbols = symbols.WhereAsArray(s => s.IsNamespace()); } } + + if (symbols.Any()) + { + return symbols; + } } return ImmutableArray.Empty; From 0851ac876cc168cc09576905e5fb6a2cb72edf34 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 17:15:46 -0800 Subject: [PATCH 3/9] UPdate how we parse MemberAccess expressions as well. --- .../CSharp/Portable/Parser/LanguageParser.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 629291df59887..cb325316d24c1 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -9678,6 +9678,26 @@ private ExpressionSyntax ParsePostFixExpression(ExpressionSyntax expr) expr = _syntaxFactory.MemberAccessExpression(SyntaxKind.PointerMemberAccessExpression, expr, this.EatToken(), this.ParseSimpleName(NameOptions.InExpression)); break; case SyntaxKind.DotToken: + // if we have the error situation: + // + // expr. + // X Y + // + // Then we don't want to parse this out as "Expr.X" + // + // It's far more likely the member access expression is simply incomplete and + // there is a new declaration on the next line. + if (this.CurrentToken.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia) && + this.PeekToken(1).Kind == SyntaxKind.IdentifierToken && + this.PeekToken(2).Kind == SyntaxKind.IdentifierToken) + { + expr = _syntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, expr, this.EatToken(), + this.AddError(this.CreateMissingIdentifierName(), ErrorCode.ERR_IdentifierExpected)); + + return expr; + } + expr = _syntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, this.EatToken(), this.ParseSimpleName(NameOptions.InExpression)); break; From ec74124afa39e51aabce202fcf373310a77c200f Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 17:25:13 -0800 Subject: [PATCH 4/9] Formatting --- .../Recommendations/CSharpRecommendationService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs index b9c329e3b9988..cc432be9021ed 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationService.cs @@ -318,10 +318,10 @@ private static ImmutableArray GetSymbolsOffOfName( } // We're in a name-only context, since if we were an expression we'd be a - // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. - var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); + // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. + var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); - var symbol = nameBinding.Symbol as INamespaceOrTypeSymbol; + var symbol = nameBinding.Symbol as INamespaceOrTypeSymbol; if (symbol != null) { if (context.IsNameOfContext) @@ -547,4 +547,4 @@ private static ImmutableArray GetSymbolsOffOfBoundExpression( : symbols; } } -} +} \ No newline at end of file From 24f45635726bacfdd1ff05038dc292570151f1bf Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 17:31:45 -0800 Subject: [PATCH 5/9] Add parsing tests. --- .../Syntax/Parsing/ExpressionParsingTests.cs | 539 ++++++++++++++++++ 1 file changed, 539 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs index 0d57aec70bf11..dd4318fb5de40 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs @@ -2267,5 +2267,544 @@ private bool IsValid_a() Assert.NotNull(root); Assert.Equal(SyntaxKind.CompilationUnit, root.Kind()); } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration1() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task.Delay(); } } +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.AwaitExpression); + { + N(SyntaxKind.AwaitKeyword); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Delay"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration2() + { + UsingTree(@" +class C +{ + async void M() + { + Task.await Task.Delay(); + } +} +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "await"); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + } + M(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Delay"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration3() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task; + } +} +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "await"); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration4() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task = 1; + } +} +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "await"); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration5() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task, Task2; + } +} +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "await"); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "Task2"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration6() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task(); + } +} +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "await"); + } + } + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration7() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task(); + } +} +"); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.LocalFunctionStatement); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "await"); + } + } + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + } +} \ No newline at end of file From fb3f23094dcf269f68015b7962c881a7147713b3 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 17:32:50 -0800 Subject: [PATCH 6/9] Disable test. --- .../Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 7c1da18a15d93..ba2e71e549f04 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -2833,7 +2833,7 @@ class C End Using End Function - + ' Public Async Function Filters_EmptyList4() As Task Using state = TestState.CreateCSharpTestState( Date: Wed, 14 Dec 2016 12:50:30 -0800 Subject: [PATCH 7/9] Add test. --- .../Syntax/Parsing/ExpressionParsingTests.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs index dd4318fb5de40..6d30f0b96bca0 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs @@ -2806,5 +2806,95 @@ async void M() } EOF(); } + + [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] + public void InProgressLocalDeclaration8() + { + UsingTree(@" +class C +{ + async void M() + { + Task. + await Task[1]; + } +} +"); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.DotToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.ExpressionStatement); + { + N(SyntaxKind.AwaitExpression); + { + N(SyntaxKind.AwaitKeyword); + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Task"); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } } } \ No newline at end of file From 77cc0c6f66e37233d9f431c05e271e051c60b224 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 16 Dec 2016 13:19:36 -0800 Subject: [PATCH 8/9] Adding tests. --- .../Syntax/Parsing/ExpressionParsingTests.cs | 93 +++++++++++++++---- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs index 6d30f0b96bca0..6664e56e7a337 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs @@ -2271,7 +2271,7 @@ private bool IsValid_a() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration1() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2280,7 +2280,16 @@ async void M() await Task.Delay(); } } -"); +"; + ParseAndValidate(text, + // (6,14): error CS1001: Identifier expected + // Task. + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(6, 14), + // (6,14): error CS1002: ; expected + // Task. + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(6, 14)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2361,7 +2370,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration2() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2369,7 +2378,19 @@ async void M() Task.await Task.Delay(); } } -"); +"; + ParseAndValidate(text, + // (6,14): error CS4003: 'await' cannot be used as an identifier within an async method or lambda expression + // Task.await Task.Delay(); + Diagnostic(ErrorCode.ERR_BadAwaitAsIdentifier, "await").WithLocation(6, 14), + // (6,24): error CS1003: Syntax error, ',' expected + // Task.await Task.Delay(); + Diagnostic(ErrorCode.ERR_SyntaxError, ".").WithArguments(",", ".").WithLocation(6, 24), + // (6,25): error CS1002: ; expected + // Task.await Task.Delay(); + Diagnostic(ErrorCode.ERR_SemicolonExpected, "Delay").WithLocation(6, 25)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2445,7 +2466,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration3() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2454,7 +2475,13 @@ async void M() await Task; } } -"); +"; + ParseAndValidate(text, + // (7,9): error CS4003: 'await' cannot be used as an identifier within an async method or lambda expression + // await Task; + Diagnostic(ErrorCode.ERR_BadAwaitAsIdentifier, "await").WithLocation(7, 9)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2514,7 +2541,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration4() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2523,7 +2550,13 @@ async void M() await Task = 1; } } -"); +"; + ParseAndValidate(text, + // (7,9): error CS4003: 'await' cannot be used as an identifier within an async method or lambda expression + // await Task = 1; + Diagnostic(ErrorCode.ERR_BadAwaitAsIdentifier, "await").WithLocation(7, 9)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2591,7 +2624,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration5() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2600,7 +2633,13 @@ async void M() await Task, Task2; } } -"); +"; + ParseAndValidate(text, + // (7,9): error CS4003: 'await' cannot be used as an identifier within an async method or lambda expression + // await Task, Task2; + Diagnostic(ErrorCode.ERR_BadAwaitAsIdentifier, "await").WithLocation(7, 9)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2665,7 +2704,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration6() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2674,7 +2713,13 @@ async void M() await Task(); } } -"); +"; + ParseAndValidate(text, + // (7,9): error CS4003: 'await' cannot be used as an identifier within an async method or lambda expression + // await Task(); + Diagnostic(ErrorCode.ERR_BadAwaitAsIdentifier, "await").WithLocation(7, 9)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2733,7 +2778,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration7() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2742,7 +2787,13 @@ async void M() await Task(); } } -"); +"; + ParseAndValidate(text, + // (7,9): error CS4003: 'await' cannot be used as an identifier within an async method or lambda expression + // await Task(); + Diagnostic(ErrorCode.ERR_BadAwaitAsIdentifier, "await").WithLocation(7, 9)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); @@ -2810,7 +2861,7 @@ async void M() [Fact, WorkItem(15885, "https://github.com/dotnet/roslyn/pull/15885")] public void InProgressLocalDeclaration8() { - UsingTree(@" + const string text = @" class C { async void M() @@ -2819,8 +2870,16 @@ async void M() await Task[1]; } } -"); - +"; + ParseAndValidate(text, + // (6,14): error CS1001: Identifier expected + // Task. + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(6, 14), + // (6,14): error CS1002: ; expected + // Task. + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(6, 14)); + + UsingTree(text); N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); From 6c6a6b5c1ada24721e1f8baf9b83d02cbfebabf4 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Sat, 17 Dec 2016 01:19:22 -0800 Subject: [PATCH 9/9] PR feedback. --- .../CSharp/Portable/Parser/LanguageParser.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index cb325316d24c1..76ffe81a6c7a4 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -7048,11 +7048,14 @@ private bool IsPossibleLocalDeclarationStatement(bool allowAnyExpression) // X.Y z