cargo_docs_md/generator/
impls.rs

1//! Implementation block rendering for documentation generation.
2//!
3//! This module provides the [`ImplRenderer`] struct which handles rendering
4//! impl blocks (both inherent and trait implementations) to markdown format.
5
6use std::borrow::Cow;
7use std::fmt::Write;
8
9use rustdoc_types::{Id, Impl, Item};
10
11use crate::generator::context::RenderContext;
12use crate::generator::render_shared::{impl_sort_key, render_impl_items};
13use crate::types::TypeRenderer;
14
15/// Blanket trait implementations to filter from output.
16///
17/// These are automatically derived by the compiler and add noise to documentation
18/// without providing useful information. Users who want them can use `--include-blanket-impls`.
19const BLANKET_TRAITS: &[&str] = &[
20    // Identity/conversion traits (blanket impls)
21    "From",
22    "Into",
23    "TryFrom",
24    "TryInto",
25    // Reflection
26    "Any",
27    // Borrowing (trivial impls)
28    "Borrow",
29    "BorrowMut",
30    // Ownership
31    "ToOwned",
32    "CloneToUninit",
33];
34
35/// Check if an impl block is for a blanket trait that should be filtered.
36///
37/// Returns `true` if the impl is for one of the commonly auto-derived traits
38/// that add noise to documentation (From, Into, Any, Borrow, etc.).
39#[must_use]
40pub fn is_blanket_impl(impl_block: &Impl) -> bool {
41    let Some(trait_ref) = &impl_block.trait_ else {
42        return false;
43    };
44
45    // Extract the trait name (last segment of the path)
46    let trait_name = trait_ref.path.split("::").last().unwrap_or(&trait_ref.path);
47
48    BLANKET_TRAITS.contains(&trait_name)
49}
50
51/// Renders impl blocks to markdown.
52///
53/// This struct handles:
54/// - Inherent implementations (`impl MyType { ... }`)
55/// - Trait implementations (`impl Trait for MyType { ... }`)
56/// - Method signatures within impl blocks
57/// - Associated types and constants
58///
59/// The renderer is generic over [`RenderContext`], allowing it to work with
60/// both single-crate (`GeneratorContext`) and multi-crate (`SingleCrateView`) modes.
61pub struct ImplRenderer<'a> {
62    /// Reference to the render context (either single-crate or multi-crate).
63    ctx: &'a dyn RenderContext,
64
65    /// Path of the current file being generated (for relative link calculation).
66    current_file: &'a str,
67
68    /// Cached type renderer to avoid repeated construction.
69    type_renderer: TypeRenderer<'a>,
70}
71
72impl<'a> ImplRenderer<'a> {
73    /// Create a new impl renderer with the given context.
74    ///
75    /// # Arguments
76    ///
77    /// * `ctx` - Render context (implements `RenderContext` trait)
78    /// * `current_file` - Path of the current file (for relative link calculation)
79    pub fn new(ctx: &'a dyn RenderContext, current_file: &'a str) -> Self {
80        let type_renderer = TypeRenderer::new(ctx.krate());
81        Self {
82            ctx,
83            current_file,
84            type_renderer,
85        }
86    }
87
88    /// Process documentation string to resolve intra-doc links.
89    ///
90    /// Delegates to the render context's `process_docs` method, which handles
91    /// both single-crate and multi-crate link resolution.
92    fn process_docs(&self, item: &Item) -> Option<String> {
93        self.ctx.process_docs(item, self.current_file)
94    }
95
96    /// Render impl blocks for a given type.
97    ///
98    /// This method looks up all impl blocks for a type and renders them
99    /// in two sections:
100    ///
101    /// 1. **Implementations** - Inherent impls (methods defined directly on the type)
102    /// 2. **Trait Implementations** - Trait impls (`impl Trait for Type`)
103    ///
104    /// # Impl Block Categories
105    ///
106    /// - **Inherent**: `impl MyType { fn method(&self) {} }`
107    /// - **Trait**: `impl Clone for MyType { ... }`
108    /// - **Synthetic**: Auto-derived by compiler (Send, Sync) - skipped
109    pub fn render_impl_blocks(&self, md: &mut String, item_id: Id) {
110        let Some(impls) = self.ctx.get_impls(&item_id) else {
111            return;
112        };
113
114        // Partition impls into trait and inherent
115        let (mut trait_impls, inherent_impls): (Vec<_>, Vec<_>) =
116            impls.iter().partition(|i| i.trait_.is_some());
117
118        // Sort trait impls by trait name + generics for deterministic output
119        trait_impls.sort_by(|a: &&&Impl, b: &&&Impl| {
120            let key_a = impl_sort_key(a, &self.type_renderer);
121            let key_b = impl_sort_key(b, &self.type_renderer);
122            key_a.cmp(&key_b)
123        });
124
125        // Deduplicate trait impls with the same signature
126        trait_impls.dedup_by(|a, b| {
127            impl_sort_key(a, &self.type_renderer) == impl_sort_key(b, &self.type_renderer)
128        });
129
130        // === Inherent Implementations ===
131        if !inherent_impls.is_empty() {
132            md.push_str("#### Implementations\n\n");
133            for impl_block in inherent_impls {
134                self.render_impl_methods(md, impl_block);
135            }
136        }
137
138        // === Trait Implementations ===
139        // Filter out blanket impls (From, Into, Any, etc.) unless explicitly included
140        let filtered_trait_impls: Vec<_> = if self.ctx.include_blanket_impls() {
141            trait_impls
142        } else {
143            trait_impls
144                .into_iter()
145                .filter(|i| !is_blanket_impl(i))
146                .collect()
147        };
148
149        if !filtered_trait_impls.is_empty() {
150            md.push_str("#### Trait Implementations\n\n");
151            for impl_block in filtered_trait_impls {
152                self.render_trait_impl(md, impl_block);
153            }
154        }
155    }
156
157    /// Render a single trait implementation block.
158    fn render_trait_impl(&self, md: &mut String, impl_block: &Impl) {
159        // Skip synthetic impls (auto-traits like Send, Sync, Unpin)
160        if impl_block.is_synthetic {
161            return;
162        }
163
164        // Build the trait name with generic args
165        let trait_name = impl_block
166            .trait_
167            .as_ref()
168            .map(|t| {
169                let mut name = t.path.clone();
170                if let Some(args) = &t.args {
171                    name.push_str(&self.render_generic_args_for_impl(args));
172                }
173                name
174            })
175            .unwrap_or_default();
176
177        let generics = self
178            .type_renderer
179            .render_generics(&impl_block.generics.params);
180        let for_type = self.type_renderer.render_type(&impl_block.for_);
181
182        let unsafe_str = if impl_block.is_unsafe { "unsafe " } else { "" };
183        let negative_str = if impl_block.is_negative { "!" } else { "" };
184
185        _ = write!(
186            md,
187            "##### `{unsafe_str}impl{generics} {negative_str}{trait_name} for {for_type}`\n\n"
188        );
189
190        self.render_impl_methods(md, impl_block);
191    }
192
193    /// Render the items (methods, constants, types) within an impl block.
194    ///
195    /// Each item is rendered as a bullet point. Items can be:
196    /// - **Functions/Methods**: Full signature with modifiers
197    /// - **Associated Constants**: `const NAME: Type`
198    /// - **Associated Types**: `type Name = Type`
199    ///
200    /// For methods, the first line of documentation is included as a brief summary.
201    /// Type links are added for resolvable types in method signatures.
202    fn render_impl_methods(&self, md: &mut String, impl_block: &Impl) {
203        let krate = self.ctx.krate();
204        render_impl_items(
205            md,
206            impl_block,
207            krate,
208            &self.type_renderer,
209            &Some(|item: &Item| self.process_docs(item)),
210            &Some(|id: rustdoc_types::Id| self.ctx.create_link(id, self.current_file)),
211        );
212    }
213
214    /// Render generic arguments for impl block signatures.
215    ///
216    /// This handles the different forms of generic arguments:
217    /// - **Angle bracketed**: `<T, U, Item = V>` (most common)
218    /// - **Parenthesized**: `(A, B) -> C` (for Fn traits)
219    /// - **Return type notation**: `(..)` (experimental)
220    fn render_generic_args_for_impl(&self, args: &rustdoc_types::GenericArgs) -> String {
221        match args {
222            rustdoc_types::GenericArgs::AngleBracketed { args, constraints } => {
223                let mut parts: Vec<Cow<str>> = args
224                    .iter()
225                    .map(|a| match a {
226                        rustdoc_types::GenericArg::Lifetime(lt) => Cow::Borrowed(lt.as_str()),
227                        rustdoc_types::GenericArg::Type(ty) => self.type_renderer.render_type(ty),
228                        rustdoc_types::GenericArg::Const(c) => {
229                            Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr))
230                        },
231                        rustdoc_types::GenericArg::Infer => Cow::Borrowed("_"),
232                    })
233                    .collect();
234
235                parts.extend(constraints.iter().map(|c| {
236                    let constraint_args = c
237                        .args
238                        .as_ref()
239                        .map(|a| self.render_generic_args_for_impl(a))
240                        .unwrap_or_default();
241
242                    match &c.binding {
243                        rustdoc_types::AssocItemConstraintKind::Equality(term) => {
244                            let term_str = match term {
245                                rustdoc_types::Term::Type(ty) => self.type_renderer.render_type(ty),
246                                rustdoc_types::Term::Constant(c) => {
247                                    Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr))
248                                },
249                            };
250                            Cow::Owned(format!("{}{constraint_args} = {term_str}", c.name))
251                        },
252                        rustdoc_types::AssocItemConstraintKind::Constraint(bounds) => {
253                            let bound_strs: Vec<Cow<str>> = bounds
254                                .iter()
255                                .map(|b| self.type_renderer.render_generic_bound(b))
256                                .collect();
257                            Cow::Owned(format!(
258                                "{}{constraint_args}: {}",
259                                c.name,
260                                bound_strs
261                                    .iter()
262                                    .map(AsRef::as_ref)
263                                    .collect::<Vec<_>>()
264                                    .join(" + ")
265                            ))
266                        },
267                    }
268                }));
269
270                if parts.is_empty() {
271                    String::new()
272                } else {
273                    format!(
274                        "<{}>",
275                        parts
276                            .iter()
277                            .map(AsRef::as_ref)
278                            .collect::<Vec<_>>()
279                            .join(", ")
280                    )
281                }
282            },
283
284            rustdoc_types::GenericArgs::Parenthesized { inputs, output } => {
285                let input_strs: Vec<Cow<str>> = inputs
286                    .iter()
287                    .map(|t| self.type_renderer.render_type(t))
288                    .collect();
289                let ret = output
290                    .as_ref()
291                    .map(|t| format!(" -> {}", self.type_renderer.render_type(t)))
292                    .unwrap_or_default();
293                format!(
294                    "({}){ret}",
295                    input_strs
296                        .iter()
297                        .map(AsRef::as_ref)
298                        .collect::<Vec<_>>()
299                        .join(", ")
300                )
301            },
302
303            rustdoc_types::GenericArgs::ReturnTypeNotation => " (..)".to_string(),
304        }
305    }
306}