From 2a58f734cd5ae3218841c968df15b00f41350db2 Mon Sep 17 00:00:00 2001 From: Neal Gafter Date: Sun, 7 Jun 2020 15:45:08 -0700 Subject: [PATCH] When there is an error in an interpolated string, take the next quote as a close quote (#44899) Fixes #44789 --- .../Portable/Parser/Lexer_StringLiteral.cs | 20 +++- .../Semantic/Semantics/InterpolationTests.cs | 10 +- .../Syntax/Parsing/ExpressionParsingTests.cs | 113 ++++++++++++++++++ 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs b/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs index 4e7537676e2..173c2b94fe7 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs @@ -372,6 +372,12 @@ private void ScanInterpolatedStringLiteralContents(ArrayBuilder i switch (lexer.TextWindow.PeekChar()) { + case '"' when RecoveringFromRunawayLexing(): + // When recovering from mismatched delimiters, we consume the next + // quote character as the close quote for the interpolated string. In + // practice this gets us out of trouble in scenarios we've encountered. + // See, for example, https://github.com/dotnet/roslyn/issues/44789 + return; case '"': if (isVerbatim && lexer.TextWindow.PeekChar(1) == '"') { @@ -588,13 +594,19 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool } goto default; + case '"' when RecoveringFromRunawayLexing(): + // When recovering from mismatched delimiters, we consume the next + // quote character as the close quote for the interpolated string. In + // practice this gets us out of trouble in scenarios we've encountered. + // See, for example, https://github.com/dotnet/roslyn/issues/44789 + return; case '"': case '\'': // handle string or character literal inside an expression hole. ScanInterpolatedStringLiteralNestedString(); continue; case '@': - if (lexer.TextWindow.PeekChar(1) == '"') + if (lexer.TextWindow.PeekChar(1) == '"' && !RecoveringFromRunawayLexing()) { // check for verbatim string inside an expression hole. ScanInterpolatedStringLiteralNestedVerbatimString(); @@ -655,6 +667,12 @@ private void ScanInterpolatedStringLiteralHoleBalancedText(char endingChar, bool } } + /// + /// The lexer can run away consuming the rest of the input when delimiters are mismatched. + /// This is a test for when we are attempting to recover from that situation. + /// + private bool RecoveringFromRunawayLexing() => this.error != null; + private void ScanInterpolatedStringLiteralNestedComment() { Debug.Assert(lexer.TextWindow.PeekChar() == '/'); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index ec1fc437193..e5710426b97 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -151,14 +151,8 @@ public static void Main(string[] args) // too many diagnostics perhaps, but it starts the right way. CreateCompilationWithMscorlib45(source).VerifyDiagnostics( // (5,71): error CS8077: A single-line comment may not be used in an interpolated string. - // Console.WriteLine("Jenny don\'t change your number \{ 8675309 // "); - Diagnostic(ErrorCode.ERR_SingleLineCommentInExpressionHole, "//").WithLocation(5, 71), - // (5,77): error CS1026: ) expected - // Console.WriteLine("Jenny don\'t change your number \{ 8675309 // "); - Diagnostic(ErrorCode.ERR_CloseParenExpected, "").WithLocation(5, 77), - // (5,77): error CS1002: ; expected - // Console.WriteLine("Jenny don\'t change your number \{ 8675309 // "); - Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(5, 77) + // Console.WriteLine($"Jenny don\'t change your number { 8675309 // "); + Diagnostic(ErrorCode.ERR_SingleLineCommentInExpressionHole, "//").WithLocation(5, 71) ); } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs index b6c3d16e292..e7b1faba8b3 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs @@ -5236,5 +5236,118 @@ public void ArrayCreation_ElementAccess() } EOF(); } + + [Fact, WorkItem(44789, "https://github.com/dotnet/roslyn/issues/44789")] + public void MismatchedInterpolatedStringContents_01() + { + var text = +@"class A +{ + void M() + { + if (b) + { + A B = new C($@""{D(.E}""); + N.O("""", P.Q); + R.S(T); + U.V(W.X, Y.Z); + } + } + + string M() => """"; +}"; + var tree = ParseTree(text, TestOptions.Regular); + // Note that the parser eventually syncs back up and stops producing diagnostics. + tree.GetDiagnostics().Verify( + // (7,31): error CS1001: Identifier expected + // A B = new C($@"{D(.E}"); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ".").WithLocation(7, 31), + // (7,33): error CS1003: Syntax error, ')' expected + // A B = new C($@"{D(.E}"); + Diagnostic(ErrorCode.ERR_SyntaxError, "}").WithArguments(")").WithLocation(7, 33), + // (7,33): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}"); + Diagnostic(ErrorCode.ERR_SyntaxError, "}").WithArguments(",", "}").WithLocation(7, 33), + // (7,34): error CS1026: ) expected + // A B = new C($@"{D(.E}"); + Diagnostic(ErrorCode.ERR_CloseParenExpected, "").WithLocation(7, 34) + ); + } + + [Fact, WorkItem(44789, "https://github.com/dotnet/roslyn/issues/44789")] + public void MismatchedInterpolatedStringContents_02() + { + var text = +@"class A +{ + void M() + { + if (b) + { + A B = new C($@""{D(.E}\F\G{H}_{I.J.K(""L"")}.M""); + N.O("""", P.Q); + R.S(T); + U.V(W.X, Y.Z); + } + } + + string M() => """"; +}"; + var tree = ParseTree(text, TestOptions.Regular); + // Note that the parser eventually syncs back up and stops producing diagnostics. + tree.GetDiagnostics().Verify( + // (7,31): error CS1001: Identifier expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ".").WithLocation(7, 31), + // (7,33): error CS1003: Syntax error, ')' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "}").WithArguments(")").WithLocation(7, 33), + // (7,33): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "}").WithArguments(",", "}").WithLocation(7, 33), + // (7,34): error CS1056: Unexpected character '\' + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "").WithArguments("\\").WithLocation(7, 34), + // (7,35): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "F").WithArguments(",", "").WithLocation(7, 35), + // (7,36): error CS1056: Unexpected character '\' + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "").WithArguments("\\").WithLocation(7, 36), + // (7,37): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "G").WithArguments(",", "").WithLocation(7, 37), + // (7,38): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(",", "{").WithLocation(7, 38), + // (7,39): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "H").WithArguments(",", "").WithLocation(7, 39), + // (7,40): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "}").WithArguments(",", "}").WithLocation(7, 40), + // (7,41): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "_").WithArguments(",", "").WithLocation(7, 41), + // (7,42): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(",", "{").WithLocation(7, 42), + // (7,43): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "I").WithArguments(",", "").WithLocation(7, 43), + // (7,49): error CS1026: ) expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_CloseParenExpected, "").WithLocation(7, 49), + // (7,49): error CS1026: ) expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_CloseParenExpected, "").WithLocation(7, 49), + // (7,50): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, "L").WithArguments(",", "").WithLocation(7, 50), + // (7,51): error CS1003: Syntax error, ',' expected + // A B = new C($@"{D(.E}\F\G{H}_{I.J.K("L")}.M"); + Diagnostic(ErrorCode.ERR_SyntaxError, @""")}.M""").WithArguments(",", "").WithLocation(7, 51) + ); + } } } -- GitLab