diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 977721a5b8a2d..f938352820df7 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -2216,6 +2216,11 @@ impl HumanEmitter { show_code_change { for part in parts { + let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) { + snippet + } else { + String::new() + }; let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display; let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display; @@ -2263,13 +2268,80 @@ impl HumanEmitter { } if let DisplaySuggestion::Diff = show_code_change { // Colorize removal with red in diff format. - buffer.set_style_range( - row_num - 2, - (padding as isize + span_start_pos as isize) as usize, - (padding as isize + span_end_pos as isize) as usize, - Style::Removal, - true, - ); + + // Below, there's some tricky buffer indexing going on. `row_num` at this + // point corresponds to: + // + // | + // LL | CODE + // | ++++ <- `row_num` + // + // in the buffer. When we have a diff format output, we end up with + // + // | + // LL - OLDER <- row_num - 2 + // LL + NEWER + // | <- row_num + // + // The `row_num - 2` is to select the buffer line that has the "old version + // of the diff" at that point. When the removal is a single line, `i` is + // `0`, `newlines` is `1` so `(newlines - i - 1)` ends up being `0`, so row + // points at `LL - OLDER`. When the removal corresponds to multiple lines, + // we end up with `newlines > 1` and `i` being `0..newlines - 1`. + // + // | + // LL - OLDER <- row_num - 2 - (newlines - last_i - 1) + // LL - CODE + // LL - BEING + // LL - REMOVED <- row_num - 2 - (newlines - first_i - 1) + // LL + NEWER + // | <- row_num + + let newlines = snippet.lines().count(); + if newlines > 0 && row_num > newlines { + // Account for removals where the part being removed spans multiple + // lines. + // FIXME: We check the number of rows because in some cases, like in + // `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered + // suggestion will only show the first line of code being replaced. The + // proper way of doing this would be to change the suggestion rendering + // logic to show the whole prior snippet, but the current output is not + // too bad to begin with, so we side-step that issue here. + for (i, line) in snippet.lines().enumerate() { + let line = normalize_whitespace(line); + let row = row_num - 2 - (newlines - i - 1); + // On the first line, we highlight between the start of the part + // span, and the end of that line. + // On the last line, we highlight between the start of the line, and + // the column of the part span end. + // On all others, we highlight the whole line. + let start = if i == 0 { + (padding as isize + span_start_pos as isize) as usize + } else { + padding + }; + let end = if i == 0 { + (padding as isize + + span_start_pos as isize + + line.len() as isize) + as usize + } else if i == newlines - 1 { + (padding as isize + span_end_pos as isize) as usize + } else { + (padding as isize + line.len() as isize) as usize + }; + buffer.set_style_range(row, start, end, Style::Removal, true); + } + } else { + // The removed code fits all in one line. + buffer.set_style_range( + row_num - 2, + (padding as isize + span_start_pos as isize) as usize, + (padding as isize + span_end_pos as isize) as usize, + Style::Removal, + true, + ); + } } // length of the code after substitution diff --git a/tests/ui/error-emitter/multiline-removal-suggestion.rs b/tests/ui/error-emitter/multiline-removal-suggestion.rs new file mode 100644 index 0000000000000..72e9ea357c9e6 --- /dev/null +++ b/tests/ui/error-emitter/multiline-removal-suggestion.rs @@ -0,0 +1,58 @@ +// Make sure suggestion for removal of a span that covers multiple lines is properly highlighted. +//@ compile-flags: --error-format=human --color=always +//@ edition:2018 +//@ only-linux +// ignore-tidy-tab +// We use `\t` instead of spaces for indentation to ensure that the highlighting logic properly +// accounts for replaced characters (like we do for `\t` with ` `). The naïve way of highlighting +// could be counting chars of the original code, instead of operating on the code as it is being +// displayed. +use std::collections::{HashMap, HashSet}; +fn foo() -> Vec<(bool, HashSet)> { + let mut hm = HashMap::>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter() + .map(|t| { + ( + is_true, + t, + ) + }).flatten() + }) + .flatten() + .collect() +} +fn bar() -> Vec<(bool, HashSet)> { + let mut hm = HashMap::>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter() + .map(|t| (is_true, t)) + .flatten() + }) + .flatten() + .collect() +} +fn baz() -> Vec<(bool, HashSet)> { + let mut hm = HashMap::>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter().map(|t| { + (is_true, t) + }).flatten() + }) + .flatten() + .collect() +} +fn bay() -> Vec<(bool, HashSet)> { + let mut hm = HashMap::>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter() + .map(|t| (is_true, t)).flatten() + }) + .flatten() + .collect() +} +fn main() {} diff --git a/tests/ui/error-emitter/multiline-removal-suggestion.svg b/tests/ui/error-emitter/multiline-removal-suggestion.svg new file mode 100644 index 0000000000000..95c7740f6995d --- /dev/null +++ b/tests/ui/error-emitter/multiline-removal-suggestion.svg @@ -0,0 +1,504 @@ + + + + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:21:8 + + | + + LL | }).flatten() + + | ^^^^^^^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `flatten` + + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL + + help: consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds + + | + + LL - ts.into_iter() + + LL - .map(|t| { + + LL - ( + + LL - is_true, + + LL - t, + + LL - ) + + LL - }).flatten() + + LL + ts.into_iter().flatten() + + | + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:13:2 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter() + + LL | | .map(|t| { + + ... | + + LL | | }).flatten() + + LL | | }) + + | |__________^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `Flatten` + + --> $SRC_DIR/core/src/iter/adapters/flatten.rs:LL:COL + + + + error[E0599]: the method `collect` exists for struct `Flatten<Map<IntoIter<bool, Vec<HashSet<u8>>>, {closure@multiline-removal-suggestion.rs:14:8}>>`, but its trait bounds were not satisfied + + --> $DIR/multiline-removal-suggestion.rs:24:4 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter() + + LL | | .map(|t| { + + ... | + + LL | | .flatten() + + LL | | .collect() + + | | -^^^^^^^ method cannot be called due to unsatisfied trait bounds + + | |_________| + + | + + | + + = note: the following trait bounds were not satisfied: + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:16:10: 16:13}>> as IntoIterator>::IntoIter = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:14:8: 14:23}>>: Iterator` + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:16:10: 16:13}>> as IntoIterator>::Item = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:14:8: 14:23}>>: Iterator` + + `Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:16:10: 16:13}>>: IntoIterator` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:14:8: 14:23}>>: Iterator` + + `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:14:8: 14:23}>>: Iterator` + + which is required by `&mut Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:14:8: 14:23}>>: Iterator` + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:32:6 + + | + + LL | .flatten() + + | ^^^^^^^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `flatten` + + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL + + help: consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds + + | + + LL - ts.into_iter() + + LL - .map(|t| (is_true, t)) + + LL + ts.into_iter() + + | + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:28:2 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter() + + LL | | .map(|t| (is_true, t)) + + LL | | .flatten() + + LL | | }) + + | |__________^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `Flatten` + + --> $SRC_DIR/core/src/iter/adapters/flatten.rs:LL:COL + + + + error[E0599]: the method `collect` exists for struct `Flatten<Map<IntoIter<bool, Vec<HashSet<u8>>>, {closure@multiline-removal-suggestion.rs:29:8}>>`, but its trait bounds were not satisfied + + --> $DIR/multiline-removal-suggestion.rs:35:4 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter() + + LL | | .map(|t| (is_true, t)) + + ... | + + LL | | .flatten() + + LL | | .collect() + + | | -^^^^^^^ method cannot be called due to unsatisfied trait bounds + + | |_________| + + | + + | + + = note: the following trait bounds were not satisfied: + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:31:10: 31:13}>> as IntoIterator>::IntoIter = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:29:8: 29:23}>>: Iterator` + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:31:10: 31:13}>> as IntoIterator>::Item = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:29:8: 29:23}>>: Iterator` + + `Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:31:10: 31:13}>>: IntoIterator` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:29:8: 29:23}>>: Iterator` + + `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:29:8: 29:23}>>: Iterator` + + which is required by `&mut Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:29:8: 29:23}>>: Iterator` + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:43:7 + + | + + LL | }).flatten() + + | ^^^^^^^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `flatten` + + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL + + help: consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds + + | + + LL - ts.into_iter().map(|t| { + + LL - (is_true, t) + + LL - }).flatten() + + LL + ts.into_iter().flatten() + + | + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:39:2 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter().map(|t| { + + LL | | (is_true, t) + + LL | | }).flatten() + + LL | | }) + + | |__________^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `Flatten` + + --> $SRC_DIR/core/src/iter/adapters/flatten.rs:LL:COL + + + + error[E0599]: the method `collect` exists for struct `Flatten<Map<IntoIter<bool, Vec<HashSet<u8>>>, {closure@multiline-removal-suggestion.rs:40:8}>>`, but its trait bounds were not satisfied + + --> $DIR/multiline-removal-suggestion.rs:46:4 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter().map(|t| { + + LL | | (is_true, t) + + ... | + + LL | | .flatten() + + LL | | .collect() + + | | -^^^^^^^ method cannot be called due to unsatisfied trait bounds + + | |_________| + + | + + | + + = note: the following trait bounds were not satisfied: + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:41:23: 41:26}>> as IntoIterator>::IntoIter = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:40:8: 40:23}>>: Iterator` + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:41:23: 41:26}>> as IntoIterator>::Item = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:40:8: 40:23}>>: Iterator` + + `Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:41:23: 41:26}>>: IntoIterator` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:40:8: 40:23}>>: Iterator` + + `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:40:8: 40:23}>>: Iterator` + + which is required by `&mut Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:40:8: 40:23}>>: Iterator` + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:53:28 + + | + + LL | .map(|t| (is_true, t)).flatten() + + | ^^^^^^^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `flatten` + + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL + + help: consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds + + | + + LL - ts.into_iter() + + LL - .map(|t| (is_true, t)).flatten() + + LL + ts.into_iter().flatten() + + | + + + + error[E0277]: `(bool, HashSet<u8>)` is not an iterator + + --> $DIR/multiline-removal-suggestion.rs:50:2 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter() + + LL | | .map(|t| (is_true, t)).flatten() + + LL | | }) + + | |__________^ `(bool, HashSet<u8>)` is not an iterator + + | + + = help: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)` + + = note: required for `(bool, HashSet<u8>)` to implement `IntoIterator` + + note: required by a bound in `Flatten` + + --> $SRC_DIR/core/src/iter/adapters/flatten.rs:LL:COL + + + + error[E0599]: the method `collect` exists for struct `Flatten<Map<IntoIter<bool, Vec<HashSet<u8>>>, {closure@multiline-removal-suggestion.rs:51:8}>>`, but its trait bounds were not satisfied + + --> $DIR/multiline-removal-suggestion.rs:56:4 + + | + + LL | / hm.into_iter() + + LL | | .map(|(is_true, ts)| { + + LL | | ts.into_iter() + + LL | | .map(|t| (is_true, t)).flatten() + + LL | | }) + + LL | | .flatten() + + LL | | .collect() + + | | -^^^^^^^ method cannot be called due to unsatisfied trait bounds + + | |_________| + + | + + | + + = note: the following trait bounds were not satisfied: + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:53:10: 53:13}>> as IntoIterator>::IntoIter = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:51:8: 51:23}>>: Iterator` + + `<Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:53:10: 53:13}>> as IntoIterator>::Item = _` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:51:8: 51:23}>>: Iterator` + + `Flatten<Map<std::vec::IntoIter<HashSet<u8>>, {closure@$DIR/multiline-removal-suggestion.rs:53:10: 53:13}>>: IntoIterator` + + which is required by `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:51:8: 51:23}>>: Iterator` + + `Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:51:8: 51:23}>>: Iterator` + + which is required by `&mut Flatten<Map<std::collections::hash_map::IntoIter<bool, Vec<HashSet<u8>>>, {closure@$DIR/multiline-removal-suggestion.rs:51:8: 51:23}>>: Iterator` + + + + error: aborting due to 12 previous errors + + + + Some errors have detailed explanations: E0277, E0599. + + For more information about an error, try `rustc --explain E0277`. + + + + + +