@@ -221,6 +221,42 @@ declare_clippy_lint! {
221
221
"possible typo for an intra-doc link"
222
222
}
223
223
224
+ declare_clippy_lint ! {
225
+ /// ### What it does
226
+ /// Checks for the doc comments of publicly visible
227
+ /// safe functions and traits and warns if there is a `# Safety` section.
228
+ ///
229
+ /// ### Why is this bad?
230
+ /// Safe functions and traits are safe to implement and therefore do not
231
+ /// need to describe safety preconditions that users are required to uphold.
232
+ ///
233
+ /// ### Examples
234
+ /// ```rust
235
+ ///# type Universe = ();
236
+ /// /// # Safety
237
+ /// ///
238
+ /// /// This function should not be called before the horsemen are ready.
239
+ /// pub fn start_apocalypse_but_safely(u: &mut Universe) {
240
+ /// unimplemented!();
241
+ /// }
242
+ /// ```
243
+ ///
244
+ /// The function is safe, so there shouldn't be any preconditions
245
+ /// that have to be explained for safety reasons.
246
+ ///
247
+ /// ```rust
248
+ ///# type Universe = ();
249
+ /// /// This function should really be documented
250
+ /// pub fn start_apocalypse(u: &mut Universe) {
251
+ /// unimplemented!();
252
+ /// }
253
+ /// ```
254
+ #[ clippy:: version = "1.66.0" ]
255
+ pub UNNECESSARY_SAFETY_DOC ,
256
+ style,
257
+ "`pub fn` or `pub trait` with `# Safety` docs"
258
+ }
259
+
224
260
#[ expect( clippy:: module_name_repetitions) ]
225
261
#[ derive( Clone ) ]
226
262
pub struct DocMarkdown {
@@ -243,7 +279,8 @@ impl_lint_pass!(DocMarkdown => [
243
279
MISSING_SAFETY_DOC ,
244
280
MISSING_ERRORS_DOC ,
245
281
MISSING_PANICS_DOC ,
246
- NEEDLESS_DOCTEST_MAIN
282
+ NEEDLESS_DOCTEST_MAIN ,
283
+ UNNECESSARY_SAFETY_DOC ,
247
284
] ) ;
248
285
249
286
impl < ' tcx > LateLintPass < ' tcx > for DocMarkdown {
@@ -254,7 +291,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
254
291
255
292
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
256
293
let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
257
- let headers = check_attrs ( cx, & self . valid_idents , attrs) ;
294
+ let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else { return } ;
258
295
match item. kind {
259
296
hir:: ItemKind :: Fn ( ref sig, _, body_id) => {
260
297
if !( is_entrypoint_fn ( cx, item. def_id . to_def_id ( ) ) || in_external_macro ( cx. tcx . sess , item. span ) ) {
@@ -271,15 +308,20 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
271
308
hir:: ItemKind :: Impl ( impl_) => {
272
309
self . in_trait_impl = impl_. of_trait . is_some ( ) ;
273
310
} ,
274
- hir:: ItemKind :: Trait ( _, unsafety, ..) => {
275
- if !headers. safety && unsafety == hir:: Unsafety :: Unsafe {
276
- span_lint (
277
- cx,
278
- MISSING_SAFETY_DOC ,
279
- cx. tcx . def_span ( item. def_id ) ,
280
- "docs for unsafe trait missing `# Safety` section" ,
281
- ) ;
282
- }
311
+ hir:: ItemKind :: Trait ( _, unsafety, ..) => match ( headers. safety , unsafety) {
312
+ ( false , hir:: Unsafety :: Unsafe ) => span_lint (
313
+ cx,
314
+ MISSING_SAFETY_DOC ,
315
+ cx. tcx . def_span ( item. def_id ) ,
316
+ "docs for unsafe trait missing `# Safety` section" ,
317
+ ) ,
318
+ ( true , hir:: Unsafety :: Normal ) => span_lint (
319
+ cx,
320
+ UNNECESSARY_SAFETY_DOC ,
321
+ cx. tcx . def_span ( item. def_id ) ,
322
+ "docs for safe trait have unnecessary `# Safety` section" ,
323
+ ) ,
324
+ _ => ( ) ,
283
325
} ,
284
326
_ => ( ) ,
285
327
}
@@ -293,7 +335,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
293
335
294
336
fn check_trait_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: TraitItem < ' _ > ) {
295
337
let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
296
- let headers = check_attrs ( cx, & self . valid_idents , attrs) ;
338
+ let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else { return } ;
297
339
if let hir:: TraitItemKind :: Fn ( ref sig, ..) = item. kind {
298
340
if !in_external_macro ( cx. tcx . sess , item. span ) {
299
341
lint_for_missing_headers ( cx, item. def_id . def_id , sig, headers, None , None ) ;
@@ -303,7 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
303
345
304
346
fn check_impl_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: ImplItem < ' _ > ) {
305
347
let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
306
- let headers = check_attrs ( cx, & self . valid_idents , attrs) ;
348
+ let Some ( headers) = check_attrs ( cx, & self . valid_idents , attrs) else { return } ;
307
349
if self . in_trait_impl || in_external_macro ( cx. tcx . sess , item. span ) {
308
350
return ;
309
351
}
@@ -343,14 +385,20 @@ fn lint_for_missing_headers(
343
385
}
344
386
345
387
let span = cx. tcx . def_span ( def_id) ;
346
-
347
- if !headers. safety && sig. header . unsafety == hir:: Unsafety :: Unsafe {
348
- span_lint (
388
+ match ( headers. safety , sig. header . unsafety ) {
389
+ ( false , hir:: Unsafety :: Unsafe ) => span_lint (
349
390
cx,
350
391
MISSING_SAFETY_DOC ,
351
392
span,
352
393
"unsafe function's docs miss `# Safety` section" ,
353
- ) ;
394
+ ) ,
395
+ ( true , hir:: Unsafety :: Normal ) => span_lint (
396
+ cx,
397
+ UNNECESSARY_SAFETY_DOC ,
398
+ span,
399
+ "safe function's docs have unnecessary `# Safety` section" ,
400
+ ) ,
401
+ _ => ( ) ,
354
402
}
355
403
if !headers. panics && panic_span. is_some ( ) {
356
404
span_lint_and_note (
@@ -452,7 +500,7 @@ struct DocHeaders {
452
500
panics : bool ,
453
501
}
454
502
455
- fn check_attrs ( cx : & LateContext < ' _ > , valid_idents : & FxHashSet < String > , attrs : & [ Attribute ] ) -> DocHeaders {
503
+ fn check_attrs ( cx : & LateContext < ' _ > , valid_idents : & FxHashSet < String > , attrs : & [ Attribute ] ) -> Option < DocHeaders > {
456
504
use pulldown_cmark:: { BrokenLink , CowStr , Options } ;
457
505
/// We don't want the parser to choke on intra doc links. Since we don't
458
506
/// actually care about rendering them, just pretend that all broken links are
@@ -473,11 +521,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
473
521
} else if attr. has_name ( sym:: doc) {
474
522
// ignore mix of sugared and non-sugared doc
475
523
// don't trigger the safety or errors check
476
- return DocHeaders {
477
- safety : true ,
478
- errors : true ,
479
- panics : true ,
480
- } ;
524
+ return None ;
481
525
}
482
526
}
483
527
@@ -489,7 +533,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
489
533
}
490
534
491
535
if doc. is_empty ( ) {
492
- return DocHeaders :: default ( ) ;
536
+ return Some ( DocHeaders :: default ( ) ) ;
493
537
}
494
538
495
539
let mut cb = fake_broken_link_callback;
@@ -512,7 +556,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
512
556
( previous, current) => Err ( ( ( previous, previous_range) , ( current, current_range) ) ) ,
513
557
}
514
558
} ) ;
515
- check_doc ( cx, valid_idents, events, & spans)
559
+ Some ( check_doc ( cx, valid_idents, events, & spans) )
516
560
}
517
561
518
562
const RUST_CODE : & [ & str ] = & [ "rust" , "no_run" , "should_panic" , "compile_fail" ] ;
0 commit comments