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.
This commit is contained in:
Nilstrieb 2023-03-13 18:52:41 +00:00
parent 729185338f
commit 427aceb9d4

View File

@ -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::*;
@ -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),
@ -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 {
@ -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() {
@ -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);