Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve raw string diagnostic #61255

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 71 additions & 34 deletions src/libsyntax/parse/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::symbol::{sym, Symbol};
use crate::parse::unescape;
use crate::parse::unescape_error_reporting::{emit_unescape_error, push_escaped_char};

use errors::{FatalError, Diagnostic, DiagnosticBuilder};
use errors::{Applicability, FatalError, Diagnostic, DiagnosticBuilder};
use syntax_pos::{BytePos, Pos, Span, NO_EXPANSION};
use core::unicode::property::Pattern_White_Space;

Expand Down Expand Up @@ -145,19 +145,64 @@ impl<'a> StringReader<'a> {
self.ch.is_none()
}

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

if hash_count > 0 {
err.note(&format!("this raw string should be terminated with `\"{}`",
"#".repeat(hash_count as usize)));
let mut err = self.sess.span_diagnostic.struct_span_fatal(start, "unterminated raw string");
err.span_label(start, "unterminated raw string");

for s in spans {
if spans_len < SPAN_THRESHOLD {
err.span_suggestion(
s,
MSG_STR,
hash_str.clone(),
Applicability::MaybeIncorrect
);
} else {
err.tool_only_span_suggestion(
s,
MSG_STR,
hash_str.clone(),
Applicability::MaybeIncorrect
);
}
}

if hash_count > 0 && spans_len >= SPAN_THRESHOLD {
err.note(&format!("this raw string should be terminated with `\"{}`", hash_str));
}

err.emit();
FatalError.raise();
}

fn fail_incorrect_raw_string_delimiter(&mut self, start: BytePos) -> ! {
loop {
match self.ch {
Some('#') | Some('"') => break,
_ => self.bump(),
}
}
let end = self.pos;
let span = self.mk_sp(start, end);
let mut err = self.sess.span_diagnostic.struct_span_fatal(
span,
"found invalid character; only `#` is allowed in raw string delimitation",
);
err.span_suggestion_hidden(
span,
"replace with `#`",
format!("{}", "#".repeat((end.0 - start.0) as usize)),
Applicability::MachineApplicable,
);
err.emit();
FatalError.raise();
}

crate fn emit_fatal_errors(&mut self) {
for err in &mut self.fatal_errs {
err.emit();
Expand Down Expand Up @@ -202,16 +247,6 @@ impl<'a> StringReader<'a> {
self.err_span(self.mk_sp(from_pos, to_pos), m)
}

/// Report a lexical error spanning [`from_pos`, `to_pos`), appending an
/// escaped character to the error message
fn fatal_span_char(&self, from_pos: BytePos, to_pos: BytePos, m: &str, c: char) -> FatalError {
let mut m = m.to_string();
m.push_str(": ");
push_escaped_char(&mut m, c);

self.fatal_span_(from_pos, to_pos, &m[..])
}

fn struct_span_fatal(&self, from_pos: BytePos, to_pos: BytePos, m: &str)
-> DiagnosticBuilder<'a>
{
Expand Down Expand Up @@ -945,6 +980,7 @@ impl<'a> StringReader<'a> {
Ok(TokenKind::lit(token::Char, symbol, suffix))
}
'b' => {
let start_bpos = self.pos;
self.bump();
let (kind, symbol) = match self.ch {
Some('\'') => {
Expand All @@ -963,7 +999,7 @@ impl<'a> StringReader<'a> {
(token::ByteStr, symbol)
},
Some('r') => {
let (start, end, hash_count) = self.scan_raw_string();
let (start, end, hash_count) = self.scan_raw_string(start_bpos);
let symbol = self.symbol_from_to(start, end);
self.validate_raw_byte_str_escape(start, end);

Expand All @@ -984,7 +1020,7 @@ impl<'a> StringReader<'a> {
Ok(TokenKind::lit(token::Str, symbol, suffix))
}
'r' => {
let (start, end, hash_count) = self.scan_raw_string();
let (start, end, hash_count) = self.scan_raw_string(self.pos);
let symbol = self.symbol_from_to(start, end);
self.validate_raw_str_escape(start, end);
let suffix = self.scan_optional_raw_name();
Expand Down Expand Up @@ -1145,8 +1181,7 @@ impl<'a> StringReader<'a> {

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

if self.is_eof() {
self.fail_unterminated_raw_string(start_bpos, hash_count);
} else if !self.ch_is('"') {
let last_bpos = self.pos;
let curr_char = self.ch.unwrap();
self.fatal_span_char(start_bpos,
last_bpos,
"found invalid character; only `#` is allowed \
in raw string delimitation",
curr_char).raise();
let bpos_span = self.mk_sp(start_bpos, self.pos);

match self.ch {
None => self.fail_unterminated_raw_string(
bpos_span,
hash_count,
vec![self.mk_sp(self.pos, self.pos)]
),
Some('"') => (),
Some(_) => self.fail_incorrect_raw_string_delimiter(self.pos),
}

self.bump();
let content_start_bpos = self.pos;
let mut content_end_bpos;
let mut spans = vec![];

'outer: loop {
match self.ch {
None => {
self.fail_unterminated_raw_string(start_bpos, hash_count);
}
None => self.fail_unterminated_raw_string(bpos_span, hash_count, spans),
Some('"') => {
content_end_bpos = self.pos;
for _ in 0..hash_count {
self.bump();
if !self.ch_is('#') {
spans.push(self.mk_sp(content_end_bpos, self.pos));
continue 'outer;
}
}
Expand Down
10 changes: 0 additions & 10 deletions src/test/ui/parser/raw-byte-string-eof.stderr

This file was deleted.

12 changes: 12 additions & 0 deletions src/test/ui/parser/raw/raw-byte-string-eof.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error: unterminated raw string
--> $DIR/raw-byte-string-eof.rs:2:5
|
LL | br##"a"#;
| ^^^^ unterminated raw string
help: you might have meant to end the raw string here
|
LL | br##"a'##;
| ^^^

error: aborting due to previous error

Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ error: raw byte string must be ASCII
LL | br"é";
| ^

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

error: aborting due to 3 previous errors

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

error: aborting due to previous error

19 changes: 19 additions & 0 deletions src/test/ui/parser/raw/raw-str-in-macro-call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// check-pass

macro_rules! m1 {
($tt:tt #) => ()
}

macro_rules! m2 {
($tt:tt) => ()
}

macro_rules! m3 {
($tt:tt #) => ()
}

fn main() {
m1!(r#"abc"##);
m2!(r#"abc"##);
m3!(r#"abc"#);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add another tests with the following combinations?

macro_rules! m {
    ($tt:tt) => ()
}

 fn main() {
    m!(r#"abc"##);
}
macro_rules! m {
    ($tt:tt #) => ()
}

 fn main() {
    m!(r#"abc"#);
}

Just want us to catch any regressions in the future.

20 changes: 20 additions & 0 deletions src/test/ui/parser/raw/raw-str-in-macro-call.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: no rules expected the token `#`
--> $DIR/raw-str-in-macro-call.rs:17:17
|
LL | macro_rules! m2 {
| --------------- when calling this macro
...
LL | m2!(r#"abc"##);
| ^ no rules expected this token in macro call

error: unexpected end of macro invocation
--> $DIR/raw-str-in-macro-call.rs:18:5
|
LL | macro_rules! m3 {
| --------------- when calling this macro
...
LL | m3!(r#"abc"#);
| ^^^^^^^^^^^^^^ missing tokens in macro arguments

error: aborting due to 2 previous errors

21 changes: 21 additions & 0 deletions src/test/ui/parser/raw/raw-str-long.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
fn main() {
let a = r##"This //~ ERROR unterminated raw string
is
a
very
long
string
which
goes
over
a
b
c
d
e
f
g
h
lines
"#;
}
12 changes: 12 additions & 0 deletions src/test/ui/parser/raw/raw-str-long.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error: unterminated raw string
--> $DIR/raw-str-long.rs:2:13
|
LL | let a = r##"This
| ^^^ unterminated raw string
help: you might have meant to end the raw string here
|
LL | "##;
| ^^^
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried about the '## not being "##.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, the reason for it is, that I look for #, not for "#[...]. I think I can change this.


error: aborting due to previous error

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ error: unterminated raw string
--> $DIR/raw-str-unterminated.rs:2:5
|
LL | r#" string literal goes on
| ^ unterminated raw string
|
= note: this raw string should be terminated with `"#`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should keep the current note for this case where no likely close is found.

| ^^ unterminated raw string

error: aborting due to previous error

File renamed without changes.
12 changes: 12 additions & 0 deletions src/test/ui/parser/raw/raw-str.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error: unterminated raw string
--> $DIR/raw-str.rs:2:13
|
LL | let x = r##"lol"#;
| ^^^ unterminated raw string
help: you might have meant to end the raw string here
|
LL | let x = r##"lol'##;
| ^^^

error: aborting due to previous error

10 changes: 0 additions & 10 deletions src/test/ui/parser/raw/raw_string.stderr

This file was deleted.