Skip to content

Commit fe62c6e

Browse files
committed
Auto merge of #80300 - LeSeulArtichaut:80275-doc-inline, r=Manishearth
Emit errors/warns on some wrong uses of rustdoc attributes This PR adds a few diagnostics: - error if conflicting `#[doc(inline)]`/`#[doc(no_inline)]` are found - introduce the `invalid_doc_attributes` lint (warn-by-default) which triggers: - if a crate-level attribute is used on a non-`crate` item - if `#[doc(inline)]`/`#[doc(no_inline)]` is used on a non-`use` item The code could probably be improved but I wanted to get feedback first. Also, some of those changes could be considered breaking changes, so I don't know what the procedure would be? ~~And finally, for the warnings, they are currently hard warnings, maybe it would be better to introduce a lint?~~ (EDIT: introduced the `invalid_doc_attributes` lint) Closes #80275. r? `@jyn514`
2 parents 455c5e0 + 804ab9f commit fe62c6e

File tree

10 files changed

+367
-94
lines changed

10 files changed

+367
-94
lines changed

compiler/rustc_passes/src/check_attr.rs

+147-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_middle::hir::map::Map;
88
use rustc_middle::ty::query::Providers;
99
use rustc_middle::ty::TyCtxt;
1010

11-
use rustc_ast::{Attribute, Lit, LitKind, NestedMetaItem};
11+
use rustc_ast::{AttrStyle, Attribute, Lit, LitKind, NestedMetaItem};
1212
use rustc_errors::{pluralize, struct_span_err, Applicability};
1313
use rustc_hir as hir;
1414
use rustc_hir::def_id::LocalDefId;
@@ -22,7 +22,7 @@ use rustc_session::lint::builtin::{
2222
};
2323
use rustc_session::parse::feature_err;
2424
use rustc_span::symbol::{sym, Symbol};
25-
use rustc_span::{Span, DUMMY_SP};
25+
use rustc_span::{MultiSpan, Span, DUMMY_SP};
2626

2727
pub(crate) fn target_from_impl_item<'tcx>(
2828
tcx: TyCtxt<'tcx>,
@@ -67,6 +67,7 @@ impl CheckAttrVisitor<'tcx> {
6767
item: Option<ItemLike<'_>>,
6868
) {
6969
let mut is_valid = true;
70+
let mut specified_inline = None;
7071
let attrs = self.tcx.hir().attrs(hir_id);
7172
for attr in attrs {
7273
is_valid &= match attr.name_or_empty() {
@@ -77,7 +78,7 @@ impl CheckAttrVisitor<'tcx> {
7778
sym::track_caller => {
7879
self.check_track_caller(hir_id, &attr.span, attrs, span, target)
7980
}
80-
sym::doc => self.check_doc_attrs(attr, hir_id, target),
81+
sym::doc => self.check_doc_attrs(attr, hir_id, target, &mut specified_inline),
8182
sym::no_link => self.check_no_link(hir_id, &attr, span, target),
8283
sym::export_name => self.check_export_name(hir_id, &attr, span, target),
8384
sym::rustc_args_required_const => {
@@ -564,7 +565,71 @@ impl CheckAttrVisitor<'tcx> {
564565
true
565566
}
566567

567-
fn check_attr_crate_level(
568+
/// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid.
569+
///
570+
/// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
571+
/// if there are conflicting attributes for one item.
572+
///
573+
/// `specified_inline` is used to keep track of whether we have
574+
/// already seen an inlining attribute for this item.
575+
/// If so, `specified_inline` holds the value and the span of
576+
/// the first `inline`/`no_inline` attribute.
577+
fn check_doc_inline(
578+
&self,
579+
attr: &Attribute,
580+
meta: &NestedMetaItem,
581+
hir_id: HirId,
582+
target: Target,
583+
specified_inline: &mut Option<(bool, Span)>,
584+
) -> bool {
585+
if target == Target::Use {
586+
let do_inline = meta.name_or_empty() == sym::inline;
587+
if let Some((prev_inline, prev_span)) = *specified_inline {
588+
if do_inline != prev_inline {
589+
let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]);
590+
spans.push_span_label(prev_span, String::from("this attribute..."));
591+
spans.push_span_label(
592+
meta.span(),
593+
String::from("...conflicts with this attribute"),
594+
);
595+
self.tcx
596+
.sess
597+
.struct_span_err(spans, "conflicting doc inlining attributes")
598+
.help("remove one of the conflicting attributes")
599+
.emit();
600+
return false;
601+
}
602+
true
603+
} else {
604+
*specified_inline = Some((do_inline, meta.span()));
605+
true
606+
}
607+
} else {
608+
self.tcx.struct_span_lint_hir(
609+
INVALID_DOC_ATTRIBUTES,
610+
hir_id,
611+
meta.span(),
612+
|lint| {
613+
let mut err = lint.build(
614+
"this attribute can only be applied to a `use` item",
615+
);
616+
err.span_label(meta.span(), "only applicable on `use` items");
617+
if attr.style == AttrStyle::Outer {
618+
err.span_label(
619+
self.tcx.hir().span(hir_id),
620+
"not a `use` item",
621+
);
622+
}
623+
err.note("read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#docno_inlinedocinline for more information")
624+
.emit();
625+
},
626+
);
627+
false
628+
}
629+
}
630+
631+
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
632+
fn check_attr_not_crate_level(
568633
&self,
569634
meta: &NestedMetaItem,
570635
hir_id: HirId,
@@ -586,40 +651,103 @@ impl CheckAttrVisitor<'tcx> {
586651
true
587652
}
588653

589-
fn check_doc_attrs(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool {
654+
/// Checks that an attribute is used at the crate level. Returns `true` if valid.
655+
fn check_attr_crate_level(
656+
&self,
657+
attr: &Attribute,
658+
meta: &NestedMetaItem,
659+
hir_id: HirId,
660+
) -> bool {
661+
if hir_id != CRATE_HIR_ID {
662+
self.tcx.struct_span_lint_hir(
663+
INVALID_DOC_ATTRIBUTES,
664+
hir_id,
665+
meta.span(),
666+
|lint| {
667+
let mut err = lint.build(
668+
"this attribute can only be applied at the crate level",
669+
);
670+
if attr.style == AttrStyle::Outer && self.tcx.hir().get_parent_item(hir_id) == CRATE_HIR_ID {
671+
if let Ok(mut src) =
672+
self.tcx.sess.source_map().span_to_snippet(attr.span)
673+
{
674+
src.insert(1, '!');
675+
err.span_suggestion_verbose(
676+
attr.span,
677+
"to apply to the crate, use an inner attribute",
678+
src,
679+
Applicability::MaybeIncorrect,
680+
);
681+
} else {
682+
err.span_help(
683+
attr.span,
684+
"to apply to the crate, use an inner attribute",
685+
);
686+
}
687+
}
688+
err.note("read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information")
689+
.emit();
690+
},
691+
);
692+
return false;
693+
}
694+
true
695+
}
696+
697+
/// Runs various checks on `#[doc]` attributes. Returns `true` if valid.
698+
///
699+
/// `specified_inline` should be initialized to `None` and kept for the scope
700+
/// of one item. Read the documentation of [`check_doc_inline`] for more information.
701+
///
702+
/// [`check_doc_inline`]: Self::check_doc_inline
703+
fn check_doc_attrs(
704+
&self,
705+
attr: &Attribute,
706+
hir_id: HirId,
707+
target: Target,
708+
specified_inline: &mut Option<(bool, Span)>,
709+
) -> bool {
590710
let mut is_valid = true;
591711

592712
if let Some(list) = attr.meta().and_then(|mi| mi.meta_item_list().map(|l| l.to_vec())) {
593713
for meta in list {
594714
if let Some(i_meta) = meta.meta_item() {
595715
match i_meta.name_or_empty() {
596716
sym::alias
597-
if !self.check_attr_crate_level(&meta, hir_id, "alias")
717+
if !self.check_attr_not_crate_level(&meta, hir_id, "alias")
598718
|| !self.check_doc_alias(&meta, hir_id, target) =>
599719
{
600720
is_valid = false
601721
}
602722

603723
sym::keyword
604-
if !self.check_attr_crate_level(&meta, hir_id, "keyword")
724+
if !self.check_attr_not_crate_level(&meta, hir_id, "keyword")
605725
|| !self.check_doc_keyword(&meta, hir_id) =>
606726
{
607727
is_valid = false
608728
}
609729

610-
sym::test if CRATE_HIR_ID != hir_id => {
611-
self.tcx.struct_span_lint_hir(
612-
INVALID_DOC_ATTRIBUTES,
730+
sym::html_favicon_url
731+
| sym::html_logo_url
732+
| sym::html_playground_url
733+
| sym::issue_tracker_base_url
734+
| sym::html_root_url
735+
| sym::html_no_source
736+
| sym::test
737+
if !self.check_attr_crate_level(&attr, &meta, hir_id) =>
738+
{
739+
is_valid = false;
740+
}
741+
742+
sym::inline | sym::no_inline
743+
if !self.check_doc_inline(
744+
&attr,
745+
&meta,
613746
hir_id,
614-
meta.span(),
615-
|lint| {
616-
lint.build(
617-
"`#![doc(test(...)]` is only allowed \
618-
as a crate-level attribute",
619-
)
620-
.emit();
621-
},
622-
);
747+
target,
748+
specified_inline,
749+
) =>
750+
{
623751
is_valid = false;
624752
}
625753

library/std/src/os/emscripten/raw.rs

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ pub type mode_t = u32;
2222
#[stable(feature = "pthread_t", since = "1.8.0")]
2323
pub type pthread_t = c_ulong;
2424

25-
#[doc(inline)]
2625
#[stable(feature = "raw_ext", since = "1.1.0")]
2726
pub type blkcnt_t = u64;
2827
#[stable(feature = "raw_ext", since = "1.1.0")]

src/test/rustdoc-ui/doc-attr2.rs

-11
This file was deleted.

src/test/rustdoc-ui/doc-attr2.stderr

-26
This file was deleted.
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![crate_type = "lib"]
2+
#![deny(warnings)]
3+
4+
#[doc(test(no_crate_inject))]
5+
//~^ ERROR can only be applied at the crate level
6+
//~| WARN is being phased out
7+
//~| HELP to apply to the crate, use an inner attribute
8+
//~| SUGGESTION #![doc(test(no_crate_inject))]
9+
#[doc(inline)]
10+
//~^ ERROR can only be applied to a `use` item
11+
//~| WARN is being phased out
12+
pub fn foo() {}
13+
14+
pub mod bar {
15+
#![doc(test(no_crate_inject))]
16+
//~^ ERROR can only be applied at the crate level
17+
//~| WARN is being phased out
18+
19+
#[doc(test(no_crate_inject))]
20+
//~^ ERROR can only be applied at the crate level
21+
//~| WARN is being phased out
22+
#[doc(inline)]
23+
//~^ ERROR can only be applied to a `use` item
24+
//~| WARN is being phased out
25+
pub fn baz() {}
26+
}
27+
28+
#[doc(inline)]
29+
#[doc(no_inline)]
30+
//~^^ ERROR conflicting doc inlining attributes
31+
//~| HELP remove one of the conflicting attributes
32+
pub use bar::baz;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
error: this attribute can only be applied at the crate level
2+
--> $DIR/invalid-doc-attr.rs:4:7
3+
|
4+
LL | #[doc(test(no_crate_inject))]
5+
| ^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/invalid-doc-attr.rs:2:9
9+
|
10+
LL | #![deny(warnings)]
11+
| ^^^^^^^^
12+
= note: `#[deny(invalid_doc_attributes)]` implied by `#[deny(warnings)]`
13+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
14+
= note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
15+
= note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
16+
help: to apply to the crate, use an inner attribute
17+
|
18+
LL | #![doc(test(no_crate_inject))]
19+
|
20+
21+
error: this attribute can only be applied to a `use` item
22+
--> $DIR/invalid-doc-attr.rs:9:7
23+
|
24+
LL | #[doc(inline)]
25+
| ^^^^^^ only applicable on `use` items
26+
...
27+
LL | pub fn foo() {}
28+
| ------------ not a `use` item
29+
|
30+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
31+
= note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
32+
= note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#docno_inlinedocinline for more information
33+
34+
error: this attribute can only be applied at the crate level
35+
--> $DIR/invalid-doc-attr.rs:15:12
36+
|
37+
LL | #![doc(test(no_crate_inject))]
38+
| ^^^^^^^^^^^^^^^^^^^^^
39+
|
40+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
41+
= note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
42+
= note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
43+
44+
error: conflicting doc inlining attributes
45+
--> $DIR/invalid-doc-attr.rs:28:7
46+
|
47+
LL | #[doc(inline)]
48+
| ^^^^^^ this attribute...
49+
LL | #[doc(no_inline)]
50+
| ^^^^^^^^^ ...conflicts with this attribute
51+
|
52+
= help: remove one of the conflicting attributes
53+
54+
error: this attribute can only be applied at the crate level
55+
--> $DIR/invalid-doc-attr.rs:19:11
56+
|
57+
LL | #[doc(test(no_crate_inject))]
58+
| ^^^^^^^^^^^^^^^^^^^^^
59+
|
60+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
61+
= note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
62+
= note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
63+
64+
error: this attribute can only be applied to a `use` item
65+
--> $DIR/invalid-doc-attr.rs:22:11
66+
|
67+
LL | #[doc(inline)]
68+
| ^^^^^^ only applicable on `use` items
69+
...
70+
LL | pub fn baz() {}
71+
| ------------ not a `use` item
72+
|
73+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
74+
= note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
75+
= note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#docno_inlinedocinline for more information
76+
77+
error: aborting due to 6 previous errors
78+

src/test/ui/attributes/doc-attr2.rs

-11
This file was deleted.

0 commit comments

Comments
 (0)