cargo_docs_md/
types.rs

1//! Type rendering utilities for converting rustdoc types to string representations.
2//!
3//! This module provides the [`TypeRenderer`] struct to convert the complex type
4//! structures from rustdoc JSON into human-readable Rust type syntax. These
5//! rendered strings are used in the generated markdown documentation.
6//!
7//! # Overview
8//!
9//! Rustdoc JSON represents types as a tree structure (the `Type` enum). The
10//! [`TypeRenderer`] walks that tree and produces the string representation
11//! you'd write in code.
12//!
13//! # Usage
14//!
15//! ```ignore
16//! let renderer = TypeRenderer::new(&krate);
17//! let type_string = renderer.render_type(&some_type);
18//! let generics = renderer.render_generics(&generic_params);
19//! ```
20//!
21//! # Example Transformations
22//!
23//! | Rustdoc Type | Rendered String |
24//! |--------------|-----------------|
25//! | `Type::Primitive("u32")` | `"u32"` |
26//! | `Type::BorrowedRef { lifetime: Some("'a"), is_mutable: true, type_: ... }` | `"&'a mut T"` |
27//! | `Type::ResolvedPath { path: "Vec", args: ... }` | `"Vec<T>"` |
28//!
29//! # Performance
30//!
31//! Uses `Cow<str>` to avoid allocations for simple types like primitives,
32//! generics, and inferred types. Complex types that require string building
33//! return owned strings.
34
35use std::borrow::Cow;
36
37use rustdoc_types::{
38    AssocItemConstraint, AssocItemConstraintKind, Crate, GenericArg, GenericArgs, GenericBound,
39    GenericParamDef, GenericParamDefKind, Term, TraitBoundModifier, Type,
40};
41
42/// Type renderer for converting rustdoc types to Rust syntax strings.
43///
44/// This struct holds a reference to the crate context and provides methods
45/// to render various type constructs into their string representations.
46///
47/// # Design Note
48///
49/// The `krate` field is currently unused because the `Type` enum is self-contained.
50/// However, it is retained for:
51/// - **Future-proofing**: May need to look up items in `krate.index` for enhanced rendering
52/// - **API consistency**: Signals that the renderer is bound to a specific crate context
53/// - **Type safety**: Prevents accidentally mixing renderers across different crates
54///
55/// # Example
56///
57/// ```ignore
58/// let renderer = TypeRenderer::new(&krate);
59/// let type_str = renderer.render_type(&some_type);
60/// let generics = renderer.render_generics(&params);
61/// ```
62#[derive(Debug, Clone, Copy)]
63pub struct TypeRenderer<'a> {
64    /// Reference to the crate for looking up type information.
65    ///
66    /// Reserved for future use (e.g., resolving paths, getting item metadata).
67    #[expect(dead_code, reason = "TODO: Reserved for future use.")]
68    krate: &'a Crate,
69}
70
71impl<'a> TypeRenderer<'a> {
72    /// Create a new type renderer with the given crate context.
73    ///
74    /// # Arguments
75    ///
76    /// * `krate` - The parsed rustdoc crate containing type definitions
77    #[must_use]
78    pub const fn new(krate: &'a Crate) -> Self {
79        Self { krate }
80    }
81
82    /// Render a rustdoc `Type` to its Rust syntax string representation.
83    ///
84    /// This is the main entry point for type rendering. It handles all variants
85    /// of the `Type` enum and recursively renders nested types.
86    ///
87    /// # Arguments
88    ///
89    /// * `ty` - The type to render
90    ///
91    /// # Returns
92    ///
93    /// A `Cow<str>` representing the type in Rust syntax. Simple types like
94    /// primitives and generics return borrowed strings to avoid allocation.
95    ///
96    /// # Supported Type Variants
97    ///
98    /// - Primitives: `u32`, `bool`, `str`, etc.
99    /// - References: `&T`, `&'a mut T`
100    /// - Pointers: `*const T`, `*mut T`
101    /// - Slices and arrays: `[T]`, `[T; N]`
102    /// - Tuples: `(A, B, C)`
103    /// - Paths: `std::vec::Vec<T>`
104    /// - Function pointers: `fn(A, B) -> C`
105    /// - Trait objects: `dyn Trait + Send`
106    /// - Impl trait: `impl Iterator<Item = T>`
107    /// - Qualified paths: `<T as Trait>::Item`
108    #[must_use]
109    pub fn render_type<'t>(&self, ty: &'t Type) -> Cow<'t, str> {
110        match ty {
111            // Named type path like `Vec<T>` or `std::collections::HashMap<K, V>`
112            Type::ResolvedPath(path) => {
113                // If no generic args, borrow the path directly
114                if path.args.is_none() {
115                    return Cow::Borrowed(&path.path);
116                }
117                let mut result = path.path.clone();
118                // Append generic arguments if present
119                if let Some(args) = &path.args {
120                    result.push_str(&self.render_generic_args(args));
121                }
122                Cow::Owned(result)
123            },
124
125            // Trait object: `dyn Trait + OtherTrait`
126            Type::DynTrait(dyn_trait) => {
127                let traits: Vec<Cow<str>> = dyn_trait
128                    .traits
129                    .iter()
130                    .map(|pt| {
131                        if pt.trait_.args.is_none() {
132                            Cow::Borrowed(pt.trait_.path.as_str())
133                        } else {
134                            let mut s = pt.trait_.path.clone();
135                            if let Some(args) = &pt.trait_.args {
136                                s.push_str(&self.render_generic_args(args));
137                            }
138                            Cow::Owned(s)
139                        }
140                    })
141                    .collect();
142                Cow::Owned(format!("dyn {}", traits.join(" + ")))
143            },
144
145            // Generic type parameter: `T`, `U`, etc.
146            // Primitive type: `u32`, `bool`, `str`, etc.
147            // Zero allocation - borrow from input
148            Type::Generic(name) | Type::Primitive(name) => Cow::Borrowed(name),
149
150            // Function pointer: `fn(A, B) -> C`
151            Type::FunctionPointer(fp) => {
152                // Render parameter types (we ignore parameter names for brevity)
153                let params: Vec<Cow<str>> = fp
154                    .sig
155                    .inputs
156                    .iter()
157                    .map(|(_, t)| self.render_type(t))
158                    .collect();
159                // Render return type if present
160                let ret = fp.sig.output.as_ref().map_or_else(String::new, |output| {
161                    format!(" -> {}", self.render_type(output))
162                });
163                Cow::Owned(format!("fn({}){}", params.join(", "), ret))
164            },
165
166            // Tuple type: `(A, B, C)` or unit `()`
167            Type::Tuple(types) => {
168                if types.is_empty() {
169                    return Cow::Borrowed("()");
170                }
171                let inner: Vec<Cow<str>> = types.iter().map(|t| self.render_type(t)).collect();
172                Cow::Owned(format!("({})", inner.join(", ")))
173            },
174
175            // Slice type: `[T]`
176            Type::Slice(inner) => Cow::Owned(format!("[{}]", self.render_type(inner))),
177
178            // Array type: `[T; N]` where N is a const expression
179            Type::Array { type_, len } => {
180                Cow::Owned(format!("[{}; {len}]", self.render_type(type_)))
181            },
182
183            // Pattern type (rare): just render the underlying type
184            Type::Pat { type_, .. } => self.render_type(type_),
185
186            // Impl trait in return position: `impl Iterator<Item = T>`
187            Type::ImplTrait(bounds) => {
188                let bound_strs: Vec<Cow<str>> = bounds
189                    .iter()
190                    .map(|b| self.render_generic_bound(b))
191                    .collect();
192                Cow::Owned(format!("impl {}", bound_strs.join(" + ")))
193            },
194
195            // Inferred type: `_` (placeholder in turbofish)
196            // Zero allocation - static string
197            Type::Infer => Cow::Borrowed("_"),
198
199            // Raw pointer: `*const T` or `*mut T`
200            Type::RawPointer { is_mutable, type_ } => {
201                let mutability = if *is_mutable { "mut" } else { "const" };
202                Cow::Owned(format!("*{mutability} {}", self.render_type(type_)))
203            },
204
205            // Reference: `&T`, `&mut T`, `&'a T`, `&'a mut T`
206            Type::BorrowedRef {
207                lifetime,
208                is_mutable,
209                type_,
210            } => {
211                // Optional lifetime annotation
212                let lt = lifetime
213                    .as_ref()
214                    .map(|l| format!("{l} "))
215                    .unwrap_or_default();
216                // Optional mut keyword
217                let mutability = if *is_mutable { "mut " } else { "" };
218                Cow::Owned(format!("&{lt}{mutability}{}", self.render_type(type_)))
219            },
220
221            // Qualified path: `<T as Trait>::Item` or `T::Item`
222            Type::QualifiedPath {
223                name,
224                self_type,
225                trait_,
226                ..
227            } => {
228                let self_ty = self.render_type(self_type);
229                Cow::Owned(trait_.as_ref().map_or_else(
230                    || format!("{self_ty}::{name}"),
231                    |trait_path| format!("<{self_ty} as {}>::{name}", trait_path.path),
232                ))
233            },
234        }
235    }
236
237    /// Render generic arguments in angle bracket or parenthesized form.
238    ///
239    /// Handles three syntaxes:
240    /// - Angle brackets: `<T, U, Item = V>` (most common)
241    /// - Parenthesized: `(A, B) -> C` (for Fn traits)
242    /// - Return type notation: `(..)` (experimental)
243    fn render_generic_args(self, args: &GenericArgs) -> String {
244        match args {
245            // Standard angle bracket syntax: `<T, U, Item = V>`
246            GenericArgs::AngleBracketed { args, constraints } => {
247                // Collect all generic arguments (types, lifetimes, consts)
248                let mut parts: Vec<Cow<str>> =
249                    args.iter().map(|a| self.render_generic_arg(a)).collect();
250
251                // Add associated type constraints (e.g., `Item = u32`)
252                parts.extend(
253                    constraints
254                        .iter()
255                        .map(|c| Cow::Owned(self.render_assoc_item_constraint(c))),
256                );
257
258                if parts.is_empty() {
259                    String::new()
260                } else {
261                    format!("<{}>", parts.join(", "))
262                }
263            },
264
265            // Parenthesized syntax for Fn traits: `Fn(A, B) -> C`
266            GenericArgs::Parenthesized { inputs, output } => {
267                let input_strs: Vec<Cow<str>> =
268                    inputs.iter().map(|t| self.render_type(t)).collect();
269                let ret = output
270                    .as_ref()
271                    .map(|t| format!(" -> {}", self.render_type(t)))
272                    .unwrap_or_default();
273
274                format!("({}){}", input_strs.join(", "), ret)
275            },
276
277            // Return type notation (experimental feature)
278            GenericArgs::ReturnTypeNotation => " (..)".to_string(),
279        }
280    }
281
282    /// Render a single generic argument.
283    ///
284    /// Arguments can be:
285    /// - Lifetimes: `'a`, `'static`
286    /// - Types: `T`, `Vec<u32>`
287    /// - Const values: `N`, `{expr}`
288    /// - Inferred: `_`
289    fn render_generic_arg(self, arg: &GenericArg) -> Cow<'_, str> {
290        match arg {
291            // Zero allocation - borrow from input
292            GenericArg::Lifetime(lt) => Cow::Borrowed(lt),
293
294            GenericArg::Type(ty) => self.render_type(ty),
295
296            // For consts, prefer the computed value; fall back to the expression
297            // These are already owned strings in rustdoc_types
298            GenericArg::Const(c) => Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr)),
299
300            // Zero allocation - static string
301            GenericArg::Infer => Cow::Borrowed("_"),
302        }
303    }
304
305    /// Render an associated type constraint.
306    ///
307    /// These appear in generic bounds:
308    /// - Equality: `Item = u32`
309    /// - Bound: `Item: Display`
310    fn render_assoc_item_constraint(self, constraint: &AssocItemConstraint) -> String {
311        // The constraint may have its own generic args (rare)
312        let args = constraint
313            .args
314            .as_ref()
315            .map(|a| self.render_generic_args(a))
316            .unwrap_or_default();
317
318        match &constraint.binding {
319            // Equality constraint: `Item = SomeType`
320            AssocItemConstraintKind::Equality(term) => {
321                format!("{}{args} = {}", constraint.name, self.render_term(term))
322            },
323
324            // Bound constraint: `Item: SomeTrait + OtherTrait`
325            AssocItemConstraintKind::Constraint(bounds) => {
326                let bound_strs: Vec<Cow<str>> = bounds
327                    .iter()
328                    .map(|b| self.render_generic_bound(b))
329                    .collect();
330
331                format!("{}{args}: {}", constraint.name, bound_strs.join(" + "))
332            },
333        }
334    }
335
336    /// Render a term, which is either a type or a constant.
337    ///
338    /// Used in associated type constraints like `Item = u32`.
339    fn render_term(self, term: &Term) -> Cow<'_, str> {
340        match term {
341            Term::Type(ty) => self.render_type(ty),
342
343            // For consts, prefer the computed value; fall back to the expression
344            Term::Constant(c) => Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr)),
345        }
346    }
347
348    /// Render a generic bound (trait bound or lifetime bound).
349    ///
350    /// # Examples
351    ///
352    /// - Trait bound: `Clone`, `Iterator<Item = T>`
353    /// - Modified bound: `?Sized`, `~const Drop`
354    /// - Lifetime bound: `'static`, `'a`
355    #[must_use]
356    pub fn render_generic_bound<'t>(&self, bound: &'t GenericBound) -> Cow<'t, str> {
357        match bound {
358            // Trait bound: `Clone`, `?Sized`, `~const Drop`
359            GenericBound::TraitBound {
360                trait_, modifier, ..
361            } => {
362                // Simple case: no modifier and no generic args - borrow directly
363                if matches!(modifier, TraitBoundModifier::None) && trait_.args.is_none() {
364                    return Cow::Borrowed(&trait_.path);
365                }
366
367                // Handle bound modifiers
368                let modifier_str = match modifier {
369                    TraitBoundModifier::None => "",
370                    TraitBoundModifier::Maybe => "?", // ?Sized
371                    TraitBoundModifier::MaybeConst => "~const ", // ~const Trait
372                };
373
374                // Build the trait path with any generic args
375                let mut result = format!("{modifier_str}{}", trait_.path);
376
377                if let Some(args) = &trait_.args {
378                    result.push_str(&self.render_generic_args(args));
379                }
380
381                Cow::Owned(result)
382            },
383
384            // Lifetime bound: `'static`, `'a`
385            // Zero allocation - borrow from input
386            GenericBound::Outlives(lt) => Cow::Borrowed(lt),
387
388            // Use bound (experimental) - typically empty in output
389            GenericBound::Use(_) => Cow::Borrowed(""),
390        }
391    }
392
393    /// Render a list of generic parameter definitions.
394    ///
395    /// Produces the `<T, U, const N: usize>` portion of a signature.
396    ///
397    /// # Arguments
398    ///
399    /// * `generics` - The list of generic parameters from rustdoc
400    ///
401    /// # Returns
402    ///
403    /// A string like `<T, U>` or empty string if no generics.
404    ///
405    /// # Filtering
406    ///
407    /// Synthetic parameters (generated by the compiler for `impl Trait`)
408    /// are filtered out since they don't appear in the source.
409    #[must_use]
410    pub fn render_generics(&self, generics: &[GenericParamDef]) -> String {
411        if generics.is_empty() {
412            return String::new();
413        }
414
415        // Filter out synthetic params and render each remaining one
416        let params: Vec<String> = generics
417            .iter()
418            .filter_map(|p| self.render_generic_param_def(p))
419            .collect();
420
421        if params.is_empty() {
422            String::new()
423        } else {
424            format!("<{}>", params.join(", "))
425        }
426    }
427
428    /// Render a single generic parameter definition.
429    ///
430    /// Returns `None` for synthetic parameters (compiler-generated).
431    ///
432    /// # Parameter Kinds
433    ///
434    /// - Lifetime: `'a`, `'a: 'b + 'c`
435    /// - Type: `T`, `T: Clone + Send`
436    /// - Const: `const N: usize`
437    fn render_generic_param_def(self, param: &GenericParamDef) -> Option<String> {
438        match &param.kind {
439            // Lifetime parameter: `'a` or `'a: 'b + 'c`
440            GenericParamDefKind::Lifetime { outlives } => {
441                let mut result = param.name.clone();
442
443                // Add outlives bounds if present
444                if !outlives.is_empty() {
445                    result.push_str(": ");
446                    result.push_str(&outlives.join(" + "));
447                }
448
449                Some(result)
450            },
451
452            // Type parameter: `T` or `T: Clone + Send`
453            GenericParamDefKind::Type {
454                bounds,
455                is_synthetic,
456                ..
457            } => {
458                // Skip synthetic parameters (compiler-generated for impl Trait)
459                if *is_synthetic {
460                    return None;
461                }
462
463                let mut result = param.name.clone();
464
465                // Add trait bounds if present
466                if !bounds.is_empty() {
467                    let bound_strs: Vec<Cow<str>> = bounds
468                        .iter()
469                        .map(|b| self.render_generic_bound(b))
470                        .collect();
471                    result.push_str(": ");
472                    result.push_str(&bound_strs.join(" + "));
473                }
474
475                Some(result)
476            },
477
478            // Const parameter: `const N: usize`
479            GenericParamDefKind::Const { type_, .. } => {
480                Some(format!("const {}: {}", param.name, self.render_type(type_)))
481            },
482        }
483    }
484
485    /// Render where clause predicates.
486    ///
487    /// Produces the `where T: Clone, U: Send` portion of a signature.
488    ///
489    /// # Arguments
490    ///
491    /// * `where_predicates` - The list of where predicates from rustdoc
492    ///
493    /// # Returns
494    ///
495    /// A formatted where clause string, or empty string if no predicates.
496    ///
497    /// # Format
498    ///
499    /// ```text
500    /// where
501    ///     T: Clone,
502    ///     U: Send
503    /// ```
504    #[must_use]
505    pub fn render_where_clause(
506        &self,
507        where_predicates: &[rustdoc_types::WherePredicate],
508    ) -> String {
509        if where_predicates.is_empty() {
510            return String::new();
511        }
512
513        let clauses: Vec<String> = where_predicates
514            .iter()
515            .map(|p| self.render_where_predicate(p))
516            .collect();
517
518        // Format with newline and indentation for readability
519        format!("\nwhere\n    {}", clauses.join(",\n    "))
520    }
521
522    /// Render a single where predicate.
523    ///
524    /// # Predicate Types
525    ///
526    /// - Bound: `T: Clone + Send`
527    /// - Lifetime: `'a: 'b + 'c`
528    /// - Equality: `<T as Iterator>::Item = u32`
529    fn render_where_predicate(self, pred: &rustdoc_types::WherePredicate) -> String {
530        match pred {
531            // Type bound predicate: `T: Clone + Send`
532            rustdoc_types::WherePredicate::BoundPredicate { type_, bounds, .. } => {
533                let bound_strs: Vec<Cow<str>> = bounds
534                    .iter()
535                    .map(|b| self.render_generic_bound(b))
536                    .collect();
537
538                format!("{}: {}", self.render_type(type_), bound_strs.join(" + "))
539            },
540
541            // Lifetime predicate: `'a: 'b + 'c`
542            rustdoc_types::WherePredicate::LifetimePredicate { lifetime, outlives } => {
543                format!("{lifetime}: {}", outlives.join(" + "))
544            },
545
546            // Equality predicate: `<T as Trait>::Item = SomeType`
547            rustdoc_types::WherePredicate::EqPredicate { lhs, rhs } => {
548                format!("{} = {}", self.render_type(lhs), self.render_term(rhs))
549            },
550        }
551    }
552
553    /// Collect all linkable type names from a type.
554    ///
555    /// This extracts type names that could potentially be linked to their definitions.
556    /// Returns a set of (`ype_name`, `type_id`) pairs for `ResolvedPath` types.
557    ///
558    /// # Linkable Types
559    ///
560    /// - `ResolvedPath` types (e.g., `Vec`, `HashMap`, `MyStruct`)
561    /// - Nested types within generics, references, slices, etc.
562    ///
563    /// # Excluded
564    ///
565    /// - Primitives (e.g., `u32`, `bool`)
566    /// - Generic parameters (e.g., `T`, `U`)
567    /// - Inferred types (`_`)
568    #[must_use]
569    pub fn collect_linkable_types(&self, ty: &Type) -> Vec<(String, rustdoc_types::Id)> {
570        let mut result = Vec::new();
571        self.collect_types_recursive(ty, &mut result);
572        result
573    }
574
575    /// Recursively collect linkable types from a type tree.
576    fn collect_types_recursive(self, ty: &Type, result: &mut Vec<(String, rustdoc_types::Id)>) {
577        match ty {
578            Type::ResolvedPath(path) => {
579                // Extract the simple name (last segment of the path)
580                let name = path.path.split("::").last().unwrap_or(&path.path);
581                result.push((name.to_string(), path.id));
582
583                // Also collect from generic arguments
584                if let Some(args) = &path.args {
585                    self.collect_from_generic_args(args, result);
586                }
587            },
588
589            Type::DynTrait(dyn_trait) => {
590                for pt in &dyn_trait.traits {
591                    let name = pt.trait_.path.split("::").last().unwrap_or(&pt.trait_.path);
592                    result.push((name.to_string(), pt.trait_.id));
593
594                    if let Some(args) = &pt.trait_.args {
595                        self.collect_from_generic_args(args, result);
596                    }
597                }
598            },
599
600            Type::BorrowedRef { type_, .. } | Type::RawPointer { type_, .. } => {
601                self.collect_types_recursive(type_, result);
602            },
603
604            Type::Slice(inner)
605            | Type::Array { type_: inner, .. }
606            | Type::Pat { type_: inner, .. } => {
607                self.collect_types_recursive(inner, result);
608            },
609
610            Type::Tuple(types) => {
611                for inner in types {
612                    self.collect_types_recursive(inner, result);
613                }
614            },
615
616            Type::FunctionPointer(fp) => {
617                for (_, input_ty) in &fp.sig.inputs {
618                    self.collect_types_recursive(input_ty, result);
619                }
620                if let Some(output) = &fp.sig.output {
621                    self.collect_types_recursive(output, result);
622                }
623            },
624
625            Type::ImplTrait(bounds) => {
626                for bound in bounds {
627                    if let GenericBound::TraitBound { trait_, .. } = bound {
628                        let name = trait_.path.split("::").last().unwrap_or(&trait_.path);
629                        result.push((name.to_string(), trait_.id));
630
631                        if let Some(args) = &trait_.args {
632                            self.collect_from_generic_args(args, result);
633                        }
634                    }
635                }
636            },
637
638            Type::QualifiedPath {
639                self_type, trait_, ..
640            } => {
641                self.collect_types_recursive(self_type, result);
642                if let Some(t) = trait_ {
643                    let name = t.path.split("::").last().unwrap_or(&t.path);
644                    result.push((name.to_string(), t.id));
645                }
646            },
647
648            // Primitives, generics, and inferred types are not linkable
649            Type::Primitive(_) | Type::Generic(_) | Type::Infer => {},
650        }
651    }
652
653    /// Collect types from generic arguments.
654    fn collect_from_generic_args(
655        self,
656        args: &GenericArgs,
657        result: &mut Vec<(String, rustdoc_types::Id)>,
658    ) {
659        match args {
660            GenericArgs::AngleBracketed { args, constraints } => {
661                for arg in args {
662                    if let GenericArg::Type(ty) = arg {
663                        self.collect_types_recursive(ty, result);
664                    }
665                }
666                for constraint in constraints {
667                    if let AssocItemConstraintKind::Equality(Term::Type(ty)) = &constraint.binding {
668                        self.collect_types_recursive(ty, result);
669                    }
670                }
671            },
672            GenericArgs::Parenthesized { inputs, output } => {
673                for input in inputs {
674                    self.collect_types_recursive(input, result);
675                }
676                if let Some(output) = output {
677                    self.collect_types_recursive(output, result);
678                }
679            },
680            GenericArgs::ReturnTypeNotation => {},
681        }
682    }
683}