Skip to content

Commit 427aceb

Browse files
committed
Improve heuristics for format_args literal being suggestable
Sometimes, we want to create subspans and point at code in the literal if possible. But this doesn't always make sense, sometimes the literal may come from macro expanded code and isn't actually there in the source. Then, we can't really make these suggestions. This now makes sure that the literal is actually there as we see it so that we will not run into ICEs on weird literal transformations.
1 parent 7291853 commit 427aceb

File tree

1 file changed

+35
-1
lines changed
  • compiler/rustc_parse_format/src

1 file changed

+35
-1
lines changed

compiler/rustc_parse_format/src/lib.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// We want to be able to build this crate with a stable compiler, so no
1515
// `#![feature]` attributes should be added.
1616

17+
use rustc_lexer::unescape;
1718
pub use Alignment::*;
1819
pub use Count::*;
1920
pub use Piece::*;
@@ -324,7 +325,7 @@ impl<'a> Parser<'a> {
324325
append_newline: bool,
325326
mode: ParseMode,
326327
) -> Parser<'a> {
327-
let input_string_kind = find_width_map_from_snippet(snippet, style);
328+
let input_string_kind = find_width_map_from_snippet(s, snippet, style);
328329
let (width_map, is_source_literal) = match input_string_kind {
329330
InputStringKind::Literal { width_mappings } => (width_mappings, true),
330331
InputStringKind::NotALiteral => (Vec::new(), false),
@@ -892,6 +893,7 @@ impl<'a> Parser<'a> {
892893
/// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
893894
/// in order to properly synthesise the intra-string `Span`s for error diagnostics.
894895
fn find_width_map_from_snippet(
896+
input: &str,
895897
snippet: Option<string::String>,
896898
str_style: Option<usize>,
897899
) -> InputStringKind {
@@ -904,8 +906,27 @@ fn find_width_map_from_snippet(
904906
return InputStringKind::Literal { width_mappings: Vec::new() };
905907
}
906908

909+
// Strip quotes.
907910
let snippet = &snippet[1..snippet.len() - 1];
908911

912+
// Macros like `println` add a newline at the end. That technically doens't make them "literals" anymore, but it's fine
913+
// since we will never need to point our spans there, so we lie about it here by ignoring it.
914+
// Since there might actually be newlines in the source code, we need to normalize away all trailing newlines.
915+
// If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up.
916+
// Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up.
917+
let input_no_nl = input.trim_end_matches('\n');
918+
let Some(unescaped) = unescape_string(snippet) else {
919+
return InputStringKind::NotALiteral;
920+
};
921+
922+
let unescaped_no_nl = unescaped.trim_end_matches('\n');
923+
924+
if unescaped_no_nl != input_no_nl {
925+
// The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect.
926+
// This can for example happen with proc macros that respan generated literals.
927+
return InputStringKind::NotALiteral;
928+
}
929+
909930
let mut s = snippet.char_indices();
910931
let mut width_mappings = vec![];
911932
while let Some((pos, c)) = s.next() {
@@ -988,6 +1009,19 @@ fn find_width_map_from_snippet(
9881009
InputStringKind::Literal { width_mappings }
9891010
}
9901011

1012+
fn unescape_string(string: &str) -> Option<string::String> {
1013+
let mut buf = string::String::new();
1014+
let mut ok = true;
1015+
unescape::unescape_literal(string, unescape::Mode::Str, &mut |_, unescaped_char| {
1016+
match unescaped_char {
1017+
Ok(c) => buf.push(c),
1018+
Err(_) => ok = false,
1019+
}
1020+
});
1021+
1022+
ok.then_some(buf)
1023+
}
1024+
9911025
// Assert a reasonable size for `Piece`
9921026
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
9931027
rustc_data_structures::static_assert_size!(Piece<'_>, 16);

0 commit comments

Comments
 (0)