witgen_macro_helper/
generator.rs

1use std::fmt::Write;
2
3use anyhow::{bail, Result};
4use syn::{
5    Attribute, Field, Fields, ItemEnum, ItemFn, ItemImpl, ItemStruct, ItemTrait, ItemType, ItemUse,
6    Lit, Signature, TraitItem, UseGroup, UseName, UsePath, UseRename, UseTree,
7};
8
9use crate::{
10    util::{pub_method, wit_ident, FuncType, SignatureUtils},
11    wit::ToWitType,
12};
13
14/// Generate a wit record
15/// ```rust
16/// /// Document String
17/// struct FooRecord {
18///    a: string,
19///    /// Comment field
20///    b: Option<i32>,
21/// }
22/// ```
23/// becomes
24/// ```ts
25/// /// Document String
26/// record foo-record {
27///   a: string,
28///   b: option<s32>,
29/// }
30/// ```
31///
32pub fn gen_wit_struct(strukt: &ItemStruct) -> Result<String> {
33    if !strukt.generics.params.is_empty() {
34        bail!("doesn't support generic parameters with witgen");
35    }
36
37    let struct_name = wit_ident(&strukt.ident)?;
38    let is_tuple_struct = strukt.fields.iter().any(|f| f.ident.is_none());
39    let fields = gen_fields(strukt.fields.iter().collect::<Vec<&Field>>())?;
40    let fields = if is_tuple_struct {
41        fields.join(", ")
42    } else {
43        fields.join(",\n")
44    };
45
46    let content = if is_tuple_struct {
47        format!("type {} = tuple<{}>\n", struct_name, fields)
48    } else {
49        format!(
50            r#"record {} {{
51{}
52}}
53"#,
54            struct_name, fields
55        )
56    };
57    Ok(content)
58}
59
60fn gen_fields(iter: Vec<&Field>) -> Result<Vec<String>> {
61    iter.into_iter()
62        .map(|field| {
63            let field_name = if let Some(ident) = &field.ident {
64                format!("  {}: ", wit_ident(ident)?)
65            } else {
66                Default::default()
67            };
68            let comment = get_doc_comment(&field.attrs, 1, false)?;
69            Ok(format!("{comment}{}{}", field_name, field.ty.to_wit()?))
70        })
71        .collect()
72}
73
74/// Generate a wit enum
75/// ```rust
76/// /// Top comment
77/// enum MyEnum {
78///   /// comment case
79///   Unit,
80///   TupleVariant(String, i32)
81/// }
82/// ```
83///
84/// ```ts
85/// /// Top comment
86/// variant my-enum {
87///   /// comment case
88///   unit,
89///   tuple-variant(tuple<string, s32>),
90/// }
91/// ```
92pub fn gen_wit_enum(enm: &ItemEnum) -> Result<String> {
93    if !enm.generics.params.is_empty() {
94        bail!("doesn't support generic parameters with witgen");
95    }
96
97    let enm_name = wit_ident(&enm.ident)?;
98    let is_wit_enum = enm
99        .variants
100        .iter()
101        .all(|v| matches!(v.fields, Fields::Unit));
102    let mut named_types = String::new();
103    let variants = enm
104        .variants
105        .iter()
106        .map(|variant| {
107            let ident = wit_ident(&variant.ident)?;
108            let comment = get_doc_comment(&variant.attrs, 1, false)?;
109            let variant_string = match &variant.fields {
110                syn::Fields::Named(named) => {
111                    let fields = gen_fields(named.named.iter().collect())?.join(",\n");
112                    let inner_type_name = &format!("{}-{}", enm_name, ident);
113                    let comment = get_doc_comment(&variant.attrs, 0, false)?;
114                    write!(
115                        &mut named_types,
116                        "{}record {} {{\n{}\n}}\n",
117                        comment, inner_type_name, fields
118                    )?;
119                    Ok(format!("{}({})", ident, inner_type_name))
120                }
121                syn::Fields::Unnamed(unamed) => {
122                    let fields = unamed
123                        .unnamed
124                        .iter()
125                        .map(|field| field.ty.to_wit())
126                        .collect::<Result<Vec<String>>>()?
127                        .join(", ");
128
129                    let formatted_field = if unamed.unnamed.len() > 1 {
130                        format!("tuple<{}>", fields)
131                    } else {
132                        fields
133                    };
134
135                    Ok(format!("{}({})", ident, formatted_field))
136                }
137                syn::Fields::Unit => Ok(ident),
138            };
139            variant_string.map(|v| format!("{}  {},", comment, v))
140        })
141        .collect::<Result<Vec<String>>>()?
142        .join("\n");
143    let ty = if is_wit_enum { "enum" } else { "variant" };
144    let content = format!(
145        r#"{} {} {{
146{}
147}}
148"#,
149        ty, enm_name, variants
150    );
151
152    Ok(format!("{}{}", content, named_types))
153}
154
155/// Generate a wit function
156/// ```rust
157/// /// Document String
158/// fn foo(a: string, b: Option<i32>) -> Result<string> { Ok(a)}
159/// ```
160/// becomes
161/// ```ts
162/// /// Document String
163/// foo: function(a: string, b: option<s32>) -> expected<string>
164/// ```
165///
166pub fn gen_wit_function(func: &ItemFn) -> Result<String> {
167    let signature = &func.sig;
168    gen_wit_function_from_signature(signature)
169}
170
171fn gen_wit_function_from_signature(signature: &Signature) -> Result<String> {
172    let fn_name = wit_ident(&signature.ident)?;
173    let fn_type = signature.fn_type();
174    let fn_args = signature.fn_args()?.join(", ");
175    let ret = signature.ret_args()?;
176
177    let preamble = if let FuncType::Instance(true) = fn_type {
178        "///@mutable\n  "
179    } else {
180        ""
181    }
182    .to_string();
183
184    Ok(format!("{preamble}{fn_name}: func({fn_args}){ret}\n"))
185}
186
187/// Generate a wit type alias
188/// ```rust
189/// /// Document String
190/// type foo = (String, option<bool>);
191/// ```
192/// becomes
193/// ```ts
194/// /// Document String
195/// type foo = tuple<string, option<bool>>
196/// ```
197///
198pub fn gen_wit_type_alias(type_alias: &ItemType) -> Result<String> {
199    if !type_alias.generics.params.is_empty() {
200        bail!("doesn't support generic parameters with witgen");
201    }
202    let ty = type_alias.ty.to_wit()?;
203    let type_alias_ident = wit_ident(&type_alias.ident)?;
204    Ok(format!("type {} = {}\n", type_alias_ident, ty))
205}
206
207pub(crate) fn get_doc_comment(
208    attrs: &[Attribute],
209    depth: usize,
210    include_paths: bool,
211) -> Result<String> {
212    let mut comment = String::new();
213    let spaces = " ".repeat(depth * 2);
214    for attr in attrs {
215        match &attr.parse_meta()? {
216            syn::Meta::NameValue(name_val) if name_val.path.is_ident("doc") => {
217                if let Lit::Str(lit_str) = &name_val.lit {
218                    let text = lit_str.value();
219                    writeln!(&mut comment, "{spaces}///{text}",)?;
220                }
221            }
222            syn::Meta::Path(path) if include_paths => {
223                if let Some(ident) = path.get_ident() {
224                    writeln!(&mut comment, "{spaces}///@{ident}")?;
225                }
226            }
227            _ => {}
228        }
229    }
230    Ok(comment)
231}
232
233#[allow(unused_variables, unreachable_code)]
234fn gen_use_names(use_tree: &UseTree) -> Result<String> {
235    let res = match use_tree {
236        UseTree::Path(_) => {
237            todo!("Can only have top level path, e.g. cannot do `use other_crate::module::...`")
238        }
239        UseTree::Name(UseName { ident }) => {
240          todo!("Cannot specify one import must be *. e.g. `use other_crate::Import` --> `use other_crate::*`");
241          wit_ident(&ident)?
242        },
243        UseTree::Rename(UseRename { ident, rename, .. }) => {
244            // TODO: Currently imported types aren't renamed when generated so this is still a todo.
245            todo!("Cannot have renamed imports yet. E.g. `use other_crate::Import as OtherImport`");
246            let (ident, rename) = (wit_ident(ident)?, (wit_ident(rename)?));
247            format!("{ident} as {rename}")
248        }
249        UseTree::Glob(_) => return Ok("*".to_string()),
250        UseTree::Group(UseGroup { items, .. }) => {
251          todo!("Cannot specify group yet. E.g. `use other_crate::{{Import1, Import2}}`");
252          items
253            .iter()
254            .map(gen_use_names)
255            .collect::<Result<Vec<String>>>()?
256            .join(",")},
257    };
258    Ok(format!("{{{res}}}"))
259}
260
261pub fn gen_wit_import(import: &ItemUse) -> Result<String> {
262    let (id, use_names) = match &import.tree {
263        UseTree::Path(UsePath { ident, tree, ..  }) => {
264          (wit_ident(ident)?, gen_use_names(tree)?)
265        }
266          ,
267        _ => todo!("Can only use top level 'path', e.g. `use import_crate::*` -> `import_crate`. More specific imports is a TODO."),
268    };
269    // Todo allow referencing specific items
270    let res = format!("use {use_names} from {id}");
271    println!("{res}");
272    Ok(res)
273}
274
275pub fn gen_wit_trait(trait_: &ItemTrait) -> Result<String> {
276    let name = wit_ident(&trait_.ident)?;
277    let mut res = format!("interface {name} {{\n");
278
279    for item in trait_.items.iter() {
280        match item {
281            TraitItem::Const(_) => todo!("Const in Trait isn't implemented yet"),
282            TraitItem::Method(method) => {
283                let comment = get_doc_comment(&method.attrs, 1, true)?;
284                write!(
285                    &mut res,
286                    "{comment}  {}",
287                    gen_wit_function_from_signature(&method.sig)?
288                )?
289            }
290            TraitItem::Type(_) => todo!("Type in Trait isn't implemented yet"),
291            TraitItem::Macro(_) => todo!("Macro in Trait isn't implemented yet"),
292            TraitItem::Verbatim(_) => todo!("Verbatim in Trait isn't implemented yet"),
293            _ => todo!("extra case in Trait isn't implemented yet"),
294        }
295    }
296    res.push_str("}\n");
297    Ok(res)
298}
299
300pub fn gen_wit_impl(impl_: &ItemImpl) -> Result<String> {
301    let name = wit_ident(&impl_.self_ty.to_wit()?)?;
302    let mut res = format!("resource {name} {{\n");
303    for method in impl_.items.iter().filter_map(pub_method) {
304        let comment = get_doc_comment(&method.attrs, 1, true)?;
305        let static_decl = if matches!(method.sig.fn_type(), FuncType::Standalone) {
306            "static "
307        } else {
308            ""
309        }
310        .to_string();
311        write!(
312            &mut res,
313            "{comment}  {static_decl}{}",
314            gen_wit_function_from_signature(&method.sig)?
315        )?
316    }
317    res.push_str("}\n");
318    Ok(res)
319}