about summary refs log tree commit diff
diff options
context:
space:
mode:
authorblyxyas <blyxyas@gmail.com>2025-05-22 12:44:37 +0200
committerblyxyas <blyxyas@gmail.com>2025-05-23 19:46:28 +0200
commit4a7598f815825fa485a9f71d759a2f2faf9342ed (patch)
tree1858ac0657e734faa45363f03b92035f06061ae9
parent5dccb101ed9efc812ab5acbb0dee915d2b3502b9 (diff)
downloadrust-4a7598f815825fa485a9f71d759a2f2faf9342ed.tar.gz
rust-4a7598f815825fa485a9f71d759a2f2faf9342ed.zip
[HEAVY PERF] Optimize documentation lints 2/2
So, after https://github.com/rust-lang/rust-clippy/pull/14693 was merged,
this is the continuation. It performs some optimizations on `Fragments::span`
, makes it so we don't call it so much, and makes a 85.75% decrease (7.51% -> 10.07%)
in execution samples of `source_span_for_markdown_range` and a 6.39% -> 0.88%
for `core::StrSearcher::new`. Overall a 13.11% icount decrase on docs-heavy crates.

Benchmarked mainly on `regex-1.10.5`.

This means that currently our heaviest function is `rustc_middle::Interners::intern_ty`, even
for documentation-heavy crates

Co-authored-by: Roope Salmi <rpsalmi@gmail.com>
-rw-r--r--clippy_lints/src/doc/lazy_continuation.rs88
-rw-r--r--clippy_lints/src/doc/mod.rs18
2 files changed, 54 insertions, 52 deletions
diff --git a/clippy_lints/src/doc/lazy_continuation.rs b/clippy_lints/src/doc/lazy_continuation.rs
index 8aeb835fe39..cb9d68224db 100644
--- a/clippy_lints/src/doc/lazy_continuation.rs
+++ b/clippy_lints/src/doc/lazy_continuation.rs
@@ -2,11 +2,10 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use itertools::Itertools;
 use rustc_errors::Applicability;
 use rustc_lint::LateContext;
-use rustc_span::{BytePos, Span};
-use std::cmp::Ordering;
+use rustc_span::BytePos;
 use std::ops::Range;
 
-use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS};
+use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS, Fragments};
 
 fn map_container_to_text(c: &super::Container) -> &'static str {
     match c {
@@ -19,29 +18,27 @@ fn map_container_to_text(c: &super::Container) -> &'static str {
 pub(super) fn check(
     cx: &LateContext<'_>,
     doc: &str,
-    range: Range<usize>,
-    mut span: Span,
+    cooked_range: Range<usize>,
+    fragments: &Fragments<'_>,
     containers: &[super::Container],
 ) {
-    if doc[range.clone()].contains('\t') {
-        // We don't do tab stops correctly.
-        return;
-    }
-
-    // Blockquote
-    let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count();
+    // Get blockquotes
+    let ccount = doc[cooked_range.clone()].chars().filter(|c| *c == '>').count();
     let blockquote_level = containers
         .iter()
         .filter(|c| matches!(c, super::Container::Blockquote))
         .count();
-    if ccount < blockquote_level {
+
+    if ccount < blockquote_level
+        && let Some(mut span) = fragments.span(cx, cooked_range.clone())
+    {
         span_lint_and_then(
             cx,
             DOC_LAZY_CONTINUATION,
             span,
             "doc quote line without `>` marker",
             |diag| {
-                let mut doc_start_range = &doc[range];
+                let mut doc_start_range = &doc[cooked_range];
                 let mut suggested = String::new();
                 for c in containers {
                     let text = map_container_to_text(c);
@@ -78,7 +75,7 @@ pub(super) fn check(
     }
 
     // List
-    let leading_spaces = doc[range].chars().filter(|c| *c == ' ').count();
+    let leading_spaces = doc[cooked_range.clone()].chars().filter(|c| *c == ' ').count();
     let list_indentation = containers
         .iter()
         .map(|c| {
@@ -89,36 +86,41 @@ pub(super) fn check(
             }
         })
         .sum();
-    match leading_spaces.cmp(&list_indentation) {
-        Ordering::Less => span_lint_and_then(
-            cx,
-            DOC_LAZY_CONTINUATION,
-            span,
-            "doc list item without indentation",
-            |diag| {
-                // simpler suggestion style for indentation
-                let indent = list_indentation - leading_spaces;
-                diag.span_suggestion_verbose(
-                    span.shrink_to_hi(),
-                    "indent this line",
-                    std::iter::repeat_n(" ", indent).join(""),
-                    Applicability::MaybeIncorrect,
-                );
-                diag.help("if this is supposed to be its own paragraph, add a blank line");
-            },
-        ),
-        Ordering::Greater => {
-            let sugg = std::iter::repeat_n(" ", list_indentation).join("");
-            span_lint_and_sugg(
+
+    if leading_spaces != list_indentation
+        && let Some(span) = fragments.span(cx, cooked_range.clone())
+    {
+        if leading_spaces < list_indentation {
+            span_lint_and_then(
                 cx,
-                DOC_OVERINDENTED_LIST_ITEMS,
+                DOC_LAZY_CONTINUATION,
                 span,
-                "doc list item overindented",
-                format!("try using `{sugg}` ({list_indentation} spaces)"),
-                sugg,
-                Applicability::MaybeIncorrect,
+                "doc list item without indentation",
+                |diag| {
+                    // simpler suggestion style for indentation
+                    let indent = list_indentation - leading_spaces;
+                    diag.span_suggestion_verbose(
+                        span.shrink_to_hi(),
+                        "indent this line",
+                        std::iter::repeat_n(" ", indent).join(""),
+                        Applicability::MaybeIncorrect,
+                    );
+                    diag.help("if this is supposed to be its own paragraph, add a blank line");
+                },
             );
-        },
-        Ordering::Equal => {},
+
+            return;
+        }
+
+        let sugg = std::iter::repeat_n(" ", list_indentation).join("");
+        span_lint_and_sugg(
+            cx,
+            DOC_OVERINDENTED_LIST_ITEMS,
+            span,
+            "doc list item overindented",
+            format!("try using `{sugg}` ({list_indentation} spaces)"),
+            sugg,
+            Applicability::MaybeIncorrect,
+        );
     }
 }
diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs
index c1491d96ceb..c46dd09d60c 100644
--- a/clippy_lints/src/doc/mod.rs
+++ b/clippy_lints/src/doc/mod.rs
@@ -3,7 +3,6 @@
 use clippy_config::Conf;
 use clippy_utils::attrs::is_doc_hidden;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
-use clippy_utils::source::snippet_opt;
 use clippy_utils::{is_entrypoint_fn, is_trait_impl_item};
 use pulldown_cmark::Event::{
     Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
@@ -1082,26 +1081,27 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
             | TaskListMarker(_) | Code(_) | Rule | InlineMath(..) | DisplayMath(..) => (),
             SoftBreak | HardBreak => {
                 if !containers.is_empty()
-                    && let Some((next_event, next_range)) = events.peek()
-                    && let Some(next_span) = fragments.span(cx, next_range.clone())
-                    && let Some(span) = fragments.span(cx, range.clone())
                     && !in_footnote_definition
+                    // Tabs aren't handled correctly vvvv
+                    && !doc[range.clone()].contains('\t')
+                    && let Some((next_event, next_range)) = events.peek()
                     && !matches!(next_event, End(_))
                 {
                     lazy_continuation::check(
                         cx,
                         doc,
                         range.end..next_range.start,
-                        Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
+                        &fragments,
                         &containers[..],
                     );
                 }
 
-                if let Some(span) = fragments.span(cx, range.clone())
+
+                if event == HardBreak
+                    && !doc[range.clone()].trim().starts_with('\\')
+                    && let Some(span) = fragments.span(cx, range.clone())
                     && !span.from_expansion()
-                    && let Some(snippet) = snippet_opt(cx, span)
-                    && !snippet.trim().starts_with('\\')
-                    && event == HardBreak {
+                    {
                     collected_breaks.push(span);
                 }
             },