Repository: rust-lang/annotate-snippets-rs
Pull request #215 (2025-06-12 02:19:14)

fix: Match rustc's multiline reordering
Note: This is another change related to matching rustc's output



Patch:
diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs
index d42dccc..eca057e 100644
--- a/src/renderer/source_map.rs
+++ b/src/renderer/source_map.rs
@@ -194,8 +194,7 @@ impl<'a> SourceMap<'a> {
         let mut primary_spans = vec![];
 
         // Find overlapping multiline annotations, put them at different depths
-        multiline_annotations
-            .sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line, ml.start.byte));
+        multiline_annotations.sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line));
         for ann in multiline_annotations.clone() {
             if ann.kind.is_primary() {
                 primary_spans.push((ann.start, ann.end));


Test patch:
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 7e25a01..bf99853 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -887,15 +887,15 @@ bar = { version = "0.1.0", optional = true }
 error: unused optional dependency
   |
 4 |    bar = { version = "0.1.0", optional = true }
-  |   _________^__________________--------------^
-  |  |         |                  |
-  |  |_________|                  This should also be long but not too long
+  |  __________^__________________--------------^
+  | |          |                  |
+  | | _________|                  This should also be long but not too long
   | ||
 5 | || this is another line
 6 | || so is this
 7 | || bar = { version = "0.1.0", optional = true }
   | ||_________________________^________________^ I need this to be really long so I can test overlaps
-  | |__________________________|
+  |  |_________________________|
   |                            I need this to be really long so I can test overlaps
 "#]];
     let renderer = Renderer::plain();
@@ -940,16 +940,16 @@ this is another line
 error: unused optional dependency
   |
 4 |     bar = { version = "0.1.0", optional = true }
-  |   __________^__________________--------------^
-  |  |          |                  |
-  |  |__________|                  This should also be long but not too long
+  |  ___________^__________________--------------^
+  | |           |                  |
+  | | __________|                  This should also be long but not too long
   | ||
 5 | ||  this is another line
   | || ____^
 6 | ||| so is this
 7 | ||| bar = { version = "0.1.0", optional = true }
   | |||_________________________^________________^ I need this to be really long so I can test overlaps
-  | |_|_________________________|
+  |  ||_________________________|
   |   |                         I need this to be really long so I can test overlaps
 8 |   | this is another line
   |   |____^ I need this to be really long so I can test overlaps
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 642c84e..cf33969 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -2,7 +2,7 @@
 //!
 //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs
 
-use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet};
+use annotate_snippets::{AnnotationKind, Group, Level, Origin, Patch, Renderer, Snippet};
 
 use annotate_snippets::renderer::OutputTheme;
 use snapbox::{assert_data_eq, str};
@@ -2020,3 +2020,94 @@ LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...0, 0, 0, 0, 0, 0, 0, 0, 0
         .term_width(120);
     assert_data_eq!(renderer.render(input), expected);
 }
+
+#[test]
+fn lint_map_unit_fn() {
+    // tests/ui/lint/lint_map_unit_fn.rs
+    let source = r#"#![deny(map_unit_fn)]
+
+fn foo(items: &mut Vec<u8>) {
+    items.sort();
+}
+
+fn main() {
+    let mut x: Vec<Vec<u8>> = vec![vec![0, 2, 1], vec![5, 4, 3]];
+    x.iter_mut().map(foo);
+    //~^ ERROR `Iterator::map` call that discard the iterator's values
+    x.iter_mut().map(|items| {
+    //~^ ERROR `Iterator::map` call that discard the iterator's values
+        items.sort();
+    });
+    let f = |items: &mut Vec<u8>| {
+        items.sort();
+    };
+    x.iter_mut().map(f);
+    //~^ ERROR `Iterator::map` call that discard the iterator's values
+}
+"#;
+
+    let input = Level::ERROR
+        .header("`Iterator::map` call that discard the iterator's values")
+        .group(
+            Group::new()
+                .element(
+                    Snippet::source(source)
+                        .origin("$DIR/lint_map_unit_fn.rs")
+                        .fold(true)
+                        .annotation(AnnotationKind::Context.span(271..278).label(
+                            "this function returns `()`, which is likely not what you wanted",
+                        ))
+                        .annotation(
+                            AnnotationKind::Context
+                                .span(271..379)
+                                .label("called `Iterator::map` with callable that returns `()`"),
+                        )
+                        .annotation(
+                            AnnotationKind::Context
+                                .span(267..380)
+                                .label("after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items")
+                        )
+                        .annotation(AnnotationKind::Primary.span(267..380)),
+                )
+                .element(
+                    Level::NOTE.title("`Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated")),
+        )
+        .group(
+            Group::new()
+                .element(Level::HELP.title("you might have meant to use `Iterator::for_each`"))
+                .element(
+                    Snippet::source(source)
+                        .origin("$DIR/lint_map_unit_fn.rs")
+                        .fold(true)
+                        .patch(Patch::new(267..270, r#"for_each"#)),
+                ),
+        );
+
+    let expected = str![[r#"
+error: `Iterator::map` call that discard the iterator's values
+  --> $DIR/lint_map_unit_fn.rs:11:18
+   |
+LL |         x.iter_mut().map(|items| {
+   |                      ^   -------
+   |                      |   |
+   |  ____________________|___this function returns `()`, which is likely not what you wanted
+   | |  __________________|
+   | | |
+LL | | |     //~^ ERROR `Iterator::map` call that discard the iterator's values
+LL | | |         items.sort();
+LL | | |     });
+   | | |     -^ after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items
+   | | |_____||
+   | |_______|
+   |         called `Iterator::map` with callable that returns `()`
+   |
+   = note: `Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated
+help: you might have meant to use `Iterator::for_each`
+   |
+LL -     x.iter_mut().map(|items| {
+LL +     x.iter_mut().for_each(|items| {
+   |
+"#]];
+    let renderer = Renderer::plain().anonymized_line_numbers(true);
+    assert_data_eq!(renderer.render(input), expected);
+}