Skip to content
Open
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
11 changes: 11 additions & 0 deletions crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl Parse for Config {
opts.only_interfaces = true;
}
Opt::With(val) => opts.with.extend(val),
Opt::NamedImports(val) => opts.named_imports.extend(val),
Opt::AdditionalDerives(paths) => {
opts.additional_derive_attributes = paths
.into_iter()
Expand Down Expand Up @@ -252,6 +253,7 @@ mod kw {
syn::custom_keyword!(ownership);
syn::custom_keyword!(interfaces);
syn::custom_keyword!(with);
syn::custom_keyword!(named_imports);
syn::custom_keyword!(except_imports);
syn::custom_keyword!(only_imports);
syn::custom_keyword!(additional_derives);
Expand All @@ -278,6 +280,7 @@ enum Opt {
Ownership(Ownership),
Interfaces(syn::LitStr),
With(HashMap<String, String>),
NamedImports(HashMap<String, String>),
AdditionalDerives(Vec<syn::Path>),
Stringify(bool),
SkipMutForwardingImpls(bool),
Expand Down Expand Up @@ -383,6 +386,14 @@ impl Parse for Opt {
let fields: Punctuated<(String, String), Token![,]> =
contents.parse_terminated(with_field_parse, Token![,])?;
Ok(Opt::With(HashMap::from_iter(fields)))
} else if l.peek(kw::named_imports) {
input.parse::<kw::named_imports>()?;
input.parse::<Token![:]>()?;
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<(String, String), Token![,]> =
contents.parse_terminated(with_field_parse, Token![,])?;
Ok(Opt::NamedImports(HashMap::from_iter(fields)))
} else if l.peek(kw::additional_derives) {
input.parse::<kw::additional_derives>()?;
input.parse::<Token![:]>()?;
Expand Down
65 changes: 65 additions & 0 deletions crates/component-macro/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,3 +820,68 @@ mod anyhow_with_custom_error {

struct MyCustomError;
}

mod named_imports {

mod sync {
wasmtime::component::bindgen!({
inline: "
package foo:foo;

interface handler {
handle: func(req: u32) -> u32;
ping: func();
}

world the-world {
import handler;
}
",
named_imports: {
"foo:foo/handler": String,
},
});

struct MyHost;

// The normal trait is generated as usual...
impl foo::foo::handler::Host for MyHost {
fn handle(&mut self, req: u32) -> u32 {
req
}
fn ping(&mut self) {}
}

// ...and the named-imports trait has the extra id parameter.
impl named_imports::foo::foo::handler::Host for MyHost {
fn handle(&mut self, _id: String, req: u32) -> u32 {
req
}
fn ping(&mut self, _id: String) {}
}
}

mod async_store {
#[derive(Clone)]
pub struct MyId(u32);

wasmtime::component::bindgen!({
inline: "
package foo:foo;

interface handler {
handle: func(req: u32) -> u32;
ping: func();
}

world the-world {
import handler;
}
",
named_imports: {
"foo:foo/handler": MyId,
},
imports: { default: async | store },
});
}
}
2 changes: 1 addition & 1 deletion crates/environ/src/component/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ impl NameMapIntern for StringPool {
/// This alternate lookup key is intended to serve the purpose where a
/// semver-compatible definition can be located, if one is defined, at perhaps
/// either a newer or an older version.
fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
pub fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
let at = name.find('@')?;
let version_string = &name[at + 1..];
let version = Version::parse(version_string).ok()?;
Expand Down
33 changes: 33 additions & 0 deletions crates/wasmtime/src/runtime/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,39 @@ pub(crate) use self::store::ComponentStoreData;
/// "wasi:filesystem/types.descriptor": MyDescriptorType,
/// },
///
/// // Generate an additional set of "named imports" bindings for the listed
/// // interfaces, used together with the component model's
/// // `(implements "...")` annotation.
/// //
/// // For each interface listed here an extra `Host` trait is generated
/// // under a top-level `named_imports` module (mirroring the interface's
/// // normal module path) whose methods each take an additional first
/// // argument: a reference to the host-chosen "id" type given as the value
/// // (here `MyHandlerId`). Alongside the trait a reflection-based
/// // `add_to_linker` is generated:
/// //
/// // ```ignore
/// // fn add_to_linker<T, D>(
/// // linker: &mut Linker<T>,
/// // component: &Component,
/// // lookup: impl FnMut(&str) -> Result<MyHandlerId>,
/// // host_getter: fn(&mut T) -> D::Data<'_>,
/// // ) -> Result<()>;
/// // ```
/// //
/// // This inspects `component`'s imports, and for each one annotated with
/// // `(implements "wasi:http/handler")` calls `lookup` with the import's
/// // name to obtain an id. That id is then cloned into each linker closure
/// // and passed as the first argument to every method call, letting a
/// // single `Host` implementation distinguish between multiple imports
/// // of the same interface.
/// //
/// // The id type must be `Clone + Send + Sync + 'static`. Interfaces that
/// // define a resource are not supported here and cause a compile error.
/// named_imports: {
/// "wasi:http/handler": MyHandlerId,
/// },
///
/// // Additional derive attributes to include on generated types (structs or enums).
/// //
/// // These are deduplicated and attached in a deterministic order.
Expand Down
26 changes: 25 additions & 1 deletion crates/wasmtime/src/runtime/component/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use wasmtime_environ::component::{
TypeComponentInstanceIndex, TypeDef, TypeEnumIndex, TypeFlagsIndex, TypeFuncIndex,
TypeFutureIndex, TypeFutureTableIndex, TypeListIndex, TypeMapIndex, TypeModuleIndex,
TypeOptionIndex, TypeRecordIndex, TypeResourceTable, TypeResourceTableIndex, TypeResultIndex,
TypeStreamIndex, TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex,
TypeStreamIndex, TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, alternate_lookup_key,
};

pub use crate::component::resources::ResourceType;
Expand Down Expand Up @@ -1135,6 +1135,30 @@ impl<'a> ComponentExtern<'a> {
ty: ComponentItem::from(engine, &env.ty, instance_ty),
}
}

/// Returns whether this item is tagged with `(implements "..")` with an
/// interface that's compatible with `name`.
///
/// This function will return `false` if `(implements "...")` is not
/// present. If it is present, and it's equal to `name`, then `true` is
/// returned. Failing that, this attempts to perform version-matching to see
/// if a compatible version of this item is implemented. For example if
/// `(implements "a:b/c@1.1.0")` is specified then this will return `true`
/// for `a:b/c@1.0.0` and `a:b/c@1.2.0` as well.
pub fn is_implements(&self, name: &str) -> bool {
let implements = match self.implements {
Some(s) => s,
None => return false,
};
if name == implements {
return true;
}

match (alternate_lookup_key(implements), alternate_lookup_key(name)) {
(Some((alt_implements, _)), Some((alt_name, _))) => alt_implements == alt_name,
_ => false,
}
}
}

/// Type of an item contained within the component
Expand Down
Loading
Loading