From 58ec13d79389f3886e71d43a0fcf501b84c1a40f Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 11 Aug 2025 06:16:34 +0200 Subject: [PATCH] feat: hint at unterminated strings in unknown prefix errors When encountering 'unknown literal prefix' errors, check for unbalanced quotes in recent code and suggest checking for unterminated string literals. --- .../crates/parser/src/lexed_str.rs | 30 ++++++++++++++++++- .../unterminated_string_unknown_prefix.rast | 15 ++++++++++ .../err/unterminated_string_unknown_prefix.rs | 5 ++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index 8fff1c3db748..dcf397142cac 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -149,6 +149,24 @@ impl<'a> Converter<'a> { } } + /// Check for likely unterminated string by analyzing STRING token content + fn has_likely_unterminated_string(&self) -> bool { + let Some(last_idx) = self.res.kind.len().checked_sub(1) else { return false }; + + for i in (0..=last_idx).rev().take(5) { + if self.res.kind[i] == STRING { + let start = self.res.start[i] as usize; + let end = self.res.start.get(i + 1).map(|&s| s as usize).unwrap_or(self.offset); + let content = &self.res.text[start..end]; + + if content.contains('(') && (content.contains("//") || content.contains(";\n")) { + return true; + } + } + } + false + } + fn finalize_with_eof(mut self) -> LexedStr<'a> { self.res.push(EOF, self.offset); self.res @@ -267,7 +285,17 @@ impl<'a> Converter<'a> { rustc_lexer::TokenKind::Unknown => ERROR, rustc_lexer::TokenKind::UnknownPrefix if token_text == "builtin" => IDENT, rustc_lexer::TokenKind::UnknownPrefix => { - errors.push("unknown literal prefix".into()); + let has_unterminated = self.has_likely_unterminated_string(); + + let error_msg = if has_unterminated { + format!( + "unknown literal prefix `{}` (note: check for unterminated string literal)", + token_text + ) + } else { + "unknown literal prefix".to_owned() + }; + errors.push(error_msg); IDENT } rustc_lexer::TokenKind::Eof => EOF, diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rast b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rast new file mode 100644 index 000000000000..f7f24ca3f810 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rast @@ -0,0 +1,15 @@ +FN_KW "fn" +WHITESPACE " " +IDENT "main" +L_PAREN "(" +R_PAREN ")" +WHITESPACE " " +L_CURLY "{" +WHITESPACE "\n " +IDENT "hello" +L_PAREN "(" +STRING "\"world);\n // a bunch of code was here\n env(\"FLAGS" +STRING "\", \"" +MINUS "-" +IDENT "help" error: unknown literal prefix `help` (note: check for unterminated string literal) +STRING "\")\n}" error: Missing trailing `"` symbol to terminate the string literal diff --git a/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rs b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rs new file mode 100644 index 000000000000..338b9582605b --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/lexer/err/unterminated_string_unknown_prefix.rs @@ -0,0 +1,5 @@ +fn main() { + hello("world); + // a bunch of code was here + env("FLAGS", "-help") +} \ No newline at end of file