Repository: rust-lang/rust
Pull request:  Fix invalid add of duplicated call locations for the rustdoc scraped examples feature

Fixes rust-lang/rust#153837.

The bug was visible in the highlighting of the scrape examples code samples: the same part of the code was marked as "highlight" (by the scraped examples feature) twice, eventually leading to a wrong state of the HTML DOM status.

To fix the bug, I added a set to not re-add a location if it's already present.

r? @Urgau 

Changed files:
filename: src/librustdoc/scrape_examples.rs
patch: @@ -3,7 +3,7 @@
 use std::fs;
 use std::path::PathBuf;
 
-use rustc_data_structures::fx::FxIndexMap;
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
 use rustc_errors::DiagCtxtHandle;
 use rustc_hir as hir;
 use rustc_hir::intravisit::{self, Visitor};
@@ -15,7 +15,7 @@ use rustc_serialize::{Decodable, Encodable};
 use rustc_session::getopts;
 use rustc_span::def_id::{CrateNum, DefPathHash, LOCAL_CRATE};
 use rustc_span::edition::Edition;
-use rustc_span::{BytePos, FileName, SourceFile};
+use rustc_span::{BytePos, FileName, SourceFile, Span};
 use tracing::{debug, trace, warn};
 
 use crate::html::render::Context;
@@ -114,6 +114,7 @@ struct FindCalls<'a, 'tcx> {
     target_crates: Vec<CrateNum>,
     calls: &'a mut AllCallLocations,
     bin_crate: bool,
+    call_ident_spans: FxHashSet<Span>,
 }
 
 impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx>
@@ -165,6 +166,10 @@ where
             }
         };
 
+        if !self.call_ident_spans.insert(ident_span) {
+            return;
+        }
+
         // If this span comes from a macro expansion, then the source code may not actually show
         // a use of the given item, so it would be a poor example. Hence, we skip all uses in
         // macros.
@@ -300,7 +305,13 @@ pub(crate) fn run(
 
         // Run call-finder on all items
         let mut calls = FxIndexMap::default();
-        let mut finder = FindCalls { calls: &mut calls, cx, target_crates, bin_crate };
+        let mut finder = FindCalls {
+            calls: &mut calls,
+            cx,
+            target_crates,
+            bin_crate,
+            call_ident_spans: FxHashSet::default(),
+        };
         tcx.hir_visit_all_item_likes_in_crate(&mut finder);
 
         // The visitor might have found a type error, which we need to

filename: tests/run-make-cargo/rustdoc-scrape-examples-duplicated-calls/Cargo.toml
patch: @@ -0,0 +1,10 @@
+[workspace]
+
+[package]
+edition = "2024"
+name = "tester"
+version = "0.1.0"
+
+[[example]]
+doc-scrape-examples = true
+name = "window"

filename: tests/run-make-cargo/rustdoc-scrape-examples-duplicated-calls/examples/window.rs
patch: @@ -0,0 +1,24 @@
+#![allow(dead_code)]
+use tester::Window;
+
+macro_rules! info {
+    ($s:literal, $x:expr) => {{
+        let _ = $x;
+    }};
+}
+
+struct WindowState {
+    window: Window,
+}
+
+impl WindowState {
+    fn takes_ref(&self) {
+        info!("{:?}", self.window.id());
+    }
+
+    fn takes_mut(&mut self) {
+        info!("{:?}", self.window.id());
+    }
+}
+
+fn main() {}

filename: tests/run-make-cargo/rustdoc-scrape-examples-duplicated-calls/rmake.rs
patch: @@ -0,0 +1,11 @@
+//! This test ensures that the call locations are not duplicated when generating scraped examples.
+//! To ensure that, we check that this call doesn't fail.
+//! Regression test for <https://github.com/rust-lang/rust/issues/153837>.
+
+use run_make_support::{cargo, htmldocck};
+
+fn main() {
+    cargo().args(["rustdoc", "-Zunstable-options", "-Zrustdoc-scrape-examples"]).run();
+
+    htmldocck().arg("target/doc").arg("src/lib.rs").run();
+}

filename: tests/run-make-cargo/rustdoc-scrape-examples-duplicated-calls/src/lib.rs
patch: @@ -0,0 +1,13 @@
+//@has tester/struct.Window.html
+//@count - '//*[@class="docblock scraped-example-list"]//span[@class="highlight"]' 1
+//@has - '//*[@class="docblock scraped-example-list"]//span[@class="highlight"]' 'id'
+//@count - '//*[@class="docblock scraped-example-list"]//span[@class="highlight focus"]' 1
+//@has - '//*[@class="docblock scraped-example-list"]//span[@class="highlight focus"]' 'id'
+
+pub struct Window {}
+
+impl Window {
+    pub fn id(&self) -> u64 {
+        todo!()
+    }
+}