aranya_capi_codegen/syntax/
util.rs

1use std::fmt;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::{GenericArgument, Ident, Path, PathArguments, Type, TypePath};
6
7/// Converts `ty` to a string, but strips out whitespace.
8pub struct Trimmed<'a, T>(pub &'a T);
9
10impl<'a> fmt::Display for Trimmed<'a, TypePath> {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        if self.0.qself.is_some() {
13            // TODO(eric): proper qself support?
14            write!(f, "<...>::")?;
15        }
16        write_path(f, &self.0.path)
17    }
18}
19
20impl<'a> fmt::Display for Trimmed<'a, Path> {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write_path(f, self.0)
23    }
24}
25
26impl<'a> fmt::Display for Trimmed<'a, Ident> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write_path(f, &self.0.clone().into())
29    }
30}
31
32fn write_path(f: &mut fmt::Formatter<'_>, path: &Path) -> fmt::Result {
33    write!(f, "\"")?;
34    if path.leading_colon.is_some() {
35        write!(f, "::")?;
36    }
37    for pair in path.segments.pairs() {
38        let seg = pair.value();
39        write!(f, "{}", seg.ident)?;
40        match &seg.arguments {
41            PathArguments::None => {}
42            PathArguments::AngleBracketed(args) => {
43                write!(f, "<")?;
44                for arg in &args.args {
45                    match &arg {
46                        GenericArgument::Type(ty) => write_type(f, ty)?,
47                        GenericArgument::Lifetime(lt) => write!(f, "'{}", lt.ident)?,
48                        _ => write!(f, "???")?,
49                    }
50                }
51                write!(f, ">")?;
52            }
53            PathArguments::Parenthesized(_) => {
54                unreachable!("Parenthesized")
55            }
56        }
57        if pair.punct().is_some() {
58            write!(f, "::")?;
59        }
60    }
61    write!(f, "\"")
62}
63
64fn write_type(f: &mut fmt::Formatter<'_>, ty: &Type) -> fmt::Result {
65    let mut code = quote!(const _: #ty = ();).to_string();
66    if let Ok(file) = syn::parse_file(&code) {
67        code = prettyplease::unparse(&file);
68    }
69    let code = code
70        .trim()
71        .strip_prefix("const _: ")
72        .unwrap_or(&code)
73        .strip_suffix(" = ();")
74        .unwrap_or(&code);
75
76    let mut space = false;
77    for c in code.chars() {
78        if c == '\n' || c == '\t' {
79            continue;
80        }
81        if c.is_whitespace() {
82            if !space {
83                write!(f, " ")?;
84                space = true;
85            }
86        } else {
87            write!(f, "{c}")?;
88            space = false;
89        }
90    }
91    Ok(())
92}
93
94#[allow(dead_code)] // TODO
95pub(super) struct Quote<'a, T>(pub &'a T);
96
97impl<'a, T: ToTokens> fmt::Display for Quote<'a, T> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let v = &self.0;
100        write!(f, "\"{}\"", quote!(#v))
101    }
102}
103
104/// Ensures that `Option<T: ToTokens>` still prints the token.
105pub(super) struct TokensOrDefault<'a, T>(pub &'a Option<T>);
106
107impl<'a, T> ToTokens for TokensOrDefault<'a, T>
108where
109    T: ToTokens + Default,
110{
111    fn to_tokens(&self, tokens: &mut TokenStream) {
112        match self.0 {
113            Some(t) => t.to_tokens(tokens),
114            None => T::default().to_tokens(tokens),
115        }
116    }
117}