Skip to content

Add doc_examples_missing_item lint - #17766

Open
LucaCappelletti94 wants to merge 1 commit into
rust-lang:masterfrom
LucaCappelletti94:doc-examples-missing-item
Open

LucaCappelletti94 wants to merge 1 commit into
rust-lang:masterfrom
LucaCappelletti94:doc-examples-missing-item

Conversation

@LucaCappelletti94

@LucaCappelletti94 LucaCappelletti94 commented Sep 21, 2026 •

Copy link
Copy Markdown

View all comments

fixes #9228

This PR adds doc_examples_missing_item, a pedantic lint that warns when an exported function, method, constant or static has Rust doctest examples but none of them mentions the item by name. Such examples are usually copied from a neighbouring item and never adapted, as in diesel-rs/diesel#5213 or OnceCell's get_mut. I ran into this failure mode while working on dejadoc.

The lint tokenizes the compiled examples, hidden lines included, and looks for the item's name as an identifier. Blocks tagged ignore or compile_fail do not count as examples, while no_run and should_panic ones do. Private and #[doc(hidden)] items are skipped unless check-private-items is set, as for the other doc lints. Trait implementation items and proc macro entry points are excluded. Struct, enum, union and trait declarations are left out, because their examples often reach them through a constructor or a method chain without naming them, which made most of their lintcheck findings noise. Trait methods are still checked.

Lintcheck reports 45 findings, all on functions, and dogfood over this repository is clean.

Disclosure under the LLM policy. I used Qwen 3.8 Next to draft, then adequately iterated and cleaned up the code.

  • Followed lint naming conventions
  • Added passing UI tests (including committed .stderr file)
  • cargo test passes locally
  • Executed cargo dev update_lints
  • Added lint documentation
  • Run cargo dev fmt

changelog: new lint: [doc_examples_missing_item]

@rustbot rustbot added the needs-fcp PRs that add, remove, or rename lints and need an FCP label Sep 21, 2026
@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Lintcheck changes for a6a1f61

Lint Added Removed Changed
clippy::doc_examples_missing_item 45 0 0

This comment will be updated if you push new changes

@LucaCappelletti94
LucaCappelletti94 marked this pull request as ready for review September 21, 2026 13:26
@rustbot

rustbot commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in clippy_lints/src/doc

cc @notriddle

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. labels Sep 21, 2026

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this so big?! needless_doctest_main.rs is less than a hundred lines of code and it’s doing a similar check to this one.

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I familiarized with the similar linting, refactored and trimmed down significantly. Thanks for the patience.

/// currently being checked.
pub(super) fn owner(cx: &LateContext<'_>, check_private_items: bool) -> Option<Checker> {
let id = cx.last_node_with_lint_attrs;
if is_lint_allowed(cx, DOC_EXAMPLES_MISSING_ITEM, id) {

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This shouldn’t be needed.

View changes since the review

if is_lint_allowed(cx, DOC_EXAMPLES_MISSING_ITEM, id) {
return None;
}
let (span, owner_def_id, ident) = match cx.tcx.hir_node(id) {

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Primarily, ignorance. I will hopefully familiarize rapidly over these utilities. Thank you for pointing it out.

impl Checker {
pub(super) fn start_block(&mut self, tags: CodeTags) {
// Excluded blocks neither establish an example nor satisfy the name check. The shared
// CodeTags classification stays untouched so neighboring lints are unaffected.

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The shared CodeTags classification stays untouched so neighboring lints are unaffected

No kidding. Delete this comment. It is slop.

View changes since the review

}

/// Whether one example mentions the item name as an identifier token. Malformed tokens such as
/// unterminated literals or comments are skipped, because a block that cannot compile is not an

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a block that cannot compile is not an example

Why not? It’ll fail when you run cargo test, so it doesn’t matter if we get a “false negative” here.

View changes since the review

}

/// Applies rustdoc's hidden-line rules. Hidden lines stay doctest code, so their idents count.
fn strip_hidden_line_markers(code: &str) -> Cow<'_, str> {

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If this lint really does need to strip hidden line markers, make it a utility function.

View changes since the review

@LucaCappelletti94
LucaCappelletti94 marked this pull request as draft September 21, 2026 16:30
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 21, 2026
@Gri-ffin Gri-ffin added the llm-assisted For PRs that were partially or completely done with assistance of AI/LLM tools label Sep 21, 2026
@LucaCappelletti94
LucaCappelletti94 force-pushed the doc-examples-missing-item branch 7 times, most recently from 757488a to 4f96294 Compare September 21, 2026 19:41
@LucaCappelletti94
LucaCappelletti94 marked this pull request as ready for review September 21, 2026 19:51
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 21, 2026
})
}

pub(super) fn check(cx: &LateContext<'_>, ident: Ident) {

@notriddle notriddle Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don’t call it “check.” It’s not actuator checking anything. It’s just reporting.

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Would report be ok?

Comment on lines +45 to +54
pub(super) fn mentions(code: &str, ident: Ident) -> bool {
if !code.contains(ident.as_str()) {
return false;
}

tokenize_with_text(code).any(|(kind, text, _)| {
matches!(kind, TokenKind::Ident | TokenKind::RawIdent)
&& text.strip_prefix("r#").unwrap_or(text) == ident.as_str()
})
}

@Gri-ffin Gri-ffin Sep 21, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looking at the lintcheck results, we might want to skip structs and traits as they would be trickier to handle, they all seem like false positives.

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I agree that this check on methods/functions has likely much lower false positives, but a few (6 our of 13) of the cases of interest in Diesel which I caught with dejadoc were on structs, therefore I would believe it still has value.

@notriddle is there any particular acceptable pattern in clippy to say "check X, but only for item kinds in set Y", so that users may decide whether they want to enable this on any particular set of items? Or should it explode into a set of lints like doc_examples_missing_*?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I wasn't aware it was possible to configure each lint, I hadn't ever had to config it in a repo, thank you for pointing it out. I will familiarize with it and proceed as suggested.

@LucaCappelletti94 LucaCappelletti94 Sep 22, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added the configuration, by default parametrized as @Gri-ffin suggests, and optionally parametrizable to include all item kinds. @notriddle please do let me whether I have set it up correctly or I have missed using some of the intended patterns/methods.

@notriddle notriddle Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don’t feel qualified to answer this question. I know Clippy has “restriction” lints that are allowed to flag harmless code, as long as the lint perform as advertised, but I’m not sure how open the team is to adding them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I removed the configuration together with the struct, enum, union and trait support, so the lint now only covers functions, methods, constants and statics. Should be good to go now.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@Gri-ffin do the more recent updates address the earlier issues you raised satisfactorily?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

They are yes, due to policy though you should find a reviewer in #t-clippy or #llm-reviews to review this PR given it was assisted by LLM. I was just doing a drive by review.

Here is the link to the policy: https://forge.rust-lang.org/policies/llm-usage.html

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I appreciate it, just wanted to make sure the lint is now as you would expect it to be.

@LucaCappelletti94
LucaCappelletti94 marked this pull request as draft September 22, 2026 17:02
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 22, 2026
@LucaCappelletti94
LucaCappelletti94 force-pushed the doc-examples-missing-item branch 3 times, most recently from a43911e to ef1b028 Compare September 22, 2026 18:15
@LucaCappelletti94
LucaCappelletti94 marked this pull request as ready for review September 22, 2026 18:24
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Sep 22, 2026
@LucaCappelletti94
LucaCappelletti94 force-pushed the doc-examples-missing-item branch 2 times, most recently from eb550b0 to cecaba3 Compare September 23, 2026 21:29

@CommanderStorm CommanderStorm left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Community review:
I have a few comments as well, nothing large.
Mostly understanding on my side.

I think the diff should show well why we require an reviewer BEFORE openeing an PR...
Lets see if someone wants to review it on zulip ✔️

View changes since this review

Comment thread tests/ui/doc/doc_examples_missing_item.rs Outdated
Comment on lines +96 to +109
// Type and trait declarations are not checked.
/// ```
/// let _ = 1;
/// ```
pub enum Direction {
Up,
}

/// ```
/// let _ = 1;
/// ```
pub union Raw {
bits: u32,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this seems like an false negative.
If it is, can you move it to an module at the end and mark it as such?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It is intended to be a negative following the other reviews, since it was explicitly asked to ignore enum, struct, union and so on. I detailed the whole PR iter here https://github.com/rust-lang/rust-clippy/pull/17766/changes#r4114976281 and I am fine with trying to find a more general solution that includes these as well.

Comment on lines +35 to +44
/// Tagged `ignore` and `compile_fail` blocks are not examples.
/// ```text
/// text_block();
/// ```
/// ```ignore
/// text_block();
/// ```
/// ```compile_fail
/// let _: u8 = text_block;
/// ```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you explain this more? Why not?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The idea is that textual entries that do not compile are not meaningful documentation of the object, as you could write let _: u8 = text_block; and have otherwise the clippy warning pass with a completely meaningless doctest. The whole idea of the lint is to force having doctests meaningful for the item being documented.

Comment thread clippy_lints/src/doc/mod.rs Outdated
return None;
}

if !matches!(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

based on https://github.com/rust-lang/rust-clippy/pull/17766/changes#r4067004507 this seems an intentional bail to avoid FPs.
Please add an TODO comment that this is the case and what is nessary to support these cases roughly

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You mean if we were to re-add support for Traits, Structs and so on and so forth as they were included originally?

The issue there primarily stems from doctests of Traits/Structs often not explicitly stating the syn token of the documented item when *-importing objects, and therefore causing false positives. My initial reasoning for including them was that in diesel I also found duplicated tests over structs, so there are definitively use cases for them, and I would also claim that explicitly including X when you are documenting X is apriori desirable.

Short of having some utility that is able to efficiently determine what the * imports are, I do not believe false positives are avoidable in such cases, and in my opinion it is also trivial to silence them by explicitly importing what is being documented.

It was then pointed out that as things stand, such a wide net caught too many false positives, and so the PR then went through a preliminary transformation of turning the items to be included into a configurable settings.

Then, it was asked to just remove the setting and keep only the fn, const and static cases, bringing the PR to the current state.

I hope I have provided enough context, do let me know what would be the direction you would suggest to go forward with. If you are aware of other lints having any solution to an analogous problem that I could adopt here, do let me know, otherwise the TODO collapses into "skipping anything that is not a Fn, Const and Static as people tend to systematically fail to include those explicitly in their doctests"

use rustc_lint::LateContext;
use rustc_span::Ident;

pub(super) fn item_ident(cx: &LateContext<'_>, check_private_items: bool) -> Option<Ident> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

instead of this function being if..return None if..return None, how about we make this into an bigger if-let, since if you specify the case where you want the ident it is simpler to reason about for readers due to not having to deal with double negations, ...

I in particularly stubled over if !check_private_items

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ok, I expect the positive case if chain might get long but I saw elsewhere in clippy lints occasionally there are fairly large ones. Will rewrite shortly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cleaned up, actually found a small duplication with another lint and cleaned both. Lmk if that is okay as part of this PR or whether it would be ideal to split it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there are now 3 different places where tests for this exist, please crate an new directory for just this testcase to make this more obvious where things are 😉

@LucaCappelletti94

Copy link
Copy Markdown
Author

Thanks @CommanderStorm for taking time to review this.

I did open the review request on Zulip (hopefully in the right place), although admittedly after opening the PR as I read the request, brain though "yes let's do that" and then promptly forgot it and opened the PR nevertheless: #llm-reviews > clippy: Add doc_examples_missing_item lint - #17766

I must admit that most of the points stresses in the first diff are my own fault, not the LLMs being sloppy. It is the first time I am contributing a lint to clippy, so I have no yet assimilated its styles, patterns and go-to solutions.

Of the review, the two (related) things regarding which I am unsure how to proceed and I would appreciate some additional comments on are #17766 (comment) and #17766 (comment)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llm-assisted For PRs that were partially or completely done with assistance of AI/LLM tools needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-community-reviews Status: This is awaiting for positive reviews from the community before a maintainer is assigned. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New lint suggestion: doctest doesn't use documented item

5 participants