From a5a9e4fe1380240db53790fb63f1a492615ffaa3 Mon Sep 17 00:00:00 2001 From: Ivan Basov Date: Tue, 5 Feb 2019 12:30:55 -0800 Subject: [PATCH] Reparse type after async (#32983) --- .../CSharp/Portable/Parser/LanguageParser.cs | 271 ++++++++--------- .../Parsing/MemberDeclarationParsingTests.cs | 282 ++++++++++++++++++ .../CSharpCompletionCommandHandlerTests.vb | 26 ++ 3 files changed, 427 insertions(+), 152 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 63f2eab29af..531ec94bebc 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -2276,124 +2276,139 @@ private MemberDeclarationSyntax ParseMemberDeclarationOrStatementCore(SyntaxKind // indexers, and non-conversion operators -- starts with a type // (possibly void). Parse that. TypeSyntax type = ParseReturnType(); - var sawRef = type.Kind == SyntaxKind.RefType; - // Check for misplaced modifiers. if we see any, then consider this member - // terminated and restart parsing. - if (GetModifier(this.CurrentToken) != DeclarationModifiers.None && - this.CurrentToken.ContextualKind != SyntaxKind.PartialKeyword && - this.CurrentToken.ContextualKind != SyntaxKind.AsyncKeyword && - IsComplete(type)) - { - var misplacedModifier = this.CurrentToken; - type = this.AddError( - type, - type.FullWidth + misplacedModifier.GetLeadingTriviaWidth(), - misplacedModifier.Width, - ErrorCode.ERR_BadModifierLocation, - misplacedModifier.Text); - - return _syntaxFactory.IncompleteMember(attributes, modifiers.ToList(), type); - } + var afterTypeResetPoint = this.GetResetPoint(); -parse_member_name:; - // If we've seen the ref keyword, we know we must have an indexer, method, or property. - if (!sawRef) + try { - // Check here for operators - // Allow old-style implicit/explicit casting operator syntax, just so we can give a better error - if (IsOperatorKeyword()) + var sawRef = type.Kind == SyntaxKind.RefType; + + // Check for misplaced modifiers. if we see any, then consider this member + // terminated and restart parsing. + if (GetModifier(this.CurrentToken) != DeclarationModifiers.None && + this.CurrentToken.ContextualKind != SyntaxKind.PartialKeyword && + this.CurrentToken.ContextualKind != SyntaxKind.AsyncKeyword && + IsComplete(type)) { - return this.ParseOperatorDeclaration(attributes, modifiers, type); + var misplacedModifier = this.CurrentToken; + type = this.AddError( + type, + type.FullWidth + misplacedModifier.GetLeadingTriviaWidth(), + misplacedModifier.Width, + ErrorCode.ERR_BadModifierLocation, + misplacedModifier.Text); + + return _syntaxFactory.IncompleteMember(attributes, modifiers.ToList(), type); } - if (IsFieldDeclaration(isEvent: false)) +parse_member_name:; + // If we've seen the ref keyword, we know we must have an indexer, method, or property. + if (!sawRef) { - if (acceptStatement) + // Check here for operators + // Allow old-style implicit/explicit casting operator syntax, just so we can give a better error + if (IsOperatorKeyword()) { - // if we are script at top-level then statements can occur - _termState |= TerminatorState.IsPossibleStatementStartOrStop; + return this.ParseOperatorDeclaration(attributes, modifiers, type); } - return this.ParseNormalFieldDeclaration(attributes, modifiers, type, parentKind); + if (IsFieldDeclaration(isEvent: false)) + { + if (acceptStatement) + { + // if we are script at top-level then statements can occur + _termState |= TerminatorState.IsPossibleStatementStartOrStop; + } + + return this.ParseNormalFieldDeclaration(attributes, modifiers, type, parentKind); + } } - } - // At this point we can either have indexers, methods, or - // properties (or something unknown). Try to break apart - // the following name and determine what to do from there. - ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt; - SyntaxToken identifierOrThisOpt; - TypeParameterListSyntax typeParameterListOpt; - this.ParseMemberName(out explicitInterfaceOpt, out identifierOrThisOpt, out typeParameterListOpt, isEvent: false); + // At this point we can either have indexers, methods, or + // properties (or something unknown). Try to break apart + // the following name and determine what to do from there. + ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt; + SyntaxToken identifierOrThisOpt; + TypeParameterListSyntax typeParameterListOpt; + this.ParseMemberName(out explicitInterfaceOpt, out identifierOrThisOpt, out typeParameterListOpt, isEvent: false); - // First, check if we got absolutely nothing. If so, then - // We need to consume a bad member and try again. - if (explicitInterfaceOpt == null && identifierOrThisOpt == null && typeParameterListOpt == null) - { - if (attributes.Count == 0 && modifiers.Count == 0 && type.IsMissing && !sawRef) + // First, check if we got absolutely nothing. If so, then + // We need to consume a bad member and try again. + if (explicitInterfaceOpt == null && identifierOrThisOpt == null && typeParameterListOpt == null) { - // we haven't advanced, the caller needs to consume the tokens ahead - return null; + if (attributes.Count == 0 && modifiers.Count == 0 && type.IsMissing && !sawRef) + { + // we haven't advanced, the caller needs to consume the tokens ahead + return null; + } + + var incompleteMember = _syntaxFactory.IncompleteMember(attributes, modifiers.ToList(), type.IsMissing ? null : type); + if (incompleteMember.ContainsDiagnostics) + { + return incompleteMember; + } + else if (parentKind == SyntaxKind.NamespaceDeclaration || + parentKind == SyntaxKind.CompilationUnit && !IsScript) + { + return this.AddErrorToLastToken(incompleteMember, ErrorCode.ERR_NamespaceUnexpected); + } + else + { + //the error position should indicate CurrentToken + return this.AddError( + incompleteMember, + incompleteMember.FullWidth + this.CurrentToken.GetLeadingTriviaWidth(), + this.CurrentToken.Width, + ErrorCode.ERR_InvalidMemberDecl, + this.CurrentToken.Text); + } } - var incompleteMember = _syntaxFactory.IncompleteMember(attributes, modifiers.ToList(), type.IsMissing ? null : type); - if (incompleteMember.ContainsDiagnostics) + // If the modifiers did not include "async", and the type we got was "async", and there was an + // error in the identifier or its type parameters, then the user is probably in the midst of typing + // an async method. In that case we reconsider "async" to be a modifier, and treat the identifier + // (with the type parameters) as the type (with type arguments). Then we go back to looking for + // the member name again. + // For example, if we get + // async Task< + // then we want async to be a modifier and Task to be a type. + if (!sawRef && + identifierOrThisOpt != null && + (typeParameterListOpt != null && typeParameterListOpt.ContainsDiagnostics + || this.CurrentToken.Kind != SyntaxKind.OpenParenToken && this.CurrentToken.Kind != SyntaxKind.OpenBraceToken && this.CurrentToken.Kind != SyntaxKind.EqualsGreaterThanToken) && + ReconsiderTypeAsAsyncModifier(ref modifiers, type, identifierOrThisOpt)) { - return incompleteMember; + this.Reset(ref afterTypeResetPoint); + explicitInterfaceOpt = null; + identifierOrThisOpt = default; + typeParameterListOpt = null; + type = ParseReturnType(); + goto parse_member_name; } - else if (parentKind == SyntaxKind.NamespaceDeclaration || - parentKind == SyntaxKind.CompilationUnit && !IsScript) + + Debug.Assert(identifierOrThisOpt != null); + + if (identifierOrThisOpt.Kind == SyntaxKind.ThisKeyword) { - return this.AddErrorToLastToken(incompleteMember, ErrorCode.ERR_NamespaceUnexpected); + return this.ParseIndexerDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); } else { - //the error position should indicate CurrentToken - return this.AddError( - incompleteMember, - incompleteMember.FullWidth + this.CurrentToken.GetLeadingTriviaWidth(), - this.CurrentToken.Width, - ErrorCode.ERR_InvalidMemberDecl, - this.CurrentToken.Text); - } - } - - // If the modifiers did not include "async", and the type we got was "async", and there was an - // error in the identifier or its type parameters, then the user is probably in the midst of typing - // an async method. In that case we reconsider "async" to be a modifier, and treat the identifier - // (with the type parameters) as the type (with type arguments). Then we go back to looking for - // the member name again. - // For example, if we get - // async Task< - // then we want async to be a modifier and Task to be a type. - if (!sawRef && - identifierOrThisOpt != null && - (typeParameterListOpt != null && typeParameterListOpt.ContainsDiagnostics - || this.CurrentToken.Kind != SyntaxKind.OpenParenToken && this.CurrentToken.Kind != SyntaxKind.OpenBraceToken && this.CurrentToken.Kind != SyntaxKind.EqualsGreaterThanToken) && - ReconsiderTypeAsAsyncModifier(ref modifiers, ref type, ref explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt)) - { - goto parse_member_name; - } - - Debug.Assert(identifierOrThisOpt != null); + switch (this.CurrentToken.Kind) + { + case SyntaxKind.OpenBraceToken: + case SyntaxKind.EqualsGreaterThanToken: + return this.ParsePropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); - if (identifierOrThisOpt.Kind == SyntaxKind.ThisKeyword) - { - return this.ParseIndexerDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); + default: + // treat anything else as a method. + return this.ParseMethodDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); + } + } } - else + finally { - switch (this.CurrentToken.Kind) - { - case SyntaxKind.OpenBraceToken: - case SyntaxKind.EqualsGreaterThanToken: - return this.ParsePropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); - - default: - // treat anything else as a method. - return this.ParseMethodDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); - } + this.Release(ref afterTypeResetPoint); } } finally @@ -2409,10 +2424,8 @@ private MemberDeclarationSyntax ParseMemberDeclarationOrStatementCore(SyntaxKind // type parameter list private bool ReconsiderTypeAsAsyncModifier( ref SyntaxListBuilder modifiers, - ref TypeSyntax type, - ref ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, - SyntaxToken identifierOrThisOpt, - TypeParameterListSyntax typeParameterListOpt) + TypeSyntax type, + SyntaxToken identifierOrThisOpt) { if (type.Kind != SyntaxKind.IdentifierName) return false; if (identifierOrThisOpt.Kind != SyntaxKind.IdentifierToken) return false; @@ -2426,61 +2439,9 @@ private MemberDeclarationSyntax ParseMemberDeclarationOrStatementCore(SyntaxKind } modifiers.Add(ConvertToKeyword(identifier)); - SimpleNameSyntax newType = typeParameterListOpt == null - ? (SimpleNameSyntax)_syntaxFactory.IdentifierName(identifierOrThisOpt) - : _syntaxFactory.GenericName(identifierOrThisOpt, TypeArgumentFromTypeParameters(typeParameterListOpt)); - type = (explicitInterfaceOpt == null) - ? (TypeSyntax)newType - : _syntaxFactory.QualifiedName(explicitInterfaceOpt.Name, explicitInterfaceOpt.DotToken, newType); - explicitInterfaceOpt = null; - identifierOrThisOpt = default(SyntaxToken); - typeParameterListOpt = default(TypeParameterListSyntax); return true; } - private TypeArgumentListSyntax TypeArgumentFromTypeParameters(TypeParameterListSyntax typeParameterList) - { - var types = _pool.AllocateSeparated(); - foreach (var p in typeParameterList.Parameters.GetWithSeparators()) - { - switch ((SyntaxKind)p.RawKind) - { - case SyntaxKind.TypeParameter: - var typeParameter = (TypeParameterSyntax)p; - var typeArgument = _syntaxFactory.IdentifierName(typeParameter.Identifier); - // NOTE: reverse order of variance keyword and attributes list so they come out in the right order. - if (typeParameter.VarianceKeyword != null) - { - // This only happens in error scenarios, so don't bother to produce a diagnostic about - // having a variance keyword on a type argument. - typeArgument = AddLeadingSkippedSyntax(typeArgument, typeParameter.VarianceKeyword); - } - if (typeParameter.AttributeLists.Node != null) - { - // This only happens in error scenarios, so don't bother to produce a diagnostic about - // having an attribute on a type argument. - typeArgument = AddLeadingSkippedSyntax(typeArgument, typeParameter.AttributeLists.Node); - } - types.Add(typeArgument); - break; - case SyntaxKind.CommaToken: - types.AddSeparator((SyntaxToken)p); - break; - default: - throw ExceptionUtilities.UnexpectedValue(p.RawKind); - } - } - - var result = _syntaxFactory.TypeArgumentList(typeParameterList.LessThanToken, types.ToList(), typeParameterList.GreaterThanToken); - _pool.Free(types); - return result; - } - - //private bool ReconsiderTypeAsAsyncModifier(ref SyntaxListBuilder modifiers, ref type, ref identifierOrThisOpt, ref typeParameterListOpt)) - // { - // goto parse_member_name; - // } - private bool IsFieldDeclaration(bool isEvent) { if (this.CurrentToken.Kind != SyntaxKind.IdentifierToken) @@ -6569,9 +6530,15 @@ private StatementSyntax ParseStatementCore() private StatementSyntax ParsePossibleDeclarationOrBadAwaitStatement() { ResetPoint resetPointBeforeStatement = this.GetResetPoint(); - StatementSyntax result = ParsePossibleDeclarationOrBadAwaitStatement(ref resetPointBeforeStatement); - this.Release(ref resetPointBeforeStatement); - return result; + try + { + StatementSyntax result = ParsePossibleDeclarationOrBadAwaitStatement(ref resetPointBeforeStatement); + return result; + } + finally + { + this.Release(ref resetPointBeforeStatement); + } } private StatementSyntax ParsePossibleDeclarationOrBadAwaitStatement(ref ResetPoint resetPointBeforeStatement) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index e5f497137cf..7effb7ae331 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -458,5 +458,287 @@ public void TrashAfterDeclaration() } EOF(); } + + [Fact] + [WorkItem(11959, "https://github.com/dotnet/roslyn/issues/11959")] + public void GenericAsyncTask_01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + UsingDeclaration("async Task' expected + // async Task", "(").WithLocation(1, 41) + ); + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeNamespace"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeType"); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(11959, "https://github.com/dotnet/roslyn/issues/11959")] + public void GenericPublicTask_01() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + UsingDeclaration("public Task' expected + // public Task", "(").WithLocation(1, 42) + ); + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeNamespace"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeType"); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(11959, "https://github.com/dotnet/roslyn/issues/11959")] + public void GenericAsyncTask_02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + UsingDeclaration("async Task' expected + // async Task", "(").WithLocation(1, 33) + ); + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeNamespace"); + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + M(SyntaxKind.GreaterThanToken); + } + } + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(11959, "https://github.com/dotnet/roslyn/issues/11959")] + public void GenericPublicTask_02() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + UsingDeclaration("public Task' expected + // public Task", "(").WithLocation(1, 34) + ); + N(SyntaxKind.IncompleteMember); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeNamespace"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Method"); + } + } + M(SyntaxKind.GreaterThanToken); + } + } + } + EOF(); + } + } + + [Fact] + [WorkItem(11959, "https://github.com/dotnet/roslyn/issues/11959")] + public void GenericAsyncTask_03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + UsingDeclaration("async Task Method();", options: options, + // (1,26): error CS1001: Identifier expected + // async Task Method(); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ">").WithLocation(1, 26) + ); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AsyncKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeNamespace"); + } + N(SyntaxKind.DotToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } + + [Fact] + [WorkItem(11959, "https://github.com/dotnet/roslyn/issues/11959")] + public void GenericPublicTask_03() + { + foreach (var options in new[] { TestOptions.Script, TestOptions.Regular }) + { + UsingDeclaration("public Task Method();", options: options, + // (1,27): error CS1001: Identifier expected + // public Task Method(); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ">").WithLocation(1, 27) + ); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.GenericName); + { + N(SyntaxKind.IdentifierToken, "Task"); + N(SyntaxKind.TypeArgumentList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SomeNamespace"); + } + N(SyntaxKind.DotToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.GreaterThanToken); + } + } + N(SyntaxKind.IdentifierToken, "Method"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + EOF(); + } + } } } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index c143bc679a4..346cde33461 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -4135,6 +4135,32 @@ class C End Using End Function + + + + Public Async Function TestGenericAsyncTaskDeclaration(completionImplementation As CompletionImplementation) As Task + Using state = TestStateFactory.CreateCSharpTestState(completionImplementation, + +namespace A.B +{ + class TestClass { } +} + +namespace A +{ + class C + { + async Task<A$$ Method() + { } + } +} + ) + + state.SendTypeChars(".") + Await state.AssertSelectedCompletionItem(displayText:="B", isSoftSelected:=True) + End Using + End Function + Private Class MultipleChangeCompletionProvider Inherits CompletionProvider -- GitLab