diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index 7ae454e90307..c3509d0c7d6c 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -192,7 +192,7 @@ auto TokenizedBuffer::IsRawIdentifier(TokenIndex token) const -> bool { // starting with `#`. It suffices to check that character is the first // character of the identifier. auto token_text = source_->text().substr(token_info.byte_offset()); - return token_text.starts_with("r#") && + return token_text.size() > 2 && token_text.starts_with("r#") && token_text[2] == value_stores_->identifiers().Get(token_info.ident_id()).front(); } diff --git a/toolchain/lex/tokenized_buffer_test.cpp b/toolchain/lex/tokenized_buffer_test.cpp index 105c5fb581e6..e7c824de6603 100644 --- a/toolchain/lex/tokenized_buffer_test.cpp +++ b/toolchain/lex/tokenized_buffer_test.cpp @@ -807,6 +807,19 @@ TEST_F(LexerTest, Identifiers) { })); } +TEST_F(LexerTest, RawIdentifierIntroducerAtEndOfFile) { + // `r#` at the very end of the source is not a raw identifier -- there is no + // identifier after the `#` -- but the leading `r` is still an identifier + // whose text begins with `r#`. Computing that text checks for the raw form + // and must not read past the end of the buffer. + auto& buffer = compile_helper_.GetTokenizedBuffer("r#"); + for (TokenIndex token : buffer.tokens()) { + if (buffer.GetKind(token) == TokenKind::Identifier) { + EXPECT_EQ(buffer.GetTokenText(token), "r"); + } + } +} + TEST_F(LexerTest, StringLiterals) { llvm::StringLiteral testcase = R"( "hello world\n"