Skip to content

Commit

Permalink
Improve heuristics for format_args literal being suggestable
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Noratrieb committed Mar 14, 2023
1 parent 7291853 commit 427aceb
Showing 1 changed file with 35 additions and 1 deletion.
36 changes: 35 additions & 1 deletion compiler/rustc_parse_format/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// We want to be able to build this crate with a stable compiler, so no
// `#![feature]` attributes should be added.

use rustc_lexer::unescape;
pub use Alignment::*;
pub use Count::*;
pub use Piece::*;
Expand Down Expand Up @@ -324,7 +325,7 @@ impl<'a> Parser<'a> {
append_newline: bool,
mode: ParseMode,
) -> Parser<'a> {
let input_string_kind = find_width_map_from_snippet(snippet, style);
let input_string_kind = find_width_map_from_snippet(s, snippet, style);
let (width_map, is_source_literal) = match input_string_kind {
InputStringKind::Literal { width_mappings } => (width_mappings, true),
InputStringKind::NotALiteral => (Vec::new(), false),
Expand Down Expand Up @@ -892,6 +893,7 @@ impl<'a> Parser<'a> {
/// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
/// in order to properly synthesise the intra-string `Span`s for error diagnostics.
fn find_width_map_from_snippet(
input: &str,
snippet: Option<string::String>,
str_style: Option<usize>,
) -> InputStringKind {
Expand All @@ -904,8 +906,27 @@ fn find_width_map_from_snippet(
return InputStringKind::Literal { width_mappings: Vec::new() };
}

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

// Macros like `println` add a newline at the end. That technically doens't make them "literals" anymore, but it's fine
// since we will never need to point our spans there, so we lie about it here by ignoring it.
// Since there might actually be newlines in the source code, we need to normalize away all trailing newlines.
// If we only trimmed it off the input, `format!("\n")` would cause a mismatch as here we they actually match up.
// Alternatively, we could just count the trailing newlines and only trim one from the input if they don't match up.
let input_no_nl = input.trim_end_matches('\n');
let Some(unescaped) = unescape_string(snippet) else {
return InputStringKind::NotALiteral;
};

let unescaped_no_nl = unescaped.trim_end_matches('\n');

if unescaped_no_nl != input_no_nl {
// The source string that we're pointing at isn't our input, so spans pointing at it will be incorrect.
// This can for example happen with proc macros that respan generated literals.
return InputStringKind::NotALiteral;
}

let mut s = snippet.char_indices();
let mut width_mappings = vec![];
while let Some((pos, c)) = s.next() {
Expand Down Expand Up @@ -988,6 +1009,19 @@ fn find_width_map_from_snippet(
InputStringKind::Literal { width_mappings }
}

fn unescape_string(string: &str) -> Option<string::String> {
let mut buf = string::String::new();
let mut ok = true;
unescape::unescape_literal(string, unescape::Mode::Str, &mut |_, unescaped_char| {
match unescaped_char {
Ok(c) => buf.push(c),
Err(_) => ok = false,
}
});

ok.then_some(buf)
}

// Assert a reasonable size for `Piece`
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(Piece<'_>, 16);
Expand Down

0 comments on commit 427aceb

Please sign in to comment.