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
+ } ;
2
5
use if_chain:: if_chain;
3
6
use itertools:: Itertools ;
4
7
use rustc_ast:: ast:: { Async , AttrKind , Attribute , FnKind , FnRetTy , ItemKind } ;
@@ -8,7 +11,10 @@ use rustc_data_structures::sync::Lrc;
8
11
use rustc_errors:: emitter:: EmitterWriter ;
9
12
use rustc_errors:: Handler ;
10
13
use rustc_hir as hir;
14
+ use rustc_hir:: intravisit:: { self , NestedVisitorMap , Visitor } ;
15
+ use rustc_hir:: { Expr , ExprKind , QPath } ;
11
16
use rustc_lint:: { LateContext , LateLintPass } ;
17
+ use rustc_middle:: hir:: map:: Map ;
12
18
use rustc_middle:: lint:: in_external_macro;
13
19
use rustc_middle:: ty;
14
20
use rustc_parse:: maybe_new_parser_from_source_str;
@@ -122,6 +128,37 @@ declare_clippy_lint! {
122
128
"`pub fn` returns `Result` without `# Errors` in doc comment"
123
129
}
124
130
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
+
125
162
declare_clippy_lint ! {
126
163
/// **What it does:** Checks for `fn main() { .. }` in doctests
127
164
///
@@ -166,7 +203,9 @@ impl DocMarkdown {
166
203
}
167
204
}
168
205
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
+ ) ;
170
209
171
210
impl < ' tcx > LateLintPass < ' tcx > for DocMarkdown {
172
211
fn check_crate ( & mut self , cx : & LateContext < ' tcx > , krate : & ' tcx hir:: Crate < ' _ > ) {
@@ -180,7 +219,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
180
219
if !( is_entrypoint_fn ( cx, cx. tcx . hir ( ) . local_def_id ( item. hir_id ) . to_def_id ( ) )
181
220
|| in_external_macro ( cx. tcx . sess , item. span ) )
182
221
{
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 ) ;
184
231
}
185
232
} ,
186
233
hir:: ItemKind :: Impl ( ref impl_) => {
@@ -200,7 +247,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
200
247
let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
201
248
if let hir:: TraitItemKind :: Fn ( ref sig, ..) = item. kind {
202
249
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 ) ;
204
251
}
205
252
}
206
253
}
@@ -211,7 +258,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
211
258
return ;
212
259
}
213
260
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 ) ;
215
270
}
216
271
}
217
272
}
@@ -223,6 +278,7 @@ fn lint_for_missing_headers<'tcx>(
223
278
sig : & hir:: FnSig < ' _ > ,
224
279
headers : DocHeaders ,
225
280
body_id : Option < hir:: BodyId > ,
281
+ panic_span : Option < Span > ,
226
282
) {
227
283
if !cx. access_levels . is_exported ( hir_id) {
228
284
return ; // Private functions do not require doc comments
@@ -235,6 +291,16 @@ fn lint_for_missing_headers<'tcx>(
235
291
"unsafe function's docs miss `# Safety` section" ,
236
292
) ;
237
293
}
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
+ }
238
304
if !headers. errors {
239
305
if is_type_diagnostic_item ( cx, return_ty ( cx, hir_id) , sym:: result_type) {
240
306
span_lint (
@@ -321,6 +387,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
321
387
struct DocHeaders {
322
388
safety : bool ,
323
389
errors : bool ,
390
+ panics : bool ,
324
391
}
325
392
326
393
fn 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
338
405
return DocHeaders {
339
406
safety : true ,
340
407
errors : true ,
408
+ panics : true ,
341
409
} ;
342
410
}
343
411
}
@@ -353,6 +421,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
353
421
return DocHeaders {
354
422
safety : false ,
355
423
errors : false ,
424
+ panics : false ,
356
425
} ;
357
426
}
358
427
@@ -394,6 +463,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
394
463
let mut headers = DocHeaders {
395
464
safety : false ,
396
465
errors : false ,
466
+ panics : false ,
397
467
} ;
398
468
let mut in_code = false ;
399
469
let mut in_link = None ;
@@ -439,6 +509,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
439
509
}
440
510
headers. safety |= in_heading && text. trim ( ) == "Safety" ;
441
511
headers. errors |= in_heading && text. trim ( ) == "Errors" ;
512
+ headers. panics |= in_heading && text. trim ( ) == "Panics" ;
442
513
let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & range. start ) ) {
443
514
Ok ( o) => o,
444
515
Err ( e) => e - 1 ,
@@ -609,3 +680,47 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
609
680
) ;
610
681
}
611
682
}
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