Skip to main content

sourcey_rustdoc/
signature.rs

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}