about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/librustc_lint/builtin.rs234
-rw-r--r--src/librustc_parse/parser/expr.rs2
-rw-r--r--src/librustdoc/html/render/cache.rs6
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs80
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs25
-rw-r--r--src/librustdoc/passes/doc_test_lints.rs28
-rw-r--r--src/test/rustdoc-ui/coverage/basic.stdout14
-rw-r--r--src/test/rustdoc-ui/coverage/doc-examples.rs27
-rw-r--r--src/test/rustdoc-ui/coverage/doc-examples.stdout7
-rw-r--r--src/test/rustdoc-ui/coverage/empty.stdout14
-rw-r--r--src/test/rustdoc-ui/coverage/enums.stdout14
-rw-r--r--src/test/rustdoc-ui/coverage/exotic.stdout16
-rw-r--r--src/test/rustdoc-ui/coverage/json.stdout2
-rw-r--r--src/test/rustdoc-ui/coverage/private.stdout14
-rw-r--r--src/test/rustdoc-ui/coverage/statics-consts.stdout14
-rw-r--r--src/test/rustdoc-ui/coverage/traits.stdout14
-rw-r--r--src/test/rustdoc/intra-doc-link-true-false.rs10
-rw-r--r--src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs8
-rw-r--r--src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr82
-rw-r--r--src/test/ui/did_you_mean/issue-54109-without-witness.fixed8
-rw-r--r--src/test/ui/did_you_mean/issue-54109-without-witness.rs8
-rw-r--r--src/test/ui/did_you_mean/issue-54109-without-witness.stderr80
-rw-r--r--src/test/ui/issues/issue-75599.rs24
-rw-r--r--src/test/ui/lint/clashing-extern-fn-recursion.rs119
24 files changed, 494 insertions, 356 deletions
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 36eb272cdfc..b337bf0a3f9 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -29,6 +29,7 @@ use rustc_ast::visit::{FnCtxt, FnKind};
 use rustc_ast::{self as ast, *};
 use rustc_ast_pretty::pprust::{self, expr_to_string};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
 use rustc_feature::{deprecated_attributes, AttributeGate, AttributeTemplate, AttributeType};
 use rustc_feature::{GateIssue, Stability};
@@ -2153,44 +2154,58 @@ impl ClashingExternDeclarations {
         b: Ty<'tcx>,
         ckind: CItemKind,
     ) -> bool {
-        debug!("structurally_same_type(cx, a = {:?}, b = {:?})", a, b);
-        let tcx = cx.tcx;
-        if a == b || rustc_middle::ty::TyS::same_type(a, b) {
-            // All nominally-same types are structurally same, too.
-            true
-        } else {
-            // Do a full, depth-first comparison between the two.
-            use rustc_middle::ty::TyKind::*;
-            let a_kind = &a.kind;
-            let b_kind = &b.kind;
-
-            let compare_layouts = |a, b| -> bool {
-                let a_layout = &cx.layout_of(a).unwrap().layout.abi;
-                let b_layout = &cx.layout_of(b).unwrap().layout.abi;
-                debug!("{:?} == {:?} = {}", a_layout, b_layout, a_layout == b_layout);
-                a_layout == b_layout
-            };
+        fn structurally_same_type_impl<'tcx>(
+            seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>,
+            cx: &LateContext<'tcx>,
+            a: Ty<'tcx>,
+            b: Ty<'tcx>,
+            ckind: CItemKind,
+        ) -> bool {
+            debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b);
+            if !seen_types.insert((a, b)) {
+                // We've encountered a cycle. There's no point going any further -- the types are
+                // structurally the same.
+                return true;
+            }
+            let tcx = cx.tcx;
+            if a == b || rustc_middle::ty::TyS::same_type(a, b) {
+                // All nominally-same types are structurally same, too.
+                true
+            } else {
+                // Do a full, depth-first comparison between the two.
+                use rustc_middle::ty::TyKind::*;
+                let a_kind = &a.kind;
+                let b_kind = &b.kind;
+
+                let compare_layouts = |a, b| -> bool {
+                    let a_layout = &cx.layout_of(a).unwrap().layout.abi;
+                    let b_layout = &cx.layout_of(b).unwrap().layout.abi;
+                    debug!("{:?} == {:?} = {}", a_layout, b_layout, a_layout == b_layout);
+                    a_layout == b_layout
+                };
+
+                #[allow(rustc::usage_of_ty_tykind)]
+                let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| {
+                    kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..))
+                };
 
-            #[allow(rustc::usage_of_ty_tykind)]
-            let is_primitive_or_pointer =
-                |kind: &ty::TyKind<'_>| kind.is_primitive() || matches!(kind, RawPtr(..));
-
-            match (a_kind, b_kind) {
-                (Adt(_, a_substs), Adt(_, b_substs)) => {
-                    let a = a.subst(cx.tcx, a_substs);
-                    let b = b.subst(cx.tcx, b_substs);
-                    debug!("Comparing {:?} and {:?}", a, b);
-
-                    if let (Adt(a_def, ..), Adt(b_def, ..)) = (&a.kind, &b.kind) {
-                        // Grab a flattened representation of all fields.
-                        let a_fields = a_def.variants.iter().flat_map(|v| v.fields.iter());
-                        let b_fields = b_def.variants.iter().flat_map(|v| v.fields.iter());
-                        compare_layouts(a, b)
+                ensure_sufficient_stack(|| {
+                    match (a_kind, b_kind) {
+                        (Adt(a_def, a_substs), Adt(b_def, b_substs)) => {
+                            let a = a.subst(cx.tcx, a_substs);
+                            let b = b.subst(cx.tcx, b_substs);
+                            debug!("Comparing {:?} and {:?}", a, b);
+
+                            // Grab a flattened representation of all fields.
+                            let a_fields = a_def.variants.iter().flat_map(|v| v.fields.iter());
+                            let b_fields = b_def.variants.iter().flat_map(|v| v.fields.iter());
+                            compare_layouts(a, b)
                             && a_fields.eq_by(
                                 b_fields,
                                 |&ty::FieldDef { did: a_did, .. },
                                  &ty::FieldDef { did: b_did, .. }| {
-                                    Self::structurally_same_type(
+                                    structurally_same_type_impl(
+                                        seen_types,
                                         cx,
                                         tcx.type_of(a_did),
                                         tcx.type_of(b_did),
@@ -2198,78 +2213,93 @@ impl ClashingExternDeclarations {
                                     )
                                 },
                             )
-                    } else {
-                        unreachable!()
-                    }
-                }
-                (Array(a_ty, a_const), Array(b_ty, b_const)) => {
-                    // For arrays, we also check the constness of the type.
-                    a_const.val == b_const.val
-                        && Self::structurally_same_type(cx, a_const.ty, b_const.ty, ckind)
-                        && Self::structurally_same_type(cx, a_ty, b_ty, ckind)
-                }
-                (Slice(a_ty), Slice(b_ty)) => Self::structurally_same_type(cx, a_ty, b_ty, ckind),
-                (RawPtr(a_tymut), RawPtr(b_tymut)) => {
-                    a_tymut.mutbl == b_tymut.mutbl
-                        && Self::structurally_same_type(cx, &a_tymut.ty, &b_tymut.ty, ckind)
-                }
-                (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
-                    // For structural sameness, we don't need the region to be same.
-                    a_mut == b_mut && Self::structurally_same_type(cx, a_ty, b_ty, ckind)
-                }
-                (FnDef(..), FnDef(..)) => {
-                    let a_poly_sig = a.fn_sig(tcx);
-                    let b_poly_sig = b.fn_sig(tcx);
-
-                    // As we don't compare regions, skip_binder is fine.
-                    let a_sig = a_poly_sig.skip_binder();
-                    let b_sig = b_poly_sig.skip_binder();
-
-                    (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
-                        == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
-                        && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
-                            Self::structurally_same_type(cx, a, b, ckind)
-                        })
-                        && Self::structurally_same_type(cx, a_sig.output(), b_sig.output(), ckind)
-                }
-                (Tuple(a_substs), Tuple(b_substs)) => {
-                    a_substs.types().eq_by(b_substs.types(), |a_ty, b_ty| {
-                        Self::structurally_same_type(cx, a_ty, b_ty, ckind)
-                    })
-                }
-                // For these, it's not quite as easy to define structural-sameness quite so easily.
-                // For the purposes of this lint, take the conservative approach and mark them as
-                // not structurally same.
-                (Dynamic(..), Dynamic(..))
-                | (Error(..), Error(..))
-                | (Closure(..), Closure(..))
-                | (Generator(..), Generator(..))
-                | (GeneratorWitness(..), GeneratorWitness(..))
-                | (Projection(..), Projection(..))
-                | (Opaque(..), Opaque(..)) => false,
-
-                // These definitely should have been caught above.
-                (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
-
-                // An Adt and a primitive type. This can be FFI-safe is the ADT is an enum with a
-                // non-null field.
-                (Adt(..), other_kind) | (other_kind, Adt(..))
-                    if is_primitive_or_pointer(other_kind) =>
-                {
-                    let (primitive, adt) =
-                        if is_primitive_or_pointer(&a.kind) { (a, b) } else { (b, a) };
-                    if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) {
-                        ty == primitive
-                    } else {
-                        compare_layouts(a, b)
+                        }
+                        (Array(a_ty, a_const), Array(b_ty, b_const)) => {
+                            // For arrays, we also check the constness of the type.
+                            a_const.val == b_const.val
+                                && structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                        }
+                        (Slice(a_ty), Slice(b_ty)) => {
+                            structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                        }
+                        (RawPtr(a_tymut), RawPtr(b_tymut)) => {
+                            a_tymut.mutbl == b_tymut.mutbl
+                                && structurally_same_type_impl(
+                                    seen_types,
+                                    cx,
+                                    &a_tymut.ty,
+                                    &b_tymut.ty,
+                                    ckind,
+                                )
+                        }
+                        (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
+                            // For structural sameness, we don't need the region to be same.
+                            a_mut == b_mut
+                                && structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                        }
+                        (FnDef(..), FnDef(..)) => {
+                            let a_poly_sig = a.fn_sig(tcx);
+                            let b_poly_sig = b.fn_sig(tcx);
+
+                            // As we don't compare regions, skip_binder is fine.
+                            let a_sig = a_poly_sig.skip_binder();
+                            let b_sig = b_poly_sig.skip_binder();
+
+                            (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
+                                == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
+                                && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
+                                    structurally_same_type_impl(seen_types, cx, a, b, ckind)
+                                })
+                                && structurally_same_type_impl(
+                                    seen_types,
+                                    cx,
+                                    a_sig.output(),
+                                    b_sig.output(),
+                                    ckind,
+                                )
+                        }
+                        (Tuple(a_substs), Tuple(b_substs)) => {
+                            a_substs.types().eq_by(b_substs.types(), |a_ty, b_ty| {
+                                structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                            })
+                        }
+                        // For these, it's not quite as easy to define structural-sameness quite so easily.
+                        // For the purposes of this lint, take the conservative approach and mark them as
+                        // not structurally same.
+                        (Dynamic(..), Dynamic(..))
+                        | (Error(..), Error(..))
+                        | (Closure(..), Closure(..))
+                        | (Generator(..), Generator(..))
+                        | (GeneratorWitness(..), GeneratorWitness(..))
+                        | (Projection(..), Projection(..))
+                        | (Opaque(..), Opaque(..)) => false,
+
+                        // These definitely should have been caught above.
+                        (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
+
+                        // An Adt and a primitive or pointer type. This can be FFI-safe if non-null
+                        // enum layout optimisation is being applied.
+                        (Adt(..), other_kind) | (other_kind, Adt(..))
+                            if is_primitive_or_pointer(other_kind) =>
+                        {
+                            let (primitive, adt) =
+                                if is_primitive_or_pointer(&a.kind) { (a, b) } else { (b, a) };
+                            if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) {
+                                ty == primitive
+                            } else {
+                                compare_layouts(a, b)
+                            }
+                        }
+                        // Otherwise, just compare the layouts. This may fail to lint for some
+                        // incompatible types, but at the very least, will stop reads into
+                        // uninitialised memory.
+                        _ => compare_layouts(a, b),
                     }
-                }
-                // Otherwise, just compare the layouts. This may fail to lint for some
-                // incompatible types, but at the very least, will stop reads into
-                // uninitialised memory.
-                _ => compare_layouts(a, b),
+                })
             }
         }
+        let mut seen_types = FxHashSet::default();
+        structurally_same_type_impl(&mut seen_types, cx, a, b, ckind)
     }
 }
 
diff --git a/src/librustc_parse/parser/expr.rs b/src/librustc_parse/parser/expr.rs
index e5c28f225c6..62aec66a255 100644
--- a/src/librustc_parse/parser/expr.rs
+++ b/src/librustc_parse/parser/expr.rs
@@ -308,7 +308,7 @@ impl<'a> Parser<'a> {
     }
 
     fn should_continue_as_assoc_expr(&mut self, lhs: &Expr) -> bool {
-        match (self.expr_is_complete(lhs), self.check_assoc_op().map(|op| op.node)) {
+        match (self.expr_is_complete(lhs), AssocOp::from_token(&self.token)) {
             // Semi-statement forms are odd:
             // See https://github.com/rust-lang/rust/issues/29071
             (true, None) => false,
diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs
index 5a9e9dda677..ccc07645620 100644
--- a/src/librustdoc/html/render/cache.rs
+++ b/src/librustdoc/html/render/cache.rs
@@ -200,10 +200,12 @@ fn get_index_type_name(clean_type: &clean::Type, accept_generic: bool) -> Option
     match *clean_type {
         clean::ResolvedPath { ref path, .. } => {
             let segments = &path.segments;
-            let path_segment = segments.iter().last().unwrap_or_else(|| panic!(
+            let path_segment = segments.iter().last().unwrap_or_else(|| {
+                panic!(
                 "get_index_type_name(clean_type: {:?}, accept_generic: {:?}) had length zero path",
                 clean_type, accept_generic
-            ));
+            )
+            });
             Some(path_segment.name.clone())
         }
         clean::Generic(ref s) if accept_generic => Some(s.clone()),
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index b722cfc8f75..0a836f46c0e 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -2,8 +2,9 @@ use crate::clean;
 use crate::config::OutputFormat;
 use crate::core::DocContext;
 use crate::fold::{self, DocFolder};
+use crate::html::markdown::{find_testable_code, ErrorCodes};
+use crate::passes::doc_test_lints::Tests;
 use crate::passes::Pass;
-
 use rustc_span::symbol::sym;
 use rustc_span::FileName;
 use serde::Serialize;
@@ -30,15 +31,19 @@ fn calculate_doc_coverage(krate: clean::Crate, ctx: &DocContext<'_>) -> clean::C
 struct ItemCount {
     total: u64,
     with_docs: u64,
+    with_examples: u64,
 }
 
 impl ItemCount {
-    fn count_item(&mut self, has_docs: bool) {
+    fn count_item(&mut self, has_docs: bool, has_doc_example: bool) {
         self.total += 1;
 
         if has_docs {
             self.with_docs += 1;
         }
+        if has_doc_example {
+            self.with_examples += 1;
+        }
     }
 
     fn percentage(&self) -> Option<f64> {
@@ -48,13 +53,25 @@ impl ItemCount {
             None
         }
     }
+
+    fn examples_percentage(&self) -> Option<f64> {
+        if self.total > 0 {
+            Some((self.with_examples as f64 * 100.0) / self.total as f64)
+        } else {
+            None
+        }
+    }
 }
 
 impl ops::Sub for ItemCount {
     type Output = Self;
 
     fn sub(self, rhs: Self) -> Self {
-        ItemCount { total: self.total - rhs.total, with_docs: self.with_docs - rhs.with_docs }
+        ItemCount {
+            total: self.total - rhs.total,
+            with_docs: self.with_docs - rhs.with_docs,
+            with_examples: self.with_examples - rhs.with_examples,
+        }
     }
 }
 
@@ -62,6 +79,7 @@ impl ops::AddAssign for ItemCount {
     fn add_assign(&mut self, rhs: Self) {
         self.total += rhs.total;
         self.with_docs += rhs.with_docs;
+        self.with_examples += rhs.with_examples;
     }
 }
 
@@ -103,33 +121,55 @@ impl CoverageCalculator {
         let mut total = ItemCount::default();
 
         fn print_table_line() {
-            println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "");
+            println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "");
         }
 
-        fn print_table_record(name: &str, count: ItemCount, percentage: f64) {
+        fn print_table_record(
+            name: &str,
+            count: ItemCount,
+            percentage: f64,
+            examples_percentage: f64,
+        ) {
             println!(
-                "| {:<35} | {:>10} | {:>10} | {:>9.1}% |",
-                name, count.with_docs, count.total, percentage
+                "| {:<35} | {:>10} | {:>10} | {:>9.1}% | {:>10} | {:>9.1}% |",
+                name,
+                count.with_docs,
+                count.total,
+                percentage,
+                count.with_examples,
+                examples_percentage,
             );
         }
 
         print_table_line();
         println!(
-            "| {:<35} | {:>10} | {:>10} | {:>10} |",
-            "File", "Documented", "Total", "Percentage"
+            "| {:<35} | {:>10} | {:>10} | {:>10} | {:>10} | {:>10} |",
+            "File", "Documented", "Total", "Percentage", "Examples", "Percentage",
         );
         print_table_line();
 
         for (file, &count) in &self.items {
-            if let Some(percentage) = count.percentage() {
-                print_table_record(&limit_filename_len(file.to_string()), count, percentage);
+            if let (Some(percentage), Some(examples_percentage)) =
+                (count.percentage(), count.examples_percentage())
+            {
+                print_table_record(
+                    &limit_filename_len(file.to_string()),
+                    count,
+                    percentage,
+                    examples_percentage,
+                );
 
                 total += count;
             }
         }
 
         print_table_line();
-        print_table_record("Total", total, total.percentage().unwrap_or(0.0));
+        print_table_record(
+            "Total",
+            total,
+            total.percentage().unwrap_or(0.0),
+            total.examples_percentage().unwrap_or(0.0),
+        );
         print_table_line();
     }
 }
@@ -137,6 +177,17 @@ impl CoverageCalculator {
 impl fold::DocFolder for CoverageCalculator {
     fn fold_item(&mut self, i: clean::Item) -> Option<clean::Item> {
         let has_docs = !i.attrs.doc_strings.is_empty();
+        let mut tests = Tests { found_tests: 0 };
+
+        find_testable_code(
+            &i.attrs.doc_strings.iter().map(|d| d.as_str()).collect::<Vec<_>>().join("\n"),
+            &mut tests,
+            ErrorCodes::No,
+            false,
+            None,
+        );
+
+        let has_doc_example = tests.found_tests != 0;
 
         match i.inner {
             _ if !i.def_id.is_local() => {
@@ -187,7 +238,10 @@ impl fold::DocFolder for CoverageCalculator {
             }
             _ => {
                 debug!("counting {:?} {:?} in {}", i.type_(), i.name, i.source.filename);
-                self.items.entry(i.source.filename.clone()).or_default().count_item(has_docs);
+                self.items
+                    .entry(i.source.filename.clone())
+                    .or_default()
+                    .count_item(has_docs, has_doc_example);
             }
         }
 
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index d4d6a8119c3..edfe8c05c6d 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -222,11 +222,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                             disambiguator,
                             None | Some(Disambiguator::Namespace(Namespace::TypeNS))
                         ) {
-                            if let Some(prim) = is_primitive(path_str, ns) {
+                            if let Some((path, prim)) = is_primitive(path_str, ns) {
                                 if extra_fragment.is_some() {
                                     return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
                                 }
-                                return Ok((prim, Some(path_str.to_owned())));
+                                return Ok((prim, Some(path.to_owned())));
                             }
                         }
                         return Ok((res, extra_fragment.clone()));
@@ -239,11 +239,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 if value != (ns == ValueNS) {
                     return Err(ErrorKind::ResolutionFailure);
                 }
-            } else if let Some(prim) = is_primitive(path_str, ns) {
+            } else if let Some((path, prim)) = is_primitive(path_str, ns) {
                 if extra_fragment.is_some() {
                     return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
                 }
-                return Ok((prim, Some(path_str.to_owned())));
+                return Ok((prim, Some(path.to_owned())));
             } else {
                 // If resolution failed, it may still be a method
                 // because methods are not handled by the resolver
@@ -269,7 +269,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 })
                 .ok_or(ErrorKind::ResolutionFailure)?;
 
-            if let Some(prim) = is_primitive(&path, TypeNS) {
+            if let Some((path, prim)) = is_primitive(&path, TypeNS) {
                 let did = primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)?;
                 return cx
                     .tcx
@@ -1220,11 +1220,22 @@ const PRIMITIVES: &[(&str, Res)] = &[
     ("f64", Res::PrimTy(hir::PrimTy::Float(rustc_ast::FloatTy::F64))),
     ("str", Res::PrimTy(hir::PrimTy::Str)),
     ("bool", Res::PrimTy(hir::PrimTy::Bool)),
+    ("true", Res::PrimTy(hir::PrimTy::Bool)),
+    ("false", Res::PrimTy(hir::PrimTy::Bool)),
     ("char", Res::PrimTy(hir::PrimTy::Char)),
 ];
 
-fn is_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
-    if ns == TypeNS { PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1) } else { None }
+fn is_primitive(path_str: &str, ns: Namespace) -> Option<(&'static str, Res)> {
+    if ns == TypeNS {
+        PRIMITIVES
+            .iter()
+            .filter(|x| x.0 == path_str)
+            .copied()
+            .map(|x| if x.0 == "true" || x.0 == "false" { ("bool", x.1) } else { x })
+            .next()
+    } else {
+        None
+    }
 }
 
 fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<DefId> {
diff --git a/src/librustdoc/passes/doc_test_lints.rs b/src/librustdoc/passes/doc_test_lints.rs
index aced7d55281..1fdc4ee247a 100644
--- a/src/librustdoc/passes/doc_test_lints.rs
+++ b/src/librustdoc/passes/doc_test_lints.rs
@@ -43,6 +43,22 @@ impl<'a, 'tcx> DocFolder for PrivateItemDocTestLinter<'a, 'tcx> {
     }
 }
 
+pub(crate) struct Tests {
+    pub(crate) found_tests: usize,
+}
+
+impl Tests {
+    pub(crate) fn new() -> Tests {
+        Tests { found_tests: 0 }
+    }
+}
+
+impl crate::test::Tester for Tests {
+    fn add_test(&mut self, _: String, _: LangString, _: usize) {
+        self.found_tests += 1;
+    }
+}
+
 pub fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
     let hir_id = match cx.as_local_hir_id(item.def_id) {
         Some(hir_id) => hir_id,
@@ -52,17 +68,7 @@ pub fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
         }
     };
 
-    struct Tests {
-        found_tests: usize,
-    }
-
-    impl crate::test::Tester for Tests {
-        fn add_test(&mut self, _: String, _: LangString, _: usize) {
-            self.found_tests += 1;
-        }
-    }
-
-    let mut tests = Tests { found_tests: 0 };
+    let mut tests = Tests::new();
 
     find_testable_code(&dox, &mut tests, ErrorCodes::No, false, None);
 
diff --git a/src/test/rustdoc-ui/coverage/basic.stdout b/src/test/rustdoc-ui/coverage/basic.stdout
index 3e916606316..7e795acc575 100644
--- a/src/test/rustdoc-ui/coverage/basic.stdout
+++ b/src/test/rustdoc-ui/coverage/basic.stdout
@@ -1,7 +1,7 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...est/rustdoc-ui/coverage/basic.rs |          7 |         14 |      50.0% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          7 |         14 |      50.0% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...est/rustdoc-ui/coverage/basic.rs |          7 |         14 |      50.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          7 |         14 |      50.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/doc-examples.rs b/src/test/rustdoc-ui/coverage/doc-examples.rs
new file mode 100644
index 00000000000..cd718f8a34d
--- /dev/null
+++ b/src/test/rustdoc-ui/coverage/doc-examples.rs
@@ -0,0 +1,27 @@
+// compile-flags:-Z unstable-options --show-coverage
+// check-pass
+
+//! This test ensure that only rust code examples are counted.
+
+/// Doc
+///
+/// ```
+/// let x = 2;
+/// ```
+pub struct Foo;
+
+/// Doc
+///
+/// ```text
+/// yolo
+/// ```
+pub trait Bar {}
+
+/// Doc
+///
+/// ```ignore (just for the sake of this test)
+/// let x = 2;
+/// ```
+pub fn foo<T: Bar, D: ::std::fmt::Debug>(a: Foo, b: u32, c: T, d: D) -> u32 {
+    0
+}
diff --git a/src/test/rustdoc-ui/coverage/doc-examples.stdout b/src/test/rustdoc-ui/coverage/doc-examples.stdout
new file mode 100644
index 00000000000..f25cf79a3f3
--- /dev/null
+++ b/src/test/rustdoc-ui/coverage/doc-examples.stdout
@@ -0,0 +1,7 @@
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...tdoc-ui/coverage/doc-examples.rs |          4 |          4 |     100.0% |          2 |      50.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          4 |          4 |     100.0% |          2 |      50.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/empty.stdout b/src/test/rustdoc-ui/coverage/empty.stdout
index 11b514fbfea..2a6a2231e5b 100644
--- a/src/test/rustdoc-ui/coverage/empty.stdout
+++ b/src/test/rustdoc-ui/coverage/empty.stdout
@@ -1,7 +1,7 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...est/rustdoc-ui/coverage/empty.rs |          0 |          1 |       0.0% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          0 |          1 |       0.0% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...est/rustdoc-ui/coverage/empty.rs |          0 |          1 |       0.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          0 |          1 |       0.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/enums.stdout b/src/test/rustdoc-ui/coverage/enums.stdout
index 87e2ad9f20d..dd86f61f8d5 100644
--- a/src/test/rustdoc-ui/coverage/enums.stdout
+++ b/src/test/rustdoc-ui/coverage/enums.stdout
@@ -1,7 +1,7 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...est/rustdoc-ui/coverage/enums.rs |          6 |          8 |      75.0% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          6 |          8 |      75.0% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...est/rustdoc-ui/coverage/enums.rs |          6 |          8 |      75.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          6 |          8 |      75.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/exotic.stdout b/src/test/rustdoc-ui/coverage/exotic.stdout
index 2bacfcfceca..f920a3abd36 100644
--- a/src/test/rustdoc-ui/coverage/exotic.stdout
+++ b/src/test/rustdoc-ui/coverage/exotic.stdout
@@ -1,8 +1,8 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...st/rustdoc-ui/coverage/exotic.rs |          1 |          1 |     100.0% |
-| <anon>                              |          2 |          2 |     100.0% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          3 |          3 |     100.0% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...st/rustdoc-ui/coverage/exotic.rs |          1 |          1 |     100.0% |          0 |       0.0% |
+| <anon>                              |          2 |          2 |     100.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          3 |          3 |     100.0% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/json.stdout b/src/test/rustdoc-ui/coverage/json.stdout
index 63b22a7d94b..7b5b083e158 100644
--- a/src/test/rustdoc-ui/coverage/json.stdout
+++ b/src/test/rustdoc-ui/coverage/json.stdout
@@ -1 +1 @@
-{"$DIR/json.rs":{"total":13,"with_docs":7}}
+{"$DIR/json.rs":{"total":13,"with_docs":7,"with_examples":0}}
diff --git a/src/test/rustdoc-ui/coverage/private.stdout b/src/test/rustdoc-ui/coverage/private.stdout
index 0d4c7c68fd0..bca3d51da59 100644
--- a/src/test/rustdoc-ui/coverage/private.stdout
+++ b/src/test/rustdoc-ui/coverage/private.stdout
@@ -1,7 +1,7 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...t/rustdoc-ui/coverage/private.rs |          4 |          7 |      57.1% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          4 |          7 |      57.1% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...t/rustdoc-ui/coverage/private.rs |          4 |          7 |      57.1% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          4 |          7 |      57.1% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/statics-consts.stdout b/src/test/rustdoc-ui/coverage/statics-consts.stdout
index 8459f90ae7b..31b48cc602a 100644
--- a/src/test/rustdoc-ui/coverage/statics-consts.stdout
+++ b/src/test/rustdoc-ui/coverage/statics-consts.stdout
@@ -1,7 +1,7 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...oc-ui/coverage/statics-consts.rs |          6 |          7 |      85.7% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          6 |          7 |      85.7% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...oc-ui/coverage/statics-consts.rs |          6 |          7 |      85.7% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          6 |          7 |      85.7% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc-ui/coverage/traits.stdout b/src/test/rustdoc-ui/coverage/traits.stdout
index e347a4da0b9..ac63b65023d 100644
--- a/src/test/rustdoc-ui/coverage/traits.stdout
+++ b/src/test/rustdoc-ui/coverage/traits.stdout
@@ -1,7 +1,7 @@
-+-------------------------------------+------------+------------+------------+
-| File                                | Documented |      Total | Percentage |
-+-------------------------------------+------------+------------+------------+
-| ...st/rustdoc-ui/coverage/traits.rs |          6 |          7 |      85.7% |
-+-------------------------------------+------------+------------+------------+
-| Total                               |          6 |          7 |      85.7% |
-+-------------------------------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+------------+
+| File                                | Documented |      Total | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+------------+
+| ...st/rustdoc-ui/coverage/traits.rs |          6 |          7 |      85.7% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
+| Total                               |          6 |          7 |      85.7% |          0 |       0.0% |
++-------------------------------------+------------+------------+------------+------------+------------+
diff --git a/src/test/rustdoc/intra-doc-link-true-false.rs b/src/test/rustdoc/intra-doc-link-true-false.rs
new file mode 100644
index 00000000000..7b21e934147
--- /dev/null
+++ b/src/test/rustdoc/intra-doc-link-true-false.rs
@@ -0,0 +1,10 @@
+#![deny(broken_intra_doc_links)]
+#![crate_name = "foo"]
+
+// ignore-tidy-linelength
+
+// @has foo/index.html
+// @has - '//*[@id="main"]//a[@href="https://doc.rust-lang.org/nightly/std/primitive.bool.html"]' 'true'
+// @has - '//*[@id="main"]//a[@href="https://doc.rust-lang.org/nightly/std/primitive.bool.html"]' 'false'
+
+//! A `bool` is either [`true`] or [`false`].
diff --git a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs
index 467daef63f6..44421b077fa 100644
--- a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs
+++ b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs
@@ -5,10 +5,8 @@ fn test_and() {
     let b = false;
 
     let _ = a and b; //~ ERROR `and` is not a logical operator
-                     //~| ERROR `and` is not a logical operator
 
     if a and b { //~ ERROR `and` is not a logical operator
-                 //~| ERROR `and` is not a logical operator
         println!("both");
     }
 
@@ -20,10 +18,8 @@ fn test_or() {
     let b = false;
 
     let _ = a or b; //~ ERROR `or` is not a logical operator
-                    //~| ERROR `or` is not a logical operator
 
     if a or b { //~ ERROR `or` is not a logical operator
-                //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -32,7 +28,6 @@ fn test_and_par() {
     let a = true;
     let b = false;
     if (a and b) {  //~ ERROR `and` is not a logical operator
-                    //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -41,7 +36,6 @@ fn test_or_par() {
     let a = true;
     let b = false;
     if (a or b) {  //~ ERROR `or` is not a logical operator
-                   //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -50,7 +44,6 @@ fn test_while_and() {
     let a = true;
     let b = false;
     while a and b {  //~ ERROR `and` is not a logical operator
-                     //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -59,7 +52,6 @@ fn test_while_or() {
     let a = true;
     let b = false;
     while a or b { //~ ERROR `or` is not a logical operator
-                   //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
diff --git a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr
index e8731cf238e..528c62f501e 100644
--- a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr
+++ b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr
@@ -7,23 +7,7 @@ LL |     let _ = a and b;
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:7:15
-   |
-LL |     let _ = a and b;
-   |               ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:10:10
-   |
-LL |     if a and b {
-   |          ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:10:10
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:9:10
    |
 LL |     if a and b {
    |          ^^^ help: use `&&` to perform logical conjunction
@@ -31,7 +15,7 @@ LL |     if a and b {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:22:15
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:20:15
    |
 LL |     let _ = a or b;
    |               ^^ help: use `||` to perform logical disjunction
@@ -39,23 +23,7 @@ LL |     let _ = a or b;
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:22:15
-   |
-LL |     let _ = a or b;
-   |               ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:25:10
-   |
-LL |     if a or b {
-   |          ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:25:10
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:22:10
    |
 LL |     if a or b {
    |          ^^ help: use `||` to perform logical disjunction
@@ -63,31 +31,15 @@ LL |     if a or b {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:34:11
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:30:11
    |
 LL |     if (a and b) {
    |           ^^^ help: use `&&` to perform logical conjunction
    |
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:34:11
-   |
-LL |     if (a and b) {
-   |           ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:43:11
-   |
-LL |     if (a or b) {
-   |           ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:43:11
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:38:11
    |
 LL |     if (a or b) {
    |           ^^ help: use `||` to perform logical disjunction
@@ -95,31 +47,15 @@ LL |     if (a or b) {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:52:13
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:46:13
    |
 LL |     while a and b {
    |             ^^^ help: use `&&` to perform logical conjunction
    |
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:52:13
-   |
-LL |     while a and b {
-   |             ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:61:13
-   |
-LL |     while a or b {
-   |             ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:61:13
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:54:13
    |
 LL |     while a or b {
    |             ^^ help: use `||` to perform logical disjunction
@@ -127,13 +63,13 @@ LL |     while a or b {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error[E0308]: mismatched types
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:15:33
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:13:33
    |
 LL |     let _recovery_witness: () = 0;
    |                            --   ^ expected `()`, found integer
    |                            |
    |                            expected due to this
 
-error: aborting due to 17 previous errors
+error: aborting due to 9 previous errors
 
 For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/did_you_mean/issue-54109-without-witness.fixed b/src/test/ui/did_you_mean/issue-54109-without-witness.fixed
index 21471d75c82..5079a37f4da 100644
--- a/src/test/ui/did_you_mean/issue-54109-without-witness.fixed
+++ b/src/test/ui/did_you_mean/issue-54109-without-witness.fixed
@@ -11,10 +11,8 @@ fn test_and() {
     let b = false;
 
     let _ = a && b; //~ ERROR `and` is not a logical operator
-                     //~| ERROR `and` is not a logical operator
 
     if a && b { //~ ERROR `and` is not a logical operator
-                 //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -24,10 +22,8 @@ fn test_or() {
     let b = false;
 
     let _ = a || b; //~ ERROR `or` is not a logical operator
-                    //~| ERROR `or` is not a logical operator
 
     if a || b { //~ ERROR `or` is not a logical operator
-                //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -36,7 +32,6 @@ fn test_and_par() {
     let a = true;
     let b = false;
     if (a && b) {  //~ ERROR `and` is not a logical operator
-                    //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -45,7 +40,6 @@ fn test_or_par() {
     let a = true;
     let b = false;
     if (a || b) {  //~ ERROR `or` is not a logical operator
-                   //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -54,7 +48,6 @@ fn test_while_and() {
     let a = true;
     let b = false;
     while a && b {  //~ ERROR `and` is not a logical operator
-                     //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -63,7 +56,6 @@ fn test_while_or() {
     let a = true;
     let b = false;
     while a || b { //~ ERROR `or` is not a logical operator
-                   //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
diff --git a/src/test/ui/did_you_mean/issue-54109-without-witness.rs b/src/test/ui/did_you_mean/issue-54109-without-witness.rs
index bb9a3a19596..00660a938d5 100644
--- a/src/test/ui/did_you_mean/issue-54109-without-witness.rs
+++ b/src/test/ui/did_you_mean/issue-54109-without-witness.rs
@@ -11,10 +11,8 @@ fn test_and() {
     let b = false;
 
     let _ = a and b; //~ ERROR `and` is not a logical operator
-                     //~| ERROR `and` is not a logical operator
 
     if a and b { //~ ERROR `and` is not a logical operator
-                 //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -24,10 +22,8 @@ fn test_or() {
     let b = false;
 
     let _ = a or b; //~ ERROR `or` is not a logical operator
-                    //~| ERROR `or` is not a logical operator
 
     if a or b { //~ ERROR `or` is not a logical operator
-                //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -36,7 +32,6 @@ fn test_and_par() {
     let a = true;
     let b = false;
     if (a and b) {  //~ ERROR `and` is not a logical operator
-                    //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -45,7 +40,6 @@ fn test_or_par() {
     let a = true;
     let b = false;
     if (a or b) {  //~ ERROR `or` is not a logical operator
-                   //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -54,7 +48,6 @@ fn test_while_and() {
     let a = true;
     let b = false;
     while a and b {  //~ ERROR `and` is not a logical operator
-                     //~| ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -63,7 +56,6 @@ fn test_while_or() {
     let a = true;
     let b = false;
     while a or b { //~ ERROR `or` is not a logical operator
-                   //~| ERROR `or` is not a logical operator
         println!("both");
     }
 }
diff --git a/src/test/ui/did_you_mean/issue-54109-without-witness.stderr b/src/test/ui/did_you_mean/issue-54109-without-witness.stderr
index fe48af592db..0350890c1fd 100644
--- a/src/test/ui/did_you_mean/issue-54109-without-witness.stderr
+++ b/src/test/ui/did_you_mean/issue-54109-without-witness.stderr
@@ -7,23 +7,7 @@ LL |     let _ = a and b;
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:13:15
-   |
-LL |     let _ = a and b;
-   |               ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:16:10
-   |
-LL |     if a and b {
-   |          ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:16:10
+  --> $DIR/issue-54109-without-witness.rs:15:10
    |
 LL |     if a and b {
    |          ^^^ help: use `&&` to perform logical conjunction
@@ -31,7 +15,7 @@ LL |     if a and b {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:26:15
+  --> $DIR/issue-54109-without-witness.rs:24:15
    |
 LL |     let _ = a or b;
    |               ^^ help: use `||` to perform logical disjunction
@@ -39,23 +23,7 @@ LL |     let _ = a or b;
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:26:15
-   |
-LL |     let _ = a or b;
-   |               ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:29:10
-   |
-LL |     if a or b {
-   |          ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:29:10
+  --> $DIR/issue-54109-without-witness.rs:26:10
    |
 LL |     if a or b {
    |          ^^ help: use `||` to perform logical disjunction
@@ -63,31 +31,15 @@ LL |     if a or b {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:38:11
+  --> $DIR/issue-54109-without-witness.rs:34:11
    |
 LL |     if (a and b) {
    |           ^^^ help: use `&&` to perform logical conjunction
    |
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:38:11
-   |
-LL |     if (a and b) {
-   |           ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:47:11
-   |
-LL |     if (a or b) {
-   |           ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:47:11
+  --> $DIR/issue-54109-without-witness.rs:42:11
    |
 LL |     if (a or b) {
    |           ^^ help: use `||` to perform logical disjunction
@@ -95,36 +47,20 @@ LL |     if (a or b) {
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
 error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:56:13
+  --> $DIR/issue-54109-without-witness.rs:50:13
    |
 LL |     while a and b {
    |             ^^^ help: use `&&` to perform logical conjunction
    |
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: `and` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:56:13
-   |
-LL |     while a and b {
-   |             ^^^ help: use `&&` to perform logical conjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
-error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:65:13
-   |
-LL |     while a or b {
-   |             ^^ help: use `||` to perform logical disjunction
-   |
-   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
-
 error: `or` is not a logical operator
-  --> $DIR/issue-54109-without-witness.rs:65:13
+  --> $DIR/issue-54109-without-witness.rs:58:13
    |
 LL |     while a or b {
    |             ^^ help: use `||` to perform logical disjunction
    |
    = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: aborting due to 16 previous errors
+error: aborting due to 8 previous errors
 
diff --git a/src/test/ui/issues/issue-75599.rs b/src/test/ui/issues/issue-75599.rs
new file mode 100644
index 00000000000..0857676e4ed
--- /dev/null
+++ b/src/test/ui/issues/issue-75599.rs
@@ -0,0 +1,24 @@
+// check-pass
+#![allow(non_upper_case_globals)]
+
+const or: usize = 1;
+const and: usize = 2;
+
+mod or {
+    pub const X: usize = 3;
+}
+
+mod and {
+    pub const X: usize = 4;
+}
+
+fn main() {
+    match 0 {
+        0 => {}
+        or => {}
+        and => {}
+        or::X => {}
+        and::X => {}
+        _ => {}
+    }
+}
diff --git a/src/test/ui/lint/clashing-extern-fn-recursion.rs b/src/test/ui/lint/clashing-extern-fn-recursion.rs
new file mode 100644
index 00000000000..ab0fd0a2e70
--- /dev/null
+++ b/src/test/ui/lint/clashing-extern-fn-recursion.rs
@@ -0,0 +1,119 @@
+// check-pass
+//
+// This tests checks that clashing_extern_declarations handles types that are recursive through a
+// pointer or ref argument. See #75512.
+
+#![crate_type = "lib"]
+
+mod raw_ptr_recursion {
+    mod a {
+        #[repr(C)]
+        struct Pointy {
+            pointy: *const Pointy,
+        }
+
+        extern "C" {
+            fn run_pointy(pointy: Pointy);
+        }
+    }
+    mod b {
+        #[repr(C)]
+        struct Pointy {
+            pointy: *const Pointy,
+        }
+
+        extern "C" {
+            fn run_pointy(pointy: Pointy);
+        }
+    }
+}
+
+mod raw_ptr_recursion_once_removed {
+    mod a {
+        #[repr(C)]
+        struct Pointy1 {
+            pointy_two: *const Pointy2,
+        }
+
+        #[repr(C)]
+        struct Pointy2 {
+            pointy_one: *const Pointy1,
+        }
+
+        extern "C" {
+            fn run_pointy2(pointy: Pointy2);
+        }
+    }
+
+    mod b {
+        #[repr(C)]
+        struct Pointy1 {
+            pointy_two: *const Pointy2,
+        }
+
+        #[repr(C)]
+        struct Pointy2 {
+            pointy_one: *const Pointy1,
+        }
+
+        extern "C" {
+            fn run_pointy2(pointy: Pointy2);
+        }
+    }
+}
+
+mod ref_recursion {
+    mod a {
+        #[repr(C)]
+        struct Reffy<'a> {
+            reffy: &'a Reffy<'a>,
+        }
+
+        extern "C" {
+            fn reffy_recursion(reffy: Reffy);
+        }
+    }
+    mod b {
+        #[repr(C)]
+        struct Reffy<'a> {
+            reffy: &'a Reffy<'a>,
+        }
+
+        extern "C" {
+            fn reffy_recursion(reffy: Reffy);
+        }
+    }
+}
+
+mod ref_recursion_once_removed {
+    mod a {
+        #[repr(C)]
+        struct Reffy1<'a> {
+            reffy: &'a Reffy2<'a>,
+        }
+
+        struct Reffy2<'a> {
+            reffy: &'a Reffy1<'a>,
+        }
+
+        extern "C" {
+            #[allow(improper_ctypes)]
+            fn reffy_once_removed(reffy: Reffy1);
+        }
+    }
+    mod b {
+        #[repr(C)]
+        struct Reffy1<'a> {
+            reffy: &'a Reffy2<'a>,
+        }
+
+        struct Reffy2<'a> {
+            reffy: &'a Reffy1<'a>,
+        }
+
+        extern "C" {
+            #[allow(improper_ctypes)]
+            fn reffy_once_removed(reffy: Reffy1);
+        }
+    }
+}