Skip to content

Commit 929cda7

Browse files
committed
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.
1 parent b06ce60 commit 929cda7

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

crates/parser/src/lexed_str.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,48 @@ impl<'a> Converter<'a> {
149149
}
150150
}
151151

152+
/// Check for likely unterminated string by analyzing STRING token content
153+
fn has_likely_unterminated_string(&self) -> bool {
154+
// Look at the most recent STRING token content
155+
if let Some(last_token_idx) = self.res.kind.len().checked_sub(1) {
156+
if self.res.kind[last_token_idx] == STRING {
157+
let start = self.res.start[last_token_idx] as usize;
158+
let content = &self.res.text[start..self.offset];
159+
160+
// Check for obvious code patterns in string content
161+
let has_code_patterns = content.contains('(')
162+
&& (
163+
content.contains("//") || // Comments in strings = suspicious
164+
content.contains(";\n")
165+
// Statements in strings = suspicious
166+
);
167+
168+
return has_code_patterns;
169+
} else {
170+
// Look for any STRING token in recent history
171+
for i in (0..=last_token_idx).rev().take(5) {
172+
if self.res.kind[i] == STRING {
173+
let start = self.res.start[i] as usize;
174+
let end = if i + 1 < self.res.start.len() {
175+
self.res.start[i + 1] as usize
176+
} else {
177+
self.offset
178+
};
179+
let content = &self.res.text[start..end];
180+
181+
let has_code_patterns = content.contains('(')
182+
&& (content.contains("//") || content.contains(";\n"));
183+
184+
if has_code_patterns {
185+
return true;
186+
}
187+
}
188+
}
189+
}
190+
}
191+
false
192+
}
193+
152194
fn finalize_with_eof(mut self) -> LexedStr<'a> {
153195
self.res.push(EOF, self.offset);
154196
self.res
@@ -267,7 +309,17 @@ impl<'a> Converter<'a> {
267309
rustc_lexer::TokenKind::Unknown => ERROR,
268310
rustc_lexer::TokenKind::UnknownPrefix if token_text == "builtin" => IDENT,
269311
rustc_lexer::TokenKind::UnknownPrefix => {
270-
errors.push("unknown literal prefix".into());
312+
let has_unterminated = self.has_likely_unterminated_string();
313+
314+
let error_msg = if has_unterminated {
315+
format!(
316+
"unknown literal prefix `{}` (note: check for unterminated string literal)",
317+
token_text
318+
)
319+
} else {
320+
"unknown literal prefix".to_string()
321+
};
322+
errors.push(error_msg);
271323
IDENT
272324
}
273325
rustc_lexer::TokenKind::Eof => EOF,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FN_KW "fn"
2+
WHITESPACE " "
3+
IDENT "main"
4+
L_PAREN "("
5+
R_PAREN ")"
6+
WHITESPACE " "
7+
L_CURLY "{"
8+
WHITESPACE "\n "
9+
IDENT "hello"
10+
L_PAREN "("
11+
STRING "\"world);\n // a bunch of code was here\n env(\"FLAGS"
12+
STRING "\", \""
13+
MINUS "-"
14+
IDENT "help" error: unknown literal prefix `help` (note: check for unterminated string literal)
15+
STRING "\")\n}" error: Missing trailing `"` symbol to terminate the string literal
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn main() {
2+
hello("world);
3+
// a bunch of code was here
4+
env("FLAGS", "-help")
5+
}

0 commit comments

Comments
 (0)