use std::borrow::Cow;
use std::fmt::Write;
use rustdoc_types::{
AssocItemConstraint, AssocItemConstraintKind, Crate, GenericArg, GenericArgs, GenericBound,
GenericParamDef, GenericParamDefKind, Id, Term, TraitBoundModifier, Type, WherePredicate,
};
use crate::generator::render_shared::RendererUtils;
use crate::utils::PathUtils;
#[derive(Debug, Clone, Copy)]
pub struct TypeRenderer<'a> {
#[expect(dead_code, reason = "TODO: Reserved for future use.")]
krate: &'a Crate,
}
impl<'a> TypeRenderer<'a> {
#[must_use]
pub const fn new(krate: &'a Crate) -> Self {
Self { krate }
}
#[must_use]
pub const fn get_type_id(&self, ty: &Type) -> Option<Id> {
match ty {
Type::ResolvedPath(path) => Some(path.id),
_ => None,
}
}
fn render_path_with_args<'p>(self, path: &'p str, args: Option<&GenericArgs>) -> Cow<'p, str> {
let sanitized: Cow<'_, str> = RendererUtils::sanitize_path(path);
if args.is_none() && matches!(sanitized, Cow::Borrowed(_)) {
return Cow::Borrowed(path);
}
let mut result: String = sanitized.into_owned();
if let Some(args) = args {
_ = write!(result, "{}", &self.render_generic_args(args));
}
Cow::Owned(result)
}
fn write_bounds_joined(self, out: &mut String, bounds: &[GenericBound]) {
let mut first = true;
for bound in bounds {
let rendered: Cow<'_, str> = self.render_generic_bound(bound);
if rendered.is_empty() {
continue;
}
if !first {
_ = write!(out, " + ");
}
_ = write!(out, "{}", &rendered);
first = false;
}
}
fn write_types_joined<'t, I>(self, out: &mut String, types: I, sep: &str)
where
I: Iterator<Item = &'t Type>,
{
let mut first = true;
for ty in types {
if !first {
_ = write!(out, "{sep}");
}
_ = write!(out, "{}", &self.render_type(ty));
first = false;
}
}
#[must_use]
pub fn render_type<'t>(&self, ty: &'t Type) -> Cow<'t, str> {
match ty {
Type::ResolvedPath(path) => {
self.render_path_with_args(&path.path, path.args.as_deref())
},
Type::DynTrait(dyn_trait) => {
let traits: Vec<String> = dyn_trait
.traits
.iter()
.map(|pt| {
let sanitized: Cow<'_, str> = RendererUtils::sanitize_path(&pt.trait_.path);
if pt.trait_.args.is_none() {
sanitized.into_owned()
} else {
let mut s = sanitized.into_owned();
if let Some(args) = &pt.trait_.args {
_ = write!(s, "{}", &self.render_generic_args(args));
}
s
}
})
.collect();
Cow::Owned(format!("dyn {}", traits.join(" + ")))
},
Type::Generic(name) | Type::Primitive(name) => Cow::Borrowed(name),
Type::FunctionPointer(fp) => {
let mut result = String::from("fn(");
self.write_types_joined(&mut result, fp.sig.inputs.iter().map(|(_, t)| t), ", ");
_ = write!(result, ")");
if let Some(output) = &fp.sig.output {
_ = write!(result, " -> ");
_ = write!(result, "{}", &self.render_type(output));
}
Cow::Owned(result)
},
Type::Tuple(types) => {
if types.is_empty() {
return Cow::Borrowed("()");
}
let mut result = String::from("(");
self.write_types_joined(&mut result, types.iter(), ", ");
_ = write!(result, ")");
Cow::Owned(result)
},
Type::Slice(inner) => Cow::Owned(format!("[{}]", self.render_type(inner))),
Type::Array { type_, len } => {
Cow::Owned(format!("[{}; {len}]", self.render_type(type_)))
},
Type::Pat { type_, .. } => self.render_type(type_),
Type::ImplTrait(bounds) => {
let mut result = String::from("impl ");
self.write_bounds_joined(&mut result, bounds);
Cow::Owned(result)
},
Type::Infer => Cow::Borrowed("_"),
Type::RawPointer { is_mutable, type_ } => {
let mutability = if *is_mutable { "mut" } else { "const" };
Cow::Owned(format!("*{mutability} {}", self.render_type(type_)))
},
Type::BorrowedRef {
lifetime,
is_mutable,
type_,
} => {
let lt = lifetime
.as_ref()
.map(|l| format!("{l} "))
.unwrap_or_default();
let mutability = if *is_mutable { "mut " } else { "" };
Cow::Owned(format!("&{lt}{mutability}{}", self.render_type(type_)))
},
Type::QualifiedPath {
name,
self_type,
trait_,
..
} => {
let self_ty: Cow<'_, str> = self.render_type(self_type);
Cow::Owned(trait_.as_ref().map_or_else(
|| format!("{self_ty}::{name}"),
|trait_path| {
let sanitized_trait = RendererUtils::sanitize_path(&trait_path.path);
format!("<{self_ty} as {sanitized_trait}>::{name}")
},
))
},
}
}
fn render_generic_args(self, args: &GenericArgs) -> String {
match args {
GenericArgs::AngleBracketed { args, constraints } => {
let mut parts: Vec<Cow<str>> =
args.iter().map(|a| self.render_generic_arg(a)).collect();
parts.extend(
constraints
.iter()
.map(|c| Cow::Owned(self.render_assoc_item_constraint(c))),
);
if parts.is_empty() {
String::new()
} else {
format!("<{}>", parts.join(", "))
}
},
GenericArgs::Parenthesized { inputs, output } => {
let mut result = String::from("(");
self.write_types_joined(&mut result, inputs.iter(), ", ");
_ = write!(result, ")");
if let Some(out) = output {
_ = write!(result, " -> ");
_ = write!(result, "{}", &self.render_type(out));
}
result
},
GenericArgs::ReturnTypeNotation => " (..)".to_string(),
}
}
fn render_generic_arg(self, arg: &GenericArg) -> Cow<'_, str> {
match arg {
GenericArg::Lifetime(lt) => Cow::Borrowed(lt),
GenericArg::Type(ty) => self.render_type(ty),
GenericArg::Const(c) => Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr)),
GenericArg::Infer => Cow::Borrowed("_"),
}
}
fn render_assoc_item_constraint(self, constraint: &AssocItemConstraint) -> String {
let args = constraint
.args
.as_ref()
.map(|a| self.render_generic_args(a))
.unwrap_or_default();
match &constraint.binding {
AssocItemConstraintKind::Equality(term) => {
format!("{}{args} = {}", constraint.name, self.render_term(term))
},
AssocItemConstraintKind::Constraint(bounds) => {
let mut result = format!("{}{args}: ", constraint.name);
self.write_bounds_joined(&mut result, bounds);
result
},
}
}
fn render_term(self, term: &Term) -> Cow<'_, str> {
match term {
Term::Type(ty) => self.render_type(ty),
Term::Constant(c) => Cow::Borrowed(c.value.as_deref().unwrap_or(&c.expr)),
}
}
#[must_use]
pub fn render_generic_bound<'t>(&self, bound: &'t GenericBound) -> Cow<'t, str> {
match bound {
GenericBound::TraitBound {
trait_, modifier, ..
} => {
let sanitized = RendererUtils::sanitize_path(&trait_.path);
if matches!(modifier, TraitBoundModifier::None)
&& trait_.args.is_none()
&& matches!(sanitized, Cow::Borrowed(_))
{
return Cow::Borrowed(&trait_.path);
}
let modifier_str = match modifier {
TraitBoundModifier::None => "",
TraitBoundModifier::Maybe => "?",
TraitBoundModifier::MaybeConst => "~const ", };
let mut result = format!("{modifier_str}{sanitized}");
if let Some(args) = &trait_.args {
_ = write!(result, "{}", &self.render_generic_args(args));
}
Cow::Owned(result)
},
GenericBound::Outlives(lt) => Cow::Borrowed(lt),
GenericBound::Use(_) => Cow::Borrowed(""),
}
}
#[must_use]
pub fn render_generics(&self, generics: &[GenericParamDef]) -> String {
if generics.is_empty() {
return String::new();
}
let params: Vec<String> = generics
.iter()
.filter_map(|p| self.render_generic_param_def(p))
.collect();
if params.is_empty() {
String::new()
} else {
format!("<{}>", params.join(", "))
}
}
fn render_generic_param_def(self, param: &GenericParamDef) -> Option<String> {
match ¶m.kind {
GenericParamDefKind::Lifetime { outlives } => {
let mut result = param.name.clone();
if !outlives.is_empty() {
_ = write!(result, ": {}", outlives.join(" + "));
}
Some(result)
},
GenericParamDefKind::Type {
bounds,
is_synthetic,
..
} => {
if *is_synthetic {
return None;
}
let mut result = param.name.clone();
if !bounds.is_empty() {
_ = write!(result, ": ");
self.write_bounds_joined(&mut result, bounds);
}
Some(result)
},
GenericParamDefKind::Const { type_, .. } => {
Some(format!("const {}: {}", param.name, self.render_type(type_)))
},
}
}
#[must_use]
pub fn render_where_clause(&self, where_predicates: &[WherePredicate]) -> String {
if where_predicates.is_empty() {
return String::new();
}
let clauses: Vec<String> = where_predicates
.iter()
.map(|p| self.render_where_predicate(p))
.collect();
format!("\nwhere\n {}", clauses.join(",\n "))
}
fn render_where_predicate(self, pred: &WherePredicate) -> String {
match pred {
WherePredicate::BoundPredicate { type_, bounds, .. } => {
let mut result = format!("{}: ", self.render_type(type_));
self.write_bounds_joined(&mut result, bounds);
result
},
WherePredicate::LifetimePredicate { lifetime, outlives } => {
format!("{lifetime}: {}", outlives.join(" + "))
},
WherePredicate::EqPredicate { lhs, rhs } => {
format!("{} = {}", self.render_type(lhs), self.render_term(rhs))
},
}
}
#[must_use]
pub fn collect_linkable_types(&self, ty: &Type) -> Vec<(String, rustdoc_types::Id)> {
let mut result: Vec<(String, Id)> = Vec::new();
self.collect_types_recursive(ty, &mut result);
result
}
fn collect_types_recursive(self, ty: &Type, result: &mut Vec<(String, rustdoc_types::Id)>) {
match ty {
Type::ResolvedPath(path) => {
let name = PathUtils::short_name(&path.path);
result.push((name.to_string(), path.id));
if let Some(args) = &path.args {
self.collect_from_generic_args(args, result);
}
},
Type::DynTrait(dyn_trait) => {
for pt in &dyn_trait.traits {
let name = PathUtils::short_name(&pt.trait_.path);
result.push((name.to_string(), pt.trait_.id));
if let Some(args) = &pt.trait_.args {
self.collect_from_generic_args(args, result);
}
}
},
Type::BorrowedRef { type_, .. } | Type::RawPointer { type_, .. } => {
self.collect_types_recursive(type_, result);
},
Type::Slice(inner)
| Type::Array { type_: inner, .. }
| Type::Pat { type_: inner, .. } => {
self.collect_types_recursive(inner, result);
},
Type::Tuple(types) => {
for inner in types {
self.collect_types_recursive(inner, result);
}
},
Type::FunctionPointer(fp) => {
for (_, input_ty) in &fp.sig.inputs {
self.collect_types_recursive(input_ty, result);
}
if let Some(output) = &fp.sig.output {
self.collect_types_recursive(output, result);
}
},
Type::ImplTrait(bounds) => {
for bound in bounds {
if let GenericBound::TraitBound { trait_, .. } = bound {
let name = PathUtils::short_name(&trait_.path);
result.push((name.to_string(), trait_.id));
if let Some(args) = &trait_.args {
self.collect_from_generic_args(args, result);
}
}
}
},
Type::QualifiedPath {
self_type, trait_, ..
} => {
self.collect_types_recursive(self_type, result);
if let Some(t) = trait_ {
let name = PathUtils::short_name(&t.path);
result.push((name.to_string(), t.id));
}
},
Type::Primitive(_) | Type::Generic(_) | Type::Infer => {},
}
}
fn collect_from_generic_args(
self,
args: &GenericArgs,
result: &mut Vec<(String, rustdoc_types::Id)>,
) {
match args {
GenericArgs::AngleBracketed { args, constraints } => {
for arg in args {
if let GenericArg::Type(ty) = arg {
self.collect_types_recursive(ty, result);
}
}
for constraint in constraints {
if let AssocItemConstraintKind::Equality(Term::Type(ty)) = &constraint.binding {
self.collect_types_recursive(ty, result);
}
}
},
GenericArgs::Parenthesized { inputs, output } => {
for input in inputs {
self.collect_types_recursive(input, result);
}
if let Some(output) = output {
self.collect_types_recursive(output, result);
}
},
GenericArgs::ReturnTypeNotation => {},
}
}
}