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