use std::collections::HashSet;
use anyhow::{Context as _, Result, anyhow, bail, ensure};
use ts_bindgen::ts_bindgen;
use wasmtime_environ::component::{
CanonicalOptions, ComponentTypesBuilder, Export, StaticModuleIndex,
};
use wasmtime_environ::wasmparser::WasmFeatures;
use wasmtime_environ::{PrimaryMap, ScopeVec, Tunables};
use wit_bindgen_core::wit_parser::Function;
use wit_component::DecodedWasm;
use wit_parser::{Package, Resolve, Stability, Type, TypeDefKind, TypeId, WorldId};
mod core;
mod files;
mod transpile_bindgen;
mod ts_bindgen;
pub mod esm_bindgen;
pub mod function_bindgen;
pub mod names;
pub mod source;
pub mod intrinsics;
use intrinsics::Intrinsic;
use transpile_bindgen::transpile_bindgen;
pub use transpile_bindgen::{AsyncMode, BindingsMode, InstantiationMode, TranspileOpts};
#[macro_export]
macro_rules! uwrite {
($dst:expr, $($arg:tt)*) => {
write!($dst, $($arg)*).unwrap()
};
}
#[macro_export]
macro_rules! uwriteln {
($dst:expr, $($arg:tt)*) => {
writeln!($dst, $($arg)*).unwrap()
};
}
pub struct Transpiled {
pub files: Vec<(String, Vec<u8>)>,
pub imports: Vec<String>,
pub exports: Vec<(String, Export)>,
}
pub struct ComponentInfo {
pub imports: Vec<String>,
pub exports: Vec<(String, wasmtime_environ::component::Export)>,
}
pub fn generate_types(
name: &str,
resolve: Resolve,
world_id: WorldId,
opts: TranspileOpts,
) -> Result<Vec<(String, Vec<u8>)>> {
let mut files = files::Files::default();
ts_bindgen(name, &resolve, world_id, &opts, &mut files)
.context("failed to generate Typescript bindings")?;
let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
for (name, source) in files.iter() {
files_out.push((name.to_string(), source.to_vec()));
}
Ok(files_out)
}
#[cfg(feature = "transpile-bindgen")]
pub fn transpile(component: &[u8], opts: TranspileOpts) -> Result<Transpiled> {
use wasmtime_environ::component::{Component, Translator};
let name = opts.name.clone();
let mut files = files::Files::default();
let decoded = wit_component::decode(component)
.context("failed to extract interface information from component")?;
let (resolve, world_id) = match decoded {
DecodedWasm::WitPackage(_, _) => bail!("unexpected wit package as input"),
DecodedWasm::Component(resolve, world_id) => (resolve, world_id),
};
let scope = ScopeVec::new();
let tunables = Tunables::default_u32();
let mut validator = wasmtime_environ::wasmparser::Validator::new_with_features(
WasmFeatures::WASM3
| WasmFeatures::WIDE_ARITHMETIC
| WasmFeatures::COMPONENT_MODEL
| WasmFeatures::CM_ASYNC
| WasmFeatures::CM_ASYNC_BUILTINS
| WasmFeatures::CM_ASYNC_STACKFUL
| WasmFeatures::CM_ERROR_CONTEXT
| WasmFeatures::CM_FIXED_LENGTH_LISTS,
);
let mut types = ComponentTypesBuilder::new(&validator);
let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope)
.translate(component)
.map_err(|e| anyhow!(e).context("failed to translate component"))?;
let modules: PrimaryMap<StaticModuleIndex, core::Translation<'_>> = modules
.into_iter()
.map(|(_i, module)| core::Translation::new(module, opts.multi_memory))
.collect::<Result<_>>()?;
let wasmtime_component = Component::default();
let types = types.finish(&wasmtime_component);
for (i, module) in modules.iter() {
files.push(&core_file_name(&name, i.as_u32()), module.wasm());
}
if !opts.no_typescript {
ts_bindgen(&name, &resolve, world_id, &opts, &mut files)
.context("failed to generate Typescript bindings")?;
}
let (imports, exports) = transpile_bindgen(
&name, &component, &modules, &types.0, &resolve, world_id, opts, &mut files,
);
let mut files_out: Vec<(String, Vec<u8>)> = Vec::new();
for (name, source) in files.iter() {
files_out.push((name.to_string(), source.to_vec()));
}
Ok(Transpiled {
files: files_out,
imports,
exports,
})
}
fn core_file_name(name: &str, idx: u32) -> String {
let i_str = if idx == 0 {
String::from("")
} else {
(idx + 1).to_string()
};
format!("{name}.core{i_str}.wasm")
}
pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId {
loop {
match &resolve.types[id].kind {
TypeDefKind::Type(Type::Id(that_id)) => id = *that_id,
_ => break id,
}
}
}
fn feature_gate_allowed(
resolve: &Resolve,
package: &Package,
stability: &Stability,
item_name: &str,
) -> Result<bool> {
Ok(match stability {
Stability::Unknown => true,
Stability::Stable { since, .. } => {
let Some(package_version) = package.name.version.as_ref() else {
return Ok(true);
};
ensure!(
package_version >= since,
"feature gate on [{item_name}] refers to an unreleased (future) package version [{since}] (current package version is [{package_version}])"
);
true
}
Stability::Unstable {
feature,
deprecated: _,
} => {
resolve.all_features || resolve.features.contains(feature)
}
})
}
pub fn get_thrown_type(
resolve: &Resolve,
return_type: Option<Type>,
) -> Option<(Option<&Type>, Option<&Type>)> {
match return_type {
None => None,
Some(Type::Id(id)) => match &resolve.types[id].kind {
TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())),
_ => None,
},
_ => None,
}
}
pub(crate) fn is_async_fn(func: &Function, canon_opts: &CanonicalOptions) -> bool {
if canon_opts.async_ {
return true;
}
func.kind.is_async()
}
enum FunctionIdentifier<'a> {
Fn(&'a Function),
CanonFnName(&'a str),
}
pub(crate) fn requires_async_porcelain(
func: FunctionIdentifier<'_>,
id: &str,
async_funcs: &HashSet<String>,
) -> bool {
let name = match func {
FunctionIdentifier::Fn(func) => func.name.as_str(),
FunctionIdentifier::CanonFnName(name) => name,
}
.trim_start_matches("[async]");
if async_funcs.contains(name) {
return true;
}
let qualified_name = format!("{id}#{name}");
if async_funcs.contains(&qualified_name) {
return true;
}
if let Some(pos) = id.find('@') {
let namespace = &id[..pos];
let namespaced_name = format!("{namespace}#{name}");
if async_funcs.contains(&namespaced_name) {
return true;
}
}
false
}
trait ManagesIntrinsics {
fn add_intrinsic(&mut self, intrinsic: Intrinsic);
}