about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Macleod <alex@macleod.io>2025-03-14 14:47:03 +0000
committerAlex Macleod <alex@macleod.io>2025-03-31 12:52:04 +0000
commitce39784f13022efa0f3b7f23c63150ae51c34d44 (patch)
treefde29cf9093643a6ecdb2d8dbf8d3fcc1cfdd6c2
parentbb0d09b220dc94fa956d59b395c825b217a8070e (diff)
downloadrust-ce39784f13022efa0f3b7f23c63150ae51c34d44.tar.gz
rust-ce39784f13022efa0f3b7f23c63150ae51c34d44.zip
Move `FindPanicUnwrap` to `missing_headers.rs`
-rw-r--r--clippy_lints/src/doc/missing_headers.rs78
-rw-r--r--clippy_lints/src/doc/mod.rs99
2 files changed, 79 insertions, 98 deletions
diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs
index e75abf28bac..65d2ff83c25 100644
--- a/clippy_lints/src/doc/missing_headers.rs
+++ b/clippy_lints/src/doc/missing_headers.rs
@@ -1,9 +1,13 @@
 use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
 use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
 use clippy_utils::ty::{implements_trait_with_env, is_type_diagnostic_item};
-use clippy_utils::{is_doc_hidden, return_ty};
-use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
+use clippy_utils::visitors::Visitable;
+use clippy_utils::{is_doc_hidden, method_chain_args, return_ty};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{AnonConst, BodyId, Expr, FnSig, OwnerId, Safety};
 use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter::OnlyBodies;
 use rustc_middle::ty;
 use rustc_span::{Span, sym};
 
@@ -13,7 +17,6 @@ pub fn check(
     sig: FnSig<'_>,
     headers: DocHeaders,
     body_id: Option<BodyId>,
-    panic_info: Option<(Span, bool)>,
     check_private_items: bool,
 ) {
     if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) {
@@ -46,13 +49,16 @@ pub fn check(
         ),
         _ => (),
     }
-    if !headers.panics && panic_info.is_some_and(|el| !el.1) {
+    if !headers.panics
+        && let Some(body_id) = body_id
+        && let Some((panic_span, false)) = FindPanicUnwrap::find_span(cx, body_id)
+    {
         span_lint_and_note(
             cx,
             MISSING_PANICS_DOC,
             span,
             "docs for function which may panic missing `# Panics` section",
-            panic_info.map(|el| el.0),
+            Some(panic_span),
             "first possible panic found here",
         );
     }
@@ -89,3 +95,65 @@ pub fn check(
         }
     }
 }
+
+struct FindPanicUnwrap<'a, 'tcx> {
+    cx: &'a LateContext<'tcx>,
+    is_const: bool,
+    panic_span: Option<Span>,
+    typeck_results: &'tcx ty::TypeckResults<'tcx>,
+}
+
+impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> {
+    pub fn find_span(cx: &'a LateContext<'tcx>, body_id: BodyId) -> Option<(Span, bool)> {
+        let mut vis = Self {
+            cx,
+            is_const: false,
+            panic_span: None,
+            typeck_results: cx.tcx.typeck_body(body_id),
+        };
+        cx.tcx.hir_body(body_id).visit(&mut vis);
+        vis.panic_span.map(|el| (el, vis.is_const))
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
+    type NestedFilter = OnlyBodies;
+
+    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+        if self.panic_span.is_some() {
+            return;
+        }
+
+        if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
+            if is_panic(self.cx, macro_call.def_id)
+                || matches!(
+                    self.cx.tcx.item_name(macro_call.def_id).as_str(),
+                    "assert" | "assert_eq" | "assert_ne"
+                )
+            {
+                self.is_const = self.cx.tcx.hir_is_inside_const_context(expr.hir_id);
+                self.panic_span = Some(macro_call.span);
+            }
+        }
+
+        // check for `unwrap` and `expect` for both `Option` and `Result`
+        if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) {
+            let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
+            if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
+                || is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
+            {
+                self.panic_span = Some(expr.span);
+            }
+        }
+
+        // and check sub-expressions
+        intravisit::walk_expr(self, expr);
+    }
+
+    // Panics in const blocks will cause compilation to fail.
+    fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
+        self.cx.tcx
+    }
+}
diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs
index d0075d01eea..ce0e3cae51c 100644
--- a/clippy_lints/src/doc/mod.rs
+++ b/clippy_lints/src/doc/mod.rs
@@ -3,11 +3,8 @@
 use clippy_config::Conf;
 use clippy_utils::attrs::is_doc_hidden;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
-use clippy_utils::macros::{is_panic, root_macro_call_first_node};
 use clippy_utils::source::snippet_opt;
-use clippy_utils::ty::is_type_diagnostic_item;
-use clippy_utils::visitors::Visitable;
-use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
+use clippy_utils::{is_entrypoint_fn, is_trait_impl_item};
 use pulldown_cmark::Event::{
     Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
     TaskListMarker, Text,
@@ -16,18 +13,15 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
 use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
-use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
+use rustc_hir::{Attribute, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
-use rustc_middle::hir::nested_filter;
-use rustc_middle::ty;
 use rustc_resolve::rustdoc::{
     DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range,
     span_of_fragments,
 };
 use rustc_session::impl_lint_pass;
+use rustc_span::Span;
 use rustc_span::edition::Edition;
-use rustc_span::{Span, sym};
 use std::ops::Range;
 use url::Url;
 
@@ -657,20 +651,16 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
                     self.check_private_items,
                 );
                 match item.kind {
-                    ItemKind::Fn { sig, body: body_id, .. } => {
+                    ItemKind::Fn { sig, body, .. } => {
                         if !(is_entrypoint_fn(cx, item.owner_id.to_def_id())
                             || item.span.in_external_macro(cx.tcx.sess.source_map()))
                         {
-                            let body = cx.tcx.hir_body(body_id);
-
-                            let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value);
                             missing_headers::check(
                                 cx,
                                 item.owner_id,
                                 sig,
                                 headers,
-                                Some(body_id),
-                                panic_info,
+                                Some(body),
                                 self.check_private_items,
                             );
                         }
@@ -697,15 +687,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
                 if let TraitItemKind::Fn(sig, ..) = trait_item.kind
                     && !trait_item.span.in_external_macro(cx.tcx.sess.source_map())
                 {
-                    missing_headers::check(
-                        cx,
-                        trait_item.owner_id,
-                        sig,
-                        headers,
-                        None,
-                        None,
-                        self.check_private_items,
-                    );
+                    missing_headers::check(cx, trait_item.owner_id, sig, headers, None, self.check_private_items);
                 }
             },
             Node::ImplItem(impl_item) => {
@@ -713,16 +695,12 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
                     && !impl_item.span.in_external_macro(cx.tcx.sess.source_map())
                     && !is_trait_impl_item(cx, impl_item.hir_id())
                 {
-                    let body = cx.tcx.hir_body(body_id);
-
-                    let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(impl_item.owner_id), body.value);
                     missing_headers::check(
                         cx,
                         impl_item.owner_id,
                         sig,
                         headers,
                         Some(body_id),
-                        panic_span,
                         self.check_private_items,
                     );
                 }
@@ -1168,71 +1146,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     headers
 }
 
-struct FindPanicUnwrap<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    is_const: bool,
-    panic_span: Option<Span>,
-    typeck_results: &'tcx ty::TypeckResults<'tcx>,
-}
-
-impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> {
-    pub fn find_span(
-        cx: &'a LateContext<'tcx>,
-        typeck_results: &'tcx ty::TypeckResults<'tcx>,
-        body: impl Visitable<'tcx>,
-    ) -> Option<(Span, bool)> {
-        let mut vis = Self {
-            cx,
-            is_const: false,
-            panic_span: None,
-            typeck_results,
-        };
-        body.visit(&mut vis);
-        vis.panic_span.map(|el| (el, vis.is_const))
-    }
-}
-
-impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
-    type NestedFilter = nested_filter::OnlyBodies;
-
-    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
-        if self.panic_span.is_some() {
-            return;
-        }
-
-        if let Some(macro_call) = root_macro_call_first_node(self.cx, expr)
-            && (is_panic(self.cx, macro_call.def_id)
-                || matches!(
-                    self.cx.tcx.item_name(macro_call.def_id).as_str(),
-                    "assert" | "assert_eq" | "assert_ne"
-                ))
-        {
-            self.is_const = self.cx.tcx.hir_is_inside_const_context(expr.hir_id);
-            self.panic_span = Some(macro_call.span);
-        }
-
-        // check for `unwrap` and `expect` for both `Option` and `Result`
-        if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) {
-            let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
-            if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
-                || is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
-            {
-                self.panic_span = Some(expr.span);
-            }
-        }
-
-        // and check sub-expressions
-        intravisit::walk_expr(self, expr);
-    }
-
-    // Panics in const blocks will cause compilation to fail.
-    fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
-
-    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
-        self.cx.tcx
-    }
-}
-
 #[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
 fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
     if range.end < range.start {