about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-12-03 05:24:35 +0000
committerbors <bors@rust-lang.org>2022-12-03 05:24:35 +0000
commit24f2704e96c9d3a4f887d2d0a7d79ecffd79bd65 (patch)
tree1488e15adc00715025273fac5f332f3a7c3c0aae
parentdd7a12593a5274dd812a7b8ab91a4ff29adb81a2 (diff)
parentd8f6cc3b63a84095d9fccd605b53851c5f12219e (diff)
downloadrust-24f2704e96c9d3a4f887d2d0a7d79ecffd79bd65.tar.gz
rust-24f2704e96c9d3a4f887d2d0a7d79ecffd79bd65.zip
Auto merge of #105196 - JohnTitor:rollup-8rxqnq6, r=JohnTitor
Rollup of 7 pull requests

Successful merges:

 - #104903 (Use ocx.normalize in report_projection_error)
 - #105032 (improve doc of into_boxed_slice and impl From<Vec<T>> for Box<[T]>)
 - #105100 (Add missing intra-doc link)
 - #105181 (Don't add a note for implementing a trait if its inner type is erroneous)
 - #105182 (Rustdoc-Json: Don't inline foreign traits)
 - #105188 (Don't elide type information when printing E0308 with `-Zverbose`)
 - #105189 (rustdoc: clean up redundant CSS on `.rustdoc-toggle.hideme`)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs6
-rw-r--r--compiler/rustc_trait_selection/src/traits/engine.rs18
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs51
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/mod.rs35
-rw-r--r--library/alloc/src/vec/mod.rs11
-rw-r--r--library/std/src/fs.rs5
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css10
-rw-r--r--src/librustdoc/json/mod.rs53
-rw-r--r--src/test/rustdoc-json/intra-doc-links/auxiliary/enum_variant_in_trait_method.rs8
-rw-r--r--src/test/rustdoc-json/intra-doc-links/foreign_variant.rs13
-rw-r--r--src/test/rustdoc-json/reexport/auxiliary/trait_with_docs.rs2
-rw-r--r--src/test/rustdoc-json/reexport/synthesize_trait_with_docs.rs10
-rw-r--r--src/test/rustdoc-json/traits/uses_extern_trait.rs11
-rw-r--r--src/test/ui/fn/signature-error-reporting-under-verbose.rs15
-rw-r--r--src/test/ui/fn/signature-error-reporting-under-verbose.stderr19
-rw-r--r--src/test/ui/trait-bounds/impl-bound-with-references-error.rs20
-rw-r--r--src/test/ui/trait-bounds/impl-bound-with-references-error.stderr24
-rw-r--r--src/tools/jsondoclint/src/validator.rs9
-rw-r--r--src/tools/jsondoclint/src/validator/tests.rs50
19 files changed, 259 insertions, 111 deletions
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 6bd1df97c16..662136ca18d 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -1255,7 +1255,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     let num_display_types = consts_offset - regions_len;
                     for (i, (ta1, ta2)) in type_arguments.take(num_display_types).enumerate() {
                         let i = i + regions_len;
-                        if ta1 == ta2 {
+                        if ta1 == ta2 && !self.tcx.sess.verbose() {
                             values.0.push_normal("_");
                             values.1.push_normal("_");
                         } else {
@@ -1271,7 +1271,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
                     let const_arguments = sub1.consts().zip(sub2.consts());
                     for (i, (ca1, ca2)) in const_arguments.enumerate() {
                         let i = i + consts_offset;
-                        if ca1 == ca2 {
+                        if ca1 == ca2 && !self.tcx.sess.verbose() {
                             values.0.push_normal("_");
                             values.1.push_normal("_");
                         } else {
@@ -1450,7 +1450,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             (ty::FnPtr(sig1), ty::FnPtr(sig2)) => self.cmp_fn_sig(sig1, sig2),
 
             _ => {
-                if t1 == t2 {
+                if t1 == t2 && !self.tcx.sess.verbose() {
                     // The two types are the same, elide and don't highlight.
                     (DiagnosticStyledString::normal("_"), DiagnosticStyledString::normal("_"))
                 } else {
diff --git a/compiler/rustc_trait_selection/src/traits/engine.rs b/compiler/rustc_trait_selection/src/traits/engine.rs
index 64d01ddb09a..c028e89e4ea 100644
--- a/compiler/rustc_trait_selection/src/traits/engine.rs
+++ b/compiler/rustc_trait_selection/src/traits/engine.rs
@@ -112,6 +112,24 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> {
         self.register_infer_ok_obligations(infer_ok)
     }
 
+    /// Makes `expected <: actual`.
+    pub fn eq_exp<T>(
+        &self,
+        cause: &ObligationCause<'tcx>,
+        param_env: ty::ParamEnv<'tcx>,
+        a_is_expected: bool,
+        a: T,
+        b: T,
+    ) -> Result<(), TypeError<'tcx>>
+    where
+        T: ToTrace<'tcx>,
+    {
+        self.infcx
+            .at(cause, param_env)
+            .eq_exp(a_is_expected, a, b)
+            .map(|infer_ok| self.register_infer_ok_obligations(infer_ok))
+    }
+
     pub fn eq<T: ToTrace<'tcx>>(
         &self,
         cause: &ObligationCause<'tcx>,
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index 049b24b3997..84e951e8023 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -1577,32 +1577,26 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
         }
 
         self.probe(|_| {
-            let mut err = error.err;
-            let mut values = None;
+            let ocx = ObligationCtxt::new_in_snapshot(self);
 
             // try to find the mismatched types to report the error with.
             //
             // this can fail if the problem was higher-ranked, in which
             // cause I have no idea for a good error message.
             let bound_predicate = predicate.kind();
-            if let ty::PredicateKind::Clause(ty::Clause::Projection(data)) =
+            let (values, err) = if let ty::PredicateKind::Clause(ty::Clause::Projection(data)) =
                 bound_predicate.skip_binder()
             {
-                let mut selcx = SelectionContext::new(self);
                 let data = self.replace_bound_vars_with_fresh_vars(
                     obligation.cause.span,
                     infer::LateBoundRegionConversionTime::HigherRankedType,
                     bound_predicate.rebind(data),
                 );
-                let mut obligations = vec![];
-                // FIXME(normalization): Change this to use `At::normalize`
-                let normalized_ty = super::normalize_projection_type(
-                    &mut selcx,
+                let normalized_ty = ocx.normalize(
+                    &obligation.cause,
                     obligation.param_env,
-                    data.projection_ty,
-                    obligation.cause.clone(),
-                    0,
-                    &mut obligations,
+                    self.tcx
+                        .mk_projection(data.projection_ty.item_def_id, data.projection_ty.substs),
                 );
 
                 debug!(?obligation.cause, ?obligation.param_env);
@@ -1618,19 +1612,34 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
                         | ObligationCauseCode::ObjectCastObligation(..)
                         | ObligationCauseCode::OpaqueType
                 );
-                if let Err(new_err) = self.at(&obligation.cause, obligation.param_env).eq_exp(
+                let expected_ty = data.term.ty().unwrap();
+
+                // constrain inference variables a bit more to nested obligations from normalize so
+                // we can have more helpful errors.
+                ocx.select_where_possible();
+
+                if let Err(new_err) = ocx.eq_exp(
+                    &obligation.cause,
+                    obligation.param_env,
                     is_normalized_ty_expected,
                     normalized_ty,
-                    data.term,
+                    expected_ty,
                 ) {
-                    values = Some((data, is_normalized_ty_expected, normalized_ty, data.term));
-                    err = new_err;
+                    (Some((data, is_normalized_ty_expected, normalized_ty, expected_ty)), new_err)
+                } else {
+                    (None, error.err)
                 }
-            }
+            } else {
+                (None, error.err)
+            };
 
             let msg = values
                 .and_then(|(predicate, _, normalized_ty, expected_ty)| {
-                    self.maybe_detailed_projection_msg(predicate, normalized_ty, expected_ty)
+                    self.maybe_detailed_projection_msg(
+                        predicate,
+                        normalized_ty.into(),
+                        expected_ty.into(),
+                    )
                 })
                 .unwrap_or_else(|| format!("type mismatch resolving `{}`", predicate));
             let mut diag = struct_span_err!(self.tcx.sess, obligation.cause.span, E0271, "{msg}");
@@ -1672,11 +1681,11 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
                 &mut diag,
                 &obligation.cause,
                 secondary_span,
-                values.map(|(_, is_normalized_ty_expected, normalized_ty, term)| {
+                values.map(|(_, is_normalized_ty_expected, normalized_ty, expected_ty)| {
                     infer::ValuePairs::Terms(ExpectedFound::new(
                         is_normalized_ty_expected,
-                        normalized_ty,
-                        term,
+                        normalized_ty.into(),
+                        expected_ty.into(),
                     ))
                 }),
                 err,
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs
index 515f3a34988..8835f2cc1b9 100644
--- a/compiler/rustc_trait_selection/src/traits/select/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs
@@ -371,23 +371,28 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
 
                     if !candidate_set.ambiguous && no_candidates_apply {
                         let trait_ref = stack.obligation.predicate.skip_binder().trait_ref;
-                        let self_ty = trait_ref.self_ty();
-                        let (trait_desc, self_desc) = with_no_trimmed_paths!({
-                            let trait_desc = trait_ref.print_only_trait_path().to_string();
-                            let self_desc = if self_ty.has_concrete_skeleton() {
-                                Some(self_ty.to_string())
+                        if !trait_ref.references_error() {
+                            let self_ty = trait_ref.self_ty();
+                            let (trait_desc, self_desc) = with_no_trimmed_paths!({
+                                let trait_desc = trait_ref.print_only_trait_path().to_string();
+                                let self_desc = if self_ty.has_concrete_skeleton() {
+                                    Some(self_ty.to_string())
+                                } else {
+                                    None
+                                };
+                                (trait_desc, self_desc)
+                            });
+                            let cause = if let Conflict::Upstream = conflict {
+                                IntercrateAmbiguityCause::UpstreamCrateUpdate {
+                                    trait_desc,
+                                    self_desc,
+                                }
                             } else {
-                                None
+                                IntercrateAmbiguityCause::DownstreamCrate { trait_desc, self_desc }
                             };
-                            (trait_desc, self_desc)
-                        });
-                        let cause = if let Conflict::Upstream = conflict {
-                            IntercrateAmbiguityCause::UpstreamCrateUpdate { trait_desc, self_desc }
-                        } else {
-                            IntercrateAmbiguityCause::DownstreamCrate { trait_desc, self_desc }
-                        };
-                        debug!(?cause, "evaluate_stack: pushing cause");
-                        self.intercrate_ambiguity_causes.as_mut().unwrap().insert(cause);
+                            debug!(?cause, "evaluate_stack: pushing cause");
+                            self.intercrate_ambiguity_causes.as_mut().unwrap().insert(cause);
+                        }
                     }
                 }
             }
diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs
index e147af2ce39..ba34ab6800f 100644
--- a/library/alloc/src/vec/mod.rs
+++ b/library/alloc/src/vec/mod.rs
@@ -1070,7 +1070,8 @@ impl<T, A: Allocator> Vec<T, A> {
 
     /// Converts the vector into [`Box<[T]>`][owned slice].
     ///
-    /// Note that this will drop any excess capacity.
+    /// If the vector has excess capacity, its items will be moved into a
+    /// newly-allocated buffer with exactly the right capacity.
     ///
     /// [owned slice]: Box
     ///
@@ -3223,6 +3224,14 @@ impl<T, A: Allocator> From<Vec<T, A>> for Box<[T], A> {
     /// ```
     /// assert_eq!(Box::from(vec![1, 2, 3]), vec![1, 2, 3].into_boxed_slice());
     /// ```
+    ///
+    /// Any excess capacity is removed:
+    /// ```
+    /// let mut vec = Vec::with_capacity(10);
+    /// vec.extend([1, 2, 3]);
+    ///
+    /// assert_eq!(Box::from(vec), vec![1, 2, 3].into_boxed_slice());
+    /// ```
     fn from(v: Vec<T, A>) -> Self {
         v.into_boxed_slice()
     }
diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs
index 0660e03c1a8..f357d505fe8 100644
--- a/library/std/src/fs.rs
+++ b/library/std/src/fs.rs
@@ -510,8 +510,9 @@ impl File {
     /// # Errors
     ///
     /// This function will return an error if the file is not opened for writing.
-    /// Also, std::io::ErrorKind::InvalidInput will be returned if the desired
-    /// length would cause an overflow due to the implementation specifics.
+    /// Also, [`std::io::ErrorKind::InvalidInput`](crate::io::ErrorKind::InvalidInput)
+    /// will be returned if the desired length would cause an overflow due to
+    /// the implementation specifics.
     ///
     /// # Examples
     ///
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index e9f2e025774..f44797fe55f 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -1585,17 +1585,11 @@ details.rustdoc-toggle[open] > summary.hideme > span {
 	display: none;
 }
 
-details.rustdoc-toggle[open] > summary::before,
-details.rustdoc-toggle[open] > summary.hideme::before {
+details.rustdoc-toggle[open] > summary::before {
 	background: url("toggle-minus-31bbd6e4c77f5c96.svg") no-repeat top left;
-	width: 16px;
-	height: 16px;
-	display: inline-block;
-	content: "";
 }
 
-details.rustdoc-toggle[open] > summary::after,
-details.rustdoc-toggle[open] > summary.hideme::after {
+details.rustdoc-toggle[open] > summary::after {
 	content: "Collapse";
 }
 
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 83be1a16eb2..1196f944faa 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -99,53 +99,6 @@ impl<'tcx> JsonRenderer<'tcx> {
             })
             .unwrap_or_default()
     }
-
-    fn get_trait_items(&mut self) -> Vec<(types::Id, types::Item)> {
-        debug!("Adding foreign trait items");
-        Rc::clone(&self.cache)
-            .traits
-            .iter()
-            .filter_map(|(&id, trait_item)| {
-                // only need to synthesize items for external traits
-                if !id.is_local() {
-                    for item in &trait_item.items {
-                        trace!("Adding subitem to {id:?}: {:?}", item.item_id);
-                        self.item(item.clone()).unwrap();
-                    }
-                    let item_id = from_item_id(id.into(), self.tcx);
-                    Some((
-                        item_id.clone(),
-                        types::Item {
-                            id: item_id,
-                            crate_id: id.krate.as_u32(),
-                            name: self
-                                .cache
-                                .paths
-                                .get(&id)
-                                .unwrap_or_else(|| {
-                                    self.cache
-                                        .external_paths
-                                        .get(&id)
-                                        .expect("Trait should either be in local or external paths")
-                                })
-                                .0
-                                .last()
-                                .map(|s| s.to_string()),
-                            visibility: types::Visibility::Public,
-                            inner: types::ItemEnum::Trait(trait_item.clone().into_tcx(self.tcx)),
-                            span: None,
-                            docs: Default::default(),
-                            links: Default::default(),
-                            attrs: Default::default(),
-                            deprecation: Default::default(),
-                        },
-                    ))
-                } else {
-                    None
-                }
-            })
-            .collect()
-    }
 }
 
 impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
@@ -276,11 +229,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
 
         let e = ExternalCrate { crate_num: LOCAL_CRATE };
 
-        // FIXME(adotinthevoid): Remove this, as it's not consistent with not
-        // inlining foreign items.
-        let foreign_trait_items = self.get_trait_items();
-        let mut index = (*self.index).clone().into_inner();
-        index.extend(foreign_trait_items);
+        let index = (*self.index).clone().into_inner();
 
         debug!("Constructing Output");
         // This needs to be the default HashMap for compatibility with the public interface for
diff --git a/src/test/rustdoc-json/intra-doc-links/auxiliary/enum_variant_in_trait_method.rs b/src/test/rustdoc-json/intra-doc-links/auxiliary/enum_variant_in_trait_method.rs
new file mode 100644
index 00000000000..bfe85f59e81
--- /dev/null
+++ b/src/test/rustdoc-json/intra-doc-links/auxiliary/enum_variant_in_trait_method.rs
@@ -0,0 +1,8 @@
+pub trait Trait {
+    /// [`Enum::Variant`]
+    fn method() {}
+}
+
+pub enum Enum {
+    Variant,
+}
diff --git a/src/test/rustdoc-json/intra-doc-links/foreign_variant.rs b/src/test/rustdoc-json/intra-doc-links/foreign_variant.rs
new file mode 100644
index 00000000000..e2968231338
--- /dev/null
+++ b/src/test/rustdoc-json/intra-doc-links/foreign_variant.rs
@@ -0,0 +1,13 @@
+// Regression test for <https://github.com/rust-lang/rust/issues/105025>
+// aux-build: enum_variant_in_trait_method.rs
+
+extern crate enum_variant_in_trait_method;
+
+pub struct Local;
+
+/// local impl
+impl enum_variant_in_trait_method::Trait for Local {}
+
+// @!has "$.index[*][?(@.name == 'Trait')]"
+// @!has "$.index[*][?(@.name == 'method')]"
+// @count "$.index[*][?(@.docs == 'local impl')].inner.items[*]" 0
diff --git a/src/test/rustdoc-json/reexport/auxiliary/trait_with_docs.rs b/src/test/rustdoc-json/reexport/auxiliary/trait_with_docs.rs
new file mode 100644
index 00000000000..1e87966b28a
--- /dev/null
+++ b/src/test/rustdoc-json/reexport/auxiliary/trait_with_docs.rs
@@ -0,0 +1,2 @@
+/// The Docs
+pub trait HasDocs {}
diff --git a/src/test/rustdoc-json/reexport/synthesize_trait_with_docs.rs b/src/test/rustdoc-json/reexport/synthesize_trait_with_docs.rs
new file mode 100644
index 00000000000..25a7c08d689
--- /dev/null
+++ b/src/test/rustdoc-json/reexport/synthesize_trait_with_docs.rs
@@ -0,0 +1,10 @@
+// Regression test for <https://github.com/rust-lang/rust/issues/105022>
+// aux-build: trait_with_docs.rs
+
+extern crate trait_with_docs;
+
+pub struct Local;
+
+impl trait_with_docs::HasDocs for Local {}
+
+// @!has "$.index[*][?(@.name == 'HasDocs')]"
diff --git a/src/test/rustdoc-json/traits/uses_extern_trait.rs b/src/test/rustdoc-json/traits/uses_extern_trait.rs
index a4add43c6a1..55a51f739ab 100644
--- a/src/test/rustdoc-json/traits/uses_extern_trait.rs
+++ b/src/test/rustdoc-json/traits/uses_extern_trait.rs
@@ -1,12 +1,5 @@
 #![no_std]
 pub fn drop_default<T: core::default::Default>(_x: T) {}
 
-// FIXME(adotinthevoid): Theses shouldn't be here
-// @has "$.index[*][?(@.name=='Debug')]"
-
-// Debug may have several items. All we depend on here the that `fmt` is first. See
-// https://github.com/rust-lang/rust/pull/104525#issuecomment-1331087852 for why we
-// can't use [*].
-
-// @set Debug_fmt = "$.index[*][?(@.name=='Debug')].inner.items[0]"
-// @has "$.index[*][?(@.name=='fmt')].id" $Debug_fmt
+// @!has "$.index[*][?(@.name=='Debug')]"
+// @!has "$.index[*][?(@.name=='Default')]"
diff --git a/src/test/ui/fn/signature-error-reporting-under-verbose.rs b/src/test/ui/fn/signature-error-reporting-under-verbose.rs
new file mode 100644
index 00000000000..d7a8c95e8b2
--- /dev/null
+++ b/src/test/ui/fn/signature-error-reporting-under-verbose.rs
@@ -0,0 +1,15 @@
+// compile-flags: -Zverbose
+
+fn foo(_: i32, _: i32) {}
+
+fn needs_ptr(_: fn(i32, u32)) {}
+//~^ NOTE function defined here
+//~| NOTE
+
+fn main() {
+    needs_ptr(foo);
+    //~^ ERROR mismatched types
+    //~| NOTE expected `u32`, found `i32`
+    //~| NOTE expected fn pointer `fn(i32, u32)`
+    //~| NOTE arguments to this function are incorrect
+}
diff --git a/src/test/ui/fn/signature-error-reporting-under-verbose.stderr b/src/test/ui/fn/signature-error-reporting-under-verbose.stderr
new file mode 100644
index 00000000000..6260fc8dcec
--- /dev/null
+++ b/src/test/ui/fn/signature-error-reporting-under-verbose.stderr
@@ -0,0 +1,19 @@
+error[E0308]: mismatched types
+  --> $DIR/signature-error-reporting-under-verbose.rs:10:15
+   |
+LL |     needs_ptr(foo);
+   |     --------- ^^^ expected `u32`, found `i32`
+   |     |
+   |     arguments to this function are incorrect
+   |
+   = note: expected fn pointer `fn(i32, u32)`
+                 found fn item `fn(i32, i32) {foo}`
+note: function defined here
+  --> $DIR/signature-error-reporting-under-verbose.rs:5:4
+   |
+LL | fn needs_ptr(_: fn(i32, u32)) {}
+   |    ^^^^^^^^^ ---------------
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/trait-bounds/impl-bound-with-references-error.rs b/src/test/ui/trait-bounds/impl-bound-with-references-error.rs
new file mode 100644
index 00000000000..e5d0a1aaed0
--- /dev/null
+++ b/src/test/ui/trait-bounds/impl-bound-with-references-error.rs
@@ -0,0 +1,20 @@
+// Regression test for #105138.
+// This test ensures that the compiler does not add note
+// for implementation of trait whose inner type is erroneous.
+
+pub enum LabelText {
+    Plain,
+}
+
+impl<T> From<T> for LabelText
+//~^ ERROR conflicting implementations of trait `From<LabelText>` for type `LabelText` [E0119]
+where
+    T: Into<Cow<'static, str>>,
+    //~^ ERROR cannot find type `Cow` in this scope [E0412]
+{
+    fn from(text: T) -> Self {
+        LabelText::Plain(text.into())
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/trait-bounds/impl-bound-with-references-error.stderr b/src/test/ui/trait-bounds/impl-bound-with-references-error.stderr
new file mode 100644
index 00000000000..95fd6bd504c
--- /dev/null
+++ b/src/test/ui/trait-bounds/impl-bound-with-references-error.stderr
@@ -0,0 +1,24 @@
+error[E0412]: cannot find type `Cow` in this scope
+  --> $DIR/impl-bound-with-references-error.rs:12:13
+   |
+LL |     T: Into<Cow<'static, str>>,
+   |             ^^^ not found in this scope
+   |
+help: consider importing this enum
+   |
+LL | use std::borrow::Cow;
+   |
+
+error[E0119]: conflicting implementations of trait `From<LabelText>` for type `LabelText`
+  --> $DIR/impl-bound-with-references-error.rs:9:1
+   |
+LL | impl<T> From<T> for LabelText
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: conflicting implementation in crate `core`:
+           - impl<T> From<T> for T;
+
+error: aborting due to 2 previous errors
+
+Some errors have detailed explanations: E0119, E0412.
+For more information about an error, try `rustc --explain E0119`.
diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs
index 5046ab9c7cb..e15f5fe3ccc 100644
--- a/src/tools/jsondoclint/src/validator.rs
+++ b/src/tools/jsondoclint/src/validator.rs
@@ -60,6 +60,8 @@ impl<'a> Validator<'a> {
 
     fn check_item(&mut self, id: &'a Id) {
         if let Some(item) = &self.krate.index.get(id) {
+            item.links.values().for_each(|id| self.add_any_id(id));
+
             match &item.inner {
                 ItemEnum::Import(x) => self.check_import(x),
                 ItemEnum::Union(x) => self.check_union(x),
@@ -376,6 +378,10 @@ impl<'a> Validator<'a> {
         }
     }
 
+    fn add_any_id(&mut self, id: &'a Id) {
+        self.add_id_checked(id, |_| true, "any kind of item");
+    }
+
     fn add_field_id(&mut self, id: &'a Id) {
         self.add_id_checked(id, Kind::is_struct_field, "StructField");
     }
@@ -446,3 +452,6 @@ fn set_remove<T: Hash + Eq + Clone>(set: &mut HashSet<T>) -> Option<T> {
         None
     }
 }
+
+#[cfg(test)]
+mod tests;
diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs
new file mode 100644
index 00000000000..c4aeee9c53b
--- /dev/null
+++ b/src/tools/jsondoclint/src/validator/tests.rs
@@ -0,0 +1,50 @@
+use std::collections::HashMap;
+
+use rustdoc_json_types::{Crate, Item, Visibility};
+
+use super::*;
+
+#[track_caller]
+fn check(krate: &Crate, errs: &[Error]) {
+    let mut validator = Validator::new(krate);
+    validator.check_crate();
+
+    assert_eq!(errs, &validator.errs[..]);
+}
+
+fn id(s: &str) -> Id {
+    Id(s.to_owned())
+}
+
+#[test]
+fn errors_on_missing_links() {
+    let k = Crate {
+        root: id("0"),
+        crate_version: None,
+        includes_private: false,
+        index: HashMap::from_iter([(
+            id("0"),
+            Item {
+                name: Some("root".to_owned()),
+                id: id(""),
+                crate_id: 0,
+                span: None,
+                visibility: Visibility::Public,
+                docs: None,
+                links: HashMap::from_iter([("Not Found".to_owned(), id("1"))]),
+                attrs: vec![],
+                deprecation: None,
+                inner: ItemEnum::Module(Module {
+                    is_crate: true,
+                    items: vec![],
+                    is_stripped: false,
+                }),
+            },
+        )]),
+        paths: HashMap::new(),
+        external_crates: HashMap::new(),
+        format_version: rustdoc_json_types::FORMAT_VERSION,
+    };
+
+    check(&k, &[Error { kind: ErrorKind::NotFound, id: id("1") }]);
+}