Auto merge of #69084 - yaahc:delayed-doc-lint, r=petrochenkov

Split non macro portion of unused_doc_comment from macro part into two passes/lints

## Motivation

This change is motivated by the needs of the [spandoc library](https://github.com/yaahc/spandoc). The specific use case is that my macro is removing doc comments when an attribute is applied to a fn with doc comments, but I would like the lint to still appear when I forget to add the `#[spandoc]` attribute to a fn, so I don't want to have to silence the lint globally.

## Approach

This change splits the `unused _doc_comment` lint into two lints, `unused_macro_doc_comment` and `unused_doc_comment`. The non macro portion is moved into an `early_lint_pass` rather than a pre_expansion_pass. This allows proc macros to silence `unused_doc_comment` warnings by either adding an attribute to silence it or by removing the doc comment before the early_pass runs.

The `unused_macro_doc_comment` lint however will still be impossible for proc-macros to silence, but the only alternative that I can see is to remove this lint entirely, which I don't think is acceptable / is a decision I'm not comfortable making personally, so instead I opted to split the macro portion of the check into a separate lint so that it can be silenced globally with an attribute if necessary without needing to globally silence the `unused_doc_comment` lint as well, which is still desireable.

fixes https://github.com/rust-lang/rust/issues/67838
This commit is contained in:
bors 2020-02-23 16:09:41 +00:00
commit b1f395de64
9 changed files with 89 additions and 82 deletions

View File

@ -14,6 +14,8 @@ use rustc_parse::configure;
use rustc_parse::parser::Parser;
use rustc_parse::validate_attr;
use rustc_parse::DirectoryOwnership;
use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_session::parse::{feature_err, ParseSess};
use rustc_span::source_map::respan;
use rustc_span::symbol::{sym, Symbol};
@ -1087,6 +1089,16 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
.note("this may become a hard error in a future release")
.emit();
}
if attr.doc_str().is_some() {
self.cx.parse_sess.buffer_lint_with_diagnostic(
&UNUSED_DOC_COMMENTS,
attr.span,
ast::CRATE_NODE_ID,
"unused doc comment",
BuiltinLintDiagnostics::UnusedDocComment(attr.span),
);
}
}
}
}

View File

@ -738,87 +738,56 @@ impl EarlyLintPass for DeprecatedAttr {
}
}
declare_lint! {
pub UNUSED_DOC_COMMENTS,
Warn,
"detects doc comments that aren't used by rustdoc"
}
fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) {
let mut attrs = attrs.into_iter().peekable();
declare_lint_pass!(UnusedDocComment => [UNUSED_DOC_COMMENTS]);
// Accumulate a single span for sugared doc comments.
let mut sugared_span: Option<Span> = None;
impl UnusedDocComment {
fn warn_if_doc(
&self,
cx: &EarlyContext<'_>,
node_span: Span,
node_kind: &str,
is_macro_expansion: bool,
attrs: &[ast::Attribute],
) {
let mut attrs = attrs.into_iter().peekable();
while let Some(attr) = attrs.next() {
if attr.is_doc_comment() {
sugared_span =
Some(sugared_span.map_or_else(|| attr.span, |span| span.with_hi(attr.span.hi())));
}
// Accumulate a single span for sugared doc comments.
let mut sugared_span: Option<Span> = None;
if attrs.peek().map(|next_attr| next_attr.is_doc_comment()).unwrap_or_default() {
continue;
}
while let Some(attr) = attrs.next() {
if attr.is_doc_comment() {
sugared_span = Some(
sugared_span.map_or_else(|| attr.span, |span| span.with_hi(attr.span.hi())),
let span = sugared_span.take().unwrap_or_else(|| attr.span);
if attr.is_doc_comment() || attr.check_name(sym::doc) {
cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| {
let mut err = lint.build("unused doc comment");
err.span_label(
node_span,
format!("rustdoc does not generate documentation for {}", node_kind),
);
}
if attrs.peek().map(|next_attr| next_attr.is_doc_comment()).unwrap_or_default() {
continue;
}
let span = sugared_span.take().unwrap_or_else(|| attr.span);
if attr.is_doc_comment() || attr.check_name(sym::doc) {
cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| {
let mut err = lint.build("unused doc comment");
err.span_label(
node_span,
format!("rustdoc does not generate documentation for {}", node_kind),
);
if is_macro_expansion {
err.help(
"to document an item produced by a macro, \
the macro must produce the documentation as part of its expansion",
);
}
err.emit();
});
}
err.emit();
});
}
}
}
impl EarlyLintPass for UnusedDocComment {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if let ast::ItemKind::Mac(..) = item.kind {
self.warn_if_doc(cx, item.span, "macro expansions", true, &item.attrs);
}
}
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
let (kind, is_macro_expansion) = match stmt.kind {
ast::StmtKind::Local(..) => ("statements", false),
ast::StmtKind::Item(..) => ("inner items", false),
ast::StmtKind::Mac(..) => ("macro expansions", true),
let kind = match stmt.kind {
ast::StmtKind::Local(..) => "statements",
ast::StmtKind::Item(..) => "inner items",
// expressions will be reported by `check_expr`.
ast::StmtKind::Semi(..) | ast::StmtKind::Expr(..) => return,
ast::StmtKind::Semi(..) | ast::StmtKind::Expr(..) | ast::StmtKind::Mac(..) => return,
};
self.warn_if_doc(cx, stmt.span, kind, is_macro_expansion, stmt.kind.attrs());
warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs());
}
fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
let arm_span = arm.pat.span.with_hi(arm.body.span.hi());
self.warn_if_doc(cx, arm_span, "match arms", false, &arm.attrs);
warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
}
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
self.warn_if_doc(cx, expr.span, "expressions", false, &expr.attrs);
warn_if_doc(cx, expr.span, "expressions", &expr.attrs);
}
}

View File

@ -565,6 +565,11 @@ pub trait LintContext: Sized {
BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
stability::deprecation_suggestion(&mut db, suggestion, span)
}
BuiltinLintDiagnostics::UnusedDocComment(span) => {
db.span_label(span, "rustdoc does not generate documentation for macros");
db.help("to document an item produced by a macro, \
the macro must produce the documentation as part of its expansion");
}
}
// Rewrap `db`, and pass control to the user.
decorate(LintDiagnosticBuilder::new(db));

View File

@ -94,7 +94,7 @@ fn lint_mod(tcx: TyCtxt<'_>, module_def_id: DefId) {
macro_rules! pre_expansion_lint_passes {
($macro:path, $args:tt) => {
$macro!($args, [KeywordIdents: KeywordIdents, UnusedDocComment: UnusedDocComment,]);
$macro!($args, [KeywordIdents: KeywordIdents,]);
};
}
@ -114,6 +114,7 @@ macro_rules! early_lint_passes {
NonAsciiIdents: NonAsciiIdents,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolon: RedundantSemicolon,
UnusedDocComment: UnusedDocComment,
]
);
};

View File

@ -190,6 +190,7 @@ pub enum BuiltinLintDiagnostics {
UnusedImports(String, Vec<(Span, String)>),
RedundantImport(Vec<(Span, bool)>, Ident),
DeprecatedMacro(Option<Symbol>, Span),
UnusedDocComment(Span),
}
/// Lints that are buffered up early on in the `Session` before the

View File

@ -564,3 +564,11 @@ declare_lint_pass! {
INLINE_NO_SANITIZE,
]
}
declare_lint! {
pub UNUSED_DOC_COMMENTS,
Warn,
"detects doc comments that aren't used by rustdoc"
}
declare_lint_pass!(UnusedDocComment => [UNUSED_DOC_COMMENTS]);

View File

@ -176,6 +176,25 @@ impl ParseSess {
});
}
pub fn buffer_lint_with_diagnostic(
&self,
lint: &'static Lint,
span: impl Into<MultiSpan>,
node_id: NodeId,
msg: &str,
diagnostic: BuiltinLintDiagnostics,
) {
self.buffered_lints.with_lock(|buffered_lints| {
buffered_lints.push(BufferedEarlyLint {
span: span.into(),
node_id,
msg: msg.into(),
lint_id: LintId::of(lint),
diagnostic,
});
});
}
/// Extend an error with a suggestion to wrap an expression with parentheses to allow the
/// parser to continue parsing the following operation as part of the same expression.
pub fn expr_parentheses_needed(

View File

@ -13,19 +13,15 @@ pub struct WasiFd {
fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] {
assert_eq!(mem::size_of::<IoSliceMut<'_>>(), mem::size_of::<wasi::Iovec>());
assert_eq!(mem::align_of::<IoSliceMut<'_>>(), mem::align_of::<wasi::Iovec>());
/// SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout
unsafe {
mem::transmute(a)
}
// SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout
unsafe { mem::transmute(a) }
}
fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] {
assert_eq!(mem::size_of::<IoSlice<'_>>(), mem::size_of::<wasi::Ciovec>());
assert_eq!(mem::align_of::<IoSlice<'_>>(), mem::align_of::<wasi::Ciovec>());
/// SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout
unsafe {
mem::transmute(a)
}
// SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout
unsafe { mem::transmute(a) }
}
impl WasiFd {

View File

@ -2,9 +2,7 @@ error: unused doc comment
--> $DIR/useless-comment.rs:9:1
|
LL | /// foo
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | mac!();
| ------- rustdoc does not generate documentation for macro expansions
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ rustdoc does not generate documentation for macros
|
note: the lint level is defined here
--> $DIR/useless-comment.rs:3:9
@ -13,6 +11,14 @@ LL | #![deny(unused_doc_comments)]
| ^^^^^^^^^^^^^^^^^^^
= help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
error: unused doc comment
--> $DIR/useless-comment.rs:32:5
|
LL | /// bar
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ rustdoc does not generate documentation for macros
|
= help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
error: unused doc comment
--> $DIR/useless-comment.rs:13:5
|
@ -68,16 +74,6 @@ LL | #[doc = "bar"]
LL | 3;
| - rustdoc does not generate documentation for expressions
error: unused doc comment
--> $DIR/useless-comment.rs:32:5
|
LL | /// bar
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | mac!();
| ------- rustdoc does not generate documentation for macro expansions
|
= help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
error: unused doc comment
--> $DIR/useless-comment.rs:35:13
|