1- use crate :: utils:: { implements_trait, is_entrypoint_fn, is_type_diagnostic_item, return_ty, span_lint} ;
1+ use crate :: utils:: {
2+ implements_trait, is_entrypoint_fn, is_type_diagnostic_item, match_panic_def_id, method_chain_args, return_ty,
3+ span_lint, span_lint_and_note,
4+ } ;
25use if_chain:: if_chain;
36use itertools:: Itertools ;
47use rustc_ast:: ast:: { Async , AttrKind , Attribute , FnKind , FnRetTy , ItemKind } ;
@@ -8,7 +11,10 @@ use rustc_data_structures::sync::Lrc;
811use rustc_errors:: emitter:: EmitterWriter ;
912use rustc_errors:: Handler ;
1013use rustc_hir as hir;
14+ use rustc_hir:: intravisit:: { self , NestedVisitorMap , Visitor } ;
15+ use rustc_hir:: { Expr , ExprKind , QPath } ;
1116use rustc_lint:: { LateContext , LateLintPass } ;
17+ use rustc_middle:: hir:: map:: Map ;
1218use rustc_middle:: lint:: in_external_macro;
1319use rustc_middle:: ty;
1420use rustc_parse:: maybe_new_parser_from_source_str;
@@ -122,6 +128,37 @@ declare_clippy_lint! {
122128 "`pub fn` returns `Result` without `# Errors` in doc comment"
123129}
124130
131+ declare_clippy_lint ! {
132+ /// **What it does:** Checks the doc comments of publicly visible functions that
133+ /// may panic and warns if there is no `# Panics` section.
134+ ///
135+ /// **Why is this bad?** Documenting the scenarios in which panicking occurs
136+ /// can help callers who do not want to panic to avoid those situations.
137+ ///
138+ /// **Known problems:** None.
139+ ///
140+ /// **Examples:**
141+ ///
142+ /// Since the following function may panic it has a `# Panics` section in
143+ /// its doc comment:
144+ ///
145+ /// ```rust
146+ /// /// # Panics
147+ /// ///
148+ /// /// Will panic if y is 0
149+ /// pub fn divide_by(x: i32, y: i32) -> i32 {
150+ /// if y == 0 {
151+ /// panic!("Cannot divide by 0")
152+ /// } else {
153+ /// x / y
154+ /// }
155+ /// }
156+ /// ```
157+ pub MISSING_PANICS_DOC ,
158+ pedantic,
159+ "`pub fn` may panic without `# Panics` in doc comment"
160+ }
161+
125162declare_clippy_lint ! {
126163 /// **What it does:** Checks for `fn main() { .. }` in doctests
127164 ///
@@ -166,7 +203,9 @@ impl DocMarkdown {
166203 }
167204}
168205
169- impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC , MISSING_ERRORS_DOC , NEEDLESS_DOCTEST_MAIN ] ) ;
206+ impl_lint_pass ! ( DocMarkdown =>
207+ [ DOC_MARKDOWN , MISSING_SAFETY_DOC , MISSING_ERRORS_DOC , MISSING_PANICS_DOC , NEEDLESS_DOCTEST_MAIN ]
208+ ) ;
170209
171210impl < ' tcx > LateLintPass < ' tcx > for DocMarkdown {
172211 fn check_crate ( & mut self , cx : & LateContext < ' tcx > , krate : & ' tcx hir:: Crate < ' _ > ) {
@@ -180,7 +219,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
180219 if !( is_entrypoint_fn ( cx, cx. tcx . hir ( ) . local_def_id ( item. hir_id ) . to_def_id ( ) )
181220 || in_external_macro ( cx. tcx . sess , item. span ) )
182221 {
183- lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers, Some ( body_id) ) ;
222+ let body = cx. tcx . hir ( ) . body ( body_id) ;
223+ let impl_item_def_id = cx. tcx . hir ( ) . local_def_id ( item. hir_id ) ;
224+ let mut fpu = FindPanicUnwrap {
225+ cx,
226+ typeck_results : cx. tcx . typeck ( impl_item_def_id) ,
227+ panic_span : None ,
228+ } ;
229+ fpu. visit_expr ( & body. value ) ;
230+ lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers, Some ( body_id) , fpu. panic_span ) ;
184231 }
185232 } ,
186233 hir:: ItemKind :: Impl ( ref impl_) => {
@@ -200,7 +247,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
200247 let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
201248 if let hir:: TraitItemKind :: Fn ( ref sig, ..) = item. kind {
202249 if !in_external_macro ( cx. tcx . sess , item. span ) {
203- lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers, None ) ;
250+ lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers, None , None ) ;
204251 }
205252 }
206253 }
@@ -211,7 +258,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
211258 return ;
212259 }
213260 if let hir:: ImplItemKind :: Fn ( ref sig, body_id) = item. kind {
214- lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers, Some ( body_id) ) ;
261+ let body = cx. tcx . hir ( ) . body ( body_id) ;
262+ let impl_item_def_id = cx. tcx . hir ( ) . local_def_id ( item. hir_id ) ;
263+ let mut fpu = FindPanicUnwrap {
264+ cx,
265+ typeck_results : cx. tcx . typeck ( impl_item_def_id) ,
266+ panic_span : None ,
267+ } ;
268+ fpu. visit_expr ( & body. value ) ;
269+ lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers, Some ( body_id) , fpu. panic_span ) ;
215270 }
216271 }
217272}
@@ -223,6 +278,7 @@ fn lint_for_missing_headers<'tcx>(
223278 sig : & hir:: FnSig < ' _ > ,
224279 headers : DocHeaders ,
225280 body_id : Option < hir:: BodyId > ,
281+ panic_span : Option < Span > ,
226282) {
227283 if !cx. access_levels . is_exported ( hir_id) {
228284 return ; // Private functions do not require doc comments
@@ -235,6 +291,16 @@ fn lint_for_missing_headers<'tcx>(
235291 "unsafe function's docs miss `# Safety` section" ,
236292 ) ;
237293 }
294+ if !headers. panics && panic_span. is_some ( ) {
295+ span_lint_and_note (
296+ cx,
297+ MISSING_PANICS_DOC ,
298+ span,
299+ "docs for function which may panic missing `# Panics` section" ,
300+ panic_span,
301+ "first possible panic found here" ,
302+ ) ;
303+ }
238304 if !headers. errors {
239305 if is_type_diagnostic_item ( cx, return_ty ( cx, hir_id) , sym:: result_type) {
240306 span_lint (
@@ -321,6 +387,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
321387struct DocHeaders {
322388 safety : bool ,
323389 errors : bool ,
390+ panics : bool ,
324391}
325392
326393fn check_attrs < ' a > ( cx : & LateContext < ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ Attribute ] ) -> DocHeaders {
@@ -338,6 +405,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
338405 return DocHeaders {
339406 safety : true ,
340407 errors : true ,
408+ panics : true ,
341409 } ;
342410 }
343411 }
@@ -353,6 +421,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
353421 return DocHeaders {
354422 safety : false ,
355423 errors : false ,
424+ panics : false ,
356425 } ;
357426 }
358427
@@ -394,6 +463,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
394463 let mut headers = DocHeaders {
395464 safety : false ,
396465 errors : false ,
466+ panics : false ,
397467 } ;
398468 let mut in_code = false ;
399469 let mut in_link = None ;
@@ -439,6 +509,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
439509 }
440510 headers. safety |= in_heading && text. trim ( ) == "Safety" ;
441511 headers. errors |= in_heading && text. trim ( ) == "Errors" ;
512+ headers. panics |= in_heading && text. trim ( ) == "Panics" ;
442513 let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & range. start ) ) {
443514 Ok ( o) => o,
444515 Err ( e) => e - 1 ,
@@ -609,3 +680,47 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
609680 ) ;
610681 }
611682}
683+
684+ struct FindPanicUnwrap < ' a , ' tcx > {
685+ cx : & ' a LateContext < ' tcx > ,
686+ panic_span : Option < Span > ,
687+ typeck_results : & ' tcx ty:: TypeckResults < ' tcx > ,
688+ }
689+
690+ impl < ' a , ' tcx > Visitor < ' tcx > for FindPanicUnwrap < ' a , ' tcx > {
691+ type Map = Map < ' tcx > ;
692+
693+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' _ > ) {
694+ if self . panic_span . is_some ( ) {
695+ return ;
696+ }
697+
698+ // check for `begin_panic`
699+ if_chain ! {
700+ if let ExprKind :: Call ( ref func_expr, _) = expr. kind;
701+ if let ExprKind :: Path ( QPath :: Resolved ( _, ref path) ) = func_expr. kind;
702+ if let Some ( path_def_id) = path. res. opt_def_id( ) ;
703+ if match_panic_def_id( self . cx, path_def_id) ;
704+ then {
705+ self . panic_span = Some ( expr. span) ;
706+ }
707+ }
708+
709+ // check for `unwrap`
710+ if let Some ( arglists) = method_chain_args ( expr, & [ "unwrap" ] ) {
711+ let reciever_ty = self . typeck_results . expr_ty ( & arglists[ 0 ] [ 0 ] ) . peel_refs ( ) ;
712+ if is_type_diagnostic_item ( self . cx , reciever_ty, sym:: option_type)
713+ || is_type_diagnostic_item ( self . cx , reciever_ty, sym:: result_type)
714+ {
715+ self . panic_span = Some ( expr. span ) ;
716+ }
717+ }
718+
719+ // and check sub-expressions
720+ intravisit:: walk_expr ( self , expr) ;
721+ }
722+
723+ fn nested_visit_map ( & mut self ) -> NestedVisitorMap < Self :: Map > {
724+ NestedVisitorMap :: OnlyBodies ( self . cx . tcx . hir ( ) )
725+ }
726+ }
0 commit comments