1use rustdoc_types::{
2 DynTrait, FunctionSignature, GenericArg, GenericArgs, GenericBound, Generics as RdGenerics,
3 Path as RdPath, PreciseCapturingArg, Type, WherePredicate,
4};
5
6use crate::links::LinkContext;
7use crate::spec::{
8 GenericParam, GenericParamKind, Generics, SigInput, SigToken, Signature, TypePath,
9};
10use rustdoc_types::Type as RdType;
11
12pub fn lower_signature(
13 sig: &FunctionSignature,
14 name: &str,
15 is_const: bool,
16 is_async: bool,
17 is_unsafe: bool,
18 links: &LinkContext<'_>,
19) -> Signature {
20 let inputs: Vec<SigInput> = sig
21 .inputs
22 .iter()
23 .map(|(name, ty)| SigInput {
24 name: name.clone(),
25 type_display: render_type(ty),
26 })
27 .collect();
28
29 let output_display = sig.output.as_ref().map(render_type);
30
31 let mut tokens: Vec<SigToken> = Vec::new();
32 let kw = |text: &str| SigToken::Keyword { text: text.to_string() };
33 let punct = |text: &str| SigToken::Punct { text: text.to_string() };
34 if is_const {
35 tokens.push(kw("const"));
36 tokens.push(SigToken::Whitespace);
37 }
38 if is_async {
39 tokens.push(kw("async"));
40 tokens.push(SigToken::Whitespace);
41 }
42 if is_unsafe {
43 tokens.push(kw("unsafe"));
44 tokens.push(SigToken::Whitespace);
45 }
46 tokens.push(kw("fn"));
47 tokens.push(SigToken::Whitespace);
48 tokens.push(SigToken::Type {
49 text: name.to_string(),
50 target: None,
51 });
52 tokens.push(punct("("));
53 for (idx, ((input_name, input_ty), input)) in sig.inputs.iter().zip(inputs.iter()).enumerate() {
54 let _ = input_name;
55 if idx > 0 {
56 tokens.push(punct(","));
57 tokens.push(SigToken::Whitespace);
58 }
59 tokens.push(SigToken::Punct { text: input.name.clone() });
60 tokens.push(punct(":"));
61 tokens.push(SigToken::Whitespace);
62 push_type_tokens(&mut tokens, &resolve_type_path(input_ty, links));
63 }
64 if sig.is_c_variadic {
65 if !inputs.is_empty() {
66 tokens.push(punct(","));
67 tokens.push(SigToken::Whitespace);
68 }
69 tokens.push(punct("..."));
70 }
71 tokens.push(punct(")"));
72
73 if let Some(output_ty) = &sig.output {
74 tokens.push(SigToken::Whitespace);
75 tokens.push(punct("->"));
76 tokens.push(SigToken::Whitespace);
77 push_type_tokens(&mut tokens, &resolve_type_path(output_ty, links));
78 }
79 let _ = output_display;
80
81 let mut display = String::new();
82 for token in &tokens {
83 match token {
84 SigToken::Keyword { text }
85 | SigToken::Punct { text }
86 | SigToken::Generic { text } => display.push_str(text),
87 SigToken::Lifetime { text } => {
88 display.push('\'');
89 display.push_str(text);
90 }
91 SigToken::Type { text, .. } => display.push_str(text),
92 SigToken::Whitespace => display.push(' '),
93 SigToken::Newline => display.push('\n'),
94 }
95 }
96
97 Signature {
98 display,
99 tokens,
100 inputs,
101 output_display,
102 is_c_variadic: sig.is_c_variadic,
103 }
104}
105
106fn push_type_tokens(out: &mut Vec<SigToken>, resolved: &ResolvedTypeDisplay) {
107 out.push(SigToken::Type {
108 text: resolved.text.clone(),
109 target: resolved.target.clone(),
110 });
111}
112
113struct ResolvedTypeDisplay {
114 text: String,
115 target: Option<TypePath>,
116}
117
118fn resolve_type_path(ty: &RdType, links: &LinkContext<'_>) -> ResolvedTypeDisplay {
119 let text = render_type(ty);
120 let target = type_path_target(ty, links);
121 ResolvedTypeDisplay { text, target }
122}
123
124fn type_path_target(ty: &RdType, links: &LinkContext<'_>) -> Option<TypePath> {
125 match ty {
126 RdType::ResolvedPath(p) => {
127 let resolved = links.resolve_id(&p.id);
128 let display = p.path.clone();
129 match resolved {
130 Some(r) => Some(TypePath {
131 crate_id: r.crate_id,
132 path: r.path,
133 display,
134 external: r.external,
135 html_root_url: r.html_root_url,
136 }),
137 None => Some(TypePath {
138 crate_id: 0,
139 path: vec![p.path.clone()],
140 display,
141 external: false,
142 html_root_url: None,
143 }),
144 }
145 }
146 RdType::BorrowedRef { type_, .. } | RdType::RawPointer { type_, .. } => {
147 type_path_target(type_, links)
148 }
149 _ => None,
150 }
151}
152
153pub fn lower_generics(generics: &RdGenerics) -> Generics {
154 let params = generics
155 .params
156 .iter()
157 .map(|p| GenericParam {
158 name: p.name.clone(),
159 kind: match p.kind {
160 rustdoc_types::GenericParamDefKind::Lifetime { .. } => GenericParamKind::Lifetime,
161 rustdoc_types::GenericParamDefKind::Type { .. } => GenericParamKind::Type,
162 rustdoc_types::GenericParamDefKind::Const { .. } => GenericParamKind::Const,
163 },
164 default_display: generic_param_default(&p.kind),
165 bounds: generic_param_bounds(&p.kind),
166 })
167 .collect();
168 let where_predicates = generics
169 .where_predicates
170 .iter()
171 .map(render_where_predicate)
172 .collect();
173 Generics {
174 params,
175 where_predicates,
176 }
177}
178
179fn generic_param_default(kind: &rustdoc_types::GenericParamDefKind) -> Option<String> {
180 match kind {
181 rustdoc_types::GenericParamDefKind::Type { default, .. } => default.as_ref().map(render_type),
182 rustdoc_types::GenericParamDefKind::Const { default, .. } => default.clone(),
183 rustdoc_types::GenericParamDefKind::Lifetime { .. } => None,
184 }
185}
186
187fn generic_param_bounds(kind: &rustdoc_types::GenericParamDefKind) -> Vec<String> {
188 match kind {
189 rustdoc_types::GenericParamDefKind::Type { bounds, .. } => {
190 bounds.iter().map(render_bound).collect()
191 }
192 rustdoc_types::GenericParamDefKind::Lifetime { outlives, .. } => outlives.clone(),
193 rustdoc_types::GenericParamDefKind::Const { .. } => Vec::new(),
194 }
195}
196
197fn render_where_predicate(pred: &WherePredicate) -> String {
198 match pred {
199 WherePredicate::BoundPredicate {
200 type_, bounds, generic_params, ..
201 } => {
202 let mut s = String::new();
203 if !generic_params.is_empty() {
204 s.push_str("for<");
205 let parts: Vec<String> = generic_params.iter().map(|p| p.name.clone()).collect();
206 s.push_str(&parts.join(", "));
207 s.push_str("> ");
208 }
209 s.push_str(&render_type(type_));
210 if !bounds.is_empty() {
211 s.push_str(": ");
212 let parts: Vec<String> = bounds.iter().map(render_bound).collect();
213 s.push_str(&parts.join(" + "));
214 }
215 s
216 }
217 WherePredicate::LifetimePredicate { lifetime, outlives } => {
218 let mut s = lifetime.clone();
219 if !outlives.is_empty() {
220 s.push_str(": ");
221 s.push_str(&outlives.join(" + "));
222 }
223 s
224 }
225 WherePredicate::EqPredicate { lhs, rhs } => {
226 format!("{} = {}", render_type(lhs), render_term(rhs))
227 }
228 }
229}
230
231pub fn render_bound(bound: &GenericBound) -> String {
232 match bound {
233 GenericBound::TraitBound {
234 trait_,
235 generic_params,
236 modifier,
237 } => {
238 let mut s = String::new();
239 if !generic_params.is_empty() {
240 s.push_str("for<");
241 let parts: Vec<String> = generic_params.iter().map(|p| p.name.clone()).collect();
242 s.push_str(&parts.join(", "));
243 s.push_str("> ");
244 }
245 match modifier {
246 rustdoc_types::TraitBoundModifier::None => {}
247 rustdoc_types::TraitBoundModifier::Maybe => s.push('?'),
248 rustdoc_types::TraitBoundModifier::MaybeConst => s.push_str("~const "),
249 }
250 s.push_str(&render_path(trait_));
251 s
252 }
253 GenericBound::Outlives(lifetime) => lifetime.clone(),
254 GenericBound::Use(captures) => {
255 let parts: Vec<String> = captures
256 .iter()
257 .map(|c| match c {
258 PreciseCapturingArg::Lifetime(s) | PreciseCapturingArg::Param(s) => s.clone(),
259 })
260 .collect();
261 format!("use<{}>", parts.join(", "))
262 }
263 }
264}
265
266fn render_term(term: &rustdoc_types::Term) -> String {
267 match term {
268 rustdoc_types::Term::Type(t) => render_type(t),
269 rustdoc_types::Term::Constant(c) => c.expr.clone(),
270 }
271}
272
273pub fn render_type(ty: &Type) -> String {
274 match ty {
275 Type::ResolvedPath(p) => render_path(p),
276 Type::DynTrait(d) => render_dyn(d),
277 Type::Generic(s) => s.clone(),
278 Type::Primitive(s) => s.clone(),
279 Type::FunctionPointer(f) => format!(
280 "fn({}){}",
281 f.sig
282 .inputs
283 .iter()
284 .map(|(_, t)| render_type(t))
285 .collect::<Vec<_>>()
286 .join(", "),
287 f.sig
288 .output
289 .as_ref()
290 .map(|t| format!(" -> {}", render_type(t)))
291 .unwrap_or_default()
292 ),
293 Type::Tuple(parts) => {
294 let inner: Vec<String> = parts.iter().map(render_type).collect();
295 format!("({})", inner.join(", "))
296 }
297 Type::Slice(inner) => format!("[{}]", render_type(inner)),
298 Type::Array { type_, len } => format!("[{}; {}]", render_type(type_), len),
299 Type::Pat { type_, .. } => render_type(type_),
300 Type::ImplTrait(bounds) => {
301 let parts: Vec<String> = bounds.iter().map(render_bound).collect();
302 format!("impl {}", parts.join(" + "))
303 }
304 Type::Infer => "_".to_string(),
305 Type::RawPointer { is_mutable, type_ } => {
306 let m = if *is_mutable { "*mut " } else { "*const " };
307 format!("{}{}", m, render_type(type_))
308 }
309 Type::BorrowedRef {
310 lifetime,
311 is_mutable,
312 type_,
313 } => {
314 let lt = lifetime
315 .as_ref()
316 .map(|l| format!("{} ", l))
317 .unwrap_or_default();
318 let m = if *is_mutable { "mut " } else { "" };
319 format!("&{}{}{}", lt, m, render_type(type_))
320 }
321 Type::QualifiedPath {
322 name,
323 args,
324 self_type,
325 trait_,
326 ..
327 } => {
328 let self_render = render_type(self_type);
329 let trait_render = trait_
330 .as_ref()
331 .map(|p| format!(" as {}", render_path(p)))
332 .unwrap_or_default();
333 let args_render = match args.as_ref() {
334 Some(a) => render_generic_args(a),
335 None => String::new(),
336 };
337 format!("<{}{}>::{}{}", self_render, trait_render, name, args_render)
338 }
339 }
340}
341
342fn render_path(path: &RdPath) -> String {
343 let mut s = path.path.clone();
344 if let Some(args) = &path.args {
345 s.push_str(&render_generic_args(args));
346 }
347 s
348}
349
350fn render_generic_args(args: &GenericArgs) -> String {
351 match args {
352 GenericArgs::AngleBracketed { args, constraints } => {
353 let mut parts: Vec<String> = args
354 .iter()
355 .map(|a| match a {
356 GenericArg::Lifetime(s) => s.clone(),
357 GenericArg::Type(t) => render_type(t),
358 GenericArg::Const(c) => c.expr.clone(),
359 GenericArg::Infer => "_".to_string(),
360 })
361 .collect();
362 for c in constraints {
363 let term = match &c.binding {
364 rustdoc_types::AssocItemConstraintKind::Equality(t) => render_term(t),
365 rustdoc_types::AssocItemConstraintKind::Constraint(bounds) => {
366 let bs: Vec<String> = bounds.iter().map(render_bound).collect();
367 bs.join(" + ")
368 }
369 };
370 let sep = matches!(
371 c.binding,
372 rustdoc_types::AssocItemConstraintKind::Equality(_)
373 )
374 .then(|| "=".to_string())
375 .unwrap_or_else(|| ":".to_string());
376 parts.push(format!("{}{}{}", c.name, sep, term));
377 }
378 if parts.is_empty() {
379 String::new()
380 } else {
381 format!("<{}>", parts.join(", "))
382 }
383 }
384 GenericArgs::Parenthesized { inputs, output } => {
385 let in_render: Vec<String> = inputs.iter().map(render_type).collect();
386 let out_render = output
387 .as_ref()
388 .map(|t| format!(" -> {}", render_type(t)))
389 .unwrap_or_default();
390 format!("({}){}", in_render.join(", "), out_render)
391 }
392 GenericArgs::ReturnTypeNotation => "(..)".to_string(),
393 }
394}
395
396fn render_dyn(d: &DynTrait) -> String {
397 let parts: Vec<String> = d
398 .traits
399 .iter()
400 .map(|t| {
401 let mut s = render_path(&t.trait_);
402 if !t.generic_params.is_empty() {
403 let lts: Vec<String> = t.generic_params.iter().map(|p| p.name.clone()).collect();
404 s.push_str(&format!(" + for<{}>", lts.join(", ")));
405 }
406 s
407 })
408 .collect();
409 let lt = d
410 .lifetime
411 .as_ref()
412 .map(|l| format!(" + {}", l))
413 .unwrap_or_default();
414 format!("dyn {}{}", parts.join(" + "), lt)
415}