diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index 7cada0b9c4c4..cd4d4e13d1b1 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -1971,6 +1971,7 @@ impl ComponentExternData { fn new(data: wasmparser::ComponentExternName<'_>) -> Self { ComponentExternData { implements: data.implements.map(|s| s.to_string()), + external_id: data.external_id.map(|s| s.to_string()), } } } diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 56c2874fa8d6..b0622bfb350d 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -449,20 +449,29 @@ where } } -/// TODO +/// An import or an export of a component or a component instance. +/// +/// This records the type of the item that is being imported or exported along +/// with any metadata associated with the item. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ComponentExtern { - /// TODO + /// Metadata associated with this item's name, such as + /// `(implements "...")`. pub data: ComponentExternData, - /// TODO + /// The type of this item. pub ty: TypeDef, } -/// TODO +/// Metadata associated with the name of a component import or export. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ComponentExternData { - /// TODO + /// The `(implements "...")` annotation, if present: the name of the + /// interface that this item implements, used when matching this item + /// against imports by interface name rather than by import name. pub implements: Option, + /// The `(external-id "...")` annotation, if present: a free-form + /// host-defined identifier which is ignored by type checking. + pub external_id: Option, } /// Types of imports and exports in the component model. diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs index a9822ea1afdc..0fadfb14d498 100644 --- a/crates/environ/src/component/types_builder.rs +++ b/crates/environ/src/component/types_builder.rs @@ -258,6 +258,7 @@ impl ComponentTypesBuilder { ty: self.convert_component_entity_type(types, ty.ty)?, data: ComponentExternData { implements: ty.implements.clone(), + external_id: ty.external_id.clone(), }, }) } diff --git a/crates/wasmtime/src/runtime/component/types.rs b/crates/wasmtime/src/runtime/component/types.rs index 4f94d17ad1cc..e299f4831946 100644 --- a/crates/wasmtime/src/runtime/component/types.rs +++ b/crates/wasmtime/src/runtime/component/types.rs @@ -1171,6 +1171,11 @@ pub struct ComponentExtern<'a> { pub ty: ComponentItem, /// The `(implements "...")` annotation, if present. pub implements: Option<&'a str>, + /// The `(external-id "...")` annotation, if present. + /// + /// This is a free-form host-defined identifier and is intended to indicate + /// how the host should satisfy an import (or interpret an export). + pub external_id: Option<&'a str>, } impl<'a> ComponentExtern<'a> { @@ -1181,6 +1186,7 @@ impl<'a> ComponentExtern<'a> { ) -> Self { Self { implements: env.data.implements.as_deref(), + external_id: env.data.external_id.as_deref(), ty: ComponentItem::from(engine, &env.ty, instance_ty), } } diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs index aeae1a71d86b..6898ad98b1ba 100644 --- a/tests/all/component_model/aot.rs +++ b/tests/all/component_model/aot.rs @@ -248,6 +248,56 @@ fn implements_shows_up() -> Result<()> { Ok(()) } +#[test] +#[cfg_attr(miri, ignore)] +fn external_id_shows_up() -> Result<()> { + let mut config = Config::new(); + config.wasm_component_model_implements(true); + let engine = Engine::new(&config)?; + let component = Component::new( + &engine, + r#" + (component + (import "a" (implements "a1:b1/c1") (external-id "user-db-prod:region-a") (instance $a)) + (import "b" (external-id "user-db-prod:region-b") (instance $b + (export "f" (external-id "func-b-f") (func)) + )) + (import "c" (instance $c)) + (export "d" (external-id "queue-1") (instance $a)) + ) + "#, + )?; + + let assert_reflection = |component: &Component| { + let ty = component.component_type(); + let mut imports = ty.imports(&engine); + let (_, a) = imports.next().unwrap(); + assert_eq!(a.implements.as_deref(), Some("a1:b1/c1")); + assert_eq!(a.external_id.as_deref(), Some("user-db-prod:region-a")); + let (_, b) = imports.next().unwrap(); + assert_eq!(b.implements.as_deref(), None); + assert_eq!(b.external_id.as_deref(), Some("user-db-prod:region-b")); + let ComponentItem::ComponentInstance(instance) = &b.ty else { + panic!("expected an instance import"); + }; + let (name, f) = instance.exports(&engine).next().unwrap(); + assert_eq!(name, "f"); + assert_eq!(f.external_id.as_deref(), Some("func-b-f")); + let (_, c) = imports.next().unwrap(); + assert_eq!(c.external_id.as_deref(), None); + let mut exports = ty.exports(&engine); + let (_, d) = exports.next().unwrap(); + assert_eq!(d.external_id.as_deref(), Some("queue-1")); + }; + + assert_reflection(&component); + let bytes = component.serialize()?; + let component = unsafe { Component::deserialize(&engine, &bytes)? }; + assert_reflection(&component); + + Ok(()) +} + #[test] #[cfg_attr(miri, ignore)] fn issue_13540_resources_in_adapter_no_concurrency() -> Result<()> {