Skip to content

Commit 0c00d9f

Browse files
committed
improved error reporting on lexer with raw literals
The lexer now emits a suggestion for `cargo fix`
1 parent 17f6558 commit 0c00d9f

10 files changed

+162
-49
lines changed

src/libsyntax/parse/lexer/mod.rs

+71-34
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::symbol::{sym, Symbol};
44
use crate::parse::unescape;
55
use crate::parse::unescape_error_reporting::{emit_unescape_error, push_escaped_char};
66

7-
use errors::{FatalError, Diagnostic, DiagnosticBuilder};
7+
use errors::{Applicability, FatalError, Diagnostic, DiagnosticBuilder};
88
use syntax_pos::{BytePos, Pos, Span, NO_EXPANSION};
99
use core::unicode::property::Pattern_White_Space;
1010

@@ -145,19 +145,64 @@ impl<'a> StringReader<'a> {
145145
self.ch.is_none()
146146
}
147147

148-
fn fail_unterminated_raw_string(&self, pos: BytePos, hash_count: u16) -> ! {
149-
let mut err = self.struct_span_fatal(pos, pos, "unterminated raw string");
150-
err.span_label(self.mk_sp(pos, pos), "unterminated raw string");
148+
fn fail_unterminated_raw_string(&self, start: Span, hash_count: u16, spans: Vec<Span>) -> ! {
149+
const SPAN_THRESHOLD: usize = 3;
150+
const MSG_STR: &str = "you might have meant to end the raw string here";
151+
let hash_str = format!("\"{}", "#".repeat(hash_count as usize));
152+
let spans_len = spans.len();
151153

152-
if hash_count > 0 {
153-
err.note(&format!("this raw string should be terminated with `\"{}`",
154-
"#".repeat(hash_count as usize)));
154+
let mut err = self.sess.span_diagnostic.struct_span_fatal(start, "unterminated raw string");
155+
err.span_label(start, "unterminated raw string");
156+
157+
for s in spans {
158+
if spans_len < SPAN_THRESHOLD {
159+
err.span_suggestion(
160+
s,
161+
MSG_STR,
162+
hash_str.clone(),
163+
Applicability::MaybeIncorrect
164+
);
165+
} else {
166+
err.tool_only_span_suggestion(
167+
s,
168+
MSG_STR,
169+
hash_str.clone(),
170+
Applicability::MaybeIncorrect
171+
);
172+
}
173+
}
174+
175+
if hash_count > 0 && spans_len >= SPAN_THRESHOLD {
176+
err.note(&format!("this raw string should be terminated with `\"{}`", hash_str));
155177
}
156178

157179
err.emit();
158180
FatalError.raise();
159181
}
160182

183+
fn fail_incorrect_raw_string_delimiter(&mut self, start: BytePos) -> ! {
184+
loop {
185+
match self.ch {
186+
Some('#') | Some('"') => break,
187+
_ => self.bump(),
188+
}
189+
}
190+
let end = self.pos;
191+
let span = self.mk_sp(start, end);
192+
let mut err = self.sess.span_diagnostic.struct_span_fatal(
193+
span,
194+
"found invalid character; only `#` is allowed in raw string delimitation",
195+
);
196+
err.span_suggestion_hidden(
197+
span,
198+
"replace with `#`",
199+
format!("{}", "#".repeat((end.0 - start.0) as usize)),
200+
Applicability::MachineApplicable,
201+
);
202+
err.emit();
203+
FatalError.raise();
204+
}
205+
161206
crate fn emit_fatal_errors(&mut self) {
162207
for err in &mut self.fatal_errs {
163208
err.emit();
@@ -202,16 +247,6 @@ impl<'a> StringReader<'a> {
202247
self.err_span(self.mk_sp(from_pos, to_pos), m)
203248
}
204249

205-
/// Report a lexical error spanning [`from_pos`, `to_pos`), appending an
206-
/// escaped character to the error message
207-
fn fatal_span_char(&self, from_pos: BytePos, to_pos: BytePos, m: &str, c: char) -> FatalError {
208-
let mut m = m.to_string();
209-
m.push_str(": ");
210-
push_escaped_char(&mut m, c);
211-
212-
self.fatal_span_(from_pos, to_pos, &m[..])
213-
}
214-
215250
fn struct_span_fatal(&self, from_pos: BytePos, to_pos: BytePos, m: &str)
216251
-> DiagnosticBuilder<'a>
217252
{
@@ -945,6 +980,7 @@ impl<'a> StringReader<'a> {
945980
Ok(TokenKind::lit(token::Char, symbol, suffix))
946981
}
947982
'b' => {
983+
let start_bpos = self.pos;
948984
self.bump();
949985
let (kind, symbol) = match self.ch {
950986
Some('\'') => {
@@ -963,7 +999,7 @@ impl<'a> StringReader<'a> {
963999
(token::ByteStr, symbol)
9641000
},
9651001
Some('r') => {
966-
let (start, end, hash_count) = self.scan_raw_string();
1002+
let (start, end, hash_count) = self.scan_raw_string(start_bpos);
9671003
let symbol = self.symbol_from_to(start, end);
9681004
self.validate_raw_byte_str_escape(start, end);
9691005

@@ -984,7 +1020,7 @@ impl<'a> StringReader<'a> {
9841020
Ok(TokenKind::lit(token::Str, symbol, suffix))
9851021
}
9861022
'r' => {
987-
let (start, end, hash_count) = self.scan_raw_string();
1023+
let (start, end, hash_count) = self.scan_raw_string(self.pos);
9881024
let symbol = self.symbol_from_to(start, end);
9891025
self.validate_raw_str_escape(start, end);
9901026
let suffix = self.scan_optional_raw_name();
@@ -1145,8 +1181,7 @@ impl<'a> StringReader<'a> {
11451181

11461182
/// Scans a raw (byte) string, returning byte position range for `"<literal>"`
11471183
/// (including quotes) along with `#` character count in `(b)r##..."<literal>"##...`;
1148-
fn scan_raw_string(&mut self) -> (BytePos, BytePos, u16) {
1149-
let start_bpos = self.pos;
1184+
fn scan_raw_string(&mut self, start_bpos: BytePos) -> (BytePos, BytePos, u16) {
11501185
self.bump();
11511186
let mut hash_count: u16 = 0;
11521187
while self.ch_is('#') {
@@ -1161,30 +1196,32 @@ impl<'a> StringReader<'a> {
11611196
hash_count += 1;
11621197
}
11631198

1164-
if self.is_eof() {
1165-
self.fail_unterminated_raw_string(start_bpos, hash_count);
1166-
} else if !self.ch_is('"') {
1167-
let last_bpos = self.pos;
1168-
let curr_char = self.ch.unwrap();
1169-
self.fatal_span_char(start_bpos,
1170-
last_bpos,
1171-
"found invalid character; only `#` is allowed \
1172-
in raw string delimitation",
1173-
curr_char).raise();
1199+
let bpos_span = self.mk_sp(start_bpos, self.pos);
1200+
1201+
match self.ch {
1202+
None => self.fail_unterminated_raw_string(
1203+
bpos_span,
1204+
hash_count,
1205+
vec![self.mk_sp(self.pos, self.pos)]
1206+
),
1207+
Some('"') => (),
1208+
Some(_) => self.fail_incorrect_raw_string_delimiter(self.pos),
11741209
}
1210+
11751211
self.bump();
11761212
let content_start_bpos = self.pos;
11771213
let mut content_end_bpos;
1214+
let mut spans = vec![];
1215+
11781216
'outer: loop {
11791217
match self.ch {
1180-
None => {
1181-
self.fail_unterminated_raw_string(start_bpos, hash_count);
1182-
}
1218+
None => self.fail_unterminated_raw_string(bpos_span, hash_count, spans),
11831219
Some('"') => {
11841220
content_end_bpos = self.pos;
11851221
for _ in 0..hash_count {
11861222
self.bump();
11871223
if !self.ch_is('#') {
1224+
spans.push(self.mk_sp(content_end_bpos, self.pos));
11881225
continue 'outer;
11891226
}
11901227
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
error: unterminated raw string
2-
--> $DIR/raw-byte-string-eof.rs:2:6
2+
--> $DIR/raw-byte-string-eof.rs:2:5
33
|
44
LL | br##"a"#;
5-
| ^ unterminated raw string
5+
| ^^^^ unterminated raw string
6+
help: you might have meant to end the raw string here
67
|
7-
= note: this raw string should be terminated with `"##`
8+
LL | br##"a'##;
9+
| ^^^
810

911
error: aborting due to previous error
1012

src/test/ui/parser/raw/raw-byte-string-literals.stderr

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ error: raw byte string must be ASCII
1010
LL | br"é";
1111
| ^
1212

13-
error: found invalid character; only `#` is allowed in raw string delimitation: ~
14-
--> $DIR/raw-byte-string-literals.rs:6:6
13+
error: found invalid character; only `#` is allowed in raw string delimitation
14+
--> $DIR/raw-byte-string-literals.rs:6:9
1515
|
1616
LL | br##~"a"~##;
17-
| ^^^
17+
| ^
18+
= help: replace with `#`
1819

1920
error: aborting due to 3 previous errors
2021

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
error: found invalid character; only `#` is allowed in raw string delimitation: ~
2-
--> $DIR/raw-str-delim.rs:2:5
1+
error: found invalid character; only `#` is allowed in raw string delimitation
2+
--> $DIR/raw-str-delim.rs:2:7
33
|
44
LL | r#~"#"~#
5-
| ^^
5+
| ^
6+
= help: replace with `#`
67

78
error: aborting due to previous error
89

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// check-pass
2+
3+
macro_rules! m1 {
4+
($tt:tt #) => ()
5+
}
6+
7+
macro_rules! m2 {
8+
($tt:tt) => ()
9+
}
10+
11+
macro_rules! m3 {
12+
($tt:tt #) => ()
13+
}
14+
15+
fn main() {
16+
m1!(r#"abc"##);
17+
m2!(r#"abc"##);
18+
m3!(r#"abc"#);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: no rules expected the token `#`
2+
--> $DIR/raw-str-in-macro-call.rs:17:17
3+
|
4+
LL | macro_rules! m2 {
5+
| --------------- when calling this macro
6+
...
7+
LL | m2!(r#"abc"##);
8+
| ^ no rules expected this token in macro call
9+
10+
error: unexpected end of macro invocation
11+
--> $DIR/raw-str-in-macro-call.rs:18:5
12+
|
13+
LL | macro_rules! m3 {
14+
| --------------- when calling this macro
15+
...
16+
LL | m3!(r#"abc"#);
17+
| ^^^^^^^^^^^^^^ missing tokens in macro arguments
18+
19+
error: aborting due to 2 previous errors
20+
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
fn main() {
2+
let a = r##"This //~ ERROR unterminated raw string
3+
is
4+
a
5+
very
6+
long
7+
string
8+
which
9+
goes
10+
over
11+
a
12+
b
13+
c
14+
d
15+
e
16+
f
17+
g
18+
h
19+
lines
20+
"#;
21+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error: unterminated raw string
2+
--> $DIR/raw-str-long.rs:2:13
3+
|
4+
LL | let a = r##"This
5+
| ^^^ unterminated raw string
6+
help: you might have meant to end the raw string here
7+
|
8+
LL | "##;
9+
| ^^^
10+
11+
error: aborting due to previous error
12+

src/test/ui/parser/raw/raw-str-unterminated.stderr

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ error: unterminated raw string
22
--> $DIR/raw-str-unterminated.rs:2:5
33
|
44
LL | r#" string literal goes on
5-
| ^ unterminated raw string
6-
|
7-
= note: this raw string should be terminated with `"#`
5+
| ^^ unterminated raw string
86

97
error: aborting due to previous error
108

src/test/ui/parser/raw/raw-str.stderr

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
error: unterminated raw string
2-
--> $DIR/raw_string.rs:2:13
2+
--> $DIR/raw-str.rs:2:13
33
|
44
LL | let x = r##"lol"#;
5-
| ^ unterminated raw string
5+
| ^^^ unterminated raw string
6+
help: you might have meant to end the raw string here
67
|
7-
= note: this raw string should be terminated with `"##`
8+
LL | let x = r##"lol'##;
9+
| ^^^
810

911
error: aborting due to previous error
1012

0 commit comments

Comments
 (0)