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}