diff --git a/src/Compilers/VisualBasic/Portable/Parser/ParseExpression.vb b/src/Compilers/VisualBasic/Portable/Parser/ParseExpression.vb index 1255458c4453c..fd0043e040cc8 100644 --- a/src/Compilers/VisualBasic/Portable/Parser/ParseExpression.vb +++ b/src/Compilers/VisualBasic/Portable/Parser/ParseExpression.vb @@ -46,7 +46,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax ' bool BailIfFirstTokenRejected // bail (return NULL) if the first token isn't a valid expression-starter, rather than reporting an error or setting ErrorInConstruct Private Function ParseExpressionCore( Optional pendingPrecedence As OperatorPrecedence = OperatorPrecedence.PrecedenceNone, - Optional bailIfFirstTokenRejected As Boolean = False + Optional bailIfFirstTokenRejected As Boolean = False, + Optional prevTerm As ExpressionSyntax = Nothing ) As ExpressionSyntax Try @@ -107,7 +108,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax expression = SyntaxFactory.AddressOfExpression(startToken, Operand) Case Else - expression = ParseTerm(bailIfFirstTokenRejected) + expression = ParseTerm(bailIfFirstTokenRejected, prevTerm:=prevTerm) If expression Is Nothing Then Return Nothing @@ -157,7 +158,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax 'Binary.Opcode = Opcode 'Binary.Left = Expr - Dim rightOperand As ExpressionSyntax = ParseExpressionCore(precedence) + Dim rightOperand As ExpressionSyntax = ParseExpressionCore(precedence, prevTerm:=prevTerm) expression = SyntaxFactory.BinaryExpression(GetBinaryOperatorHelper([operator]), expression, [operator], rightOperand) @@ -174,8 +175,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax End Function Private Function ParseTerm( - Optional BailIfFirstTokenRejected As Boolean = False, - Optional RedimOrNewParent As Boolean = False + Optional bailIfFirstTokenRejected As Boolean = False, + Optional redimOrNewParent As Boolean = False, + Optional prevTerm As ExpressionSyntax = Nothing ) As ExpressionSyntax '// Note: this function will only ever return NULL if the flag "BailIfFirstTokenIsRejected" is set, @@ -232,10 +234,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax term = ParseSimpleNameExpressionAllowingKeywordAndTypeArguments() Case SyntaxKind.ExclamationToken - term = ParseQualifiedExpr(Nothing) + term = ParseQualifiedExpr(prevTerm) Case SyntaxKind.DotToken - term = ParseQualifiedExpr(Nothing) + term = ParseQualifiedExpr(prevTerm) Case SyntaxKind.GlobalKeyword ' NB. GetNextToken has the side-effect of advancing CurrentToken. @@ -296,7 +298,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax Dim tokenHasFullWidthChars As Boolean = TokenContainsFullWidthChars(start) If tokenHasFullWidthChars Then - If BailIfFirstTokenRejected Then + If bailIfFirstTokenRejected Then Return Nothing End If @@ -410,9 +412,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax Case SyntaxKind.DollarSignDoubleQuoteToken term = ParseInterpolatedStringExpression() - Case Else - If start.Kind = SyntaxKind.QuestionToken AndAlso CanStartConsequenceExpression(Me.PeekToken(1).Kind, qualified:=False) Then ' This looks like ?. or ?! @@ -420,9 +420,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax qToken = CheckFeatureAvailability(Feature.NullPropagatingOperator, qToken) GetNextToken() - term = SyntaxFactory.ConditionalAccessExpression(term, qToken, ParsePostFixExpression(RedimOrNewParent, term:=Nothing)) + term = SyntaxFactory.ConditionalAccessExpression(prevTerm, qToken, ParsePostFixExpression(redimOrNewParent, term:=Nothing)) Else - If BailIfFirstTokenRejected Then + If start.IsKeyword Then + + ' Is it in the form : [keyword] [qualifier] [identifier] + ' If so treat the keyword as an identifier and continue to parse. + Dim nextToken = PeekNextToken() + + Select Case nextToken.Kind + Case SyntaxKind.DotToken, + SyntaxKind.QuestionToken, + SyntaxKind.ExclamationToken + + Dim keywordAsIdentifier = ParseSimpleNameExpressionAllowingKeywordAndTypeArguments() + keywordAsIdentifier = ReportSyntaxError(keywordAsIdentifier, ERRID.ERR_InvalidUseOfKeyword) + term = ParseExpressionCore(prevTerm:=keywordAsIdentifier) + Return term + End Select + End If + + If bailIfFirstTokenRejected Then Return Nothing End If @@ -439,7 +457,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax ' Valid suffixes are ".", "!", and "(". Everything else is considered ' to end the term. - term = ParsePostFixExpression(RedimOrNewParent, term) + term = ParsePostFixExpression(redimOrNewParent, term) End If If CurrentToken IsNot Nothing AndAlso CurrentToken.Kind = SyntaxKind.QuestionToken Then diff --git a/src/Compilers/VisualBasic/Test/Syntax/Parser/InterpolatedStringParsingTests.vb b/src/Compilers/VisualBasic/Test/Syntax/Parser/InterpolatedStringParsingTests.vb index 86f4216e0800d..f429ea83ceef5 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Parser/InterpolatedStringParsingTests.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Parser/InterpolatedStringParsingTests.vb @@ -922,4 +922,108 @@ BC30648: String constants must end with a double quote. End Sub + + Public Sub Test_KeywordExpression() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Overrides Function ToString() As String + Return $""{End.ToString()}"" + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return $"{End.ToString()}" + ~~~ +) + End Sub + + + Public Sub Test_ErrorRecovery_KeywordExpression_InUnclosedInterpolation() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Overrides Function ToString() As String + Return $""{End.ToString()"" + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return $"{End.ToString()" + ~~~ +BC30370: '}' expected. + Return $"{End.ToString()" + ~ +) + End Sub + + + Public Sub Test_ErrorRecovery_KeywordExpression_InUnclosedInterpolation2() + Dim code = +"Class Example + Public ReadOnly Property [End] As DateTime = DateTime.Now + Public Overrides Function ToString() As String + Return $""{End.Day"" + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return $"{End.Day" + ~~~ +BC30370: '}' expected. + Return $"{End.Day" + ~ + + ) + End Sub + + + Public Sub Test_ErrorRecovery_KeywordExpression_InUnclosedInterpolation3() + Dim code = +"Class Example + Public ReadOnly Property [End] As DateTime? = DateTime.Now + Public Overrides Function ToString() As String + Return $""{End?.Day"" + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return $"{End?.Day" + ~~~ +BC30370: '}' expected. + Return $"{End?.Day" + ~ + + ) + End Sub + + + Public Sub Test_ErrorRecovery_KeywordExpression_InUnclosedInterpolation4() + Dim code = +"Class Example + Public ReadOnly Property [End] As New System.Collections.Generic.Dictionary(Of String,Integer) From {{""A"", 1},{""B"",2}} + Public Overrides Function ToString() As String + Return $""{End!A"" + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return $"{End!A" + ~~~ +BC30370: '}' expected. + Return $"{End!A" + ~ + + ) + End Sub End Class diff --git a/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb b/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb index 9e36af014aa7d..592b723ba9bad 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb @@ -2917,4 +2917,146 @@ End Module]]>.Value) Assert.Equal(CInt(ERRID.ERR_TooLongOrComplexExpression), diagnostic.Code) End Sub + + Public Sub Test_KeywordExpr_0() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Function Result() As String + Return End + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30201: Expression expected. + Return End + ~ +) + End Sub + + + Public Sub Test_KeywordExpr_1() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Function Result() As String + Return End. + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return End. + ~~~ +BC30203: Identifier expected. + Return End. + ~ +) + End Sub + + + Public Sub Test_KeywordExpr_2() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Function Result() As String + Return Async. + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30203: Identifier expected. + Return Async. + ~ +) + End Sub + + + Public Sub Test_KeywordExpr_3() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Function Result() As String + Return Async + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +) + End Sub + + + Public Sub Test_KeywordExpr_4() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Function Result() As String + Return Await. + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30203: Identifier expected. + Return Await. + ~ +) + End Sub + + + Public Sub Test_KeywordExpr_5() + Dim code = +"Class Example + Public ReadOnly Property [End] As Integer + Public Function Result() As String + Return Await + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +) + End Sub + + + Public Sub Test_KeywordExpr_6() + Dim code = +"Class Example + Public ReadOnly Property [End] As DateTime? = DateTime.Now + Public Overrides Function ToString() As String + Return End?.Day + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return End?.Day + ~~~ + + ) + End Sub + + + Public Sub Test_KeywordExpr_7() + Dim code = +"Class Example + Public ReadOnly Property [End] As New System.Collections.Generic.Dictionary(Of String,Integer) From {{""A"", 1},{""B"",2}} + Public Overrides Function ToString() As String + Return End!A + End Function +End Class" + Dim result = Parse(code) + result.AssertTheseDiagnostics( + +BC30183: Keyword is not valid as an identifier. + Return End!A + ~~~ + + ) + End Sub End Class