Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 8dee9bc

Browse files
committed
Reserve prefixed identifiers and string literals (RFC 3101)
This commit denies any identifiers immediately followed by one of three tokens `"`, `'` or `#`, which is stricter than the requirements of RFC 3101 but may be necessary according to the discussion at [Zulip]. [Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/268952-edition-2021/topic/reserved.20prefixes/near/238470099
1 parent 831ae3c commit 8dee9bc

File tree

5 files changed

+172
-7
lines changed

5 files changed

+172
-7
lines changed

compiler/rustc_lexer/src/lib.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ pub enum TokenKind {
6666
Ident,
6767
/// "r#ident"
6868
RawIdent,
69+
/// `foo#`, `foo'`, `foo"`. Note the tailer is not included.
70+
BadPrefix,
6971
/// "12_u8", "1.0e-40", "b"123"". See `LiteralKind` for more details.
7072
Literal { kind: LiteralKind, suffix_start: usize },
7173
/// "'a"
@@ -323,7 +325,7 @@ impl Cursor<'_> {
323325
let kind = RawStr { n_hashes, err };
324326
Literal { kind, suffix_start }
325327
}
326-
_ => self.ident(),
328+
_ => self.ident_or_bad_prefix(),
327329
},
328330

329331
// Byte literal, byte string literal, raw byte string literal or identifier.
@@ -358,12 +360,12 @@ impl Cursor<'_> {
358360
let kind = RawByteStr { n_hashes, err };
359361
Literal { kind, suffix_start }
360362
}
361-
_ => self.ident(),
363+
_ => self.ident_or_bad_prefix(),
362364
},
363365

364366
// Identifier (this should be checked after other variant that can
365367
// start as identifier).
366-
c if is_id_start(c) => self.ident(),
368+
c if is_id_start(c) => self.ident_or_bad_prefix(),
367369

368370
// Numeric literal.
369371
c @ '0'..='9' => {
@@ -487,11 +489,16 @@ impl Cursor<'_> {
487489
RawIdent
488490
}
489491

490-
fn ident(&mut self) -> TokenKind {
492+
fn ident_or_bad_prefix(&mut self) -> TokenKind {
491493
debug_assert!(is_id_start(self.prev()));
492494
// Start is already eaten, eat the rest of identifier.
493495
self.eat_while(is_id_continue);
494-
Ident
496+
// Good prefixes must have been handled eariler. So if
497+
// we see a prefix here, it is definitely a bad prefix.
498+
match self.first() {
499+
'#' | '"' | '\'' => BadPrefix,
500+
_ => Ident,
501+
}
495502
}
496503

497504
fn number(&mut self, first_digit: char) -> LiteralKind {

compiler/rustc_parse/src/lexer/mod.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_errors::{error_code, Applicability, DiagnosticBuilder, FatalError, PRe
55
use rustc_lexer::unescape::{self, Mode};
66
use rustc_lexer::{Base, DocStyle, RawStrError};
77
use rustc_session::parse::ParseSess;
8+
use rustc_span::edition::Edition;
89
use rustc_span::symbol::{sym, Symbol};
910
use rustc_span::{BytePos, Pos, Span};
1011

@@ -166,12 +167,18 @@ impl<'a> StringReader<'a> {
166167
self.cook_doc_comment(content_start, content, CommentKind::Block, doc_style)
167168
}
168169
rustc_lexer::TokenKind::Whitespace => return None,
169-
rustc_lexer::TokenKind::Ident | rustc_lexer::TokenKind::RawIdent => {
170+
rustc_lexer::TokenKind::Ident
171+
| rustc_lexer::TokenKind::RawIdent
172+
| rustc_lexer::TokenKind::BadPrefix => {
170173
let is_raw_ident = token == rustc_lexer::TokenKind::RawIdent;
174+
let is_bad_prefix = token == rustc_lexer::TokenKind::BadPrefix;
171175
let mut ident_start = start;
172176
if is_raw_ident {
173177
ident_start = ident_start + BytePos(2);
174178
}
179+
if is_bad_prefix {
180+
self.report_reserved_prefix(start);
181+
}
175182
let sym = nfc_normalize(self.str_from(ident_start));
176183
let span = self.mk_sp(start, self.pos);
177184
self.sess.symbol_gallery.insert(sym, span);
@@ -491,6 +498,29 @@ impl<'a> StringReader<'a> {
491498
FatalError.raise()
492499
}
493500

501+
fn report_reserved_prefix(&self, start: BytePos) {
502+
// See RFC 3101.
503+
if self.sess.edition < Edition::Edition2021 {
504+
return;
505+
}
506+
507+
let mut err = self.sess.span_diagnostic.struct_span_err(
508+
self.mk_sp(start, self.pos),
509+
&format!("prefix `{}` is unknown", self.str_from_to(start, self.pos)),
510+
);
511+
err.span_label(self.mk_sp(start, self.pos), "unknown prefix");
512+
err.span_label(
513+
self.mk_sp(self.pos, self.pos),
514+
&format!(
515+
"help: consider inserting a whitespace before this `{}`",
516+
self.str_from_to(self.pos, self.pos + BytePos(1)),
517+
),
518+
);
519+
err.note("prefixed identifiers and string literals are reserved since Rust 2021");
520+
521+
err.emit();
522+
}
523+
494524
/// Note: It was decided to not add a test case, because it would be too big.
495525
/// <https://github.com/rust-lang/rust/pull/50296#issuecomment-392135180>
496526
fn report_too_many_hashes(&self, start: BytePos, found: usize) -> ! {

src/librustdoc/html/highlight.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ impl<'a> Classifier<'a> {
413413
},
414414
c => c,
415415
},
416-
TokenKind::RawIdent => Class::Ident,
416+
TokenKind::RawIdent | TokenKind::BadPrefix => Class::Ident,
417417
TokenKind::Lifetime { .. } => Class::Lifetime,
418418
};
419419
// Anything that didn't return above is the simple case where we the
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// compile-flags: -Z unstable-options --edition 2021
2+
3+
macro_rules! demo2 {
4+
( $a:tt $b:tt ) => { println!("two tokens") };
5+
}
6+
7+
macro_rules! demo3 {
8+
( $a:tt $b:tt $c:tt ) => { println!("three tokens") };
9+
}
10+
11+
macro_rules! demo4 {
12+
( $a:tt $b:tt $c:tt $d:tt ) => { println!("four tokens") };
13+
}
14+
15+
fn main() {
16+
demo3!(foo#bar); //~ ERROR prefix `foo` is unknown
17+
demo2!(foo"bar"); //~ ERROR prefix `foo` is unknown
18+
demo2!(foo'b'); //~ ERROR prefix `foo` is unknown
19+
20+
demo2!(foo'b); //~ ERROR prefix `foo` is unknown
21+
demo3!(foo# bar); //~ ERROR prefix `foo` is unknown
22+
demo4!(foo#! bar); //~ ERROR prefix `foo` is unknown
23+
demo4!(foo## bar); //~ ERROR prefix `foo` is unknown
24+
25+
demo4!(foo#bar#);
26+
//~^ ERROR prefix `foo` is unknown
27+
//~| ERROR prefix `bar` is unknown
28+
29+
demo3!(foo # bar);
30+
demo3!(foo #bar);
31+
demo4!(foo!#bar);
32+
demo4!(foo ##bar);
33+
34+
demo3!(r"foo"#bar);
35+
demo3!(r#foo#bar);
36+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
error: prefix `foo` is unknown
2+
--> $DIR/reserved-prefixes.rs:16:12
3+
|
4+
LL | demo3!(foo#bar);
5+
| ^^^- help: consider inserting a whitespace before this `#`
6+
| |
7+
| unknown prefix
8+
|
9+
= note: prefixed identifiers and string literals are reserved since Rust 2021
10+
11+
error: prefix `foo` is unknown
12+
--> $DIR/reserved-prefixes.rs:17:12
13+
|
14+
LL | demo2!(foo"bar");
15+
| ^^^- help: consider inserting a whitespace before this `"`
16+
| |
17+
| unknown prefix
18+
|
19+
= note: prefixed identifiers and string literals are reserved since Rust 2021
20+
21+
error: prefix `foo` is unknown
22+
--> $DIR/reserved-prefixes.rs:18:12
23+
|
24+
LL | demo2!(foo'b');
25+
| ^^^- help: consider inserting a whitespace before this `'`
26+
| |
27+
| unknown prefix
28+
|
29+
= note: prefixed identifiers and string literals are reserved since Rust 2021
30+
31+
error: prefix `foo` is unknown
32+
--> $DIR/reserved-prefixes.rs:20:12
33+
|
34+
LL | demo2!(foo'b);
35+
| ^^^- help: consider inserting a whitespace before this `'`
36+
| |
37+
| unknown prefix
38+
|
39+
= note: prefixed identifiers and string literals are reserved since Rust 2021
40+
41+
error: prefix `foo` is unknown
42+
--> $DIR/reserved-prefixes.rs:21:12
43+
|
44+
LL | demo3!(foo# bar);
45+
| ^^^- help: consider inserting a whitespace before this `#`
46+
| |
47+
| unknown prefix
48+
|
49+
= note: prefixed identifiers and string literals are reserved since Rust 2021
50+
51+
error: prefix `foo` is unknown
52+
--> $DIR/reserved-prefixes.rs:22:12
53+
|
54+
LL | demo4!(foo#! bar);
55+
| ^^^- help: consider inserting a whitespace before this `#`
56+
| |
57+
| unknown prefix
58+
|
59+
= note: prefixed identifiers and string literals are reserved since Rust 2021
60+
61+
error: prefix `foo` is unknown
62+
--> $DIR/reserved-prefixes.rs:23:12
63+
|
64+
LL | demo4!(foo## bar);
65+
| ^^^- help: consider inserting a whitespace before this `#`
66+
| |
67+
| unknown prefix
68+
|
69+
= note: prefixed identifiers and string literals are reserved since Rust 2021
70+
71+
error: prefix `foo` is unknown
72+
--> $DIR/reserved-prefixes.rs:25:12
73+
|
74+
LL | demo4!(foo#bar#);
75+
| ^^^- help: consider inserting a whitespace before this `#`
76+
| |
77+
| unknown prefix
78+
|
79+
= note: prefixed identifiers and string literals are reserved since Rust 2021
80+
81+
error: prefix `bar` is unknown
82+
--> $DIR/reserved-prefixes.rs:25:16
83+
|
84+
LL | demo4!(foo#bar#);
85+
| ^^^- help: consider inserting a whitespace before this `#`
86+
| |
87+
| unknown prefix
88+
|
89+
= note: prefixed identifiers and string literals are reserved since Rust 2021
90+
91+
error: aborting due to 9 previous errors
92+

0 commit comments

Comments
 (0)