1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2use std::collections::HashSet;
3
4use anyhow::{bail, ensure, Context, Result};
5use ts_bindgen::ts_bindgen;
6use wasmtime_environ::component::{
7 CanonicalOptions, ComponentTypesBuilder, Export, StaticModuleIndex,
8};
9use wasmtime_environ::wasmparser::WasmFeatures;
10use wasmtime_environ::{PrimaryMap, ScopeVec, Tunables};
11use wit_bindgen_core::wit_parser::{Function, FunctionKind};
12use wit_component::DecodedWasm;
13use wit_parser::{Package, Resolve, Stability, Type, TypeDefKind, TypeId, WorldId};
14
15mod core;
16mod files;
17mod transpile_bindgen;
18mod ts_bindgen;
19
20pub mod esm_bindgen;
21pub mod function_bindgen;
22pub mod intrinsics;
23pub mod names;
24pub mod source;
25
26use transpile_bindgen::transpile_bindgen;
27pub use transpile_bindgen::{AsyncMode, BindingsMode, InstantiationMode, TranspileOpts};
28
29#[macro_export]
36macro_rules! uwrite {
37 ($dst:expr, $($arg:tt)*) => {
38 write!($dst, $($arg)*).unwrap()
39 };
40}
41
42#[macro_export]
49macro_rules! uwriteln {
50 ($dst:expr, $($arg:tt)*) => {
51 writeln!($dst, $($arg)*).unwrap()
52 };
53}
54
55pub struct Transpiled {
56 pub files: Vec<(String, Vec<u8>)>,
57 pub imports: Vec<String>,
58 pub exports: Vec<(String, Export)>,
59}
60
61pub struct ComponentInfo {
62 pub imports: Vec<String>,
63 pub exports: Vec<(String, wasmtime_environ::component::Export)>,
64}
65
66pub fn generate_types(
67 name: &str,
68 resolve: Resolve,
69 world_id: WorldId,
70 opts: TranspileOpts,
71) -> Result<Vec<(String, Vec<u8>)>> {
72 let mut files = files::Files::default();
73
74 ts_bindgen(name, &resolve, world_id, &opts, &mut files)
75 .context("failed to generate Typescript bindings")?;
76
77 let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
78 for (name, source) in files.iter() {
79 files_out.push((name.to_string(), source.to_vec()));
80 }
81 Ok(files_out)
82}
83
84#[cfg(feature = "transpile-bindgen")]
87pub fn transpile(component: &[u8], opts: TranspileOpts) -> Result<Transpiled> {
88 use wasmtime_environ::component::{Component, Translator};
89
90 let name = opts.name.clone();
91 let mut files = files::Files::default();
92
93 let decoded = wit_component::decode(component)
100 .context("failed to extract interface information from component")?;
101
102 let (resolve, world_id) = match decoded {
103 DecodedWasm::WitPackage(_, _) => bail!("unexpected wit package as input"),
104 DecodedWasm::Component(resolve, world_id) => (resolve, world_id),
105 };
106
107 let scope = ScopeVec::new();
122 let tunables = Tunables::default_u32();
123
124 let mut validator = wasmtime_environ::wasmparser::Validator::new_with_features(
130 WasmFeatures::WASM2
131 | WasmFeatures::COMPONENT_MODEL
132 | WasmFeatures::CM_ASYNC
133 | WasmFeatures::CM_ASYNC_BUILTINS
134 | WasmFeatures::CM_ERROR_CONTEXT
135 | WasmFeatures::MEMORY64
136 | WasmFeatures::MULTI_MEMORY,
137 );
138
139 let mut types = ComponentTypesBuilder::new(&validator);
140
141 let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope)
142 .translate(component)
143 .context("failed to translate component")?;
144
145 let modules: PrimaryMap<StaticModuleIndex, core::Translation<'_>> = modules
146 .into_iter()
147 .map(|(_i, module)| core::Translation::new(module, opts.multi_memory))
148 .collect::<Result<_>>()?;
149
150 let wasmtime_component = Component::default();
151 let types = types.finish(&wasmtime_component);
152
153 for (i, module) in modules.iter() {
156 files.push(&core_file_name(&name, i.as_u32()), module.wasm());
157 }
158
159 if !opts.no_typescript {
160 ts_bindgen(&name, &resolve, world_id, &opts, &mut files)
161 .context("failed to generate Typescript bindings")?;
162 }
163
164 let (imports, exports) = transpile_bindgen(
165 &name, &component, &modules, &types.0, &resolve, world_id, opts, &mut files,
166 );
167
168 let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
169 for (name, source) in files.iter() {
170 files_out.push((name.to_string(), source.to_vec()));
171 }
172 Ok(Transpiled {
173 files: files_out,
174 imports,
175 exports,
176 })
177}
178
179fn core_file_name(name: &str, idx: u32) -> String {
180 let i_str = if idx == 0 {
181 String::from("")
182 } else {
183 (idx + 1).to_string()
184 };
185 format!("{name}.core{i_str}.wasm")
186}
187
188pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId {
189 loop {
190 match &resolve.types[id].kind {
191 TypeDefKind::Type(Type::Id(that_id)) => id = *that_id,
192 _ => break id,
193 }
194 }
195}
196
197fn feature_gate_allowed(
200 resolve: &Resolve,
201 package: &Package,
202 stability: &Stability,
203 item_name: &str,
204) -> Result<bool> {
205 Ok(match stability {
206 Stability::Unknown => true,
207 Stability::Stable { since, .. } => {
208 let Some(package_version) = package.name.version.as_ref() else {
209 return Ok(true);
212 };
213
214 ensure!(
215 package_version >= since,
216 "feature gate on [{item_name}] refers to an unreleased (future) package version [{since}] (current package version is [{package_version}])"
217 );
218
219 true
221 }
222 Stability::Unstable {
223 feature,
224 deprecated: _,
225 } => {
226 resolve.all_features || resolve.features.contains(feature)
229 }
230 })
231}
232
233pub fn get_thrown_type(
235 resolve: &Resolve,
236 return_type: Option<Type>,
237) -> Option<(Option<&Type>, Option<&Type>)> {
238 match return_type {
239 None => None,
240 Some(Type::Id(id)) => match &resolve.types[id].kind {
241 TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())),
242 _ => None,
243 },
244 _ => None,
245 }
246}
247
248pub(crate) fn is_guest_async_lifted_fn(func: &Function, canon_opts: &CanonicalOptions) -> bool {
257 if canon_opts.async_ {
258 return true;
259 }
260 matches!(
261 func.kind,
262 FunctionKind::AsyncMethod(_)
263 | FunctionKind::AsyncStatic(_)
264 | FunctionKind::AsyncFreestanding
265 )
266}
267
268enum FunctionIdentifier<'a> {
270 Fn(&'a Function),
271 CanonFnName(&'a str),
272}
273
274pub(crate) fn requires_async_porcelain(
290 func: FunctionIdentifier<'_>,
291 id: &str,
292 async_funcs: &HashSet<String>,
293) -> bool {
294 let name = match func {
295 FunctionIdentifier::Fn(func) => func.name.as_str(),
296 FunctionIdentifier::CanonFnName(name) => name,
297 };
298
299 if async_funcs.contains(name) {
300 return true;
301 }
302
303 let qualified_name = format!("{id}#{name}");
304 if async_funcs.contains(&qualified_name) {
305 return true;
306 }
307
308 if let Some(pos) = id.find('@') {
309 let namespace = &id[..pos];
310 let namespaced_name = format!("{namespace}#{name}");
311
312 if async_funcs.contains(&namespaced_name) {
313 return true;
314 }
315 }
316 false
317}