-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Handle str literals written with '
lexed as lifetime
#122217
Changes from all commits
982918f
4a10b01
999a0dc
6f388ef
ea1883d
f4d30b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,7 @@ pub(crate) fn parse_token_trees<'psess, 'src>( | |
cursor, | ||
override_span, | ||
nbsp_is_whitespace: false, | ||
last_lifetime: None, | ||
}; | ||
let (stream, res, unmatched_delims) = | ||
tokentrees::TokenTreesReader::parse_all_token_trees(string_reader); | ||
|
@@ -105,6 +106,10 @@ struct StringReader<'psess, 'src> { | |
/// in this file, it's safe to treat further occurrences of the non-breaking | ||
/// space character as whitespace. | ||
nbsp_is_whitespace: bool, | ||
|
||
/// Track the `Span` for the leading `'` of the last lifetime. Used for | ||
/// diagnostics to detect possible typo where `"` was meant. | ||
last_lifetime: Option<Span>, | ||
} | ||
|
||
impl<'psess, 'src> StringReader<'psess, 'src> { | ||
|
@@ -130,6 +135,18 @@ impl<'psess, 'src> StringReader<'psess, 'src> { | |
|
||
debug!("next_token: {:?}({:?})", token.kind, self.str_from(start)); | ||
|
||
if let rustc_lexer::TokenKind::Semi | ||
| rustc_lexer::TokenKind::LineComment { .. } | ||
| rustc_lexer::TokenKind::BlockComment { .. } | ||
| rustc_lexer::TokenKind::CloseParen | ||
| rustc_lexer::TokenKind::CloseBrace | ||
| rustc_lexer::TokenKind::CloseBracket = token.kind | ||
{ | ||
// Heuristic: we assume that it is unlikely we're dealing with an unterminated | ||
// string surrounded by single quotes. | ||
self.last_lifetime = None; | ||
} | ||
|
||
// Now "cook" the token, converting the simple `rustc_lexer::TokenKind` enum into a | ||
// rich `rustc_ast::TokenKind`. This turns strings into interned symbols and runs | ||
// additional validation. | ||
|
@@ -247,6 +264,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> { | |
// expansion purposes. See #12512 for the gory details of why | ||
// this is necessary. | ||
let lifetime_name = self.str_from(start); | ||
self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1))); | ||
if starts_with_number { | ||
let span = self.mk_sp(start, self.pos); | ||
self.dcx().struct_err("lifetimes cannot start with a number") | ||
|
@@ -395,10 +413,21 @@ impl<'psess, 'src> StringReader<'psess, 'src> { | |
match kind { | ||
rustc_lexer::LiteralKind::Char { terminated } => { | ||
if !terminated { | ||
self.dcx() | ||
let mut err = self | ||
.dcx() | ||
.struct_span_fatal(self.mk_sp(start, end), "unterminated character literal") | ||
.with_code(E0762) | ||
.emit() | ||
.with_code(E0762); | ||
if let Some(lt_sp) = self.last_lifetime { | ||
err.multipart_suggestion( | ||
"if you meant to write a string literal, use double quotes", | ||
vec![ | ||
(lt_sp, "\"".to_string()), | ||
(self.mk_sp(start, start + BytePos(1)), "\"".to_string()), | ||
], | ||
Applicability::MaybeIncorrect, | ||
); | ||
} | ||
err.emit() | ||
} | ||
self.cook_unicode(token::Char, Mode::Char, start, end, 1, 1) // ' ' | ||
} | ||
|
@@ -669,15 +698,33 @@ impl<'psess, 'src> StringReader<'psess, 'src> { | |
let expn_data = prefix_span.ctxt().outer_expn_data(); | ||
|
||
if expn_data.edition >= Edition::Edition2021 { | ||
let mut silence = false; | ||
// In Rust 2021, this is a hard error. | ||
let sugg = if prefix == "rb" { | ||
Some(errors::UnknownPrefixSugg::UseBr(prefix_span)) | ||
} else if expn_data.is_root() { | ||
Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi())) | ||
if self.cursor.first() == '\'' | ||
&& let Some(start) = self.last_lifetime | ||
&& self.cursor.third() != '\'' | ||
{ | ||
// An "unclosed `char`" error will be emitted already, silence redundant error. | ||
silence = true; | ||
Some(errors::UnknownPrefixSugg::MeantStr { | ||
start, | ||
end: self.mk_sp(self.pos, self.pos + BytePos(1)), | ||
}) | ||
} else { | ||
Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi())) | ||
} | ||
} else { | ||
None | ||
}; | ||
self.dcx().emit_err(errors::UnknownPrefix { span: prefix_span, prefix, sugg }); | ||
let err = errors::UnknownPrefix { span: prefix_span, prefix, sugg }; | ||
if silence { | ||
self.dcx().create_err(err).delay_as_bug(); | ||
} else { | ||
self.dcx().emit_err(err); | ||
} | ||
Comment on lines
+722
to
+727
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would probably structure it a bit differently, namely |
||
} else { | ||
// Before Rust 2021, only emit a lint for migration. | ||
self.psess.buffer_lint_with_diagnostic( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,11 +95,21 @@ pub(crate) fn emit_unescape_error( | |
} | ||
escaped.push(c); | ||
} | ||
let sugg = format!("{prefix}\"{escaped}\""); | ||
MoreThanOneCharSugg::Quotes { | ||
span: full_lit_span, | ||
is_byte: mode == Mode::Byte, | ||
sugg, | ||
if escaped.len() != lit.len() || full_lit_span.is_empty() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that this prevents the negative overflow & that makes sense but why wasn't this an issue before? Don't have the time to dig deep into the code myself. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before we were replacing the full span with the new string (instead of doing |
||
let sugg = format!("{prefix}\"{escaped}\""); | ||
MoreThanOneCharSugg::QuotesFull { | ||
span: full_lit_span, | ||
is_byte: mode == Mode::Byte, | ||
sugg, | ||
} | ||
} else { | ||
MoreThanOneCharSugg::Quotes { | ||
start: full_lit_span | ||
.with_hi(full_lit_span.lo() + BytePos((prefix.len() + 1) as u32)), | ||
end: full_lit_span.with_lo(full_lit_span.hi() - BytePos(1)), | ||
is_byte: mode == Mode::Byte, | ||
prefix, | ||
} | ||
} | ||
}); | ||
dcx.emit_err(UnescapeError::MoreThanOneChar { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since
second
andthird
are exclusively used in the error path if I'm not mistaken (haven't double-checked), perf shouldn't matter that much and we can maybe avoid introducing those helpers? Idk, do we have access toself.chars
in the parser? If so, we could just use.clone().nth(N)
, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.chars
is private to the lexer. I initially made it public, but given it is used only in one place it felt better to introduce the method. But then we could instead just have annth
method that expands to.clone().nth(N)
instead.