Skip to content

[rustdoc] Do not emit redundant_explicit_links lint if the doc comment comes from expansion #141648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

GuillaumeGomez
Copy link
Member

Fixes #141553.

The problem was that we change the context for the attributes in some cases to get better error output, preventing us to detect if the attribute comes from expansion. Most of the changes are about keeping track of the "does this span comes from expansion" information.

r? @Manishearth

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels May 27, 2025
@rust-log-analyzer

This comment has been minimized.

@GuillaumeGomez GuillaumeGomez force-pushed the redundant_explicit_links-expansion branch from 5e8b79b to ec9530a Compare May 27, 2025 13:53
@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented May 27, 2025

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@lolbinarycat lolbinarycat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly nits about comments, but a few concerns about the actual logic as well.

@bors
Copy link
Collaborator

bors commented Jun 1, 2025

☔ The latest upstream changes (presumably #141869) made this pull request unmergeable. Please resolve the merge conflicts.

@Manishearth
Copy link
Member

(I've been rather busy catching up with stuff after vacation; I probably won't be able to review this myself soon ,sorry!)

@GuillaumeGomez GuillaumeGomez force-pushed the redundant_explicit_links-expansion branch from b486863 to 9ac0d61 Compare June 3, 2025 20:12
@rust-log-analyzer

This comment has been minimized.

@GuillaumeGomez
Copy link
Member Author

No worries, take your time. We can pick another reviewer in the meantime.

r? notriddle

@rustbot rustbot assigned notriddle and unassigned Manishearth Jun 3, 2025
@GuillaumeGomez GuillaumeGomez force-pushed the redundant_explicit_links-expansion branch from 0ada37b to 8bcc067 Compare June 10, 2025 12:45
@GuillaumeGomez
Copy link
Member Author

Applied suggestions and added more tests.

Copy link
Contributor

@lolbinarycat lolbinarycat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's 1 edge case I think this doesn't handle 100% correctly, and other than that I just have a few nits about wording, 1 tiny inefficiency, and some comments about how some control flow could be rewritten to use pattern matching (if we wanted that)

Comment on lines 212 to +216
if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
let doc = beautify_doc_string(doc_str, comment_kind);
let (span, kind) = if attr.is_doc_comment() {
(attr.span(), DocFragmentKind::SugaredDoc)
let (span, kind, from_expansion) = if attr.is_doc_comment() {
let span = attr.span();
(span, DocFragmentKind::SugaredDoc, span.from_expansion())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a bit odd to me, doc_str_and_comment_kind knows if a doc fragment is raw or a comment, but it then we throw that info away only to recalculate it immediately. These functions probably get inlined and probably llvm de-duplicates the match, but it's still a bit messy, especially considering this is the only call site of doc_str_and_comment_kind besides 1 place in clippy that only checks if the return value is None.

Some(start.to(end))
Some((
first_fragment.span.to(last_fragment.span),
fragments.iter().any(|frag| frag.from_expansion),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably produces false negatives if all of the following are true:

  1. there is a macro that produces sugared doc comments
  2. there is a non-macro generated sugared doc comment containing a redundant explicit link
  3. there are no raw doc fragments on the item

This may be an acceptable regression, but I thought it was worth noting.

This could perhaps be made even more unlikely by trying to use the unambiguous substring heuristic first, or it could be eliminated completely by first calculating the final source span, then only checking the from_expansion of fragments that overlap with that source span.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest opening an issue once this PR is merged and send a PR with a regression test case. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you propose a solution below, copying it then.

Comment on lines 685 to 690
Some((
span.from_inner(InnerSpan::new(
md_range.start + start_bytes,
md_range.end + start_bytes + end_bytes,
)),
from_expansion,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we wanted to avoid the false negative I pointed out before, something like this could work:

Suggested change
Some((
span.from_inner(InnerSpan::new(
md_range.start + start_bytes,
md_range.end + start_bytes + end_bytes,
)),
from_expansion,
let src_span = span.from_inner(InnerSpan::new(
md_range.start + start_bytes,
md_range.end + start_bytes + end_bytes,
));
Some((
src_span,
fragments.iter().any(|frag| frag.span.overlaps(src_span) && frag.from_expansion),

Comment on lines 183 to 191
let (display_span, from_expansion) = source_span_for_markdown_range(
cx.tcx,
doc,
resolvable_link_range,
&item.attrs.doc_strings,
)?;
if from_expansion {
return None;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: Technically this type of control flow could be expressed more tersely as let (foo, false) ... else { return None }; but that might not be the most readable thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I think the let else is acceptable since we already know what the first element of the tuple is. I'm gonna implement this one then!

Comment on lines +36 to +40
#[doc = mac3!()]
/// a [`BufferProvider`](crate::BufferProvider).
//~^ ERROR: redundant_explicit_links
pub fn f() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda would like to see some tests exercising the all-sugared doc fragment case, where half of the fragments are macro-generated and half aren't. Ideally there would be two cases for this, one where the redundant link is in the macro generated fragment and thus the lint is suppressed, and one where the redundant link is in the non-macro generated fragment and thus the link is emitted.

@GuillaumeGomez GuillaumeGomez force-pushed the redundant_explicit_links-expansion branch from 8bcc067 to 8c7779b Compare June 13, 2025 14:47
@notriddle
Copy link
Contributor

I understand that this bail-out is needed because, in the event of a false positive, disabling the lint in the macro is impossible. There's nowhere to attach the attribute.

In that case, would it be simpler and make more sense to disable all of the markdown lints, and not just this one?

Copy link
Contributor

@lolbinarycat lolbinarycat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. we can revert span_for_fragments
  2. more places could use if/else

@@ -659,8 +681,13 @@ pub fn source_span_for_markdown_range_inner(
}
}

Some(span_of_fragments(fragments)?.from_inner(InnerSpan::new(
let (span, _) = span_of_fragments_with_expansion(fragments)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revert span_of_fragments_with_expansion to just be span_of_fragments again now, right?

any future potential users of it would probably be suspect to the same edge case as is present here, and having a useless iteration isn't ideal.

}
None => item.attr_span(cx.tcx),
};
let (explicit_span, from_expansion) = source_span_for_markdown_range(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could also be let else

if from_expansion {
return None;
}
let (display_span, from_expansion) = source_span_for_markdown_range(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could also also be let else

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

redundant_explicit_links rustdoc lint should have macro guard
7 participants