about summary refs log tree commit diff
path: root/clippy_lints/src/doc/missing_headers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src/doc/missing_headers.rs')
-rw-r--r--clippy_lints/src/doc/missing_headers.rs51
1 files changed, 46 insertions, 5 deletions
diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs
index e75abf28bac..039937e0207 100644
--- a/clippy_lints/src/doc/missing_headers.rs
+++ b/clippy_lints/src/doc/missing_headers.rs
@@ -1,11 +1,14 @@
 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::ty::{implements_trait_with_env, is_type_diagnostic_item};
-use clippy_utils::{is_doc_hidden, return_ty};
+use clippy_utils::macros::{is_panic, root_macro_call_first_node};
+use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item};
+use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty};
 use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
 use rustc_lint::LateContext;
 use rustc_middle::ty;
 use rustc_span::{Span, sym};
+use std::ops::ControlFlow;
 
 pub fn check(
     cx: &LateContext<'_>,
@@ -13,7 +16,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 +48,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) = find_panic(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 +94,39 @@ pub fn check(
         }
     }
 }
+
+fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> {
+    let mut panic_span = None;
+    let typeck = cx.tcx.typeck_body(body_id);
+    for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| {
+        if let Some(macro_call) = root_macro_call_first_node(cx, expr)
+            && (is_panic(cx, macro_call.def_id)
+                || matches!(
+                    cx.tcx.get_diagnostic_name(macro_call.def_id),
+                    Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro)
+                ))
+            && !cx.tcx.hir_is_inside_const_context(expr.hir_id)
+            && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
+            && panic_span.is_none()
+        {
+            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_else(|| method_chain_args(expr, &["expect"]))
+            && let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs()
+            && matches!(
+                get_type_diagnostic_name(cx, receiver_ty),
+                Some(sym::Option | sym::Result)
+            )
+            && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
+            && panic_span.is_none()
+        {
+            panic_span = Some(expr.span);
+        }
+
+        // Visit all nodes to fulfill any `#[expect]`s after the first linted panic
+        ControlFlow::<!>::Continue(())
+    });
+    panic_span
+}