diff --git a/src/hyperlight_component_util/src/emit.rs b/src/hyperlight_component_util/src/emit.rs index d6c67cdf7..3918773fa 100644 --- a/src/hyperlight_component_util/src/emit.rs +++ b/src/hyperlight_component_util/src/emit.rs @@ -15,14 +15,122 @@ limitations under the License. */ //! A bunch of utilities used by the actual code emit functions -use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use std::vec::Vec; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::Ident; -use crate::etypes::{BoundedTyvar, Defined, Handleable, ImportExport, TypeBound, Tyvar}; +use crate::etypes::{ + BoundedTyvar, Defined, ExternDecl, ExternDesc, Handleable, ImportExport, TypeBound, Tyvar, +}; + +fn version_to_kebab(version: &[&str]) -> String { + version + .join("-") + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() { + c.to_ascii_lowercase() + } else { + '-' + } + }) + .collect() +} + +/// Scan a list of import extern decls for interface name collisions. +/// Returns short import names that need disambiguating. +pub fn find_colliding_import_names(imports: &[ExternDecl]) -> HashSet { + let mut counts = HashMap::::new(); + for ed in imports { + if let ExternDesc::Instance(_) = &ed.desc { + let wn = split_wit_name(ed.kebab_name); + *counts.entry(wn.name.to_string()).or_default() += 1; + } + } + counts + .into_iter() + .filter(|(_, c)| *c > 1) + .map(|(n, _)| n) + .collect() +} + +/// Return `(type_name, getter_name)` for an import instance. +/// +/// Colliding interface names are emitted with their namespace-qualified name, +/// plus the version suffix when present: +/// `wasi:http/types` -> `WasiHttpTypes` / `wasi_http_types`. +pub fn import_member_names(wn: &WitName, collisions: &HashSet) -> (Ident, Ident) { + if collisions.contains(wn.name) { + if wn.namespaces.is_empty() { + let mut type_name = component_first_camel(wn.name); + let mut getter = wn.name.to_string(); + if !wn._version.is_empty() { + let v = version_to_kebab(&wn._version); + getter.push_str("-v"); + getter.push_str(&v); + type_name.push('V'); + type_name.push_str(&component_first_camel(&v)); + } + return (format_ident!("{}", type_name), kebab_to_getter(&getter)); + } + + let (package, namespaces) = wn + .namespaces + .split_last() + .expect("colliding qualified imports have a package component"); + + // Preserve package hyphens as `_` so `a:bc/types` and + // `a:b-c/types` don't collide. + let mut getter_components = namespaces + .iter() + .map(|ns| ns.replace('-', "")) + .collect::>(); + getter_components.push(package.replace('-', "_")); + let getter_prefix = getter_components.join("_"); + let mut qualified_getter = format!("{}-{}", getter_prefix, wn.name); + + // Use the same boundary-preserving approach for type names. + let mut type_prefix: String = namespaces + .iter() + .map(|ns| component_first_camel(ns)) + .collect(); + type_prefix.push_str(&component_first_camel(&package.replace('-', "_"))); + let mut type_name = format!("{}{}", type_prefix, component_first_camel(wn.name)); + + if !wn._version.is_empty() { + let v = version_to_kebab(&wn._version); + qualified_getter.push_str("-v"); + qualified_getter.push_str(&v); + type_name.push('V'); + type_name.push_str(&component_first_camel(&v)); + } + ( + format_ident!("{}", type_name), + kebab_to_getter(&qualified_getter), + ) + } else { + (kebab_to_type(wn.name), kebab_to_getter(wn.name)) + } +} + +/// Capitalize only the first letter of a kebab component, removing hyphens +/// without capitalizing subsequent sub-words. +fn component_first_camel(s: &str) -> String { + let mut result = String::new(); + let mut chars = s.chars(); + if let Some(first) = chars.next() { + result.extend(first.to_uppercase()); + } + for c in chars { + if c != '-' { + result.push(c); + } + } + result +} /// A representation of a trait definition that we will eventually /// emit. This is used to allow easily adding onto the trait each time @@ -284,6 +392,11 @@ pub struct State<'a, 'b> { pub is_wasmtime_guest: bool, /// Are we working on an export or an import of the component type? pub is_export: bool, + /// Set of interface names that collide across different packages + /// (e.g. "types" appears in both wasi:filesystem/types and wasi:http/types). + /// When a name is in this set, the parent namespace is prepended to + /// disambiguate the trait member name. + pub colliding_import_names: HashSet, } /// Create a State with all of its &mut references pointing to @@ -336,6 +449,7 @@ impl<'a, 'b> State<'a, 'b> { is_guest, is_wasmtime_guest, is_export: false, + colliding_import_names: HashSet::new(), } } pub fn clone<'c>(&'c mut self) -> State<'c, 'b> { @@ -357,6 +471,7 @@ impl<'a, 'b> State<'a, 'b> { is_guest: self.is_guest, is_wasmtime_guest: self.is_wasmtime_guest, is_export: self.is_export, + colliding_import_names: self.colliding_import_names.clone(), } } /// Obtain a reference to the [`Mod`] that we are currently @@ -437,10 +552,17 @@ impl<'a, 'b> State<'a, 'b> { /// variable, given its absolute index (i.e. ignoring /// [`State::var_offset`]) pub fn noff_var_id(&self, n: u32) -> Ident { - let Some(n) = self.bound_vars[n as usize].origin.last_name() else { + let origin = &self.bound_vars[n as usize].origin; + let Some(name) = origin.last_name() else { panic!("missing origin on tyvar in rust emit") }; - kebab_to_type(n) + let wn = split_wit_name(name); + if origin.is_imported() { + let (tn, _) = import_member_names(&wn, &self.colliding_import_names); + tn + } else { + kebab_to_type(wn.name) + } } /// Copy the state, changing it to emit into the helper module of /// the current trait @@ -803,3 +925,253 @@ pub fn kebab_to_fn(n: &str) -> FnName { } FnName::Plain(kebab_to_snake(n)) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::etypes::{ExternDecl, ExternDesc, Instance}; + + /// Helper to build a minimal `ExternDecl` whose desc is an Instance. + fn instance_decl(kebab_name: &str) -> ExternDecl<'_> { + ExternDecl { + kebab_name, + desc: ExternDesc::Instance(Instance { + exports: Vec::new(), + }), + } + } + + /// Helper to build a minimal `ExternDecl` whose desc is a Func (not an Instance). + fn func_decl(kebab_name: &str) -> ExternDecl<'_> { + ExternDecl { + kebab_name, + desc: ExternDesc::Func(crate::etypes::Func { + params: Vec::new(), + result: None, + }), + } + } + + // --- split_wit_name tests --- + + #[test] + fn split_wit_name_simple() { + let wn = split_wit_name("my-interface"); + assert_eq!(wn.name, "my-interface"); + assert!(wn.namespaces.is_empty()); + } + + #[test] + fn split_wit_name_with_package() { + let wn = split_wit_name("wasi:http/types"); + assert_eq!(wn.name, "types"); + assert_eq!(wn.namespaces, vec!["wasi", "http"]); + } + + #[test] + fn split_wit_name_with_version() { + let wn = split_wit_name("wasi:http/types@0.2.0"); + assert_eq!(wn.name, "types"); + assert_eq!(wn.namespaces, vec!["wasi", "http"]); + } + + // --- find_colliding_import_names tests --- + + #[test] + fn no_collisions_with_distinct_names() { + let imports = vec![ + instance_decl("wasi:http/types"), + instance_decl("wasi:filesystem/preopens"), + ]; + let collisions = find_colliding_import_names(&imports); + assert_eq!(collisions.len(), 0); + } + + #[test] + fn detects_collision_on_same_short_name() { + let imports = vec![ + instance_decl("wasi:http/types"), + instance_decl("wasi:filesystem/types"), + ]; + let collisions = find_colliding_import_names(&imports); + assert_eq!(collisions.len(), 1); + assert!(collisions.contains("types")); + } + + #[test] + fn no_collision_for_non_instance_decls() { + let imports = vec![instance_decl("wasi:http/types"), func_decl("types")]; + let collisions = find_colliding_import_names(&imports); + assert_eq!(collisions.len(), 0); + } + + #[test] + fn multiple_collisions() { + let imports = vec![ + instance_decl("a:foo/types"), + instance_decl("b:bar/types"), + instance_decl("a:foo/handler"), + instance_decl("c:baz/handler"), + ]; + let collisions = find_colliding_import_names(&imports); + assert_eq!(collisions.len(), 2); + assert!(collisions.contains("types")); + assert!(collisions.contains("handler")); + } + + #[test] + fn single_import_no_collision() { + let imports = vec![instance_decl("wasi:http/types")]; + let collisions = find_colliding_import_names(&imports); + assert_eq!(collisions.len(), 0); + } + + #[test] + fn empty_imports_no_collision() { + let collisions = find_colliding_import_names(&[]); + assert_eq!(collisions.len(), 0); + } + + // --- import_member_names tests --- + + #[test] + fn no_collision_uses_short_name() { + let wn = split_wit_name("wasi:http/types"); + let collisions = HashSet::new(); + let (ty, getter) = import_member_names(&wn, &collisions); + assert_eq!(ty.to_string(), "Types"); + assert_eq!(getter.to_string(), "r#types"); + } + + #[test] + fn collision_prepends_parent_namespace() { + let wn = split_wit_name("wasi:http/types"); + let collisions = find_colliding_import_names(&[ + instance_decl("wasi:http/types"), + instance_decl("wasi:filesystem/types"), + ]); + let (ty, getter) = import_member_names(&wn, &collisions); + assert_eq!(ty.to_string(), "WasiHttpTypes"); + assert_eq!(getter.to_string(), "r#wasi_http_types"); + } + + #[test] + fn collision_different_parents_produce_different_names() { + let collisions = find_colliding_import_names(&[ + instance_decl("wasi:http/types"), + instance_decl("wasi:filesystem/types"), + ]); + + let wn_http = split_wit_name("wasi:http/types"); + let (ty_http, getter_http) = import_member_names(&wn_http, &collisions); + + let wn_fs = split_wit_name("wasi:filesystem/types"); + let (ty_fs, getter_fs) = import_member_names(&wn_fs, &collisions); + + assert_eq!(ty_http.to_string(), "WasiHttpTypes"); + assert_eq!(ty_fs.to_string(), "WasiFilesystemTypes"); + assert_eq!(getter_http.to_string(), "r#wasi_http_types"); + assert_eq!(getter_fs.to_string(), "r#wasi_filesystem_types"); + } + + #[test] + fn collision_same_parent_different_package_produces_different_names() { + let collisions = find_colliding_import_names(&[ + instance_decl("a:pkg/types"), + instance_decl("b:pkg/types"), + ]); + + let wn_a = split_wit_name("a:pkg/types"); + let (ty_a, getter_a) = import_member_names(&wn_a, &collisions); + + let wn_b = split_wit_name("b:pkg/types"); + let (ty_b, getter_b) = import_member_names(&wn_b, &collisions); + + assert_eq!(ty_a.to_string(), "APkgTypes"); + assert_eq!(getter_a.to_string(), "r#a_pkg_types"); + assert_eq!(ty_b.to_string(), "BPkgTypes"); + assert_eq!(getter_b.to_string(), "r#b_pkg_types"); + } + + #[test] + fn colliding_bare_import_keeps_short_name() { + let wn = split_wit_name("types"); + let collisions = + find_colliding_import_names(&[instance_decl("types"), instance_decl("pkg:types")]); + let (ty, getter) = import_member_names(&wn, &collisions); + assert_eq!(ty.to_string(), "Types"); + assert_eq!(getter.to_string(), "r#types"); + } + + #[test] + fn versioned_collision_adds_version_after_namespace() { + let collisions = find_colliding_import_names(&[ + instance_decl("a:pkg/types@1.0.0"), + instance_decl("a:pkg/types@2.0.0"), + ]); + + let wn_v1 = split_wit_name("a:pkg/types@1.0.0"); + let (ty_v1, getter_v1) = import_member_names(&wn_v1, &collisions); + + let wn_v2 = split_wit_name("a:pkg/types@2.0.0"); + let (ty_v2, getter_v2) = import_member_names(&wn_v2, &collisions); + + assert_eq!(ty_v1.to_string(), "APkgTypesV100"); + assert_eq!(ty_v2.to_string(), "APkgTypesV200"); + assert_eq!(getter_v1.to_string(), "r#a_pkg_types_v1_0_0"); + assert_eq!(getter_v2.to_string(), "r#a_pkg_types_v2_0_0"); + } + + #[test] + fn version_is_added_for_colliding_versioned_imports() { + let collisions = find_colliding_import_names(&[ + instance_decl("a:pkg/types@1.0.0"), + instance_decl("b:pkg/types@1.0.0"), + ]); + + let wn = split_wit_name("a:pkg/types@1.0.0"); + let (ty, getter) = import_member_names(&wn, &collisions); + + assert_eq!(ty.to_string(), "APkgTypesV100"); + assert_eq!(getter.to_string(), "r#a_pkg_types_v1_0_0"); + } + + #[test] + fn hyphenated_namespace_components_produce_distinct_type_names() { + // "a:b-c/types" has a hyphenated package component, while + // "a-b:c/types" has a hyphenated namespace component. Preserving the + // package hyphen as `_` keeps the generated names distinct. + let collisions = find_colliding_import_names(&[ + instance_decl("a:b-c/types"), + instance_decl("a-b:c/types"), + ]); + + let wn1 = split_wit_name("a:b-c/types"); + let wn2 = split_wit_name("a-b:c/types"); + let (ty1, getter1) = import_member_names(&wn1, &collisions); + let (ty2, getter2) = import_member_names(&wn2, &collisions); + + assert_eq!(ty1.to_string(), "AB_cTypes"); + assert_eq!(getter1.to_string(), "r#a_b_c_types"); + assert_eq!(ty2.to_string(), "AbCTypes"); + assert_eq!(getter2.to_string(), "r#ab_c_types"); + } + + #[test] + fn plain_and_hyphenated_namespace_components_produce_distinct_type_names() { + let collisions = find_colliding_import_names(&[ + instance_decl("a:bc/types"), + instance_decl("a:b-c/types"), + ]); + + let wn_plain = split_wit_name("a:bc/types"); + let wn_hyphenated = split_wit_name("a:b-c/types"); + let (ty_plain, getter_plain) = import_member_names(&wn_plain, &collisions); + let (ty_hyphenated, getter_hyphenated) = import_member_names(&wn_hyphenated, &collisions); + + assert_eq!(ty_plain.to_string(), "ABcTypes"); + assert_eq!(getter_plain.to_string(), "r#a_bc_types"); + assert_eq!(ty_hyphenated.to_string(), "AB_cTypes"); + assert_eq!(getter_hyphenated.to_string(), "r#a_b_c_types"); + } +} diff --git a/src/hyperlight_component_util/src/guest.rs b/src/hyperlight_component_util/src/guest.rs index c6d28c11d..18e555e3d 100644 --- a/src/hyperlight_component_util/src/guest.rs +++ b/src/hyperlight_component_util/src/guest.rs @@ -18,9 +18,9 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use crate::emit::{ - FnName, ResolvedBoundVar, ResourceItemName, State, WitName, kebab_to_exports_name, kebab_to_fn, - kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, - split_wit_name, + FnName, ResolvedBoundVar, ResourceItemName, State, WitName, find_colliding_import_names, + import_member_names, kebab_to_exports_name, kebab_to_fn, kebab_to_getter, + kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, split_wit_name, }; use crate::etypes::{Component, Defined, ExternDecl, ExternDesc, Handleable, Instance, Tyvar}; use crate::hl::{ @@ -117,8 +117,7 @@ fn emit_import_extern_decl<'a, 'b, 'c>( let wn = split_wit_name(ed.kebab_name); emit_import_instance(s, wn.clone(), it); - let getter = kebab_to_getter(wn.name); - let tn = kebab_to_type(wn.name); + let (tn, getter) = import_member_names(&wn, &s.colliding_import_names); quote! { type #tn = Self; #[allow(refining_impl_trait)] @@ -287,6 +286,7 @@ fn emit_component<'a, 'b, 'c>( let export_trait = kebab_to_exports_name(wn.name); s.import_param_var = Some(format_ident!("I")); s.self_param_var = Some(format_ident!("S")); + s.colliding_import_names = find_colliding_import_names(&ct.imports); let rtsid = format_ident!("{}Resources", r#trait); resource::emit_tables( diff --git a/src/hyperlight_component_util/src/host.rs b/src/hyperlight_component_util/src/host.rs index de0b49ec0..eb94c699e 100644 --- a/src/hyperlight_component_util/src/host.rs +++ b/src/hyperlight_component_util/src/host.rs @@ -18,8 +18,9 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use crate::emit::{ - FnName, ResourceItemName, State, WitName, kebab_to_exports_name, kebab_to_fn, kebab_to_getter, - kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, split_wit_name, + FnName, ResourceItemName, State, WitName, find_colliding_import_names, import_member_names, + kebab_to_exports_name, kebab_to_fn, kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, + kebab_to_type, kebab_to_var, split_wit_name, }; use crate::etypes::{Component, ExternDecl, ExternDesc, Instance, Tyvar}; use crate::hl::{ @@ -264,10 +265,9 @@ fn emit_import_extern_decl<'a, 'b, 'c>( ExternDesc::Instance(it) => { let mut s = s.clone(); let wn = split_wit_name(ed.kebab_name); - let type_name = kebab_to_type(wn.name); - let getter = kebab_to_getter(wn.name); + let (type_name, getter) = import_member_names(&wn, &s.colliding_import_names); let tp = s.cur_trait_path(); - let get_self = get_self.with_getter(tp, type_name, getter); //quote! { #get_self let mut slf = &mut #tp::#getter(&mut *slf); }; + let get_self = get_self.with_getter(tp, type_name, getter); emit_import_instance(&mut s, get_self, wn.clone(), it) } ExternDesc::Component(_) => { @@ -326,6 +326,7 @@ fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Com let rtsid = format_ident!("{}Resources", r#trait); s.import_param_var = Some(format_ident!("I")); + s.colliding_import_names = find_colliding_import_names(&ct.imports); resource::emit_tables( &mut s, rtsid.clone(), diff --git a/src/hyperlight_component_util/src/rtypes.rs b/src/hyperlight_component_util/src/rtypes.rs index d6c4a8b38..8f0ef7823 100644 --- a/src/hyperlight_component_util/src/rtypes.rs +++ b/src/hyperlight_component_util/src/rtypes.rs @@ -24,9 +24,9 @@ use quote::{format_ident, quote}; use syn::Ident; use crate::emit::{ - FnName, ResourceItemName, State, WitName, kebab_to_cons, kebab_to_exports_name, kebab_to_fn, - kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, - split_wit_name, + FnName, ResourceItemName, State, WitName, find_colliding_import_names, import_member_names, + kebab_to_cons, kebab_to_exports_name, kebab_to_fn, kebab_to_getter, kebab_to_imports_name, + kebab_to_namespace, kebab_to_type, kebab_to_var, split_wit_name, }; use crate::etypes::{ self, Component, Defined, ExternDecl, ExternDesc, Func, Handleable, ImportExport, Instance, @@ -68,9 +68,10 @@ fn emit_resource_ref(s: &mut State, n: u32, path: Vec) -> TokenStr let id = format_ident!("HostResource{}", n); return quote! { #id }; } - // There is always at least one element in the path, which names - // the thing we are referring to - let rtrait = kebab_to_type(path[path.len() - 1].name()); + let Some(resource) = path.last() else { + panic!("resource reference path must contain the resource type"); + }; + let rtrait = kebab_to_type(resource.name()); // Deal specially with being in the local instance, where there is // no instance type & so it is not easy to resolve the @@ -92,22 +93,34 @@ fn emit_resource_ref(s: &mut State, n: u32, path: Vec) -> TokenStr // followed by the resource type itself. We locate the resource // trait by using that final instance name directly; any other // names are just used to get to the type that implements it - let instance = path[path.len() - 2].name(); - let iwn = split_wit_name(instance); + let instance = &path[path.len() - 2]; + let iwn = split_wit_name(instance.name()); let extras = path[0..path.len() - 2] .iter() .map(|p| { let wn = split_wit_name(p.name()); - kebab_to_type(wn.name) + if p.imported() && s.colliding_import_names.contains(wn.name) { + let (tn, _) = import_member_names(&wn, &s.colliding_import_names); + tn + } else { + kebab_to_type(wn.name) + } }) .collect::>(); let extras = quote! { #(#extras::)* }; let rp = s.root_path(); let tns = iwn.namespace_path(); let instance_mod = kebab_to_namespace(iwn.name); - let instance_type = kebab_to_type(iwn.name); + // Use the disambiguated trait member only for imported instances. Exported + // instances must keep their public WIT member names. + let instance_type = if instance.imported() { + let (tn, _) = import_member_names(&iwn, &s.colliding_import_names); + tn + } else { + kebab_to_type(iwn.name) + }; let mut sv = quote! { Self }; - if path[path.len() - 2].imported() { + if instance.imported() { if let Some(iv) = &s.import_param_var { sv = quote! { #iv } }; @@ -740,18 +753,22 @@ fn emit_extern_decl<'a, 'b, 'c>( TokenStream::new() }; - let getter = kebab_to_getter(wn.name); + let (member_tn, member_getter) = if origin_was_export { + (kebab_to_type(wn.name), kebab_to_getter(wn.name)) + } else { + import_member_names(&wn, &s.colliding_import_names) + }; let rp = s.root_path(); let tns = wn.namespace_path(); - let tn = kebab_to_type(wn.name); + let trait_tn = kebab_to_type(wn.name); let trait_bound = if tns.is_empty() { - quote! { #rp #tn } + quote! { #rp #trait_tn } } else { - quote! { #rp #tns::#tn } + quote! { #rp #tns::#trait_tn } }; quote! { - type #tn: #trait_bound #vs; - fn #getter(&mut self) -> impl ::core::borrow::BorrowMut; + type #member_tn: #trait_bound #vs; + fn #member_getter(&mut self) -> impl ::core::borrow::BorrowMut; } } ExternDesc::Component(_) => { @@ -844,6 +861,7 @@ fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Com .map(Clone::clone) .collect::>(); s.cur_trait = Some(import_name.clone()); + s.colliding_import_names = find_colliding_import_names(&ct.imports); let imports = ct .imports .iter() diff --git a/src/hyperlight_host/tests/wit_test.rs b/src/hyperlight_host/tests/wit_test.rs index e96f24089..b0ccb5ae6 100644 --- a/src/hyperlight_host/tests/wit_test.rs +++ b/src/hyperlight_host/tests/wit_test.rs @@ -487,4 +487,53 @@ mod bindgen_test_cases { }; assert_eq!(result.message, "executed"); } + + #[allow(dead_code)] + struct ExportHost; + + impl test::bindgen_test_cases::Executor for ExportHost { + fn execute(&mut self) -> test::bindgen_test_cases::executor::ExecutionResult { + test::bindgen_test_cases::executor::ExecutionResult { + message: String::from("executed"), + } + } + } + + impl test::bindgen_test_cases::Types for ExportHost { + fn get_status(&mut self) -> test::bindgen_test_cases::types::Status { + test::bindgen_test_cases::types::Status { + message: String::from("ok"), + } + } + } + + impl test::bindgen_test_cases::UsesExportedTypes + for ExportHost + { + fn get_status(&mut self) -> test::bindgen_test_cases::types::Status { + test::bindgen_test_cases::types::Status { + message: String::from("ok"), + } + } + } + + #[allow(refining_impl_trait)] + impl + test::bindgen_test_cases::BindgenTestCasesExports for ExportHost + { + type Executor = Self; + fn executor(&mut self) -> &mut Self { + self + } + + type Types = Self; + fn types(&mut self) -> &mut Self { + self + } + + type UsesExportedTypes = Self; + fn uses_exported_types(&mut self) -> &mut Self { + self + } + } } diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-bc-plain/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-bc-plain/types.wit new file mode 100644 index 000000000..1a745802c --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-bc-plain/types.wit @@ -0,0 +1,10 @@ +package a:bc; + +interface types { + resource thing; + + record plain-bc-info { + label: string, + } + get-plain-bc-info: func() -> plain-bc-info; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-bc/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-bc/types.wit new file mode 100644 index 000000000..5fb0623ed --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-bc/types.wit @@ -0,0 +1,10 @@ +package a:b-c; + +interface types { + resource thing; + + record bc-info { + label: string, + } + get-bc-info: func() -> bc-info; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-pkg/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-pkg/types.wit new file mode 100644 index 000000000..5015410fc --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/a-pkg/types.wit @@ -0,0 +1,11 @@ +package a:pkg; + +interface types { + resource thing; + + record info { + name: string, + value: u32, + } + get-info: func() -> info; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/ab-c/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/ab-c/types.wit new file mode 100644 index 000000000..77636f41f --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/ab-c/types.wit @@ -0,0 +1,10 @@ +package a-b:c; + +interface types { + resource thing; + + record ab-c-info { + tag: string, + } + get-ab-c-info: func() -> ab-c-info; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/b-pkg/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/b-pkg/types.wit new file mode 100644 index 000000000..475c1002c --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/b-pkg/types.wit @@ -0,0 +1,11 @@ +package b:pkg; + +interface types { + resource thing; + + record detail { + label: string, + count: u64, + } + get-detail: func() -> detail; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/c-pkg-v1/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/c-pkg-v1/types.wit new file mode 100644 index 000000000..cb8d6c8f6 --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/c-pkg-v1/types.wit @@ -0,0 +1,10 @@ +package c:pkg@1.0.0; + +interface types { + resource thing; + + record info { + value: u32, + } + get-info: func() -> info; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/deps/c-pkg-v2/types.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/c-pkg-v2/types.wit new file mode 100644 index 000000000..29ae1ba1c --- /dev/null +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/deps/c-pkg-v2/types.wit @@ -0,0 +1,10 @@ +package c:pkg@2.0.0; + +interface types { + resource thing; + + record info { + value: u32, + } + get-info: func() -> info; +} diff --git a/src/tests/rust_guests/witguest/bindgen-test-cases/world.wit b/src/tests/rust_guests/witguest/bindgen-test-cases/world.wit index 071cc4ffc..d438bd58d 100644 --- a/src/tests/rust_guests/witguest/bindgen-test-cases/world.wit +++ b/src/tests/rust_guests/witguest/bindgen-test-cases/world.wit @@ -1,7 +1,61 @@ package test:bindgen-test-cases; world bindgen-test-cases { + import types: interface { + get-status: func() -> string; + } + import a:pkg/types; + import b:pkg/types; + import c:pkg/types@1.0.0; + import c:pkg/types@2.0.0; + import a:bc/types; + import a:b-c/types; + import a-b:c/types; + import uses-a-pkg-resource; + import uses-b-pkg-resource; + import uses-c-pkg-v1-resource; + import uses-c-pkg-v2-resource; + import uses-a-bc-plain-resource; + import uses-a-bc-resource; + import uses-ab-c-resource; export executor; + export types; + export uses-exported-types; +} + +interface uses-a-pkg-resource { + use a:pkg/types.{thing}; + use-thing: func(x: borrow); +} + +interface uses-b-pkg-resource { + use b:pkg/types.{thing}; + use-thing: func(x: borrow); +} + +interface uses-c-pkg-v1-resource { + use c:pkg/types@1.0.0.{thing}; + use-thing: func(x: borrow); +} + +interface uses-c-pkg-v2-resource { + use c:pkg/types@2.0.0.{thing}; + use-thing: func(x: borrow); +} + +interface uses-a-bc-plain-resource { + use a:bc/types.{thing}; + use-thing: func(x: borrow); +} + +interface uses-a-bc-resource { + use a:b-c/types.{thing}; + use-thing: func(x: borrow); +} + +interface uses-ab-c-resource { + use a-b:c/types.{thing}; + use-thing: func(x: borrow); } interface executor { @@ -11,3 +65,16 @@ interface executor { execute: func() -> execution-result; } + +interface types { + record status { + message: string, + } + + get-status: func() -> status; +} + +interface uses-exported-types { + use types.{status}; + get-status: func() -> status; +}