Skip to content

Commit 0226e65

Browse files
committed
unnecessary_safety_comment: check for impl items as well
changelog: [`unnecessary_safety_comment`]: check for impl items as well Signed-off-by: Zihan <[email protected]>
1 parent 847ca73 commit 0226e65

File tree

3 files changed

+175
-93
lines changed

3 files changed

+175
-93
lines changed

clippy_lints/src/undocumented_unsafe_blocks.rs

Lines changed: 115 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -189,90 +189,98 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
189189
return;
190190
}
191191

192-
let mk_spans = |pos: BytePos| {
193-
let source_map = cx.tcx.sess.source_map();
194-
let span = Span::new(pos, pos, SyntaxContext::root(), None);
195-
let help_span = source_map.span_extend_to_next_char(span, '\n', true);
196-
let span = if source_map.is_multiline(item.span) {
197-
source_map.span_until_char(item.span, '\n')
198-
} else {
199-
item.span
200-
};
201-
(span, help_span)
202-
};
203-
204-
let item_has_safety_comment = item_has_safety_comment(cx, item);
192+
let item_has_safety_comment = item_has_safety_comment(cx, item.span, item.hir_id());
205193
match item_has_safety_comment {
206-
HasSafetyComment::Yes(pos) => check_has_safety_comment(cx, item, mk_spans(pos)),
207-
HasSafetyComment::No => check_has_no_safety_comment(cx, item),
194+
HasSafetyComment::Yes(pos) => check_item_has_safety_comment(cx, item, mk_spans(cx, item.span, pos)),
195+
HasSafetyComment::No => check_item_has_no_safety_comment(cx, item),
208196
HasSafetyComment::Maybe => {},
209197
}
210198
}
199+
200+
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &hir::ImplItem<'_>) {
201+
if item.span.in_external_macro(cx.tcx.sess.source_map()) {
202+
return;
203+
}
204+
205+
let item_has_safety_comment = item_has_safety_comment(cx, item.span, item.hir_id());
206+
match item_has_safety_comment {
207+
HasSafetyComment::Yes(pos) => check_impl_item_has_safety_comment(cx, item, mk_spans(cx, item.span, pos)),
208+
_ => {},
209+
}
210+
}
211+
}
212+
213+
fn mk_spans(cx: &LateContext<'_>, item_span: Span, doc_pos: BytePos) -> (Span, Span) {
214+
let source_map = cx.tcx.sess.source_map();
215+
let span = Span::new(doc_pos, doc_pos, SyntaxContext::root(), None);
216+
let help_span = source_map.span_extend_to_next_char(span, '\n', true);
217+
let span = if source_map.is_multiline(item_span) {
218+
source_map.span_until_char(item_span, '\n')
219+
} else {
220+
item_span
221+
};
222+
(span, help_span)
223+
}
224+
225+
fn check_impl_item_has_safety_comment(cx: &LateContext<'_>, item: &hir::ImplItem<'_>, spans: (Span, Span)) {
226+
match &item.kind {
227+
&hir::ImplItemKind::Const(.., body) => {
228+
if !check_body_unsafeness(cx, body) {
229+
lint_unnecessary_safety_comment(cx, cx.tcx.def_descr(item.owner_id.to_def_id()), spans, body.hir_id)
230+
}
231+
},
232+
hir::ImplItemKind::Type(..) => {},
233+
_ => lint_unnecessary_safety_comment(cx, cx.tcx.def_descr(item.owner_id.to_def_id()), spans, item.hir_id()),
234+
}
211235
}
212236

213-
fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span)) {
237+
fn check_item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, spans: (Span, Span)) {
214238
match &item.kind {
215239
ItemKind::Impl(Impl {
216240
of_trait: Some(of_trait),
217241
..
218-
}) if of_trait.safety.is_safe() => {
219-
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
220-
span_lint_and_then(
221-
cx,
222-
UNNECESSARY_SAFETY_COMMENT,
223-
span,
224-
"impl has unnecessary safety comment",
225-
|diag| {
226-
diag.span_help(help_span, "consider removing the safety comment");
227-
},
228-
);
229-
}
230-
},
242+
}) if of_trait.safety.is_safe() => lint_unnecessary_safety_comment(cx, "impl", spans, item.hir_id()),
231243
ItemKind::Impl(_) => {},
232244
// const and static items only need a safety comment if their body is an unsafe block, lint otherwise
233245
&ItemKind::Const(.., body) | &ItemKind::Static(.., body) => {
234-
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
235-
let body = cx.tcx.hir_body(body);
236-
if !matches!(
237-
body.value.kind, hir::ExprKind::Block(block, _)
238-
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
239-
) {
240-
span_lint_and_then(
241-
cx,
242-
UNNECESSARY_SAFETY_COMMENT,
243-
span,
244-
format!(
245-
"{} has unnecessary safety comment",
246-
cx.tcx.def_descr(item.owner_id.to_def_id()),
247-
),
248-
|diag| {
249-
diag.span_help(help_span, "consider removing the safety comment");
250-
},
251-
);
252-
}
246+
if !check_body_unsafeness(cx, body) {
247+
lint_unnecessary_safety_comment(cx, cx.tcx.def_descr(item.owner_id.to_def_id()), spans, body.hir_id)
253248
}
254249
},
255250
// Aside from unsafe impls and consts/statics with an unsafe block, items in general
256251
// do not have safety invariants that need to be documented, so lint those.
257-
_ => {
258-
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
259-
span_lint_and_then(
260-
cx,
261-
UNNECESSARY_SAFETY_COMMENT,
262-
span,
263-
format!(
264-
"{} has unnecessary safety comment",
265-
cx.tcx.def_descr(item.owner_id.to_def_id()),
266-
),
267-
|diag| {
268-
diag.span_help(help_span, "consider removing the safety comment");
269-
},
270-
);
271-
}
272-
},
252+
_ => lint_unnecessary_safety_comment(cx, cx.tcx.def_descr(item.owner_id.to_def_id()), spans, item.hir_id()),
273253
}
274254
}
275-
fn check_has_no_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) {
255+
256+
fn lint_unnecessary_safety_comment(
257+
cx: &LateContext<'_>,
258+
item_name: &str,
259+
(span, help_span): (Span, Span),
260+
hir_id: HirId,
261+
) {
262+
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, hir_id) {
263+
span_lint_and_then(
264+
cx,
265+
UNNECESSARY_SAFETY_COMMENT,
266+
span,
267+
format!("{item_name} has unnecessary safety comment",),
268+
|diag| {
269+
diag.span_help(help_span, "consider removing the safety comment");
270+
},
271+
);
272+
}
273+
}
274+
275+
fn check_body_unsafeness(cx: &LateContext<'_>, body: hir::BodyId) -> bool {
276+
let body = cx.tcx.hir_body(body);
277+
matches!(
278+
body.value.kind, hir::ExprKind::Block(block, _)
279+
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
280+
)
281+
}
282+
283+
fn check_item_has_no_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) {
276284
if let ItemKind::Impl(Impl {
277285
of_trait: Some(of_trait),
278286
..
@@ -488,26 +496,29 @@ enum HasSafetyComment {
488496
Maybe,
489497
}
490498

491-
/// Checks if the lines immediately preceding the item contain a safety comment.
499+
/// Checks if the lines immediately preceding the item/impl_item contain a safety comment.
492500
#[allow(clippy::collapsible_match)]
493-
fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSafetyComment {
494-
match span_from_macro_expansion_has_safety_comment(cx, item.span) {
501+
fn item_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment {
502+
match span_from_macro_expansion_has_safety_comment(cx, span) {
495503
HasSafetyComment::Maybe => (),
496504
has_safety_comment => return has_safety_comment,
497505
}
498506

499-
if item.span.ctxt() != SyntaxContext::root() {
507+
if span.ctxt() != SyntaxContext::root() {
500508
return HasSafetyComment::No;
501509
}
502-
let comment_start = match cx.tcx.parent_hir_node(item.hir_id()) {
503-
Node::Crate(parent_mod) => comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item),
504-
Node::Item(parent_item) => {
505-
if let ItemKind::Mod(_, parent_mod) = &parent_item.kind {
506-
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
507-
} else {
508-
// Doesn't support impls in this position. Pretend a comment was found.
509-
return HasSafetyComment::Maybe;
510-
}
510+
let comment_start = match cx.tcx.parent_hir_node(hir_id) {
511+
Node::Crate(parent_mod) => {
512+
comment_start_before_item(cx, &mod_items_hir_id(parent_mod), parent_mod.spans.inner_span, hir_id)
513+
},
514+
Node::Item(parent_item) => match &parent_item.kind {
515+
ItemKind::Mod(_, parent_mod) => {
516+
comment_start_before_item(cx, &mod_items_hir_id(parent_mod), parent_item.span, hir_id)
517+
},
518+
ItemKind::Impl(impl_block) => {
519+
comment_start_before_item(cx, &impl_items_hir_id(impl_block), parent_item.span, hir_id)
520+
},
521+
_ => return HasSafetyComment::Maybe,
511522
},
512523
Node::Stmt(stmt) => {
513524
if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) {
@@ -527,7 +538,7 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
527538
let source_map = cx.sess().source_map();
528539
// If the comment is in the first line of the file, there is no preceding line
529540
if let Some(comment_start) = comment_start
530-
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
541+
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
531542
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start.into())
532543
&& let include_first_line_of_file = matches!(comment_start, CommentStartBeforeItem::Start)
533544
&& (include_first_line_of_file || Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf))
@@ -550,6 +561,18 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
550561
HasSafetyComment::Maybe
551562
}
552563

564+
fn mod_items_hir_id(module: &hir::Mod<'_>) -> Vec<HirId> {
565+
module.item_ids.iter().map(|&item_id| item_id.hir_id()).collect()
566+
}
567+
568+
fn impl_items_hir_id(impl_block: &Impl<'_>) -> Vec<HirId> {
569+
impl_block
570+
.items
571+
.iter()
572+
.map(|&impl_item_id| impl_item_id.hir_id())
573+
.collect()
574+
}
575+
553576
/// Checks if the lines immediately preceding the item contain a safety comment.
554577
#[allow(clippy::collapsible_match)]
555578
fn stmt_has_safety_comment(
@@ -572,8 +595,6 @@ fn stmt_has_safety_comment(
572595
_ => return HasSafetyComment::Maybe,
573596
};
574597

575-
// if span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attrib
576-
// }
577598
let mut span = span;
578599
if accept_comment_above_attributes {
579600
span = include_attrs_in_span(cx, hir_id, span);
@@ -617,30 +638,30 @@ impl From<CommentStartBeforeItem> for BytePos {
617638
}
618639
}
619640

620-
fn comment_start_before_item_in_mod(
641+
fn comment_start_before_item(
621642
cx: &LateContext<'_>,
622-
parent_mod: &hir::Mod<'_>,
623-
parent_mod_span: Span,
624-
item: &hir::Item<'_>,
643+
items_at_same_level: &[HirId],
644+
parent_span: Span,
645+
target_item: HirId,
625646
) -> Option<CommentStartBeforeItem> {
626-
parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
627-
if *item_id == item.item_id() {
647+
items_at_same_level.iter().enumerate().find_map(|(idx, &hir_id)| {
648+
if hir_id == target_item {
628649
if idx == 0 {
629650
// mod A { /* comment */ unsafe impl T {} ... }
630651
// ^------------------------------------------^ returns the start of this span
631652
// ^---------------------^ finally checks comments in this range
632-
if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) {
653+
if let Some(sp) = walk_span_to_context(parent_span, SyntaxContext::root()) {
633654
return Some(CommentStartBeforeItem::Offset(sp.lo()));
634655
}
635656
} else {
636657
// some_item /* comment */ unsafe impl T {}
637658
// ^-------^ returns the end of this span
638659
// ^---------------^ finally checks comments in this range
639-
let prev_item = cx.tcx.hir_item(parent_mod.item_ids[idx - 1]);
640-
if prev_item.span.is_dummy() {
660+
let prev_item_span = cx.tcx.hir_span(items_at_same_level[idx - 1]);
661+
if prev_item_span.is_dummy() {
641662
return Some(CommentStartBeforeItem::Start);
642663
}
643-
if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) {
664+
if let Some(sp) = walk_span_to_context(prev_item_span, SyntaxContext::root()) {
644665
return Some(CommentStartBeforeItem::Offset(sp.hi()));
645666
}
646667
}
@@ -695,7 +716,9 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
695716
..
696717
}) => {
697718
return maybe_mod_item
698-
.and_then(|item| comment_start_before_item_in_mod(cx, mod_, *span, &item))
719+
.and_then(|item: hir::Item<'_>| {
720+
comment_start_before_item(cx, &mod_items_hir_id(mod_), *span, item.hir_id())
721+
})
699722
.map(|comment_start| mod_.spans.inner_span.with_lo(comment_start.into()))
700723
.or(Some(*span));
701724
},

tests/ui/unnecessary_safety_comment.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,27 @@ mod issue_12048 {
9999
}
100100
}
101101

102+
mod issue_15034 {
103+
struct Foo;
104+
trait Baz {
105+
fn baz() {}
106+
}
107+
108+
impl Baz for Foo {
109+
// Safety: unnecessary
110+
fn baz() {}
111+
//~^ unnecessary_safety_comment
112+
}
113+
114+
impl Foo {
115+
// SAFETY: unnecessary
116+
const BAR: usize = 0;
117+
//~^ unnecessary_safety_comment
118+
119+
// Safety: unnecessary
120+
fn foo() {}
121+
//~^ unnecessary_safety_comment
122+
}
123+
}
124+
102125
fn main() {}

tests/ui/unnecessary_safety_comment.stderr

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,41 @@ help: consider removing the safety comment
112112
LL | // SAFETY: unnecessary
113113
| ^^^^^^^^^^^^^^^^^^^^^^
114114

115-
error: aborting due to 9 previous errors
115+
error: associated function has unnecessary safety comment
116+
--> tests/ui/unnecessary_safety_comment.rs:110:9
117+
|
118+
LL | fn baz() {}
119+
| ^^^^^^^^^^^
120+
|
121+
help: consider removing the safety comment
122+
--> tests/ui/unnecessary_safety_comment.rs:109:9
123+
|
124+
LL | // Safety: unnecessary
125+
| ^^^^^^^^^^^^^^^^^^^^^^
126+
127+
error: associated constant has unnecessary safety comment
128+
--> tests/ui/unnecessary_safety_comment.rs:116:9
129+
|
130+
LL | const BAR: usize = 0;
131+
| ^^^^^^^^^^^^^^^^^^^^^
132+
|
133+
help: consider removing the safety comment
134+
--> tests/ui/unnecessary_safety_comment.rs:115:9
135+
|
136+
LL | // SAFETY: unnecessary
137+
| ^^^^^^^^^^^^^^^^^^^^^^
138+
139+
error: associated function has unnecessary safety comment
140+
--> tests/ui/unnecessary_safety_comment.rs:120:9
141+
|
142+
LL | fn foo() {}
143+
| ^^^^^^^^^^^
144+
|
145+
help: consider removing the safety comment
146+
--> tests/ui/unnecessary_safety_comment.rs:119:9
147+
|
148+
LL | // Safety: unnecessary
149+
| ^^^^^^^^^^^^^^^^^^^^^^
150+
151+
error: aborting due to 12 previous errors
116152

0 commit comments

Comments
 (0)