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//!
6//! # Path Types in `rustdoc_types::Impl`
7//!
8//! When working with impl blocks, there are two different path representations:
9//!
10//! - **`Impl.trait_: Option<Path>`** - The trait being implemented
11//! - `Path.path` is a `String` like `"Clone"` or `"std::fmt::Debug"`
12//! - Use for display/rendering in documentation output
13//! - Extract trait name with `path.rsplit("::").next()`
14//!
15//! - **`ItemSummary.path: Vec<String>`** - Structured path from `krate.paths`
16//! - Example: `["std", "clone", "Clone"]`
17//! - Use for lookups and resolution via item IDs
18//!
19//! The `Path.path` string is already formatted for display, while
20//! `ItemSummary.path` is structured for programmatic manipulation.
21
22use std::borrow::Cow;
23use std::collections::HashSet;
24use std::fmt::Write;
25
26use rustdoc_types::{
27 AssocItemConstraintKind, Crate, GenericArg, GenericArgs, GenericBound, GenericParamDefKind, Id,
28 Impl, Item, ItemEnum, Term, Type, WherePredicate,
29};
30
31use crate::generator::context::RenderContext;
32use crate::generator::render_shared::{RendererInternals, RendererUtils};
33use crate::linker::ImplContext;
34use crate::types::TypeRenderer;
35use crate::utils::PathUtils;
36
37/// Blanket trait implementations to filter from output.
38///
39/// These are automatically derived by the compiler and add noise to documentation
40/// without providing useful information. Users who want them can use `--include-blanket-impls`.
41const BLANKET_TRAITS: &[&str] = &[
42 // Identity/conversion traits (blanket impls)
43 "From",
44 "Into",
45 "TryFrom",
46 "TryInto",
47 // Reflection
48 "Any",
49 // Borrowing (trivial impls)
50 "Borrow",
51 "BorrowMut",
52 // Ownership
53 "ToOwned",
54 "CloneToUninit",
55];
56
57/// Trivial derive trait implementations that can be collapsed.
58///
59/// These are traits commonly derived via `#[derive(...)]` that have standard,
60/// predictable implementations. When `RenderConfig.hide_trivial_derives` is enabled,
61/// these are grouped into a collapsible `<details>` block with a summary table.
62///
63/// The list includes:
64/// - **Cloning**: `Clone`, `Copy`
65/// - **Formatting**: `Debug`
66/// - **Default values**: `Default`
67/// - **Equality**: `PartialEq`, `Eq`
68/// - **Hashing**: `Hash`
69/// - **Ordering**: `PartialOrd`, `Ord`
70pub const TRIVIAL_DERIVE_TRAITS: &[&str] = &[
71 // Cloning traits
72 "Clone",
73 "Copy",
74 // Formatting
75 "Debug",
76 // Default values
77 "Default",
78 // Equality comparison
79 "PartialEq",
80 "Eq",
81 // Hashing
82 "Hash",
83 // Ordering comparison
84 "PartialOrd",
85 "Ord",
86];
87
88/// Short descriptions for trivial derive traits, used in summary tables.
89///
90/// Maps trait names to brief descriptions for the collapsible summary table.
91pub const TRIVIAL_DERIVE_DESCRIPTIONS: &[(&str, &str)] = &[
92 ("Clone", "Returns a copy of the value"),
93 ("Copy", "Marker trait for types with copy semantics"),
94 ("Debug", "Formats the value for debugging"),
95 ("Default", "Returns the default value"),
96 ("PartialEq", "Compares for equality"),
97 ("Eq", "Marker for total equality"),
98 ("Hash", "Feeds this value into a Hasher"),
99 ("PartialOrd", "Partial ordering comparison"),
100 ("Ord", "Total ordering comparison"),
101];
102
103/// Utility functions to work with impl's and generics
104pub struct ImplUtils;
105
106impl ImplUtils {
107 /// Check if an impl block is for a trivial derive trait.
108 ///
109 /// Returns `true` if the impl is for one of the commonly derived traits
110 /// `(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)`.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use rustdoc_types::Impl;
116 /// // For an impl block with trait "Clone", returns true
117 /// // For an impl block with trait "Display", returns false
118 /// ```
119 #[must_use]
120 pub fn is_trivial_derive_impl(impl_block: &Impl) -> bool {
121 let Some(trait_ref) = &impl_block.trait_ else {
122 return false;
123 };
124
125 // Extract the trait name (last segment of the path)
126 let trait_name = PathUtils::short_name(&trait_ref.path);
127
128 TRIVIAL_DERIVE_TRAITS.contains(&trait_name)
129 }
130
131 /// Get the description for a trivial derive trait.
132 ///
133 /// Returns `None` if the trait is not in the trivial derives list.
134 #[must_use]
135 pub fn get_trivial_derive_description(trait_name: &str) -> Option<&'static str> {
136 // Extract just the trait name if it's a full path
137 let name = PathUtils::short_name(trait_name);
138
139 TRIVIAL_DERIVE_DESCRIPTIONS
140 .iter()
141 .find(|(t, _)| *t == name)
142 .map(|(_, desc)| *desc)
143 }
144
145 /// Check if an impl block is for a blanket trait that should be filtered.
146 ///
147 /// Returns `true` if the impl is for one of the commonly auto-derived traits
148 /// that add noise to documentation (From, Into, Any, Borrow, etc.).
149 #[must_use]
150 pub fn is_blanket_impl(impl_block: &Impl) -> bool {
151 let Some(trait_ref) = &impl_block.trait_ else {
152 return false;
153 };
154
155 // Extract the trait name (last segment of the path)
156 // Using rsplit().next() is more efficient than split().last()
157 let trait_name = trait_ref
158 .path
159 .rsplit("::")
160 .next()
161 .unwrap_or(&trait_ref.path);
162
163 BLANKET_TRAITS.contains(&trait_name)
164 }
165
166 /// Check if a type is generic (contains a type parameter like `T`).
167 ///
168 /// This is used to determine whether to show generic parameters in impl blocks.
169 /// For blanket impls like `impl<T> Trait for T`, we only show the generics if
170 /// the `for_` type is actually generic. When the impl is instantiated for a
171 /// concrete type like `TocEntry`, we hide the generics.
172 ///
173 /// # Examples
174 ///
175 /// ```text
176 /// // Generic type - returns true
177 /// is_generic_type(&Type::Generic("T")) == true
178 ///
179 /// // Concrete type - returns false
180 /// is_generic_type(&Type::ResolvedPath { name: "TocEntry", .. }) == false
181 ///
182 /// // Container with generic - returns true
183 /// is_generic_type(&Type::ResolvedPath {
184 /// name: "Vec",
185 /// args: Some(GenericArgs::AngleBracketed { args: [Type::Generic("T")] })
186 /// }) == true
187 /// ```
188 #[must_use]
189 pub fn is_generic_type(ty: &Type) -> bool {
190 match ty {
191 // Direct generic parameter like `T`
192 Type::Generic(_) => true,
193
194 // Check nested types for generic parameters
195 Type::ResolvedPath(path) => {
196 // Check generic args of the path
197 path.args
198 .as_ref()
199 .is_some_and(|args| Self::generic_args_contain_generic(args))
200 },
201
202 // References, raw pointers, and arrays: check the inner type
203 Type::BorrowedRef { type_, .. }
204 | Type::RawPointer { type_, .. }
205 | Type::Array { type_, .. } => Self::is_generic_type(type_),
206
207 // Slices: check the element type
208 Type::Slice(inner) => Self::is_generic_type(inner),
209
210 // Tuples: check all element types
211 Type::Tuple(types) => types.iter().any(Self::is_generic_type),
212
213 // Qualified paths like `<T as Trait>::Item`
214 Type::QualifiedPath { self_type, .. } => Self::is_generic_type(self_type),
215
216 // Primitives and other types are not generic
217 _ => false,
218 }
219 }
220
221 /// Check if generic args contain any generic type parameters.
222 fn generic_args_contain_generic(args: &GenericArgs) -> bool {
223 match args {
224 GenericArgs::AngleBracketed { args, .. } => args.iter().any(|arg| match arg {
225 GenericArg::Type(ty) => Self::is_generic_type(ty),
226
227 GenericArg::Const(_) | GenericArg::Lifetime(_) | GenericArg::Infer => false,
228 }),
229
230 GenericArgs::Parenthesized { inputs, output } => {
231 inputs.iter().any(Self::is_generic_type)
232 || output.as_ref().is_some_and(Self::is_generic_type)
233 },
234
235 GenericArgs::ReturnTypeNotation => false,
236 }
237 }
238
239 // Contains AI generated docs - Start (STILL NOT PROPERLY CHECKED)
240 // TODO: Clean this up later to reduce it down to the necessary details,
241 // various information related to rustdoc JSON and rustdoc_types crate
242 // needs to be checked.
243
244 /// Extract generic parameter names that appear in the impl signature (for_ and trait).
245 ///
246 /// This extracts generics from only the visible parts of the impl header:
247 /// 1. The `for_` type: `impl<T> Trait for Foo<T>` → extracts `T` from `Foo<T>`
248 /// 2. The trait path: `impl<U> Trait<U> for Foo` → extracts `U` from `Trait<U>`
249 ///
250 /// Unlike [`extract_impl_visible_generics`], this does NOT extract from where clauses.
251 /// Use this for rendering purposes when deciding which generic params to show in
252 /// `impl<...>`. Generics that only appear in where clauses should not be shown in
253 /// the impl header since the where clause itself is not rendered.
254 ///
255 /// # Examples
256 ///
257 /// ```text
258 /// impl<T: Sized> IntoEither for T
259 /// └─ for_ type is Generic("T") → extracts ["T"]
260 /// └─ When rendered for `Either<L, R>`, for_ becomes `Either<L, R>`
261 /// └─ Extracts ["L", "R"], NOT ["T"]
262 /// └─ T is only in where clause, so `impl<T>` should NOT be shown
263 ///
264 /// impl<T> Clone for Wrapper<T>
265 /// └─ for_ type has `Wrapper<T>` → extracts ["T"]
266 /// └─ Result: render as `impl<T>`
267 ///
268 /// impl<T, U> Trait<U> for Foo<T>
269 /// └─ for_ type has `Foo<T>` → extracts ["T"]
270 /// └─ trait path has `Trait<U>` → extracts ["U"]
271 /// └─ Result: render as `impl<T, U>` (both visible)
272 /// ```
273 #[must_use]
274 pub fn extract_impl_signature_generics(impl_block: &Impl) -> HashSet<String> {
275 let mut names = HashSet::new();
276
277 // Extract from for_ type (the type being implemented for)
278 Self::extract_type_generics_into(&impl_block.for_, &mut names);
279
280 // Extract from trait path generic arguments
281 if let Some(trait_ref) = &impl_block.trait_
282 && let Some(args) = &trait_ref.args
283 {
284 Self::extract_generic_args_into(args, &mut names);
285 }
286
287 names
288 }
289
290 /// Extract all generic parameter names visible in an impl block's signature.
291 ///
292 /// # Why This Is Needed
293 ///
294 /// Rustdoc JSON stores impl generics in two places that can have DIFFERENT names:
295 /// - `impl_block.generics.params` - the declared params (e.g., `T` from `impl<T>`)
296 /// - `impl_block.for_` - the actual type (e.g., `StyledObject<D>`)
297 ///
298 /// For blanket impls, these names often differ! The compiler transforms:
299 /// ```text
300 /// impl<D: Display> ToString for StyledObject<D>
301 /// ```
302 /// Into JSON where `generics.params` has `T` but `for_` has `StyledObject<D>`.
303 /// If we blindly use `generics.params`, we'd render `impl<T> ToString for StyledObject<D>`.
304 ///
305 /// # Solution
306 ///
307 /// Extract generics from what's VISIBLE in the signature:
308 /// 1. The `for_` type: `Foo<T>` → extracts `T`
309 /// 2. The trait path: `Iterator<Item = U>` → extracts `U`
310 /// 3. Where clauses: `where T: Clone` → extracts `T`
311 ///
312 /// Then filter `generics.params` to only include names that appear in visible locations.
313 ///
314 /// **Note:** For rendering the impl header, use [`extract_impl_signature_generics`] instead,
315 /// which excludes where clause generics.
316 ///
317 /// # Examples
318 ///
319 /// ```text
320 /// impl<T> Clone for Wrapper<T>
321 /// └─ for_ type has `Wrapper<T>` → extracts ["T"]
322 /// └─ Result: render as `impl<T>`
323 ///
324 /// impl<T, U> Trait<U> for Foo<T> where T: Clone
325 /// └─ for_ type has `Foo<T>` → extracts ["T"]
326 /// └─ trait path has `Trait<U>` → extracts ["U"]
327 /// └─ where clause has `T: Clone` → extracts ["T"]
328 /// └─ Result: render as `impl<T, U>` (both visible)
329 ///
330 /// impl<D: Display> ToString for StyledObject<D>
331 /// └─ for_ type has `StyledObject<D>` → extracts ["D"]
332 /// └─ Result: render as `impl<D>` (not `impl<T>`)
333 /// ```
334 #[must_use]
335 pub fn extract_impl_visible_generics(impl_block: &Impl) -> HashSet<String> {
336 // Start with signature generics (for_ type and trait path)
337 let mut names = Self::extract_impl_signature_generics(impl_block);
338
339 // Also extract from where clause predicates
340 for pred in &impl_block.generics.where_predicates {
341 match pred {
342 // `where T: Clone + Iterator<Item = U>` extracts both T and U
343 WherePredicate::BoundPredicate { type_, bounds, .. } => {
344 Self::extract_type_generics_into(type_, &mut names);
345
346 for bound in bounds {
347 Self::extract_bound_generics_into(bound, &mut names);
348 }
349 },
350
351 // `where <T as Trait>::Assoc = U` extracts both T and U
352 WherePredicate::EqPredicate { lhs, rhs, .. } => {
353 Self::extract_type_generics_into(lhs, &mut names);
354
355 Self::extract_term_generics_into(rhs, &mut names);
356 },
357
358 // Lifetime predicates like `where 'a: 'b` don't have type params
359 WherePredicate::LifetimePredicate { .. } => {},
360 }
361 }
362
363 names
364 }
365
366 /// Extract generic parameter names from a type into a set.
367 ///
368 /// Recursively traverses the type structure to find all `Type::Generic` variants.
369 /// This handles all the ways generics can be nested in complex types.
370 ///
371 /// # Type Tree Visualization
372 ///
373 /// ```text
374 /// HashMap<K, Vec<T>>
375 /// └─ ResolvedPath("HashMap")
376 /// └─ args: [K, Vec<T>]
377 /// │ └─ ResolvedPath("Vec")
378 /// │ └─ args: [T]
379 /// │ └─ Generic("T") ← extracted!
380 /// └─ Generic("K") ← extracted!
381 /// ```
382 fn extract_type_generics_into(ty: &Type, names: &mut HashSet<String>) {
383 match ty {
384 // Base case: A bare generic parameter like `T`, `U`, `Item`
385 // Example: In `fn foo<T>(x: T)`, the parameter type is Generic("T")
386 Type::Generic(name) => {
387 names.insert(name.clone());
388 },
389
390 // Named types like `Vec<T>`, `HashMap<K, V>`, `Option<T>`
391 // The generics are inside the `args` field
392 // Example: `Vec<T>` → ResolvedPath { path: "Vec", args: [Generic("T")] }
393 Type::ResolvedPath(path) => {
394 if let Some(args) = &path.args {
395 Self::extract_generic_args_into(args, names);
396 }
397 },
398
399 // References and pointers: `&T`, `&mut T`, `*const T`, `*mut T`
400 // Example: `&Vec<T>` → BorrowedRef { type_: ResolvedPath("Vec<T>") }
401 Type::BorrowedRef { type_, .. } | Type::RawPointer { type_, .. } => {
402 Self::extract_type_generics_into(type_, names);
403 },
404
405 // Slices and arrays: `[T]`, `[T; N]`
406 // Example: `[T]` → Slice(Generic("T"))
407 // Example: `[u8; 32]` → Array { type_: Primitive("u8"), len: 32 }
408 Type::Slice(t) | Type::Array { type_: t, .. } => {
409 Self::extract_type_generics_into(t, names);
410 },
411
412 // Tuples: `(T, U, V)`
413 // Example: `(T, u32, V)` → Tuple([Generic("T"), Primitive, Generic("V")])
414 Type::Tuple(types) => {
415 for t in types {
416 Self::extract_type_generics_into(t, names);
417 }
418 },
419
420 // Qualified paths: `<T as Trait>::Item`, `<I as Iterator>::Item`
421 // These appear in associated type definitions
422 // Example: `<I as Iterator>::Item` → QualifiedPath {
423 // self_type: Generic("I"),
424 // trait_: Some(Path("Iterator")),
425 // name: "Item"
426 // }
427 Type::QualifiedPath {
428 self_type, trait_, ..
429 } => {
430 // Extract from the self type (e.g., `I` in `<I as Iterator>::Item`)
431 Self::extract_type_generics_into(self_type, names);
432
433 // Also check trait generic args (e.g., `<T as Trait<U>>::Item`)
434 if let Some(tr) = trait_
435 && let Some(args) = &tr.args
436 {
437 Self::extract_generic_args_into(args, names);
438 }
439 },
440
441 // Function pointers: `fn(T) -> U`, `fn(&T, V) -> W`
442 // Example: `fn(T) -> U` → FunctionPointer {
443 // inputs: [("_", Generic("T"))],
444 // output: Some(Generic("U"))
445 // }
446 Type::FunctionPointer(fp) => {
447 // Function pointer inputs are (name, type) tuples - we only need the type
448 for (_, input_ty) in &fp.sig.inputs {
449 Self::extract_type_generics_into(input_ty, names);
450 }
451
452 if let Some(output) = &fp.sig.output {
453 Self::extract_type_generics_into(output, names);
454 }
455 },
456
457 // impl Trait: `impl Iterator<Item = T>`, `impl Clone + Send`
458 // Example: `impl Iterator<Item = T>` → ImplTrait([TraitBound(Iterator<Item=T>)])
459 Type::ImplTrait(bounds) => {
460 for bound in bounds {
461 Self::extract_bound_generics_into(bound, names);
462 }
463 },
464
465 // dyn Trait: `dyn Iterator<Item = T>`, `dyn Fn(T) -> U`
466 // Example: `dyn Iterator<Item = T>` → DynTrait with trait bounds
467 Type::DynTrait(dyn_trait) => {
468 for poly in &dyn_trait.traits {
469 if let Some(args) = &poly.trait_.args {
470 Self::extract_generic_args_into(args, names);
471 }
472 }
473 },
474
475 // Leaf types with no generics
476 // - Primitive: `i32`, `bool`, `str`, etc.
477 // - Infer: `_` (the compiler infers this)
478 // - Pat: pattern types (experimental)
479 Type::Primitive(_) | Type::Infer | Type::Pat { .. } => {},
480 }
481 }
482
483 /// Extract generic parameter names from generic arguments.
484 ///
485 /// Generic arguments come in three forms:
486 ///
487 /// # Forms
488 ///
489 /// ```text
490 /// AngleBracketed: Vec<T, U> → args: [T, U], constraints: []
491 /// Iterator<Item = T> → args: [], constraints: [Item = T]
492 ///
493 /// Parenthesized: Fn(T, U) -> V → inputs: [T, U], output: Some(V)
494 /// FnOnce(T) → inputs: [T], output: None
495 ///
496 /// ReturnTypeNotation: method(..) → (no generics to extract)
497 /// ```
498 fn extract_generic_args_into(args: &GenericArgs, names: &mut HashSet<String>) {
499 match args {
500 // Angle-bracketed args: `<T, U, Item = V>`
501 //
502 // Two parts:
503 // - `args`: positional type args like `T` and `U`
504 // - `constraints`: associated type bindings like `Item = V`
505 //
506 // Example: `Iterator<Item = T>` has constraints with `Item = T`
507 // Example: `HashMap<K, V>` has args [K, V] with no constraints
508 GenericArgs::AngleBracketed { args, constraints } => {
509 // Extract from positional type arguments: <T, U, ...>
510 for arg in args {
511 // Only Type args have generics - Lifetime('a) and Const(N) don't
512 if let GenericArg::Type(ty) = arg {
513 Self::extract_type_generics_into(ty, names);
514 }
515 }
516
517 // Extract from associated type constraints: <Item = T, Output = U>
518 // These can be either:
519 // - Equality: `Item = T` (term is a Type or Const)
520 // - Constraint: `Item: Clone` (has trait bounds)
521 for constraint in constraints {
522 match &constraint.binding {
523 // `Item = T` → extract T from the right-hand side
524 AssocItemConstraintKind::Equality(term) => {
525 Self::extract_term_generics_into(term, names);
526 },
527
528 // `Item: Iterator<Item = U>` → extract from bounds
529 AssocItemConstraintKind::Constraint(bounds) => {
530 for bound in bounds {
531 Self::extract_bound_generics_into(bound, names);
532 }
533 },
534 }
535 }
536 },
537
538 // Parenthesized args: `(T, U) -> V` (used for Fn traits)
539 //
540 // Example: `Fn(i32, T) -> U` → inputs: [i32, T], output: Some(U)
541 // Example: `FnMut(&T)` → inputs: [&T], output: None (returns ())
542 GenericArgs::Parenthesized { inputs, output } => {
543 for input in inputs {
544 Self::extract_type_generics_into(input, names);
545 }
546
547 if let Some(out) = output {
548 Self::extract_type_generics_into(out, names);
549 }
550 },
551
552 // Return type notation: `method(..)` - experimental, no generics
553 GenericArgs::ReturnTypeNotation => {},
554 }
555 }
556
557 /// Extract generic parameter names from a type or const term.
558 ///
559 /// A `Term` appears in associated type bindings like `Iterator<Item = T>`.
560 /// - `Term::Type(T)` - the `T` in `Item = T`
561 /// - `Term::Constant(N)` - const generics like `Item = 42`
562 fn extract_term_generics_into(term: &Term, names: &mut HashSet<String>) {
563 match term {
564 // Type term: `Item = T` → extract T
565 Term::Type(ty) => Self::extract_type_generics_into(ty, names),
566
567 // Const term: `Item = 42` → no type generics to extract
568 Term::Constant(_) => {},
569 }
570 }
571
572 /// Extract generic parameter names from a generic bound.
573 ///
574 /// Bounds appear in where clauses: `where T: Clone + Iterator<Item = U>`
575 ///
576 /// We only care about `TraitBound` (not `Outlives` like `'a: 'b`).
577 /// From the trait bound, we extract any generic args on the trait itself.
578 ///
579 /// # Example
580 ///
581 /// ```text
582 /// where T: Iterator<Item = U>
583 /// └─ TraitBound { trait_: Path("Iterator"), args: [Item = U] }
584 /// └─ extracts U
585 /// ```
586 fn extract_bound_generics_into(bound: &GenericBound, names: &mut HashSet<String>) {
587 // Only TraitBound has generic args - Outlives is for lifetimes only
588 if let GenericBound::TraitBound { trait_, .. } = bound
589 && let Some(args) = &trait_.args
590 {
591 Self::extract_generic_args_into(args, names);
592 }
593 }
594
595 /// Check if an impl has associated types referencing generics NOT visible in the signature.
596 ///
597 /// # The Problem
598 ///
599 /// Some impl blocks have generics that only appear in associated types, not in the
600 /// visible signature. When rendered, this produces confusing output:
601 ///
602 /// ```text
603 /// impl DoubleEndedIterator for Foo ← No `I` visible!
604 /// type Item = <I as Iterator>::Item ← Where does `I` come from?
605 /// ```
606 ///
607 /// # Detection Strategy
608 ///
609 /// 1. Extract "visible" generics from: `for_` type, trait path, where clause
610 /// 2. Extract "declared" generics from: `impl_block.generics.params`
611 /// 3. Compute "hidden" = declared - visible
612 /// 4. Check if any associated type references a hidden generic
613 ///
614 /// # Examples
615 ///
616 /// ```text
617 /// // FILTER THIS - `I` is hidden but used in associated type
618 /// impl<I: Iterator> DoubleEndedIterator for Foo {
619 /// type Item = <I as Iterator>::Item;
620 /// }
621 /// visible = {} (Foo has no generics)
622 /// declared = {I} (from impl<I>)
623 /// hidden = {I} (declared - visible)
624 /// assoc type uses I → FILTER!
625 ///
626 /// // KEEP THIS - `T` is visible in for_ type
627 /// impl<T> Iterator for Wrapper<T> {
628 /// type Item = T;
629 /// }
630 /// visible = {T} (from Wrapper<T>)
631 /// declared = {T} (from impl<T>)
632 /// hidden = {} (all visible)
633 /// → KEEP!
634 ///
635 /// // KEEP THIS - `U` is visible in trait path
636 /// impl<T, U> Trait<U> for Foo<T> {
637 /// type Output = (T, U);
638 /// }
639 /// visible = {T, U} (T from Foo<T>, U from Trait<U>)
640 /// declared = {T, U} (from impl<T, U>)
641 /// hidden = {} (all visible)
642 /// → KEEP!
643 /// ```
644 #[must_use]
645 pub fn has_hidden_generic_refs(impl_block: &Impl, krate: &Crate) -> bool {
646 // ┌─────────────────────────────────────────────────────────────────────┐
647 // │ Step 1: Get generics that appear in the VISIBLE signature │
648 // │ │
649 // │ These come from: for_ type, trait path, where clause │
650 // │ Example: `impl<T> Trait for Foo<T> where T: Clone` │
651 // │ visible = {T} (appears in Foo<T> and where clause) │
652 // └─────────────────────────────────────────────────────────────────────┘
653 let visible_generics = Self::extract_impl_visible_generics(impl_block);
654
655 // ┌─────────────────────────────────────────────────────────────────────┐
656 // │ Step 2: Get generics that are DECLARED in impl<...> │
657 // │ │
658 // │ Only type parameters, not lifetimes ('a) or const generics (N) │
659 // │ Example: `impl<'a, T, const N: usize>` → declared = {T} │
660 // └─────────────────────────────────────────────────────────────────────┘
661 let impl_generics: HashSet<_> = impl_block
662 .generics
663 .params
664 .iter()
665 .filter_map(|p| match &p.kind {
666 // Only type parameters can be "hidden" in our sense
667 GenericParamDefKind::Type { .. } => Some(p.name.clone()),
668
669 // Const and lifetime params don't cause the issue we're filtering
670 GenericParamDefKind::Const { .. } | GenericParamDefKind::Lifetime { .. } => None,
671 })
672 .collect();
673
674 // ┌─────────────────────────────────────────────────────────────────────┐
675 // │ Step 3: Compute hidden = declared - visible │
676 // │ │
677 // │ These are params declared in impl<...> but not appearing anywhere │
678 // │ in the visible signature (for_, trait path, where clause) │
679 // └─────────────────────────────────────────────────────────────────────┘
680 let hidden_generics: HashSet<_> = impl_generics
681 .difference(&visible_generics)
682 .cloned()
683 .collect();
684
685 // If nothing is hidden, the impl is fine - no filtering needed
686 if hidden_generics.is_empty() {
687 return false;
688 }
689
690 // ┌─────────────────────────────────────────────────────────────────────┐
691 // │ Step 4: Check if any associated type USES a hidden generic │
692 // │ │
693 // │ Having hidden generics is only a problem if they're actually used. │
694 // │ We check each associated type definition in the impl block. │
695 // │ │
696 // │ Example: `type Item = <I as Iterator>::Item` uses hidden `I` │
697 // └─────────────────────────────────────────────────────────────────────┘
698 for item_id in &impl_block.items {
699 // Look up the item in the crate's index
700 let Some(item) = krate.index.get(item_id) else {
701 continue;
702 };
703
704 // We only care about associated types with a concrete definition
705 // (not `type Item;` without a `= ...` part)
706 if let ItemEnum::AssocType {
707 type_: Some(ty), ..
708 } = &item.inner
709 {
710 // Extract all generics referenced in the associated type's definition
711 let mut referenced = HashSet::new();
712 Self::extract_type_generics_into(ty, &mut referenced);
713
714 // If any referenced generic is hidden, this impl should be filtered
715 if referenced.iter().any(|name| hidden_generics.contains(name)) {
716 return true;
717 }
718 }
719 }
720
721 // No hidden generics are used in associated types - impl is okay to render
722 false
723 }
724
725 // Contains AI generated docs - End
726}
727
728/// Renders impl blocks to markdown.
729///
730/// This struct handles:
731/// - Inherent implementations (`impl MyType { ... }`)
732/// - Trait implementations (`impl Trait for MyType { ... }`)
733/// - Method signatures within impl blocks
734/// - Associated types and constants
735///
736/// The renderer is generic over [`RenderContext`], allowing it to work with
737/// both single-crate (`GeneratorContext`) and multi-crate (`SingleCrateView`) modes.
738pub struct ImplRenderer<'a> {
739 /// Reference to the render context (either single-crate or multi-crate).
740 ctx: &'a dyn RenderContext,
741
742 /// Path of the current file being generated (for relative link calculation).
743 current_file: &'a str,
744
745 /// Cached type renderer to avoid repeated construction.
746 type_renderer: TypeRenderer<'a>,
747}
748
749impl<'a> ImplRenderer<'a> {
750 /// Create a new impl renderer with the given context.
751 ///
752 /// # Arguments
753 ///
754 /// * `ctx` - Render context (implements `RenderContext` trait)
755 /// * `current_file` - Path of the current file (for relative link calculation)
756 pub fn new(ctx: &'a dyn RenderContext, current_file: &'a str) -> Self {
757 let type_renderer = TypeRenderer::new(ctx.krate());
758 Self {
759 ctx,
760 current_file,
761 type_renderer,
762 }
763 }
764
765 /// Process documentation string to resolve intra-doc links.
766 ///
767 /// Delegates to the render context's `process_docs` method, which handles
768 /// both single-crate and multi-crate link resolution.
769 fn process_docs(&self, item: &Item) -> Option<String> {
770 self.ctx.process_docs(item, self.current_file)
771 }
772
773 /// Render impl blocks for a given type.
774 ///
775 /// This method looks up all impl blocks for a type and renders them
776 /// in two sections:
777 ///
778 /// 1. **Implementations** - Inherent impls (methods defined directly on the type)
779 /// 2. **Trait Implementations** - Trait impls (`impl Trait for Type`)
780 ///
781 /// # Impl Block Categories
782 ///
783 /// - **Inherent**: `impl MyType { fn method(&self) {} }`
784 /// - **Trait**: `impl Clone for MyType { ... }`
785 /// - **Synthetic**: Auto-derived by compiler (Send, Sync) - skipped
786 pub fn render_impl_blocks(&self, md: &mut String, item_id: Id) {
787 let Some(impls) = self.ctx.get_impls(&item_id) else {
788 return;
789 };
790
791 // Partition impls into trait and inherent
792 let (mut trait_impls, inherent_impls): (Vec<_>, Vec<_>) =
793 impls.iter().partition(|i| i.trait_.is_some());
794
795 // Sort trait impls by trait name + generics for deterministic output
796 trait_impls.sort_by(|a: &&&Impl, b: &&&Impl| {
797 let key_a = RendererInternals::impl_sort_key(a, &self.type_renderer);
798 let key_b = RendererInternals::impl_sort_key(b, &self.type_renderer);
799
800 key_a.cmp(&key_b)
801 });
802
803 // Deduplicate trait impls with the same signature
804 trait_impls.dedup_by(|a, b| {
805 RendererInternals::impl_sort_key(a, &self.type_renderer)
806 == RendererInternals::impl_sort_key(b, &self.type_renderer)
807 });
808
809 // === Inherent Implementations ===
810 if !inherent_impls.is_empty() {
811 _ = write!(md, "#### Implementations\n\n");
812
813 for impl_block in inherent_impls {
814 self.render_impl_methods(md, impl_block);
815 }
816 }
817
818 // === Trait Implementations ===
819 // Filter out:
820 // 1. Blanket impls (From, Into, Any, etc.) unless explicitly included
821 // 2. Impls with hidden generic references (Issue 5 fix)
822 // e.g., `type Item = <I as Iterator>::Item` where I is not visible
823 let krate = self.ctx.krate();
824 let filtered_trait_impls: Vec<_> = if self.ctx.include_blanket_impls() {
825 trait_impls
826 .into_iter()
827 .filter(|i| !ImplUtils::has_hidden_generic_refs(i, krate))
828 .collect()
829 } else {
830 trait_impls
831 .into_iter()
832 .filter(|i| {
833 !ImplUtils::is_blanket_impl(i) && !ImplUtils::has_hidden_generic_refs(i, krate)
834 })
835 .collect()
836 };
837
838 if filtered_trait_impls.is_empty() {
839 return;
840 }
841
842 _ = write!(md, "#### Trait Implementations\n\n");
843
844 // Check if we should collapse trivial derives
845 let hide_trivial = self.ctx.render_config().hide_trivial_derives;
846
847 if hide_trivial {
848 // Partition into trivial derives and non-trivial impls
849 let (trivial_impls, other_impls): (Vec<_>, Vec<_>) = filtered_trait_impls
850 .into_iter()
851 .partition(|i| ImplUtils::is_trivial_derive_impl(i));
852
853 // Render trivial derives in a collapsible block
854 if !trivial_impls.is_empty() {
855 Self::render_trivial_derives_collapsed(md, &trivial_impls);
856 }
857
858 // Render non-trivial trait impls normally
859 for impl_block in other_impls {
860 self.render_trait_impl(md, impl_block);
861 }
862 } else {
863 // Render all trait impls normally (no collapsing)
864 for impl_block in filtered_trait_impls {
865 self.render_trait_impl(md, impl_block);
866 }
867 }
868 }
869
870 /// Render trivial derive trait implementations in a collapsible block.
871 ///
872 /// Groups `Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord`
873 /// implementations into a `<details>` block with a summary table.
874 fn render_trivial_derives_collapsed(md: &mut String, impls: &[&&Impl]) {
875 let count = impls.len();
876 let summary = format!("Derived Traits ({count} implementations)");
877
878 _ = write!(
879 md,
880 "{}",
881 RendererInternals::render_collapsible_start(&summary)
882 );
883
884 // Render as a summary table
885 _ = write!(
886 md,
887 "| Trait | Description |\n\
888 | ----- | ----------- |\n"
889 );
890
891 for impl_block in impls {
892 if let Some(trait_ref) = &impl_block.trait_ {
893 let trait_name = trait_ref
894 .path
895 .rsplit("::")
896 .next()
897 .unwrap_or(&trait_ref.path);
898
899 let description = ImplUtils::get_trivial_derive_description(trait_name)
900 .unwrap_or("Derived trait");
901
902 _ = writeln!(md, "| `{trait_name}` | {description} |");
903 }
904 }
905
906 _ = write!(md, "{}", RendererInternals::render_collapsible_end());
907 }
908
909 /// Render a single trait implementation block.
910 fn render_trait_impl(&self, md: &mut String, impl_block: &Impl) {
911 // Skip synthetic impls (auto-traits like Send, Sync, Unpin)
912 if impl_block.is_synthetic {
913 return;
914 }
915
916 // Build the trait name with generic args
917 let trait_name = impl_block
918 .trait_
919 .as_ref()
920 .map(|t| {
921 let mut name = RendererUtils::sanitize_path(&t.path).into_owned();
922
923 if let Some(args) = &t.args {
924 _ = write!(name, "{}", &self.render_generic_args_for_impl(args));
925 }
926
927 name
928 })
929 .unwrap_or_default();
930
931 // Extract generics that appear in the signature (for_ type and trait path).
932 // This fixes the generic parameter mismatch issue where `impl_block.generics.params`
933 // might have different names (e.g., T) than what's used in for_ type (e.g., D).
934 //
935 // IMPORTANT: We use `extract_impl_signature_generics` which excludes where clause
936 // generics. For blanket impls like `impl<T: Sized> IntoEither for T`, when rendered
937 // for `Either<L, R>`, the `T` only appears in the where clause `where T: Sized`.
938 // We don't want to show `impl<T> IntoEither for Either<L, R>` since T doesn't appear
939 // in the visible header - it would be confusing to users.
940 let signature_generics = ImplUtils::extract_impl_signature_generics(impl_block);
941
942 let generics = if signature_generics.is_empty() {
943 String::new()
944 } else {
945 // Filter impl params to only include those that appear in for_ type or trait path
946 let filtered: Vec<_> = impl_block
947 .generics
948 .params
949 .iter()
950 .filter(|p| signature_generics.contains(&p.name))
951 .cloned()
952 .collect();
953
954 if filtered.is_empty() {
955 String::new()
956 } else {
957 self.type_renderer.render_generics(&filtered)
958 }
959 };
960
961 let for_type = self.type_renderer.render_type(&impl_block.for_);
962 let unsafe_str = if impl_block.is_unsafe { "unsafe " } else { "" };
963 let negative_str = if impl_block.is_negative { "!" } else { "" };
964
965 _ = write!(
966 md,
967 "##### `{unsafe_str}impl{generics} {negative_str}{trait_name} for {for_type}`\n\n"
968 );
969
970 self.render_impl_methods(md, impl_block);
971 }
972
973 /// Render the items (methods, constants, types) within an impl block.
974 ///
975 /// Each item is rendered as a bullet point. Items can be:
976 /// - **Functions/Methods**: Full signature with modifiers
977 /// - **Associated Constants**: `const NAME: Type`
978 /// - **Associated Types**: `type Name = Type`
979 ///
980 /// For methods, the first line of documentation is included as a brief summary.
981 /// Type links are added for resolvable types in method signatures.
982 /// Method anchors are generated for deep linking (e.g., `#typename-methodname`).
983 fn render_impl_methods(&self, md: &mut String, impl_block: &Impl) {
984 let krate = self.ctx.krate();
985
986 // Extract the type name for method anchor generation
987 let type_name = self.type_renderer.render_type(&impl_block.for_);
988
989 // Determine impl context for anchor generation:
990 // - For trait impls, include the trait name to avoid duplicate anchors
991 // when multiple traits define the same associated type (e.g., Output)
992 // - For inherent impls, use simple anchors
993 let impl_ctx = impl_block.trait_.as_ref().map_or(
994 ImplContext::Inherent,
995 |t| ImplContext::Trait(PathUtils::short_name(&t.path)),
996 );
997
998 RendererInternals::render_impl_items(
999 md,
1000 impl_block,
1001 krate,
1002 &self.type_renderer,
1003 &Some(|item: &Item| self.process_docs(item)),
1004 &Some(|id: Id| self.ctx.create_link(id, self.current_file)),
1005 Some(type_name.as_ref()),
1006 impl_ctx,
1007 );
1008 }
1009
1010 /// Render generic arguments for impl block signatures.
1011 ///
1012 /// This handles the different forms of generic arguments:
1013 /// - **Angle bracketed**: `<T, U, Item = V>` (most common)
1014 /// - **Parenthesized**: `(A, B) -> C` (for Fn traits)
1015 /// - **Return type notation**: `(..)` (experimental)
1016 fn render_generic_args_for_impl(&self, args: &GenericArgs) -> String {
1017 match args {
1018 GenericArgs::AngleBracketed { args, constraints } => {
1019 let mut parts: Vec<Cow<str>> = args
1020 .iter()
1021 .map(|a| match a {
1022 GenericArg::Lifetime(lt) => Cow::Borrowed(lt.as_str()),
1023
1024 GenericArg::Type(ty) => self.type_renderer.render_type(ty),
1025
1026 GenericArg::Const(c) => {
1027 Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr))
1028 },
1029
1030 GenericArg::Infer => Cow::Borrowed("_"),
1031 })
1032 .collect();
1033
1034 parts.extend(constraints.iter().map(|c| {
1035 let constraint_args = c
1036 .args
1037 .as_ref()
1038 .map(|a| self.render_generic_args_for_impl(a))
1039 .unwrap_or_default();
1040
1041 match &c.binding {
1042 AssocItemConstraintKind::Equality(term) => {
1043 let term_str = match term {
1044 Term::Type(ty) => self.type_renderer.render_type(ty),
1045
1046 Term::Constant(c) => {
1047 Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr))
1048 },
1049 };
1050
1051 Cow::Owned(format!("{}{constraint_args} = {term_str}", c.name))
1052 },
1053
1054 AssocItemConstraintKind::Constraint(bounds) => {
1055 let bound_strs: Vec<Cow<str>> = bounds
1056 .iter()
1057 .map(|b| self.type_renderer.render_generic_bound(b))
1058 .collect();
1059
1060 Cow::Owned(format!(
1061 "{}{constraint_args}: {}",
1062 c.name,
1063 bound_strs
1064 .iter()
1065 .map(AsRef::as_ref)
1066 .collect::<Vec<_>>()
1067 .join(" + ")
1068 ))
1069 },
1070 }
1071 }));
1072
1073 if parts.is_empty() {
1074 String::new()
1075 } else {
1076 format!(
1077 "<{}>",
1078 parts
1079 .iter()
1080 .map(AsRef::as_ref)
1081 .collect::<Vec<_>>()
1082 .join(", ")
1083 )
1084 }
1085 },
1086
1087 GenericArgs::Parenthesized { inputs, output } => {
1088 let input_strs: Vec<Cow<str>> = inputs
1089 .iter()
1090 .map(|t| self.type_renderer.render_type(t))
1091 .collect();
1092 let ret = output
1093 .as_ref()
1094 .map(|t| format!(" -> {}", self.type_renderer.render_type(t)))
1095 .unwrap_or_default();
1096
1097 format!(
1098 "({}){ret}",
1099 input_strs
1100 .iter()
1101 .map(AsRef::as_ref)
1102 .collect::<Vec<_>>()
1103 .join(", ")
1104 )
1105 },
1106
1107 GenericArgs::ReturnTypeNotation => " (..)".to_string(),
1108 }
1109 }
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114 use super::*;
1115
1116 mod trivial_derive_traits_tests {
1117 use super::*;
1118
1119 #[test]
1120 fn trivial_traits_list_has_nine_entries() {
1121 assert_eq!(TRIVIAL_DERIVE_TRAITS.len(), 9);
1122 }
1123
1124 #[test]
1125 fn descriptions_match_traits_count() {
1126 assert_eq!(
1127 TRIVIAL_DERIVE_TRAITS.len(),
1128 TRIVIAL_DERIVE_DESCRIPTIONS.len()
1129 );
1130 }
1131
1132 #[test]
1133 fn all_traits_have_descriptions() {
1134 for trait_name in TRIVIAL_DERIVE_TRAITS {
1135 assert!(
1136 ImplUtils::get_trivial_derive_description(trait_name).is_some(),
1137 "Missing description for trait: {trait_name}"
1138 );
1139 }
1140 }
1141
1142 #[test]
1143 fn get_description_for_clone() {
1144 assert_eq!(
1145 ImplUtils::get_trivial_derive_description("Clone"),
1146 Some("Returns a copy of the value")
1147 );
1148 }
1149
1150 #[test]
1151 fn get_description_for_debug() {
1152 assert_eq!(
1153 ImplUtils::get_trivial_derive_description("Debug"),
1154 Some("Formats the value for debugging")
1155 );
1156 }
1157
1158 #[test]
1159 fn get_description_for_full_path() {
1160 // Should extract trait name from full path
1161 assert_eq!(
1162 ImplUtils::get_trivial_derive_description("std::clone::Clone"),
1163 Some("Returns a copy of the value")
1164 );
1165 }
1166
1167 #[test]
1168 fn get_description_for_unknown_trait() {
1169 assert_eq!(ImplUtils::get_trivial_derive_description("Display"), None);
1170 }
1171
1172 #[test]
1173 fn contains_cloning_traits() {
1174 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Clone"));
1175 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Copy"));
1176 }
1177
1178 #[test]
1179 fn contains_equality_traits() {
1180 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"PartialEq"));
1181 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Eq"));
1182 }
1183
1184 #[test]
1185 fn contains_ordering_traits() {
1186 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"PartialOrd"));
1187 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Ord"));
1188 }
1189
1190 #[test]
1191 fn contains_hash_trait() {
1192 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Hash"));
1193 }
1194
1195 #[test]
1196 fn contains_debug_and_default() {
1197 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Debug"));
1198 assert!(TRIVIAL_DERIVE_TRAITS.contains(&"Default"));
1199 }
1200
1201 #[test]
1202 fn does_not_contain_display() {
1203 assert!(!TRIVIAL_DERIVE_TRAITS.contains(&"Display"));
1204 }
1205
1206 #[test]
1207 fn does_not_contain_serialize() {
1208 assert!(!TRIVIAL_DERIVE_TRAITS.contains(&"Serialize"));
1209 assert!(!TRIVIAL_DERIVE_TRAITS.contains(&"Deserialize"));
1210 }
1211 }
1212}