diff --git a/toolchain/lexer/string_literal.cpp b/toolchain/lexer/string_literal.cpp index 8edc24324e26..ef68177d6668 100644 --- a/toolchain/lexer/string_literal.cpp +++ b/toolchain/lexer/string_literal.cpp @@ -363,8 +363,13 @@ static auto ExpandEscapeSequencesAndRemoveIndent( } } + // Tracks the length of the result at the last time we expanded an escape + // to ensure we don't misinterpret it as unescaped when backtracking. + size_t last_escape_length = 0; + // Process the contents of the line. while (true) { + // Append the next segment of plain text. auto end_of_regular_text = contents.find_if([](char c) { return c == '\n' || c == '\\' || (IsHorizontalWhitespace(c) && c != ' '); @@ -377,10 +382,11 @@ static auto ExpandEscapeSequencesAndRemoveIndent( } if (contents.consume_front("\n")) { - // Trailing whitespace before a newline doesn't contribute to the string - // literal value. + // Trailing whitespace in the source before a newline doesn't contribute + // to the string literal value. However, escaped whitespace (like `\t`) + // and any whitespace just before that does contribute. while (!result.empty() && result.back() != '\n' && - IsSpace(result.back())) { + IsSpace(result.back()) && result.length() > last_escape_length) { result.pop_back(); } result += '\n'; @@ -425,6 +431,7 @@ static auto ExpandEscapeSequencesAndRemoveIndent( // Handle this escape sequence. ExpandAndConsumeEscapeSequence(emitter, contents, result); + last_escape_length = result.length(); } } } diff --git a/toolchain/lexer/string_literal_test.cpp b/toolchain/lexer/string_literal_test.cpp index 71e935f7d7fe..9b0b8d65d4e6 100644 --- a/toolchain/lexer/string_literal_test.cpp +++ b/toolchain/lexer/string_literal_test.cpp @@ -202,13 +202,15 @@ TEST_F(StringLiteralTest, StringLiteralContents) { // Trailing whitespace handling. {"'''\n Hello \\\n World \t \n Bye! \\\n '''", "Hello World\nBye! "}, + {"'''\n\\t\n'''", "\t\n"}, + {"'''\n\\t \n'''", "\t\n"}, }; - for (auto [test, contents] : testcases) { + for (auto [test, expected] : testcases) { error_tracker.Reset(); auto value = Parse(test.trim()); EXPECT_FALSE(error_tracker.seen_error()) << "`" << test << "`"; - EXPECT_EQ(value, contents); + EXPECT_EQ(value, expected); } }