Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5e7e5ec

Browse files
authoredMar 31, 2020
Rollup merge of rust-lang#70522 - rcoh:60762-raw-string-errors, r=petrochenkov
Improve error messages for raw strings (rust-lang#60762) This diff improves error messages around raw strings in a few ways: - Catch extra trailing `#` in the parser. This can't be handled in the lexer because we could be in a macro that actually expects another # (see test) - Refactor & unify error handling in the lexer between ByteStrings and RawByteStrings - Detect potentially intended terminators (longest sequence of "#*" is suggested) Fixes rust-lang#60762 cc @estebank who reviewed the original (abandoned) PR for the same ticket. r? @Centril
2 parents b737d4c + 20e2190 commit 5e7e5ec

22 files changed

+383
-74
lines changed
 

‎src/librustc_lexer/src/lib.rs

Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
mod cursor;
1818
pub mod unescape;
1919

20+
#[cfg(test)]
21+
mod tests;
22+
2023
use self::LiteralKind::*;
2124
use self::TokenKind::*;
2225
use crate::cursor::{Cursor, EOF_CHAR};
26+
use std::convert::TryInto;
2327

2428
/// Parsed token.
2529
/// It doesn't contain information about data that has been parsed,
@@ -132,9 +136,80 @@ pub enum LiteralKind {
132136
/// "b"abc"", "b"abc"
133137
ByteStr { terminated: bool },
134138
/// "r"abc"", "r#"abc"#", "r####"ab"###"c"####", "r#"a"
135-
RawStr { n_hashes: usize, started: bool, terminated: bool },
139+
RawStr(UnvalidatedRawStr),
136140
/// "br"abc"", "br#"abc"#", "br####"ab"###"c"####", "br#"a"
137-
RawByteStr { n_hashes: usize, started: bool, terminated: bool },
141+
RawByteStr(UnvalidatedRawStr),
142+
}
143+
144+
/// Represents something that looks like a raw string, but may have some
145+
/// problems. Use `.validate()` to convert it into something
146+
/// usable.
147+
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
148+
pub struct UnvalidatedRawStr {
149+
/// The prefix (`r###"`) is valid
150+
valid_start: bool,
151+
/// The number of leading `#`
152+
n_start_hashes: usize,
153+
/// The number of trailing `#`. `n_end_hashes` <= `n_start_hashes`
154+
n_end_hashes: usize,
155+
/// The offset starting at `r` or `br` where the user may have intended to end the string.
156+
/// Currently, it is the longest sequence of pattern `"#+"`.
157+
possible_terminator_offset: Option<usize>,
158+
}
159+
160+
/// Error produced validating a raw string. Represents cases like:
161+
/// - `r##~"abcde"##`: `LexRawStrError::InvalidStarter`
162+
/// - `r###"abcde"##`: `LexRawStrError::NoTerminator { expected: 3, found: 2, possible_terminator_offset: Some(11)`
163+
/// - Too many `#`s (>65536): `TooManyDelimiters`
164+
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
165+
pub enum LexRawStrError {
166+
/// Non `#` characters exist between `r` and `"` eg. `r#~"..`
167+
InvalidStarter,
168+
/// The string was never terminated. `possible_terminator_offset` is the number of characters after `r` or `br` where they
169+
/// may have intended to terminate it.
170+
NoTerminator { expected: usize, found: usize, possible_terminator_offset: Option<usize> },
171+
/// More than 65536 `#`s exist.
172+
TooManyDelimiters,
173+
}
174+
175+
/// Raw String that contains a valid prefix (`#+"`) and postfix (`"#+`) where
176+
/// there are a matching number of `#` characters in both. Note that this will
177+
/// not consume extra trailing `#` characters: `r###"abcde"####` is lexed as a
178+
/// `ValidatedRawString { n_hashes: 3 }` followed by a `#` token.
179+
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
180+
pub struct ValidatedRawStr {
181+
n_hashes: u16,
182+
}
183+
184+
impl ValidatedRawStr {
185+
pub fn num_hashes(&self) -> u16 {
186+
self.n_hashes
187+
}
188+
}
189+
190+
impl UnvalidatedRawStr {
191+
pub fn validate(self) -> Result<ValidatedRawStr, LexRawStrError> {
192+
if !self.valid_start {
193+
return Err(LexRawStrError::InvalidStarter);
194+
}
195+
196+
// Only up to 65535 `#`s are allowed in raw strings
197+
let n_start_safe: u16 =
198+
self.n_start_hashes.try_into().map_err(|_| LexRawStrError::TooManyDelimiters)?;
199+
200+
if self.n_start_hashes > self.n_end_hashes {
201+
Err(LexRawStrError::NoTerminator {
202+
expected: self.n_start_hashes,
203+
found: self.n_end_hashes,
204+
possible_terminator_offset: self.possible_terminator_offset,
205+
})
206+
} else {
207+
// Since the lexer should never produce a literal with n_end > n_start, if n_start <= n_end,
208+
// they must be equal.
209+
debug_assert_eq!(self.n_start_hashes, self.n_end_hashes);
210+
Ok(ValidatedRawStr { n_hashes: n_start_safe })
211+
}
212+
}
138213
}
139214

140215
/// Base of numeric literal encoding according to its prefix.
@@ -209,7 +284,7 @@ pub fn is_whitespace(c: char) -> bool {
209284
// Dedicated whitespace characters from Unicode
210285
| '\u{2028}' // LINE SEPARATOR
211286
| '\u{2029}' // PARAGRAPH SEPARATOR
212-
=> true,
287+
=> true,
213288
_ => false,
214289
}
215290
}
@@ -258,12 +333,12 @@ impl Cursor<'_> {
258333
'r' => match (self.first(), self.second()) {
259334
('#', c1) if is_id_start(c1) => self.raw_ident(),
260335
('#', _) | ('"', _) => {
261-
let (n_hashes, started, terminated) = self.raw_double_quoted_string();
336+
let raw_str_i = self.raw_double_quoted_string(1);
262337
let suffix_start = self.len_consumed();
263-
if terminated {
338+
if raw_str_i.n_end_hashes == raw_str_i.n_start_hashes {
264339
self.eat_literal_suffix();
265340
}
266-
let kind = RawStr { n_hashes, started, terminated };
341+
let kind = RawStr(raw_str_i);
267342
Literal { kind, suffix_start }
268343
}
269344
_ => self.ident(),
@@ -293,12 +368,14 @@ impl Cursor<'_> {
293368
}
294369
('r', '"') | ('r', '#') => {
295370
self.bump();
296-
let (n_hashes, started, terminated) = self.raw_double_quoted_string();
371+
let raw_str_i = self.raw_double_quoted_string(2);
297372
let suffix_start = self.len_consumed();
373+
let terminated = raw_str_i.n_start_hashes == raw_str_i.n_end_hashes;
298374
if terminated {
299375
self.eat_literal_suffix();
300376
}
301-
let kind = RawByteStr { n_hashes, started, terminated };
377+
378+
let kind = RawByteStr(raw_str_i);
302379
Literal { kind, suffix_start }
303380
}
304381
_ => self.ident(),
@@ -594,37 +671,49 @@ impl Cursor<'_> {
594671
false
595672
}
596673

597-
/// Eats the double-quoted string and returns a tuple of
598-
/// (amount of the '#' symbols, raw string started, raw string terminated)
599-
fn raw_double_quoted_string(&mut self) -> (usize, bool, bool) {
674+
/// Eats the double-quoted string and returns an `UnvalidatedRawStr`.
675+
fn raw_double_quoted_string(&mut self, prefix_len: usize) -> UnvalidatedRawStr {
600676
debug_assert!(self.prev() == 'r');
601-
let mut started: bool = false;
602-
let mut finished: bool = false;
677+
let mut valid_start: bool = false;
678+
let start_pos = self.len_consumed();
679+
let (mut possible_terminator_offset, mut max_hashes) = (None, 0);
603680

604681
// Count opening '#' symbols.
605-
let n_hashes = self.eat_while(|c| c == '#');
682+
let n_start_hashes = self.eat_while(|c| c == '#');
606683

607684
// Check that string is started.
608685
match self.bump() {
609-
Some('"') => started = true,
610-
_ => return (n_hashes, started, finished),
686+
Some('"') => valid_start = true,
687+
_ => {
688+
return UnvalidatedRawStr {
689+
valid_start,
690+
n_start_hashes,
691+
n_end_hashes: 0,
692+
possible_terminator_offset,
693+
};
694+
}
611695
}
612696

613697
// Skip the string contents and on each '#' character met, check if this is
614698
// a raw string termination.
615-
while !finished {
699+
loop {
616700
self.eat_while(|c| c != '"');
617701

618702
if self.is_eof() {
619-
return (n_hashes, started, finished);
703+
return UnvalidatedRawStr {
704+
valid_start,
705+
n_start_hashes,
706+
n_end_hashes: max_hashes,
707+
possible_terminator_offset,
708+
};
620709
}
621710

622711
// Eat closing double quote.
623712
self.bump();
624713

625714
// Check that amount of closing '#' symbols
626715
// is equal to the amount of opening ones.
627-
let mut hashes_left = n_hashes;
716+
let mut hashes_left = n_start_hashes;
628717
let is_closing_hash = |c| {
629718
if c == '#' && hashes_left != 0 {
630719
hashes_left -= 1;
@@ -633,10 +722,23 @@ impl Cursor<'_> {
633722
false
634723
}
635724
};
636-
finished = self.eat_while(is_closing_hash) == n_hashes;
725+
let n_end_hashes = self.eat_while(is_closing_hash);
726+
727+
if n_end_hashes == n_start_hashes {
728+
return UnvalidatedRawStr {
729+
valid_start,
730+
n_start_hashes,
731+
n_end_hashes,
732+
possible_terminator_offset: None,
733+
};
734+
} else if n_end_hashes > max_hashes {
735+
// Keep track of possible terminators to give a hint about where there might be
736+
// a missing terminator
737+
possible_terminator_offset =
738+
Some(self.len_consumed() - start_pos - n_end_hashes + prefix_len);
739+
max_hashes = n_end_hashes;
740+
}
637741
}
638-
639-
(n_hashes, started, finished)
640742
}
641743

642744
fn eat_decimal_digits(&mut self) -> bool {

‎src/librustc_lexer/src/tests.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::*;
4+
5+
fn check_raw_str(
6+
s: &str,
7+
expected: UnvalidatedRawStr,
8+
validated: Result<ValidatedRawStr, LexRawStrError>,
9+
) {
10+
let mut cursor = Cursor::new(s);
11+
let tok = cursor.raw_double_quoted_string(0);
12+
assert_eq!(tok, expected);
13+
assert_eq!(tok.validate(), validated);
14+
}
15+
16+
#[test]
17+
fn test_naked_raw_str() {
18+
check_raw_str(
19+
r#""abc""#,
20+
UnvalidatedRawStr {
21+
n_start_hashes: 0,
22+
n_end_hashes: 0,
23+
valid_start: true,
24+
possible_terminator_offset: None,
25+
},
26+
Ok(ValidatedRawStr { n_hashes: 0 }),
27+
);
28+
}
29+
30+
#[test]
31+
fn test_raw_no_start() {
32+
check_raw_str(
33+
r##""abc"#"##,
34+
UnvalidatedRawStr {
35+
n_start_hashes: 0,
36+
n_end_hashes: 0,
37+
valid_start: true,
38+
possible_terminator_offset: None,
39+
},
40+
Ok(ValidatedRawStr { n_hashes: 0 }),
41+
);
42+
}
43+
44+
#[test]
45+
fn test_too_many_terminators() {
46+
// this error is handled in the parser later
47+
check_raw_str(
48+
r###"#"abc"##"###,
49+
UnvalidatedRawStr {
50+
n_start_hashes: 1,
51+
n_end_hashes: 1,
52+
valid_start: true,
53+
possible_terminator_offset: None,
54+
},
55+
Ok(ValidatedRawStr { n_hashes: 1 }),
56+
);
57+
}
58+
59+
#[test]
60+
fn test_unterminated() {
61+
check_raw_str(
62+
r#"#"abc"#,
63+
UnvalidatedRawStr {
64+
n_start_hashes: 1,
65+
n_end_hashes: 0,
66+
valid_start: true,
67+
possible_terminator_offset: None,
68+
},
69+
Err(LexRawStrError::NoTerminator {
70+
expected: 1,
71+
found: 0,
72+
possible_terminator_offset: None,
73+
}),
74+
);
75+
check_raw_str(
76+
r###"##"abc"#"###,
77+
UnvalidatedRawStr {
78+
n_start_hashes: 2,
79+
n_end_hashes: 1,
80+
valid_start: true,
81+
possible_terminator_offset: Some(7),
82+
},
83+
Err(LexRawStrError::NoTerminator {
84+
expected: 2,
85+
found: 1,
86+
possible_terminator_offset: Some(7),
87+
}),
88+
);
89+
// We're looking for "# not just any #
90+
check_raw_str(
91+
r###"##"abc#"###,
92+
UnvalidatedRawStr {
93+
n_start_hashes: 2,
94+
n_end_hashes: 0,
95+
valid_start: true,
96+
possible_terminator_offset: None,
97+
},
98+
Err(LexRawStrError::NoTerminator {
99+
expected: 2,
100+
found: 0,
101+
possible_terminator_offset: None,
102+
}),
103+
)
104+
}
105+
106+
#[test]
107+
fn test_invalid_start() {
108+
check_raw_str(
109+
r##"#~"abc"#"##,
110+
UnvalidatedRawStr {
111+
n_start_hashes: 1,
112+
n_end_hashes: 0,
113+
valid_start: false,
114+
possible_terminator_offset: None,
115+
},
116+
Err(LexRawStrError::InvalidStarter),
117+
);
118+
}
119+
}

‎src/librustc_parse/lexer/mod.rs

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
use rustc_ast::token::{self, Token, TokenKind};
22
use rustc_ast::util::comments;
33
use rustc_data_structures::sync::Lrc;
4-
use rustc_errors::{error_code, DiagnosticBuilder, FatalError};
5-
use rustc_lexer::unescape;
4+
use rustc_errors::{error_code, Applicability, DiagnosticBuilder, FatalError};
65
use rustc_lexer::Base;
6+
use rustc_lexer::{unescape, LexRawStrError, UnvalidatedRawStr, ValidatedRawStr};
77
use rustc_session::parse::ParseSess;
88
use rustc_span::symbol::{sym, Symbol};
99
use rustc_span::{BytePos, Pos, Span};
1010

1111
use log::debug;
1212
use std::char;
13-
use std::convert::TryInto;
1413

1514
mod tokentrees;
1615
mod unescape_error_reporting;
1716
mod unicode_chars;
17+
1818
use unescape_error_reporting::{emit_unescape_error, push_escaped_char};
1919

2020
#[derive(Clone, Debug)]
@@ -376,30 +376,22 @@ impl<'a> StringReader<'a> {
376376
let id = self.symbol_from_to(content_start, content_end);
377377
(token::ByteStr, id)
378378
}
379-
rustc_lexer::LiteralKind::RawStr { n_hashes, started, terminated } => {
380-
if !started {
381-
self.report_non_started_raw_string(start);
382-
}
383-
if !terminated {
384-
self.report_unterminated_raw_string(start, n_hashes)
385-
}
386-
let n_hashes: u16 = self.restrict_n_hashes(start, n_hashes);
379+
rustc_lexer::LiteralKind::RawStr(unvalidated_raw_str) => {
380+
let valid_raw_str = self.validate_and_report_errors(start, unvalidated_raw_str);
381+
let n_hashes = valid_raw_str.num_hashes();
387382
let n = u32::from(n_hashes);
383+
388384
let content_start = start + BytePos(2 + n);
389385
let content_end = suffix_start - BytePos(1 + n);
390386
self.validate_raw_str_escape(content_start, content_end);
391387
let id = self.symbol_from_to(content_start, content_end);
392388
(token::StrRaw(n_hashes), id)
393389
}
394-
rustc_lexer::LiteralKind::RawByteStr { n_hashes, started, terminated } => {
395-
if !started {
396-
self.report_non_started_raw_string(start);
397-
}
398-
if !terminated {
399-
self.report_unterminated_raw_string(start, n_hashes)
400-
}
401-
let n_hashes: u16 = self.restrict_n_hashes(start, n_hashes);
390+
rustc_lexer::LiteralKind::RawByteStr(unvalidated_raw_str) => {
391+
let validated_raw_str = self.validate_and_report_errors(start, unvalidated_raw_str);
392+
let n_hashes = validated_raw_str.num_hashes();
402393
let n = u32::from(n_hashes);
394+
403395
let content_start = start + BytePos(3 + n);
404396
let content_end = suffix_start - BytePos(1 + n);
405397
self.validate_raw_byte_str_escape(content_start, content_end);
@@ -485,6 +477,26 @@ impl<'a> StringReader<'a> {
485477
}
486478
}
487479

480+
fn validate_and_report_errors(
481+
&self,
482+
start: BytePos,
483+
unvalidated_raw_str: UnvalidatedRawStr,
484+
) -> ValidatedRawStr {
485+
match unvalidated_raw_str.validate() {
486+
Err(LexRawStrError::InvalidStarter) => self.report_non_started_raw_string(start),
487+
Err(LexRawStrError::NoTerminator { expected, found, possible_terminator_offset }) => {
488+
self.report_unterminated_raw_string(
489+
start,
490+
expected,
491+
possible_terminator_offset,
492+
found,
493+
)
494+
}
495+
Err(LexRawStrError::TooManyDelimiters) => self.report_too_many_hashes(start),
496+
Ok(valid) => valid,
497+
}
498+
}
499+
488500
fn report_non_started_raw_string(&self, start: BytePos) -> ! {
489501
let bad_char = self.str_from(start).chars().last().unwrap();
490502
self.struct_fatal_span_char(
@@ -498,38 +510,51 @@ impl<'a> StringReader<'a> {
498510
FatalError.raise()
499511
}
500512

501-
fn report_unterminated_raw_string(&self, start: BytePos, n_hashes: usize) -> ! {
513+
fn report_unterminated_raw_string(
514+
&self,
515+
start: BytePos,
516+
n_hashes: usize,
517+
possible_offset: Option<usize>,
518+
found_terminators: usize,
519+
) -> ! {
502520
let mut err = self.sess.span_diagnostic.struct_span_fatal_with_code(
503521
self.mk_sp(start, start),
504522
"unterminated raw string",
505523
error_code!(E0748),
506524
);
525+
507526
err.span_label(self.mk_sp(start, start), "unterminated raw string");
508527

509528
if n_hashes > 0 {
510529
err.note(&format!(
511530
"this raw string should be terminated with `\"{}`",
512-
"#".repeat(n_hashes as usize)
531+
"#".repeat(n_hashes)
513532
));
514533
}
515534

535+
if let Some(possible_offset) = possible_offset {
536+
let lo = start + BytePos(possible_offset as u32);
537+
let hi = lo + BytePos(found_terminators as u32);
538+
let span = self.mk_sp(lo, hi);
539+
err.span_suggestion(
540+
span,
541+
"consider terminating the string here",
542+
"#".repeat(n_hashes),
543+
Applicability::MaybeIncorrect,
544+
);
545+
}
546+
516547
err.emit();
517548
FatalError.raise()
518549
}
519550

520-
fn restrict_n_hashes(&self, start: BytePos, n_hashes: usize) -> u16 {
521-
match n_hashes.try_into() {
522-
Ok(n_hashes) => n_hashes,
523-
Err(_) => {
524-
self.fatal_span_(
525-
start,
526-
self.pos,
527-
"too many `#` symbols: raw strings may be \
528-
delimited by up to 65535 `#` symbols",
529-
)
530-
.raise();
531-
}
532-
}
551+
fn report_too_many_hashes(&self, start: BytePos) -> ! {
552+
self.fatal_span_(
553+
start,
554+
self.pos,
555+
"too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols",
556+
)
557+
.raise();
533558
}
534559

535560
fn validate_char_escape(&self, content_start: BytePos, content_end: BytePos) {

‎src/librustc_parse/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![feature(crate_visibility_modifier)]
55
#![feature(bindings_after_at)]
66
#![feature(try_blocks)]
7+
#![feature(or_patterns)]
78

89
use rustc_ast::ast;
910
use rustc_ast::token::{self, Nonterminal};

‎src/librustc_parse/parser/diagnostics.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_ast::ast::{
66
};
77
use rustc_ast::ast::{AttrVec, ItemKind, Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind};
88
use rustc_ast::ptr::P;
9-
use rustc_ast::token::{self, TokenKind};
9+
use rustc_ast::token::{self, Lit, LitKind, TokenKind};
1010
use rustc_ast::util::parser::AssocOp;
1111
use rustc_ast_pretty::pprust;
1212
use rustc_data_structures::fx::FxHashSet;
@@ -255,6 +255,10 @@ impl<'a> Parser<'a> {
255255
}
256256
}
257257

258+
if self.check_too_many_raw_str_terminators(&mut err) {
259+
return Err(err);
260+
}
261+
258262
let sm = self.sess.source_map();
259263
if self.prev_token.span == DUMMY_SP {
260264
// Account for macro context where the previous span might not be
@@ -282,6 +286,29 @@ impl<'a> Parser<'a> {
282286
Err(err)
283287
}
284288

289+
fn check_too_many_raw_str_terminators(&mut self, err: &mut DiagnosticBuilder<'_>) -> bool {
290+
match (&self.prev_token.kind, &self.token.kind) {
291+
(
292+
TokenKind::Literal(Lit {
293+
kind: LitKind::StrRaw(n_hashes) | LitKind::ByteStrRaw(n_hashes),
294+
..
295+
}),
296+
TokenKind::Pound,
297+
) => {
298+
err.set_primary_message("too many `#` when terminating raw string");
299+
err.span_suggestion(
300+
self.token.span,
301+
"remove the extra `#`",
302+
String::new(),
303+
Applicability::MachineApplicable,
304+
);
305+
err.note(&format!("the raw string started with {} `#`s", n_hashes));
306+
true
307+
}
308+
_ => false,
309+
}
310+
}
311+
285312
pub fn maybe_annotate_with_ascription(
286313
&mut self,
287314
err: &mut DiagnosticBuilder<'_>,
@@ -491,7 +518,7 @@ impl<'a> Parser<'a> {
491518
.unwrap_or_else(|_| pprust::expr_to_string(&e))
492519
};
493520
err.span_suggestion_verbose(
494-
inner_op.span.shrink_to_hi(),
521+
inner_op.span.shrink_to_hi(),
495522
"split the comparison into two",
496523
format!(" && {}", expr_to_str(&r1)),
497524
Applicability::MaybeIncorrect,
@@ -1086,7 +1113,7 @@ impl<'a> Parser<'a> {
10861113
self.look_ahead(2, |t| t.is_ident())
10871114
|| self.look_ahead(1, |t| t == &token::ModSep)
10881115
&& (self.look_ahead(2, |t| t.is_ident()) || // `foo:bar::baz`
1089-
self.look_ahead(2, |t| t == &token::Lt)) // `foo:bar::<baz>`
1116+
self.look_ahead(2, |t| t == &token::Lt)) // `foo:bar::<baz>`
10901117
}
10911118

10921119
pub(super) fn recover_seq_parse_error(

‎src/test/ui/parser/raw-str-unbalanced.rs

Lines changed: 0 additions & 4 deletions
This file was deleted.

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

Lines changed: 0 additions & 8 deletions
This file was deleted.

‎src/test/ui/parser/raw-byte-string-eof.stderr renamed to ‎src/test/ui/parser/raw/raw-byte-string-eof.stderr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ error[E0748]: unterminated raw string
22
--> $DIR/raw-byte-string-eof.rs:2:5
33
|
44
LL | br##"a"#;
5-
| ^ unterminated raw string
5+
| ^ - help: consider terminating the string here: `##`
6+
| |
7+
| unterminated raw string
68
|
79
= note: this raw string should be terminated with `"##`
810

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// check-pass
2+
3+
macro_rules! m1 {
4+
($tt:tt #) => ()
5+
}
6+
7+
macro_rules! m2 {
8+
($tt:tt) => ()
9+
}
10+
11+
fn main() {
12+
m1!(r#"abc"##);
13+
m2!(r#"abc"#);
14+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
static s: &'static str =
2+
r#"
3+
"## //~ too many `#` when terminating raw string
4+
;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: too many `#` when terminating raw string
2+
--> $DIR/raw-str-unbalanced.rs:3:9
3+
|
4+
LL | "##
5+
| ^ help: remove the extra `#`
6+
|
7+
= note: the raw string started with 1 `#`s
8+
9+
error: aborting due to previous error
10+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn main() {
2+
let x = r###"here's a long string"# "# "##;
3+
//~^ ERROR unterminated raw string
4+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0748]: unterminated raw string
2+
--> $DIR/raw-string-2.rs:2:13
3+
|
4+
LL | let x = r###"here's a long string"# "# "##;
5+
| ^ unterminated raw string -- help: consider terminating the string here: `###`
6+
|
7+
= note: this raw string should be terminated with `"###`
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0748`.

‎src/test/ui/parser/raw/raw_string.stderr renamed to ‎src/test/ui/parser/raw/raw-string.stderr

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
error[E0748]: unterminated raw string
2-
--> $DIR/raw_string.rs:2:13
2+
--> $DIR/raw-string.rs:2:13
33
|
44
LL | let x = r##"lol"#;
5-
| ^ unterminated raw string
5+
| ^ - help: consider terminating the string here: `##`
6+
| |
7+
| unterminated raw string
68
|
79
= note: this raw string should be terminated with `"##`
810

0 commit comments

Comments
 (0)
Please sign in to comment.