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 item_kind: Some(r.kind),
137 }),
138 None => Some(TypePath {
139 crate_id: 0,
140 path: vec![p.path.clone()],
141 display,
142 external: false,
143 html_root_url: None,
144 item_kind: None,
145 }),
146 }
147 }
148 RdType::BorrowedRef { type_, .. } | RdType::RawPointer { type_, .. } => {
149 type_path_target(type_, links)
150 }
151 _ => None,
152 }
153}
154
155pub fn lower_generics(generics: &RdGenerics) -> Generics {
156 let params = generics
157 .params
158 .iter()
159 .map(|p| GenericParam {
160 name: p.name.clone(),
161 kind: match p.kind {
162 rustdoc_types::GenericParamDefKind::Lifetime { .. } => GenericParamKind::Lifetime,
163 rustdoc_types::GenericParamDefKind::Type { .. } => GenericParamKind::Type,
164 rustdoc_types::GenericParamDefKind::Const { .. } => GenericParamKind::Const,
165 },
166 default_display: generic_param_default(&p.kind),
167 bounds: generic_param_bounds(&p.kind),
168 })
169 .collect();
170 let where_predicates = generics
171 .where_predicates
172 .iter()
173 .map(render_where_predicate)
174 .collect();
175 Generics {
176 params,
177 where_predicates,
178 }
179}
180
181fn generic_param_default(kind: &rustdoc_types::GenericParamDefKind) -> Option<String> {
182 match kind {
183 rustdoc_types::GenericParamDefKind::Type { default, .. } => default.as_ref().map(render_type),
184 rustdoc_types::GenericParamDefKind::Const { default, .. } => default.clone(),
185 rustdoc_types::GenericParamDefKind::Lifetime { .. } => None,
186 }
187}
188
189fn generic_param_bounds(kind: &rustdoc_types::GenericParamDefKind) -> Vec<String> {
190 match kind {
191 rustdoc_types::GenericParamDefKind::Type { bounds, .. } => {
192 bounds.iter().map(render_bound).collect()
193 }
194 rustdoc_types::GenericParamDefKind::Lifetime { outlives, .. } => outlives.clone(),
195 rustdoc_types::GenericParamDefKind::Const { .. } => Vec::new(),
196 }
197}
198
199fn render_where_predicate(pred: &WherePredicate) -> String {
200 match pred {
201 WherePredicate::BoundPredicate {
202 type_, bounds, generic_params, ..
203 } => {
204 let mut s = String::new();
205 if !generic_params.is_empty() {
206 s.push_str("for<");
207 let parts: Vec<String> = generic_params.iter().map(|p| p.name.clone()).collect();
208 s.push_str(&parts.join(", "));
209 s.push_str("> ");
210 }
211 s.push_str(&render_type(type_));
212 if !bounds.is_empty() {
213 s.push_str(": ");
214 let parts: Vec<String> = bounds.iter().map(render_bound).collect();
215 s.push_str(&parts.join(" + "));
216 }
217 s
218 }
219 WherePredicate::LifetimePredicate { lifetime, outlives } => {
220 let mut s = lifetime.clone();
221 if !outlives.is_empty() {
222 s.push_str(": ");
223 s.push_str(&outlives.join(" + "));
224 }
225 s
226 }
227 WherePredicate::EqPredicate { lhs, rhs } => {
228 format!("{} = {}", render_type(lhs), render_term(rhs))
229 }
230 }
231}
232
233pub fn render_bound(bound: &GenericBound) -> String {
234 match bound {
235 GenericBound::TraitBound {
236 trait_,
237 generic_params,
238 modifier,
239 } => {
240 let mut s = String::new();
241 if !generic_params.is_empty() {
242 s.push_str("for<");
243 let parts: Vec<String> = generic_params.iter().map(|p| p.name.clone()).collect();
244 s.push_str(&parts.join(", "));
245 s.push_str("> ");
246 }
247 match modifier {
248 rustdoc_types::TraitBoundModifier::None => {}
249 rustdoc_types::TraitBoundModifier::Maybe => s.push('?'),
250 rustdoc_types::TraitBoundModifier::MaybeConst => s.push_str("~const "),
251 }
252 s.push_str(&render_path(trait_));
253 s
254 }
255 GenericBound::Outlives(lifetime) => lifetime.clone(),
256 GenericBound::Use(captures) => {
257 let parts: Vec<String> = captures
258 .iter()
259 .map(|c| match c {
260 PreciseCapturingArg::Lifetime(s) | PreciseCapturingArg::Param(s) => s.clone(),
261 })
262 .collect();
263 format!("use<{}>", parts.join(", "))
264 }
265 }
266}
267
268fn render_term(term: &rustdoc_types::Term) -> String {
269 match term {
270 rustdoc_types::Term::Type(t) => render_type(t),
271 rustdoc_types::Term::Constant(c) => c.expr.clone(),
272 }
273}
274
275pub fn render_type(ty: &Type) -> String {
276 match ty {
277 Type::ResolvedPath(p) => render_path(p),
278 Type::DynTrait(d) => render_dyn(d),
279 Type::Generic(s) => s.clone(),
280 Type::Primitive(s) => s.clone(),
281 Type::FunctionPointer(f) => format!(
282 "fn({}){}",
283 f.sig
284 .inputs
285 .iter()
286 .map(|(_, t)| render_type(t))
287 .collect::<Vec<_>>()
288 .join(", "),
289 f.sig
290 .output
291 .as_ref()
292 .map(|t| format!(" -> {}", render_type(t)))
293 .unwrap_or_default()
294 ),
295 Type::Tuple(parts) => {
296 let inner: Vec<String> = parts.iter().map(render_type).collect();
297 format!("({})", inner.join(", "))
298 }
299 Type::Slice(inner) => format!("[{}]", render_type(inner)),
300 Type::Array { type_, len } => format!("[{}; {}]", render_type(type_), len),
301 Type::Pat { type_, .. } => render_type(type_),
302 Type::ImplTrait(bounds) => {
303 let parts: Vec<String> = bounds.iter().map(render_bound).collect();
304 format!("impl {}", parts.join(" + "))
305 }
306 Type::Infer => "_".to_string(),
307 Type::RawPointer { is_mutable, type_ } => {
308 let m = if *is_mutable { "*mut " } else { "*const " };
309 format!("{}{}", m, render_type(type_))
310 }
311 Type::BorrowedRef {
312 lifetime,
313 is_mutable,
314 type_,
315 } => {
316 let lt = lifetime
317 .as_ref()
318 .map(|l| format!("{} ", l))
319 .unwrap_or_default();
320 let m = if *is_mutable { "mut " } else { "" };
321 format!("&{}{}{}", lt, m, render_type(type_))
322 }
323 Type::QualifiedPath {
324 name,
325 args,
326 self_type,
327 trait_,
328 ..
329 } => {
330 let self_render = render_type(self_type);
331 let trait_render = trait_
332 .as_ref()
333 .map(|p| format!(" as {}", render_path(p)))
334 .unwrap_or_default();
335 let args_render = match args.as_ref() {
336 Some(a) => render_generic_args(a),
337 None => String::new(),
338 };
339 format!("<{}{}>::{}{}", self_render, trait_render, name, args_render)
340 }
341 }
342}
343
344fn render_path(path: &RdPath) -> String {
345 let mut s = path.path.clone();
346 if let Some(args) = &path.args {
347 s.push_str(&render_generic_args(args));
348 }
349 s
350}
351
352fn render_generic_args(args: &GenericArgs) -> String {
353 match args {
354 GenericArgs::AngleBracketed { args, constraints } => {
355 let mut parts: Vec<String> = args
356 .iter()
357 .map(|a| match a {
358 GenericArg::Lifetime(s) => s.clone(),
359 GenericArg::Type(t) => render_type(t),
360 GenericArg::Const(c) => c.expr.clone(),
361 GenericArg::Infer => "_".to_string(),
362 })
363 .collect();
364 for c in constraints {
365 let term = match &c.binding {
366 rustdoc_types::AssocItemConstraintKind::Equality(t) => render_term(t),
367 rustdoc_types::AssocItemConstraintKind::Constraint(bounds) => {
368 let bs: Vec<String> = bounds.iter().map(render_bound).collect();
369 bs.join(" + ")
370 }
371 };
372 let sep = matches!(
373 c.binding,
374 rustdoc_types::AssocItemConstraintKind::Equality(_)
375 )
376 .then(|| "=".to_string())
377 .unwrap_or_else(|| ":".to_string());
378 parts.push(format!("{}{}{}", c.name, sep, term));
379 }
380 if parts.is_empty() {
381 String::new()
382 } else {
383 format!("<{}>", parts.join(", "))
384 }
385 }
386 GenericArgs::Parenthesized { inputs, output } => {
387 let in_render: Vec<String> = inputs.iter().map(render_type).collect();
388 let out_render = output
389 .as_ref()
390 .map(|t| format!(" -> {}", render_type(t)))
391 .unwrap_or_default();
392 format!("({}){}", in_render.join(", "), out_render)
393 }
394 GenericArgs::ReturnTypeNotation => "(..)".to_string(),
395 }
396}
397
398fn render_dyn(d: &DynTrait) -> String {
399 let parts: Vec<String> = d
400 .traits
401 .iter()
402 .map(|t| {
403 let mut s = render_path(&t.trait_);
404 if !t.generic_params.is_empty() {
405 let lts: Vec<String> = t.generic_params.iter().map(|p| p.name.clone()).collect();
406 s.push_str(&format!(" + for<{}>", lts.join(", ")));
407 }
408 s
409 })
410 .collect();
411 let lt = d
412 .lifetime
413 .as_ref()
414 .map(|l| format!(" + {}", l))
415 .unwrap_or_default();
416 format!("dyn {}{}", parts.join(" + "), lt)
417}