Skip to content

Commit 6c8001b

Browse files
authored
Rollup merge of #96008 - fmease:warn-on-useless-doc-hidden-on-assoc-impl-items, r=lcnr
Warn on unused `#[doc(hidden)]` attributes on trait impl items [Zulip conversation](https://rust-lang.zulipchat.com/#narrow/stream/266220-rustdoc/topic/.E2.9C.94.20Validy.20checks.20for.20.60.23.5Bdoc.28hidden.29.5D.60). Whether an associated item in a trait impl is shown or hidden in the documentation entirely depends on the corresponding item in the trait declaration. Rustdoc completely ignores `#[doc(hidden)]` attributes on impl items. No error or warning is emitted: ```rust pub trait Tr { fn f(); } pub struct Ty; impl Tr for Ty { #[doc(hidden)] fn f() {} } // ^^^^^^^^^^^^^^ ignored by rustdoc and currently // no error or warning issued ``` This may lead users to the wrong belief that the attribute has an effect. In fact, several such cases are found in the standard library (I've removed all of them in this PR). There does not seem to exist any incentive to allow this in the future either: Impl'ing a trait for a type means the type *fully* conforms to its API. Users can add `#[doc(hidden)]` to the whole impl if they want to hide the implementation or add the attribute to the corresponding associated item in the trait declaration to hide the specific item. Hiding an implementation of an associated item does not make much sense: The associated item can still be found on the trait page. This PR emits the warn-by-default lint `unused_attribute` for this case with a future-incompat warning. `@rustbot` label T-compiler T-rustdoc A-lint
2 parents 28d800c + 9d157ad commit 6c8001b

File tree

18 files changed

+225
-26
lines changed

18 files changed

+225
-26
lines changed

Diff for: compiler/rustc_passes/src/check_attr.rs

+74-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! conflicts between multiple such attributes attached to the same
55
//! item.
66
7-
use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
7+
use rustc_ast::tokenstream::DelimSpan;
8+
use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MacArgs, MetaItemKind, NestedMetaItem};
89
use rustc_data_structures::fx::FxHashMap;
910
use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
1011
use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
@@ -810,6 +811,68 @@ impl CheckAttrVisitor<'_> {
810811
}
811812
}
812813

814+
/// Checks `#[doc(hidden)]` attributes. Returns `true` if valid.
815+
fn check_doc_hidden(
816+
&self,
817+
attr: &Attribute,
818+
meta_index: usize,
819+
meta: &NestedMetaItem,
820+
hir_id: HirId,
821+
target: Target,
822+
) -> bool {
823+
if let Target::AssocConst
824+
| Target::AssocTy
825+
| Target::Method(MethodKind::Trait { body: true }) = target
826+
{
827+
let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
828+
let containing_item = self.tcx.hir().expect_item(parent_hir_id);
829+
830+
if Target::from_item(containing_item) == Target::Impl {
831+
let meta_items = attr.meta_item_list().unwrap();
832+
833+
let (span, replacement_span) = if meta_items.len() == 1 {
834+
(attr.span, attr.span)
835+
} else {
836+
let meta_span = meta.span();
837+
(
838+
meta_span,
839+
meta_span.until(match meta_items.get(meta_index + 1) {
840+
Some(next_item) => next_item.span(),
841+
None => match attr.get_normal_item().args {
842+
MacArgs::Delimited(DelimSpan { close, .. }, ..) => close,
843+
_ => unreachable!(),
844+
},
845+
}),
846+
)
847+
};
848+
849+
// FIXME: #[doc(hidden)] was previously erroneously allowed on trait impl items,
850+
// so for backward compatibility only emit a warning and do not mark it as invalid.
851+
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, span, |lint| {
852+
lint.build("`#[doc(hidden)]` is ignored on trait impl items")
853+
.warn(
854+
"this was previously accepted by the compiler but is \
855+
being phased out; it will become a hard error in \
856+
a future release!",
857+
)
858+
.note(
859+
"whether the impl item is `doc(hidden)` or not \
860+
entirely depends on the corresponding trait item",
861+
)
862+
.span_suggestion(
863+
replacement_span,
864+
"remove this attribute",
865+
String::new(),
866+
Applicability::MachineApplicable,
867+
)
868+
.emit();
869+
});
870+
}
871+
}
872+
873+
true
874+
}
875+
813876
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
814877
fn check_attr_not_crate_level(
815878
&self,
@@ -928,7 +991,7 @@ impl CheckAttrVisitor<'_> {
928991
let mut is_valid = true;
929992

930993
if let Some(mi) = attr.meta() && let Some(list) = mi.meta_item_list() {
931-
for meta in list {
994+
for (meta_index, meta) in list.into_iter().enumerate() {
932995
if let Some(i_meta) = meta.meta_item() {
933996
match i_meta.name_or_empty() {
934997
sym::alias
@@ -969,6 +1032,15 @@ impl CheckAttrVisitor<'_> {
9691032
is_valid = false;
9701033
}
9711034

1035+
sym::hidden if !self.check_doc_hidden(attr,
1036+
meta_index,
1037+
meta,
1038+
hir_id,
1039+
target,
1040+
) => {
1041+
is_valid = false;
1042+
}
1043+
9721044
// no_default_passes: deprecated
9731045
// passes: deprecated
9741046
// plugins: removed, but rustdoc warns about it itself

Diff for: library/alloc/src/collections/vec_deque/iter.rs

-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ impl<'a, T> Iterator for Iter<'a, T> {
122122
}
123123

124124
#[inline]
125-
#[doc(hidden)]
126125
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
127126
// Safety: The TrustedRandomAccess contract requires that callers only pass an index
128127
// that is in bounds.

Diff for: library/alloc/src/collections/vec_deque/iter_mut.rs

-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ impl<'a, T> Iterator for IterMut<'a, T> {
100100
}
101101

102102
#[inline]
103-
#[doc(hidden)]
104103
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
105104
// Safety: The TrustedRandomAccess contract requires that callers only pass an index
106105
// that is in bounds.

Diff for: library/alloc/src/vec/into_iter.rs

-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
202202
self.len()
203203
}
204204

205-
#[doc(hidden)]
206205
unsafe fn __iterator_get_unchecked(&mut self, i: usize) -> Self::Item
207206
where
208207
Self: TrustedRandomAccessNoCoerce,

Diff for: library/core/src/convert/num.rs

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ macro_rules! impl_float_to_int {
2525
$(
2626
#[unstable(feature = "convert_float_to_int", issue = "67057")]
2727
impl FloatToInt<$Int> for $Float {
28-
#[doc(hidden)]
2928
#[inline]
3029
unsafe fn to_int_unchecked(self) -> $Int {
3130
// SAFETY: the safety contract must be upheld by the caller.

Diff for: library/core/src/iter/adapters/cloned.rs

-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ where
6060
self.it.map(T::clone).fold(init, f)
6161
}
6262

63-
#[doc(hidden)]
6463
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> T
6564
where
6665
Self: TrustedRandomAccessNoCoerce,

Diff for: library/core/src/iter/adapters/copied.rs

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ where
8181
self.it.advance_by(n)
8282
}
8383

84-
#[doc(hidden)]
8584
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> T
8685
where
8786
Self: TrustedRandomAccessNoCoerce,

Diff for: library/core/src/iter/adapters/enumerate.rs

-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ where
128128
}
129129

130130
#[rustc_inherit_overflow_checks]
131-
#[doc(hidden)]
132131
#[inline]
133132
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> <Self as Iterator>::Item
134133
where

Diff for: library/core/src/iter/adapters/fuse.rs

-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ where
129129
}
130130

131131
#[inline]
132-
#[doc(hidden)]
133132
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item
134133
where
135134
Self: TrustedRandomAccessNoCoerce,

Diff for: library/core/src/iter/adapters/map.rs

-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ where
124124
self.iter.fold(init, map_fold(self.f, g))
125125
}
126126

127-
#[doc(hidden)]
128127
#[inline]
129128
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> B
130129
where

Diff for: library/core/src/iter/adapters/zip.rs

-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ where
9595
}
9696

9797
#[inline]
98-
#[doc(hidden)]
9998
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item
10099
where
101100
Self: TrustedRandomAccessNoCoerce,

Diff for: library/core/src/iter/range.rs

-1
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,6 @@ impl<A: Step> Iterator for ops::Range<A> {
752752
}
753753

754754
#[inline]
755-
#[doc(hidden)]
756755
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item
757756
where
758757
Self: TrustedRandomAccessNoCoerce,

Diff for: library/core/src/slice/iter.rs

-11
Original file line numberDiff line numberDiff line change
@@ -1322,7 +1322,6 @@ impl<'a, T> Iterator for Windows<'a, T> {
13221322
}
13231323
}
13241324

1325-
#[doc(hidden)]
13261325
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
13271326
// SAFETY: since the caller guarantees that `i` is in bounds,
13281327
// which means that `i` cannot overflow an `isize`, and the
@@ -1478,7 +1477,6 @@ impl<'a, T> Iterator for Chunks<'a, T> {
14781477
}
14791478
}
14801479

1481-
#[doc(hidden)]
14821480
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
14831481
let start = idx * self.chunk_size;
14841482
// SAFETY: the caller guarantees that `i` is in bounds,
@@ -1657,7 +1655,6 @@ impl<'a, T> Iterator for ChunksMut<'a, T> {
16571655
}
16581656
}
16591657

1660-
#[doc(hidden)]
16611658
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
16621659
let start = idx * self.chunk_size;
16631660
// SAFETY: see comments for `Chunks::__iterator_get_unchecked`.
@@ -1830,7 +1827,6 @@ impl<'a, T> Iterator for ChunksExact<'a, T> {
18301827
self.next_back()
18311828
}
18321829

1833-
#[doc(hidden)]
18341830
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
18351831
let start = idx * self.chunk_size;
18361832
// SAFETY: mostly identical to `Chunks::__iterator_get_unchecked`.
@@ -1984,7 +1980,6 @@ impl<'a, T> Iterator for ChunksExactMut<'a, T> {
19841980
self.next_back()
19851981
}
19861982

1987-
#[doc(hidden)]
19881983
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
19891984
let start = idx * self.chunk_size;
19901985
// SAFETY: see comments for `ChunksMut::__iterator_get_unchecked`.
@@ -2248,7 +2243,6 @@ impl<'a, T, const N: usize> Iterator for ArrayChunks<'a, T, N> {
22482243
self.iter.last()
22492244
}
22502245

2251-
#[doc(hidden)]
22522246
unsafe fn __iterator_get_unchecked(&mut self, i: usize) -> &'a [T; N] {
22532247
// SAFETY: The safety guarantees of `__iterator_get_unchecked` are
22542248
// transferred to the caller.
@@ -2367,7 +2361,6 @@ impl<'a, T, const N: usize> Iterator for ArrayChunksMut<'a, T, N> {
23672361
self.iter.last()
23682362
}
23692363

2370-
#[doc(hidden)]
23712364
unsafe fn __iterator_get_unchecked(&mut self, i: usize) -> &'a mut [T; N] {
23722365
// SAFETY: The safety guarantees of `__iterator_get_unchecked` are transferred to
23732366
// the caller.
@@ -2520,7 +2513,6 @@ impl<'a, T> Iterator for RChunks<'a, T> {
25202513
}
25212514
}
25222515

2523-
#[doc(hidden)]
25242516
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
25252517
let end = self.v.len() - idx * self.chunk_size;
25262518
let start = match end.checked_sub(self.chunk_size) {
@@ -2689,7 +2681,6 @@ impl<'a, T> Iterator for RChunksMut<'a, T> {
26892681
}
26902682
}
26912683

2692-
#[doc(hidden)]
26932684
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
26942685
let end = self.v.len() - idx * self.chunk_size;
26952686
let start = match end.checked_sub(self.chunk_size) {
@@ -2856,7 +2847,6 @@ impl<'a, T> Iterator for RChunksExact<'a, T> {
28562847
self.next_back()
28572848
}
28582849

2859-
#[doc(hidden)]
28602850
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
28612851
let end = self.v.len() - idx * self.chunk_size;
28622852
let start = end - self.chunk_size;
@@ -3016,7 +3006,6 @@ impl<'a, T> Iterator for RChunksExactMut<'a, T> {
30163006
self.next_back()
30173007
}
30183008

3019-
#[doc(hidden)]
30203009
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
30213010
let end = self.v.len() - idx * self.chunk_size;
30223011
let start = end - self.chunk_size;

Diff for: library/core/src/slice/iter/macros.rs

-1
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,6 @@ macro_rules! iterator {
325325
None
326326
}
327327

328-
#[doc(hidden)]
329328
#[inline]
330329
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
331330
// SAFETY: the caller must guarantee that `i` is in bounds of

Diff for: library/core/src/str/iter.rs

-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,6 @@ impl Iterator for Bytes<'_> {
298298
}
299299

300300
#[inline]
301-
#[doc(hidden)]
302301
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> u8 {
303302
// SAFETY: the caller must uphold the safety contract
304303
// for `Iterator::__iterator_get_unchecked`.

Diff for: src/test/ui/lint/unused/unused-attr-doc-hidden.fixed

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#![deny(unused_attributes)]
2+
#![crate_type = "lib"]
3+
// run-rustfix
4+
5+
pub trait Trait {
6+
type It;
7+
const IT: ();
8+
fn it0();
9+
fn it1();
10+
fn it2();
11+
}
12+
13+
pub struct Implementor;
14+
15+
impl Trait for Implementor {
16+
17+
type It = ();
18+
//~^^ ERROR `#[doc(hidden)]` is ignored
19+
//~| WARNING this was previously accepted
20+
21+
22+
const IT: () = ();
23+
//~^^ ERROR `#[doc(hidden)]` is ignored
24+
//~| WARNING this was previously accepted
25+
26+
#[doc(alias = "aka")]
27+
fn it0() {}
28+
//~^^ ERROR `#[doc(hidden)]` is ignored
29+
//~| WARNING this was previously accepted
30+
31+
#[doc(alias = "this", )]
32+
fn it1() {}
33+
//~^^ ERROR `#[doc(hidden)]` is ignored
34+
//~| WARNING this was previously accepted
35+
36+
#[doc()]
37+
fn it2() {}
38+
//~^^ ERROR `#[doc(hidden)]` is ignored
39+
//~| WARNING this was previously accepted
40+
//~| ERROR `#[doc(hidden)]` is ignored
41+
//~| WARNING this was previously accepted
42+
}

Diff for: src/test/ui/lint/unused/unused-attr-doc-hidden.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#![deny(unused_attributes)]
2+
#![crate_type = "lib"]
3+
// run-rustfix
4+
5+
pub trait Trait {
6+
type It;
7+
const IT: ();
8+
fn it0();
9+
fn it1();
10+
fn it2();
11+
}
12+
13+
pub struct Implementor;
14+
15+
impl Trait for Implementor {
16+
#[doc(hidden)]
17+
type It = ();
18+
//~^^ ERROR `#[doc(hidden)]` is ignored
19+
//~| WARNING this was previously accepted
20+
21+
#[doc(hidden)]
22+
const IT: () = ();
23+
//~^^ ERROR `#[doc(hidden)]` is ignored
24+
//~| WARNING this was previously accepted
25+
26+
#[doc(hidden, alias = "aka")]
27+
fn it0() {}
28+
//~^^ ERROR `#[doc(hidden)]` is ignored
29+
//~| WARNING this was previously accepted
30+
31+
#[doc(alias = "this", hidden,)]
32+
fn it1() {}
33+
//~^^ ERROR `#[doc(hidden)]` is ignored
34+
//~| WARNING this was previously accepted
35+
36+
#[doc(hidden, hidden)]
37+
fn it2() {}
38+
//~^^ ERROR `#[doc(hidden)]` is ignored
39+
//~| WARNING this was previously accepted
40+
//~| ERROR `#[doc(hidden)]` is ignored
41+
//~| WARNING this was previously accepted
42+
}

0 commit comments

Comments
 (0)