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                    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}