-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[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
base: master
Are you sure you want to change the base?
[rustdoc] Do not emit redundant_explicit_links lint if the doc comment comes from expansion #141648
Conversation
This comment has been minimized.
This comment has been minimized.
5e8b79b
to
ec9530a
Compare
This comment has been minimized.
This comment has been minimized.
Some changes occurred in src/tools/clippy cc @rust-lang/clippy |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this 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.
☔ The latest upstream changes (presumably #141869) made this pull request unmergeable. Please resolve the merge conflicts. |
(I've been rather busy catching up with stuff after vacation; I probably won't be able to review this myself soon ,sorry!) |
b486863
to
9ac0d61
Compare
This comment has been minimized.
This comment has been minimized.
No worries, take your time. We can pick another reviewer in the meantime. r? notriddle |
0ada37b
to
8bcc067
Compare
Applied suggestions and added more tests. |
There was a problem hiding this 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)
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()) |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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:
- there is a macro that produces sugared doc comments
- there is a non-macro generated sugared doc comment containing a redundant explicit link
- 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.
There was a problem hiding this comment.
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. :)
There was a problem hiding this comment.
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.
Some(( | ||
span.from_inner(InnerSpan::new( | ||
md_range.start + start_bytes, | ||
md_range.end + start_bytes + end_bytes, | ||
)), | ||
from_expansion, |
There was a problem hiding this comment.
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:
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), |
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; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
#[doc = mac3!()] | ||
/// a [`BufferProvider`](crate::BufferProvider). | ||
//~^ ERROR: redundant_explicit_links | ||
pub fn f() {} |
There was a problem hiding this comment.
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.
…t comes from expansion
…ing from expansion
…it_links` new API
8bcc067
to
8c7779b
Compare
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? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- we can revert
span_for_fragments
- 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)?; |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
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