use crate::interface::InterfaceGenerator;
use anyhow::{bail, Result};
use heck::*;
use indexmap::IndexSet;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::{self, Write as _};
use std::mem;
use std::str::FromStr;
use wit_bindgen_core::abi::{Bitcast, WasmType};
use wit_bindgen_core::{
name_package_module, uwrite, uwriteln, wit_parser::*, Files, InterfaceGenerator as _, Source,
Types, WorldGenerator,
};
mod bindgen;
mod interface;
struct InterfaceName {
remapped: bool,
path: String,
}
#[derive(Default)]
struct RustWasm {
types: Types,
src: Source,
opts: Opts,
import_modules: Vec<(String, Vec<String>)>,
export_modules: Vec<(String, Vec<String>)>,
skip: HashSet<String>,
interface_names: HashMap<InterfaceId, InterfaceName>,
interface_last_seen_as_import: HashMap<InterfaceId, bool>,
import_funcs_called: bool,
with_name_counter: usize,
generated_interfaces: HashSet<String>,
world: Option<WorldId>,
rt_module: IndexSet<RuntimeItem>,
export_macros: Vec<(String, String)>,
with: GenerationConfiguration,
}
#[derive(Default)]
struct GenerationConfiguration {
map: HashMap<String, InterfaceGeneration>,
generate_by_default: bool,
}
impl GenerationConfiguration {
fn get(&self, key: &str) -> Option<&InterfaceGeneration> {
self.map.get(key).or_else(|| {
self.generate_by_default
.then_some(&InterfaceGeneration::Generate)
})
}
fn insert(&mut self, name: String, generate: InterfaceGeneration) {
self.map.insert(name, generate);
}
fn iter(&self) -> impl Iterator<Item = (&String, &InterfaceGeneration)> {
self.map.iter()
}
}
enum InterfaceGeneration {
Remap(String),
Generate,
}
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
enum RuntimeItem {
AllocCrate,
StringType,
StdAllocModule,
VecType,
StringLift,
InvalidEnumDiscriminant,
CharLift,
BoolLift,
CabiDealloc,
RunCtorsOnce,
AsI32,
AsI64,
AsF32,
AsF64,
ResourceType,
BoxType,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ExportKey {
World,
Name(String),
}
#[cfg(feature = "clap")]
fn parse_with(s: &str) -> Result<(String, WithOption), String> {
let (k, v) = s.split_once('=').ok_or_else(|| {
format!("expected string of form `<key>=<value>[,<key>=<value>...]`; got `{s}`")
})?;
let v = match v {
"generate" => WithOption::Generate,
other => WithOption::Path(other.to_string()),
};
Ok((k.to_string(), v))
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "clap", derive(clap::Args))]
pub struct Opts {
#[cfg_attr(feature = "clap", arg(long))]
pub format: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub std_feature: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub raw_strings: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub skip: Vec<String>,
#[cfg_attr(feature = "clap", arg(long))]
pub stubs: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub export_prefix: Option<String>,
#[cfg_attr(feature = "clap", arg(long, default_value_t = Ownership::Owning))]
pub ownership: Ownership,
#[cfg_attr(feature = "clap", arg(long))]
pub runtime_path: Option<String>,
#[cfg_attr(feature = "clap", arg(long))]
pub bitflags_path: Option<String>,
#[cfg_attr(feature = "clap", arg(long = "additional_derive_attribute", short = 'd', default_values_t = Vec::<String>::new()))]
pub additional_derive_attributes: Vec<String>,
#[cfg_attr(feature = "clap", arg(long, value_parser = parse_with, value_delimiter = ','))]
pub with: Vec<(String, WithOption)>,
#[cfg_attr(feature = "clap", arg(long))]
pub generate_all: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub type_section_suffix: Option<String>,
#[cfg_attr(feature = "clap", arg(long))]
pub disable_run_ctors_once_workaround: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub default_bindings_module: Option<String>,
#[cfg_attr(feature = "clap", arg(long))]
pub export_macro_name: Option<String>,
#[cfg_attr(feature = "clap", arg(long))]
pub pub_export_macro: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub generate_unused_types: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub disable_custom_section_link_helpers: bool,
}
impl Opts {
pub fn build(self) -> Box<dyn WorldGenerator> {
let mut r = RustWasm::new();
r.skip = self.skip.iter().cloned().collect();
r.opts = self;
Box::new(r)
}
}
impl RustWasm {
fn new() -> RustWasm {
RustWasm::default()
}
fn interface<'a>(
&'a mut self,
identifier: Identifier<'a>,
wasm_import_module: Option<&'a str>,
resolve: &'a Resolve,
in_import: bool,
) -> InterfaceGenerator<'a> {
let mut sizes = SizeAlign::default();
sizes.fill(resolve);
InterfaceGenerator {
identifier,
wasm_import_module,
src: Source::default(),
in_import,
gen: self,
sizes,
resolve,
return_pointer_area_size: 0,
return_pointer_area_align: 0,
needs_runtime_module: false,
}
}
fn emit_modules(&mut self, modules: Vec<(String, Vec<String>)>) {
#[derive(Default)]
struct Module {
submodules: BTreeMap<String, Module>,
contents: Vec<String>,
}
let mut map = Module::default();
for (module, path) in modules {
let mut cur = &mut map;
for name in path[..path.len() - 1].iter() {
cur = cur
.submodules
.entry(name.clone())
.or_insert(Module::default());
}
cur.contents.push(module);
}
emit(&mut self.src, map);
fn emit(me: &mut Source, module: Module) {
for (name, submodule) in module.submodules {
uwriteln!(me, "#[allow(dead_code)]");
uwriteln!(me, "pub mod {name} {{");
emit(me, submodule);
uwriteln!(me, "}}");
}
for submodule in module.contents {
uwriteln!(me, "{submodule}");
}
}
}
fn runtime_path(&self) -> &str {
self.opts
.runtime_path
.as_deref()
.unwrap_or("wit_bindgen::rt")
}
fn bitflags_path(&self) -> String {
self.opts
.bitflags_path
.to_owned()
.unwrap_or(format!("{}::bitflags", self.runtime_path()))
}
fn name_interface(
&mut self,
resolve: &Resolve,
id: InterfaceId,
name: &WorldKey,
is_export: bool,
) -> Result<bool> {
let with_name = resolve.name_world_key(name);
let Some(remapping) = self.with.get(&with_name) else {
bail!(MissingWith(with_name));
};
self.generated_interfaces.insert(with_name);
let entry = match remapping {
InterfaceGeneration::Remap(remapped_path) => {
let name = format!("__with_name{}", self.with_name_counter);
self.with_name_counter += 1;
uwriteln!(self.src, "use {remapped_path} as {name};");
InterfaceName {
remapped: true,
path: name,
}
}
InterfaceGeneration::Generate => {
let path = compute_module_path(name, resolve, is_export).join("::");
InterfaceName {
remapped: false,
path,
}
}
};
let remapped = entry.remapped;
self.interface_names.insert(id, entry);
Ok(remapped)
}
fn finish_runtime_module(&mut self) {
if self.rt_module.is_empty() {
return;
}
self.src.push_str("mod _rt {\n");
let mut emitted = IndexSet::new();
while !self.rt_module.is_empty() {
for item in mem::take(&mut self.rt_module) {
if emitted.insert(item) {
self.emit_runtime_item(item);
}
}
}
self.src.push_str("}\n");
}
fn emit_runtime_item(&mut self, item: RuntimeItem) {
match item {
RuntimeItem::AllocCrate => {
uwriteln!(self.src, "extern crate alloc as alloc_crate;");
}
RuntimeItem::StdAllocModule => {
self.rt_module.insert(RuntimeItem::AllocCrate);
uwriteln!(self.src, "pub use alloc_crate::alloc;");
}
RuntimeItem::StringType => {
self.rt_module.insert(RuntimeItem::AllocCrate);
uwriteln!(self.src, "pub use alloc_crate::string::String;");
}
RuntimeItem::BoxType => {
self.rt_module.insert(RuntimeItem::AllocCrate);
uwriteln!(self.src, "pub use alloc_crate::boxed::Box;");
}
RuntimeItem::VecType => {
self.rt_module.insert(RuntimeItem::AllocCrate);
uwriteln!(self.src, "pub use alloc_crate::vec::Vec;");
}
RuntimeItem::CabiDealloc => {
self.rt_module.insert(RuntimeItem::StdAllocModule);
self.src.push_str(
"\
pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) {
if size == 0 {
return;
}
let layout = alloc::Layout::from_size_align_unchecked(size, align);
alloc::dealloc(ptr, layout);
}
",
);
}
RuntimeItem::StringLift => {
self.rt_module.insert(RuntimeItem::StringType);
self.src.push_str(
"\
pub unsafe fn string_lift(bytes: Vec<u8>) -> String {
if cfg!(debug_assertions) {
String::from_utf8(bytes).unwrap()
} else {
String::from_utf8_unchecked(bytes)
}
}
",
);
}
RuntimeItem::InvalidEnumDiscriminant => {
self.src.push_str(
"\
pub unsafe fn invalid_enum_discriminant<T>() -> T {
if cfg!(debug_assertions) {
panic!(\"invalid enum discriminant\")
} else {
core::hint::unreachable_unchecked()
}
}
",
);
}
RuntimeItem::CharLift => {
self.src.push_str(
"\
pub unsafe fn char_lift(val: u32) -> char {
if cfg!(debug_assertions) {
core::char::from_u32(val).unwrap()
} else {
core::char::from_u32_unchecked(val)
}
}
",
);
}
RuntimeItem::BoolLift => {
self.src.push_str(
"\
pub unsafe fn bool_lift(val: u8) -> bool {
if cfg!(debug_assertions) {
match val {
0 => false,
1 => true,
_ => panic!(\"invalid bool discriminant\"),
}
} else {
val != 0
}
}
",
);
}
RuntimeItem::RunCtorsOnce => {
let rt = self.runtime_path();
self.src.push_str(&format!(
r#"
#[cfg(target_arch = "wasm32")]
pub fn run_ctors_once() {{
{rt}::run_ctors_once();
}}
"#,
));
}
RuntimeItem::AsI32 => {
self.emit_runtime_as_trait(
"i32",
&["i32", "u32", "i16", "u16", "i8", "u8", "char", "usize"],
);
}
RuntimeItem::AsI64 => {
self.emit_runtime_as_trait("i64", &["i64", "u64"]);
}
RuntimeItem::AsF32 => {
self.emit_runtime_as_trait("f32", &["f32"]);
}
RuntimeItem::AsF64 => {
self.emit_runtime_as_trait("f64", &["f64"]);
}
RuntimeItem::ResourceType => {
self.src.push_str(
r#"
use core::fmt;
use core::marker;
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
/// A type which represents a component model resource, either imported or
/// exported into this component.
///
/// This is a low-level wrapper which handles the lifetime of the resource
/// (namely this has a destructor). The `T` provided defines the component model
/// intrinsics that this wrapper uses.
///
/// One of the chief purposes of this type is to provide `Deref` implementations
/// to access the underlying data when it is owned.
///
/// This type is primarily used in generated code for exported and imported
/// resources.
#[repr(transparent)]
pub struct Resource<T: WasmResource> {
// NB: This would ideally be `u32` but it is not. The fact that this has
// interior mutability is not exposed in the API of this type except for the
// `take_handle` method which is supposed to in theory be private.
//
// This represents, almost all the time, a valid handle value. When it's
// invalid it's stored as `u32::MAX`.
handle: AtomicU32,
_marker: marker::PhantomData<T>,
}
/// A trait which all wasm resources implement, namely providing the ability to
/// drop a resource.
///
/// This generally is implemented by generated code, not user-facing code.
#[allow(clippy::missing_safety_doc)]
pub unsafe trait WasmResource {
/// Invokes the `[resource-drop]...` intrinsic.
unsafe fn drop(handle: u32);
}
impl<T: WasmResource> Resource<T> {
#[doc(hidden)]
pub unsafe fn from_handle(handle: u32) -> Self {
debug_assert!(handle != u32::MAX);
Self {
handle: AtomicU32::new(handle),
_marker: marker::PhantomData,
}
}
/// Takes ownership of the handle owned by `resource`.
///
/// Note that this ideally would be `into_handle` taking `Resource<T>` by
/// ownership. The code generator does not enable that in all situations,
/// unfortunately, so this is provided instead.
///
/// Also note that `take_handle` is in theory only ever called on values
/// owned by a generated function. For example a generated function might
/// take `Resource<T>` as an argument but then call `take_handle` on a
/// reference to that argument. In that sense the dynamic nature of
/// `take_handle` should only be exposed internally to generated code, not
/// to user code.
#[doc(hidden)]
pub fn take_handle(resource: &Resource<T>) -> u32 {
resource.handle.swap(u32::MAX, Relaxed)
}
#[doc(hidden)]
pub fn handle(resource: &Resource<T>) -> u32 {
resource.handle.load(Relaxed)
}
}
impl<T: WasmResource> fmt::Debug for Resource<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Resource")
.field("handle", &self.handle)
.finish()
}
}
impl<T: WasmResource> Drop for Resource<T> {
fn drop(&mut self) {
unsafe {
match self.handle.load(Relaxed) {
// If this handle was "taken" then don't do anything in the
// destructor.
u32::MAX => {}
// ... but otherwise do actually destroy it with the imported
// component model intrinsic as defined through `T`.
other => T::drop(other),
}
}
}
}
"#,
);
}
}
}
fn emit_runtime_as_trait(&mut self, ty: &str, to_convert: &[&str]) {
let upcase = ty.to_uppercase();
self.src.push_str(&format!(
r#"
pub fn as_{ty}<T: As{upcase}>(t: T) -> {ty} {{
t.as_{ty}()
}}
pub trait As{upcase} {{
fn as_{ty}(self) -> {ty};
}}
impl<'a, T: Copy + As{upcase}> As{upcase} for &'a T {{
fn as_{ty}(self) -> {ty} {{
(*self).as_{ty}()
}}
}}
"#
));
for to_convert in to_convert {
self.src.push_str(&format!(
r#"
impl As{upcase} for {to_convert} {{
#[inline]
fn as_{ty}(self) -> {ty} {{
self as {ty}
}}
}}
"#
));
}
}
fn finish_export_macro(&mut self, resolve: &Resolve, world_id: WorldId) {
if self.export_macros.is_empty() {
return;
}
let world = &resolve.worlds[world_id];
let world_name = world.name.to_snake_case();
let default_bindings_module = self
.opts
.default_bindings_module
.clone()
.unwrap_or("self".to_string());
let (macro_export, use_vis) = if self.opts.pub_export_macro {
("#[macro_export]", "pub")
} else {
("", "pub(crate)")
};
let export_macro_name = self
.opts
.export_macro_name
.as_deref()
.unwrap_or("export")
.to_string();
uwriteln!(
self.src,
r#"
/// Generates `#[no_mangle]` functions to export the specified type as the
/// root implementation of all generated traits.
///
/// For more information see the documentation of `wit_bindgen::generate!`.
///
/// ```rust
/// # macro_rules! {export_macro_name} {{ ($($t:tt)*) => (); }}
/// # trait Guest {{}}
/// struct MyType;
///
/// impl Guest for MyType {{
/// // ...
/// }}
///
/// {export_macro_name}!(MyType);
/// ```
#[allow(unused_macros)]
#[doc(hidden)]
{macro_export}
macro_rules! __export_{world_name}_impl {{
($ty:ident) => ({default_bindings_module}::{export_macro_name}!($ty with_types_in {default_bindings_module}););
($ty:ident with_types_in $($path_to_types_root:tt)*) => ("#
);
for (name, path_to_types) in self.export_macros.iter() {
let mut path = "$($path_to_types_root)*".to_string();
if !path_to_types.is_empty() {
path.push_str("::");
path.push_str(path_to_types)
}
uwriteln!(self.src, "{path}::{name}!($ty with_types_in {path});");
}
if self.opts.pub_export_macro {
uwriteln!(self.src, "const _: () = {{");
self.emit_custom_section(resolve, world_id, "imports and exports", None);
uwriteln!(self.src, "}};");
}
uwriteln!(self.src, ")\n}}");
uwriteln!(
self.src,
"#[doc(inline)]\n\
{use_vis} use __export_{world_name}_impl as {export_macro_name};"
);
if self.opts.stubs {
uwriteln!(self.src, "export!(Stub);");
}
}
fn emit_custom_section(
&mut self,
resolve: &Resolve,
world_id: WorldId,
section_suffix: &str,
func_name: Option<&str>,
) {
self.src.push_str("\n#[cfg(target_arch = \"wasm32\")]\n");
let opts_suffix = self.opts.type_section_suffix.as_deref().unwrap_or("");
let world = &resolve.worlds[world_id];
let world_name = &world.name;
let pkg = &resolve.packages[world.package.unwrap()].name;
let version = env!("CARGO_PKG_VERSION");
self.src.push_str(&format!(
"#[link_section = \"component-type:wit-bindgen:{version}:\
{pkg}:{world_name}:{section_suffix}{opts_suffix}\"]\n"
));
let mut producers = wasm_metadata::Producers::empty();
producers.add(
"processed-by",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
);
let component_type = wit_component::metadata::encode(
resolve,
world_id,
wit_component::StringEncoding::UTF8,
Some(&producers),
)
.unwrap();
self.src.push_str("#[doc(hidden)]\n");
self.src.push_str(&format!(
"pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; {}] = *b\"\\\n",
component_type.len()
));
let old_indent = self.src.set_indent(0);
let mut line_length = 0;
let s = self.src.as_mut_string();
for byte in component_type.iter() {
if line_length >= 80 {
s.push_str("\\\n");
line_length = 0;
}
match byte {
b'\\' => {
s.push_str("\\\\");
line_length += 2;
}
b'"' => {
s.push_str("\\\"");
line_length += 2;
}
b if b.is_ascii_alphanumeric() || b.is_ascii_punctuation() => {
s.push(char::from(*byte));
line_length += 1;
}
0 => {
s.push_str("\\0");
line_length += 2;
}
_ => {
uwrite!(s, "\\x{:02x}", byte);
line_length += 4;
}
}
}
self.src.push_str("\";\n");
self.src.set_indent(old_indent);
if let Some(func_name) = func_name {
let rt = self.runtime_path().to_string();
uwriteln!(
self.src,
"
#[inline(never)]
#[doc(hidden)]
pub fn {func_name}() {{
{rt}::maybe_link_cabi_realloc();
}}
",
);
}
}
}
impl WorldGenerator for RustWasm {
fn preprocess(&mut self, resolve: &Resolve, world: WorldId) {
wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION"));
uwriteln!(self.src, "// Options used:");
if self.opts.std_feature {
uwriteln!(self.src, "// * std_feature");
}
if self.opts.raw_strings {
uwriteln!(self.src, "// * raw_strings");
}
if !self.opts.skip.is_empty() {
uwriteln!(self.src, "// * skip: {:?}", self.opts.skip);
}
if !matches!(self.opts.ownership, Ownership::Owning) {
uwriteln!(self.src, "// * ownership: {:?}", self.opts.ownership);
}
if !self.opts.additional_derive_attributes.is_empty() {
uwriteln!(
self.src,
"// * additional derives {:?}",
self.opts.additional_derive_attributes
);
}
for (k, v) in self.opts.with.iter() {
uwriteln!(self.src, "// * with {k:?} = {v}");
}
if let Some(default) = &self.opts.default_bindings_module {
uwriteln!(self.src, "// * default-bindings-module: {default:?}");
}
if self.opts.disable_run_ctors_once_workaround {
uwriteln!(self.src, "// * disable-run-ctors-once-workaround");
}
if let Some(s) = &self.opts.export_macro_name {
uwriteln!(self.src, "// * export-macro-name: {s}");
}
if self.opts.pub_export_macro {
uwriteln!(self.src, "// * pub-export-macro");
}
self.types.analyze(resolve);
self.world = Some(world);
let world = &resolve.worlds[world];
for (key, item) in world.imports.iter().chain(world.exports.iter()) {
if let WorldItem::Interface { id, .. } = item {
if resolve.interfaces[*id].package == world.package {
let name = resolve.name_world_key(key);
if self.with.get(&name).is_none() {
self.with.insert(name, InterfaceGeneration::Generate);
}
}
}
}
for (k, v) in self.opts.with.iter() {
self.with.insert(k.clone(), v.clone().into());
}
self.with.generate_by_default = self.opts.generate_all;
}
fn import_interface(
&mut self,
resolve: &Resolve,
name: &WorldKey,
id: InterfaceId,
_files: &mut Files,
) -> Result<()> {
self.interface_last_seen_as_import.insert(id, true);
let wasm_import_module = resolve.name_world_key(name);
let mut gen = self.interface(
Identifier::Interface(id, name),
Some(&wasm_import_module),
resolve,
true,
);
let (snake, module_path) = gen.start_append_submodule(name);
if gen.gen.name_interface(resolve, id, name, false)? {
return Ok(());
}
gen.types(id);
gen.generate_imports(resolve.interfaces[id].functions.values());
gen.finish_append_submodule(&snake, module_path);
Ok(())
}
fn import_funcs(
&mut self,
resolve: &Resolve,
world: WorldId,
funcs: &[(&str, &Function)],
_files: &mut Files,
) {
self.import_funcs_called = true;
let mut gen = self.interface(Identifier::World(world), Some("$root"), resolve, true);
gen.generate_imports(funcs.iter().map(|(_, func)| *func));
let src = gen.finish();
self.src.push_str(&src);
}
fn export_interface(
&mut self,
resolve: &Resolve,
name: &WorldKey,
id: InterfaceId,
_files: &mut Files,
) -> Result<()> {
self.interface_last_seen_as_import.insert(id, false);
let mut gen = self.interface(Identifier::Interface(id, name), None, resolve, false);
let (snake, module_path) = gen.start_append_submodule(name);
if gen.gen.name_interface(resolve, id, name, true)? {
return Ok(());
}
gen.types(id);
let macro_name =
gen.generate_exports(Some((id, name)), resolve.interfaces[id].functions.values())?;
gen.finish_append_submodule(&snake, module_path);
self.export_macros
.push((macro_name, self.interface_names[&id].path.clone()));
if self.opts.stubs {
let world_id = self.world.unwrap();
let mut gen = self.interface(Identifier::World(world_id), None, resolve, false);
gen.generate_stub(Some((id, name)), resolve.interfaces[id].functions.values());
let stub = gen.finish();
self.src.push_str(&stub);
}
Ok(())
}
fn export_funcs(
&mut self,
resolve: &Resolve,
world: WorldId,
funcs: &[(&str, &Function)],
_files: &mut Files,
) -> Result<()> {
let mut gen = self.interface(Identifier::World(world), None, resolve, false);
let macro_name = gen.generate_exports(None, funcs.iter().map(|f| f.1))?;
let src = gen.finish();
self.src.push_str(&src);
self.export_macros.push((macro_name, String::new()));
if self.opts.stubs {
let mut gen = self.interface(Identifier::World(world), None, resolve, false);
gen.generate_stub(None, funcs.iter().map(|f| f.1));
let stub = gen.finish();
self.src.push_str(&stub);
}
Ok(())
}
fn import_types(
&mut self,
resolve: &Resolve,
world: WorldId,
types: &[(&str, TypeId)],
_files: &mut Files,
) {
let mut gen = self.interface(Identifier::World(world), Some("$root"), resolve, true);
for (name, ty) in types {
gen.define_type(name, *ty);
}
let src = gen.finish();
self.src.push_str(&src);
}
fn finish_imports(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) {
if !self.import_funcs_called {
self.import_funcs(resolve, world, &[], files);
}
}
fn finish(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) -> Result<()> {
let name = &resolve.worlds[world].name;
let imports = mem::take(&mut self.import_modules);
self.emit_modules(imports);
let exports = mem::take(&mut self.export_modules);
self.emit_modules(exports);
self.finish_runtime_module();
self.finish_export_macro(resolve, world);
let mut resolve_copy;
let (resolve_to_encode, world_to_encode) = if self.opts.pub_export_macro {
resolve_copy = resolve.clone();
let world_copy = resolve_copy.worlds.alloc(World {
exports: Default::default(),
name: format!("{name}-with-all-of-its-exports-removed"),
..resolve.worlds[world].clone()
});
(&resolve_copy, world_copy)
} else {
(resolve, world)
};
self.emit_custom_section(
resolve_to_encode,
world_to_encode,
"encoded world",
if self.opts.disable_custom_section_link_helpers {
None
} else {
Some("__link_custom_section_describing_imports")
},
);
if self.opts.stubs {
self.src.push_str("\n#[derive(Debug)]\npub struct Stub;\n");
}
let mut src = mem::take(&mut self.src);
if self.opts.format {
let syntax_tree = syn::parse_file(src.as_str()).unwrap();
*src.as_mut_string() = prettyplease::unparse(&syntax_tree);
}
let module_name = name.to_snake_case();
files.push(&format!("{module_name}.rs"), src.as_bytes());
let remapped_keys = self
.with
.iter()
.map(|(k, _)| k)
.cloned()
.collect::<HashSet<String>>();
let mut unused_keys = remapped_keys
.difference(&self.generated_interfaces)
.collect::<Vec<&String>>();
unused_keys.sort();
if !unused_keys.is_empty() {
bail!("unused remappings provided via `with`: {unused_keys:?}");
}
Ok(())
}
}
fn compute_module_path(name: &WorldKey, resolve: &Resolve, is_export: bool) -> Vec<String> {
let mut path = Vec::new();
if is_export {
path.push("exports".to_string());
}
match name {
WorldKey::Name(name) => {
path.push(to_rust_ident(name));
}
WorldKey::Interface(id) => {
let iface = &resolve.interfaces[*id];
let pkg = iface.package.unwrap();
let pkgname = resolve.packages[pkg].name.clone();
path.push(to_rust_ident(&pkgname.namespace));
path.push(name_package_module(resolve, pkg));
path.push(to_rust_ident(iface.name.as_ref().unwrap()));
}
}
path
}
enum Identifier<'a> {
World(WorldId),
Interface(InterfaceId, &'a WorldKey),
}
fn group_by_resource<'a>(
funcs: impl Iterator<Item = &'a Function>,
) -> BTreeMap<Option<TypeId>, Vec<&'a Function>> {
let mut by_resource = BTreeMap::<_, Vec<_>>::new();
for func in funcs {
match &func.kind {
FunctionKind::Freestanding => by_resource.entry(None).or_default().push(func),
FunctionKind::Method(ty) | FunctionKind::Static(ty) | FunctionKind::Constructor(ty) => {
by_resource.entry(Some(*ty)).or_default().push(func);
}
}
}
by_resource
}
#[derive(Default, Debug, Clone, Copy)]
pub enum Ownership {
#[default]
Owning,
Borrowing {
duplicate_if_necessary: bool,
},
}
impl FromStr for Ownership {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"owning" => Ok(Self::Owning),
"borrowing" => Ok(Self::Borrowing {
duplicate_if_necessary: false,
}),
"borrowing-duplicate-if-necessary" => Ok(Self::Borrowing {
duplicate_if_necessary: true,
}),
_ => Err(format!(
"unrecognized ownership: `{s}`; \
expected `owning`, `borrowing`, or `borrowing-duplicate-if-necessary`"
)),
}
}
}
impl fmt::Display for Ownership {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Ownership::Owning => "owning",
Ownership::Borrowing {
duplicate_if_necessary: false,
} => "borrowing",
Ownership::Borrowing {
duplicate_if_necessary: true,
} => "borrowing-duplicate-if-necessary",
})
}
}
#[derive(Debug, Clone)]
pub enum WithOption {
Path(String),
Generate,
}
impl std::fmt::Display for WithOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WithOption::Path(p) => f.write_fmt(format_args!("\"{p}\"")),
WithOption::Generate => f.write_str("generate"),
}
}
}
impl From<WithOption> for InterfaceGeneration {
fn from(opt: WithOption) -> Self {
match opt {
WithOption::Path(p) => InterfaceGeneration::Remap(p),
WithOption::Generate => InterfaceGeneration::Generate,
}
}
}
#[derive(Default)]
struct FnSig {
async_: bool,
unsafe_: bool,
private: bool,
use_item_name: bool,
generics: Option<String>,
self_arg: Option<String>,
self_is_first_param: bool,
}
pub fn to_rust_ident(name: &str) -> String {
match name {
"as" => "as_".into(),
"break" => "break_".into(),
"const" => "const_".into(),
"continue" => "continue_".into(),
"crate" => "crate_".into(),
"else" => "else_".into(),
"enum" => "enum_".into(),
"extern" => "extern_".into(),
"false" => "false_".into(),
"fn" => "fn_".into(),
"for" => "for_".into(),
"if" => "if_".into(),
"impl" => "impl_".into(),
"in" => "in_".into(),
"let" => "let_".into(),
"loop" => "loop_".into(),
"match" => "match_".into(),
"mod" => "mod_".into(),
"move" => "move_".into(),
"mut" => "mut_".into(),
"pub" => "pub_".into(),
"ref" => "ref_".into(),
"return" => "return_".into(),
"self" => "self_".into(),
"static" => "static_".into(),
"struct" => "struct_".into(),
"super" => "super_".into(),
"trait" => "trait_".into(),
"true" => "true_".into(),
"type" => "type_".into(),
"unsafe" => "unsafe_".into(),
"use" => "use_".into(),
"where" => "where_".into(),
"while" => "while_".into(),
"async" => "async_".into(),
"await" => "await_".into(),
"dyn" => "dyn_".into(),
"abstract" => "abstract_".into(),
"become" => "become_".into(),
"box" => "box_".into(),
"do" => "do_".into(),
"final" => "final_".into(),
"macro" => "macro_".into(),
"override" => "override_".into(),
"priv" => "priv_".into(),
"typeof" => "typeof_".into(),
"unsized" => "unsized_".into(),
"virtual" => "virtual_".into(),
"yield" => "yield_".into(),
"try" => "try_".into(),
s => s.to_snake_case(),
}
}
fn to_upper_camel_case(name: &str) -> String {
match name {
"guest" => "Guest_".to_string(),
s => s.to_upper_camel_case(),
}
}
fn wasm_type(ty: WasmType) -> &'static str {
match ty {
WasmType::I32 => "i32",
WasmType::I64 => "i64",
WasmType::F32 => "f32",
WasmType::F64 => "f64",
WasmType::Pointer => "*mut u8",
WasmType::Length => "usize",
WasmType::PointerOrI64 => "::core::mem::MaybeUninit::<u64>",
}
}
fn int_repr(repr: Int) -> &'static str {
match repr {
Int::U8 => "u8",
Int::U16 => "u16",
Int::U32 => "u32",
Int::U64 => "u64",
}
}
fn bitcast(casts: &[Bitcast], operands: &[String], results: &mut Vec<String>) {
for (cast, operand) in casts.iter().zip(operands) {
results.push(perform_cast(operand, cast));
}
}
fn perform_cast(operand: &str, cast: &Bitcast) -> String {
match cast {
Bitcast::None => operand.to_owned(),
Bitcast::I32ToI64 => format!("i64::from({})", operand),
Bitcast::F32ToI32 => format!("({}).to_bits() as i32", operand),
Bitcast::F64ToI64 => format!("({}).to_bits() as i64", operand),
Bitcast::I64ToI32 => format!("{} as i32", operand),
Bitcast::I32ToF32 => format!("f32::from_bits({} as u32)", operand),
Bitcast::I64ToF64 => format!("f64::from_bits({} as u64)", operand),
Bitcast::F32ToI64 => format!("i64::from(({}).to_bits())", operand),
Bitcast::I64ToF32 => format!("f32::from_bits({} as u32)", operand),
Bitcast::I64ToP64 => format!("::core::mem::MaybeUninit::new({} as u64)", operand),
Bitcast::P64ToI64 => format!("{}.assume_init() as i64", operand),
Bitcast::PToP64 => {
format!(
"{{
let mut t = ::core::mem::MaybeUninit::<u64>::uninit();
t.as_mut_ptr().cast::<*mut u8>().write({});
t
}}",
operand
)
}
Bitcast::P64ToP => {
format!("{}.as_ptr().cast::<*mut u8>().read()", operand)
}
Bitcast::I32ToP | Bitcast::LToP => {
format!("{} as *mut u8", operand)
}
Bitcast::PToI32 | Bitcast::LToI32 => {
format!("{} as i32", operand)
}
Bitcast::I32ToL | Bitcast::I64ToL | Bitcast::PToL => {
format!("{} as usize", operand)
}
Bitcast::LToI64 => {
format!("{} as i64", operand)
}
Bitcast::Sequence(sequence) => {
let [first, second] = &**sequence;
perform_cast(&perform_cast(operand, first), second)
}
}
}
enum RustFlagsRepr {
U8,
U16,
U32,
U64,
U128,
}
impl RustFlagsRepr {
fn new(f: &Flags) -> RustFlagsRepr {
match f.repr() {
FlagsRepr::U8 => RustFlagsRepr::U8,
FlagsRepr::U16 => RustFlagsRepr::U16,
FlagsRepr::U32(1) => RustFlagsRepr::U32,
FlagsRepr::U32(2) => RustFlagsRepr::U64,
FlagsRepr::U32(3 | 4) => RustFlagsRepr::U128,
FlagsRepr::U32(n) => panic!("unsupported number of flags: {}", n * 32),
}
}
}
impl fmt::Display for RustFlagsRepr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RustFlagsRepr::U8 => "u8".fmt(f),
RustFlagsRepr::U16 => "u16".fmt(f),
RustFlagsRepr::U32 => "u32".fmt(f),
RustFlagsRepr::U64 => "u64".fmt(f),
RustFlagsRepr::U128 => "u128".fmt(f),
}
}
}
#[derive(Debug, Clone)]
pub struct MissingWith(pub String);
impl fmt::Display for MissingWith {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "missing `with` mapping for the key `{}`", self.0)
}
}
impl std::error::Error for MissingWith {}