Skip to main content

ts_gen/codegen/
functions.rs

1//! FunctionDecl / VariableDecl → free function and static bindings.
2
3use proc_macro2::TokenStream;
4use quote::quote;
5
6use crate::codegen::signatures::{
7    expand_signatures, generate_concrete_params, is_void_return, ExpandedSignature, SignatureKind,
8};
9use crate::codegen::typemap::{to_return_type, to_syn_type, CodegenContext, TypePosition};
10use crate::parse::scope::ScopeId;
11use std::collections::HashSet;
12
13use crate::ir::{FunctionDecl, ModuleContext, VariableDecl};
14
15/// Generate wasm_bindgen extern blocks for a free function.
16///
17/// Expands optional params into multiple overloads, and generates `try_` variants.
18pub fn generate_function(
19    decl: &FunctionDecl,
20    ctx: &ModuleContext,
21    cgctx: Option<&CodegenContext<'_>>,
22    doc: &Option<String>,
23    scope: ScopeId,
24) -> TokenStream {
25    let mut used_names = HashSet::new();
26    let sigs = expand_signatures(
27        &decl.js_name,
28        &[decl.params.as_slice()],
29        &decl.return_type,
30        SignatureKind::Function,
31        doc,
32        &mut used_names,
33        cgctx,
34        scope,
35    );
36
37    let items: Vec<TokenStream> = sigs
38        .iter()
39        .map(|sig| generate_expanded_free_function(sig, ctx, cgctx, None, scope))
40        .collect();
41
42    quote! { #(#items)* }
43}
44
45/// Generate wasm_bindgen extern blocks for a free function inside a namespace.
46pub fn generate_function_with_js_namespace(
47    decl: &FunctionDecl,
48    ctx: &ModuleContext,
49    js_namespace: &str,
50    cgctx: Option<&CodegenContext<'_>>,
51    doc: &Option<String>,
52    scope: ScopeId,
53) -> TokenStream {
54    let mut used_names = HashSet::new();
55    let sigs = expand_signatures(
56        &decl.js_name,
57        &[decl.params.as_slice()],
58        &decl.return_type,
59        SignatureKind::Function,
60        doc,
61        &mut used_names,
62        cgctx,
63        scope,
64    );
65
66    let items: Vec<TokenStream> = sigs
67        .iter()
68        .map(|sig| generate_expanded_free_function(sig, ctx, cgctx, Some(js_namespace), scope))
69        .collect();
70
71    quote! { #(#items)* }
72}
73
74/// Generate a single extern block for one expanded free function signature.
75fn generate_expanded_free_function(
76    sig: &ExpandedSignature,
77    ctx: &ModuleContext,
78    cgctx: Option<&CodegenContext<'_>>,
79    js_namespace: Option<&str>,
80    scope: ScopeId,
81) -> TokenStream {
82    let rust_ident = super::typemap::make_ident(&sig.rust_name);
83    let params = generate_concrete_params(&sig.params, cgctx, scope);
84    let ret_ty = to_return_type(&sig.return_type, sig.catch, cgctx, scope);
85    let doc = super::doc_tokens(&sig.doc);
86    let has_variadic = sig.params.last().is_some_and(|p| p.variadic);
87
88    let mut wb_parts: Vec<TokenStream> = Vec::new();
89    if has_variadic {
90        wb_parts.push(quote! { variadic });
91    }
92    if sig.catch {
93        wb_parts.push(quote! { catch });
94    }
95    // Emit js_name when the JS name differs from the Rust name.
96    // wasm-bindgen uses the Rust fn name as the JS name by default,
97    // so we need js_name for any camelCase → snake_case conversion.
98    if sig.rust_name != sig.js_name {
99        let js_name = &sig.js_name;
100        wb_parts.push(quote! { js_name = #js_name });
101    }
102    if let Some(ns) = js_namespace {
103        wb_parts.push(quote! { js_namespace = #ns });
104    }
105
106    let wb_attr = if wb_parts.is_empty() {
107        quote! {}
108    } else {
109        quote! { #[wasm_bindgen(#(#wb_parts),*)] }
110    };
111
112    let ret = if is_void_return(&sig.return_type) && !sig.catch {
113        quote! {}
114    } else {
115        quote! { -> #ret_ty }
116    };
117
118    let wb_extern_attr = match ctx {
119        ModuleContext::Module(m) => quote! { #[wasm_bindgen(module = #m)] },
120        ModuleContext::Global => quote! { #[wasm_bindgen] },
121    };
122
123    quote! {
124        #wb_extern_attr
125        extern "C" {
126            #doc
127            #wb_attr
128            pub fn #rust_ident(#params) #ret;
129        }
130    }
131}
132
133/// Generate a wasm_bindgen extern block for a global constant/variable.
134pub fn generate_variable(
135    decl: &VariableDecl,
136    ctx: &ModuleContext,
137    cgctx: Option<&CodegenContext<'_>>,
138    doc: &Option<String>,
139    js_namespace: Option<&str>,
140    scope: ScopeId,
141) -> TokenStream {
142    let rust_ident = super::typemap::make_ident(&decl.name);
143    let ty = to_syn_type(&decl.type_ref, TypePosition::RETURN, cgctx, scope);
144    let doc = super::doc_tokens(doc);
145
146    let mut wb_parts: Vec<TokenStream> = vec![quote! { thread_local_v2 }];
147    if decl.js_name != decl.name {
148        let js_name = &decl.js_name;
149        wb_parts.push(quote! { js_name = #js_name });
150    }
151    if let Some(ns) = js_namespace {
152        wb_parts.push(quote! { js_namespace = #ns });
153    }
154
155    let wb_attr = quote! { #[wasm_bindgen(#(#wb_parts),*)] };
156
157    let wb_extern_attr = match ctx {
158        ModuleContext::Module(m) => quote! { #[wasm_bindgen(module = #m)] },
159        ModuleContext::Global => quote! { #[wasm_bindgen] },
160    };
161
162    quote! {
163        #wb_extern_attr
164        extern "C" {
165            #doc
166            #wb_attr
167            pub static #rust_ident: #ty;
168        }
169    }
170}