Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support resource maps in component::bindgen! #7069

Merged
merged 2 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions crates/component-macro/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,85 @@ macro_rules! gentest {
}

component_macro_test_helpers::foreach!(gentest);

mod with_key_and_resources {
use anyhow::Result;
use wasmtime::component::Resource;

wasmtime::component::bindgen!({
inline: "
package demo:pkg

interface bar {
resource a
resource b
}

world foo {
resource a
resource b

import foo: interface {
resource a
resource b
}

import bar
}
",
with: {
"a": MyA,
"b": MyA,
"foo/a": MyA,
"foo/b": MyA,
"demo:pkg/bar/a": MyA,
"demo:pkg/bar/b": MyA,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesnt look like the foo/foo/a cases are covered here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I don't think that's actually possible via WIT right now since resources are either top-level imports, in an anonymous import, or in an interface which these three cases map to

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what exercises the a from line 46 and b from line 47?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's foo/a and foo/b. The name of the world being "foo" is probably confusing here since the name of the world isn't used for these strings at all. The syntax here is definitely a bit made up by me and isn't exactly specified in WIT at all or anything like that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it! thank you.

},
});

pub struct MyA;

struct MyComponent;

impl FooImports for MyComponent {}

impl HostA for MyComponent {
fn drop(&mut self, _: Resource<MyA>) -> Result<()> {
loop {}
}
}

impl HostB for MyComponent {
fn drop(&mut self, _: Resource<MyA>) -> Result<()> {
loop {}
}
}

impl foo::Host for MyComponent {}

impl foo::HostA for MyComponent {
fn drop(&mut self, _: Resource<MyA>) -> Result<()> {
loop {}
}
}

impl foo::HostB for MyComponent {
fn drop(&mut self, _: Resource<MyA>) -> Result<()> {
loop {}
}
}

impl demo::pkg::bar::Host for MyComponent {}

impl demo::pkg::bar::HostA for MyComponent {
fn drop(&mut self, _: Resource<MyA>) -> Result<()> {
loop {}
}
}

impl demo::pkg::bar::HostB for MyComponent {
fn drop(&mut self, _: Resource<MyA>) -> Result<()> {
loop {}
}
}
}
18 changes: 11 additions & 7 deletions crates/wasmtime/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,20 @@ pub(crate) use self::store::ComponentStoreData;
/// // Restrict the code generated to what's needed for the interface
/// // imports in the inlined WIT document fragment.
/// interfaces: "
/// import package.foo
/// import wasi:cli/command
/// ",
///
/// // Remap interface names to module names, imported from elsewhere.
/// // Using this option will prevent any code from being generated
/// // for the names mentioned in the mapping, assuming instead that the
/// // names mentioned come from a previous use of the `bindgen!` macro
/// // with `only_interfaces: true`.
/// // Remap imported interfaces or resources to types defined in Rust
/// // elsewhere. Using this option will prevent any code from being
/// // generated for interfaces mentioned here. Resources named here will
/// // not have a type generated to represent the resource.
/// //
/// // Interfaces mapped with this option should be previously generated
/// // with an invocation of this macro. Resources need to be mapped to a
/// // Rust type name.
/// with: {
/// "a": somewhere::else::a,
/// "wasi:random/random": some::other::wasi::random::random,
/// "wasi:filesystem/types/descriptor": MyDescriptorType,
/// },
/// });
/// ```
Expand Down
59 changes: 39 additions & 20 deletions crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,16 +912,28 @@ impl<'a> InterfaceGenerator<'a> {
self.assert_type(id, &name);
}

fn type_resource(&mut self, id: TypeId, _name: &str, resource: &TypeDef, docs: &Docs) {
let camel = resource
.name
.as_ref()
.expect("resources are required to be named")
.to_upper_camel_case();
fn type_resource(&mut self, id: TypeId, name: &str, resource: &TypeDef, docs: &Docs) {
let camel = name.to_upper_camel_case();

if self.types_imported() {
self.rustdoc(docs);
uwriteln!(self.src, "pub enum {camel} {{}}");

let with_key = match self.current_interface {
Some((_, key, _)) => format!("{}/{name}", self.resolve.name_world_key(key)),
None => name.to_string(),
};
match self.gen.opts.with.get(&with_key) {
Some(path) => {
uwriteln!(
self.src,
"pub use {}{path} as {camel};",
self.path_to_root()
);
}
None => {
uwriteln!(self.src, "pub enum {camel} {{}}");
}
}

if self.gen.opts.async_.maybe_async() {
uwriteln!(self.src, "#[wasmtime::component::__internal::async_trait]")
Expand Down Expand Up @@ -1913,6 +1925,24 @@ impl<'a> InterfaceGenerator<'a> {
self.push_str("\n");
}
}

fn path_to_root(&self) -> String {
let mut path_to_root = String::new();
if let Some((_, key, is_export)) = self.current_interface {
match key {
WorldKey::Name(_) => {
path_to_root.push_str("super::");
}
WorldKey::Interface(_) => {
path_to_root.push_str("super::super::super::");
}
}
if is_export {
path_to_root.push_str("super::");
}
}
path_to_root
}
}

impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
Expand All @@ -1925,23 +1955,12 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
}

fn path_to_interface(&self, interface: InterfaceId) -> Option<String> {
let mut path_to_root = String::new();
if let Some((cur, key, is_export)) = self.current_interface {
if let Some((cur, _, _)) = self.current_interface {
if cur == interface {
return None;
}
match key {
WorldKey::Name(_) => {
path_to_root.push_str("super::");
}
WorldKey::Interface(_) => {
path_to_root.push_str("super::super::super::");
}
}
if is_export {
path_to_root.push_str("super::");
}
}
let mut path_to_root = self.path_to_root();
let InterfaceName { path, .. } = &self.gen.interface_names[&interface];
path_to_root.push_str(path);
Some(path_to_root)
Expand Down