diff --git a/crates/wasm-encoder/src/component/imports.rs b/crates/wasm-encoder/src/component/imports.rs index 26adc807a4..6f0f00ec14 100644 --- a/crates/wasm-encoder/src/component/imports.rs +++ b/crates/wasm-encoder/src/component/imports.rs @@ -165,15 +165,34 @@ pub struct ComponentExternName<'a> { /// An optional `(implements ...)` directive (See 🏷️ in the component model /// explainer). pub implements: Option>, + /// An optional `(versionsuffix ...)` directive (See 🔗 in the component + /// model explainer). + pub version_suffix: Option>, + /// An optional `(external-id ...)` directive (See 🏷️ in the component model + /// explainer). + pub external_id: Option>, } impl Encode for ComponentExternName<'_> { fn encode(&self, bytes: &mut Vec) { let mut options = Vec::new(); - if let Some(s) = &self.implements { + let ComponentExternName { + name: _, + implements, + version_suffix, + external_id, + } = self; + + if let Some(s) = implements { options.push((0x00, s.as_bytes())); } + if let Some(s) = version_suffix { + options.push((0x01, s.as_bytes())); + } + if let Some(s) = external_id { + options.push((0x02, s.as_bytes())); + } if options.is_empty() { // Prior to WebAssembly/component-model#263 import and export names @@ -213,16 +232,15 @@ impl<'a> From<&'a str> for ComponentExternName<'a> { ComponentExternName { name: Cow::Borrowed(name), implements: None, + external_id: None, + version_suffix: None, } } } impl<'a> From<&'a String> for ComponentExternName<'a> { fn from(name: &'a String) -> Self { - ComponentExternName { - name: Cow::Borrowed(name), - implements: None, - } + ComponentExternName::from(name.as_str()) } } @@ -231,6 +249,8 @@ impl<'a> From for ComponentExternName<'a> { ComponentExternName { name: Cow::Owned(name), implements: None, + external_id: None, + version_suffix: None, } } } @@ -238,10 +258,17 @@ impl<'a> From for ComponentExternName<'a> { #[cfg(feature = "wasmparser")] impl<'a> From> for ComponentExternName<'a> { fn from(name: wasmparser::ComponentExternName<'a>) -> Self { - let wasmparser::ComponentExternName { name, implements } = name; + let wasmparser::ComponentExternName { + name, + implements, + external_id, + version_suffix, + } = name; ComponentExternName { name: name.into(), implements: implements.map(|s| s.into()), + external_id: external_id.map(|s| s.into()), + version_suffix: version_suffix.map(|s| s.into()), } } } diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index cc4d21c076..d2abd4d33d 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -352,6 +352,12 @@ define_wasm_features! { /// Corresponds to the 🏷️ character in /// . pub cm_implements: CM_IMPLEMENTS(1 << 40) = false; + + /// Support for the `(versionsuffix "...")` directive in the component model + /// + /// Corresponds to the 🔗 character in + /// . + pub cm_canon_names: CM_CANON_NAMES(1 << 41) = false; } } diff --git a/crates/wasmparser/src/readers/component/imports.rs b/crates/wasmparser/src/readers/component/imports.rs index 5c45e663e4..332eaa8cc1 100644 --- a/crates/wasmparser/src/readers/component/imports.rs +++ b/crates/wasmparser/src/readers/component/imports.rs @@ -115,6 +115,8 @@ pub type ComponentImportSectionReader<'a> = SectionLimited<'a, ComponentImport<' pub struct ComponentExternName<'a> { pub name: &'a str, pub implements: Option<&'a str>, + pub version_suffix: Option<&'a str>, + pub external_id: Option<&'a str>, } impl<'a> FromReader<'a> for ComponentExternName<'a> { @@ -142,7 +144,7 @@ impl<'a> FromReader<'a> for ComponentExternName<'a> { 0x01 => false, 0x02 => { - if reader.cm_implements() { + if reader.cm_implements() || reader.cm_canon_names() { true } else { bail!( @@ -157,6 +159,8 @@ impl<'a> FromReader<'a> for ComponentExternName<'a> { let mut ret = ComponentExternName { name: reader.read_string()?, implements: None, + version_suffix: None, + external_id: None, }; if has_options { for _ in 0..reader.read_var_u32()? { @@ -168,6 +172,18 @@ impl<'a> FromReader<'a> for ComponentExternName<'a> { } ret.implements = Some(name); } + ComponentNameOpt::VersionSuffix(name) => { + if ret.version_suffix.is_some() { + bail!(pos, "duplicate 'versionsuffix' option in name"); + } + ret.version_suffix = Some(name); + } + ComponentNameOpt::ExternalId(name) => { + if ret.external_id.is_some() { + bail!(pos, "duplicate 'external-id' option in name"); + } + ret.external_id = Some(name); + } } } } @@ -177,12 +193,16 @@ impl<'a> FromReader<'a> for ComponentExternName<'a> { enum ComponentNameOpt<'a> { Implements(&'a str), + VersionSuffix(&'a str), + ExternalId(&'a str), } impl<'a> FromReader<'a> for ComponentNameOpt<'a> { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { match reader.read_u8()? { 0x00 => Ok(ComponentNameOpt::Implements(reader.read()?)), + 0x01 => Ok(ComponentNameOpt::VersionSuffix(reader.read()?)), + 0x02 => Ok(ComponentNameOpt::ExternalId(reader.read()?)), x => return reader.invalid_leading_byte(x, "name option"), } } diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index b043077bc4..a5e9813734 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -4654,15 +4654,17 @@ impl ComponentNameContext { info: &mut TypeInfo, features: &WasmFeatures, ) -> Result<()> { + let ComponentExternName { + name, + implements, + external_id, + version_suffix, + } = *name; // First validate that `name` is even a valid kebab name, meaning it's // in kebab-case, is an ID, etc. let kebab = - ComponentName::new_with_features(name.name, offset, *features).with_context(|| { - format!( - "{} name `{}` is not a valid extern name", - kind.desc(), - name.name - ) + ComponentName::new_with_features(name, offset, *features).with_context(|| { + format!("{} name `{name}` is not a valid extern name", kind.desc(),) })?; if let ExternKind::Export = kind { @@ -4676,12 +4678,12 @@ impl ComponentNameContext { ComponentNameKind::Hash(_) | ComponentNameKind::Url(_) | ComponentNameKind::Dependency(_) => { - bail!(offset, "name `{}` is not a valid export name", name.name) + bail!(offset, "name `{name}` is not a valid export name") } } } - if let Some(implements) = name.implements { + if let Some(implements) = implements { require_feature::cm_implements( *features, "the `cm-implements` feature is not active", @@ -4689,16 +4691,12 @@ impl ComponentNameContext { )?; match kebab.kind() { ComponentNameKind::Label(_) => {} - _ => bail!( - offset, - "name `{}` is not valid with `implements`", - name.name - ), + _ => bail!(offset, "name `{name}` is not valid with `implements`",), } match ty { ComponentEntityType::Instance(_) => {} - _ => bail!(offset, "only instance names can have an `implements`"), + _ => bail!(offset, "only instances can have an `implements`"), } let implements = ComponentName::new_with_features(implements, offset, *features) @@ -4709,9 +4707,33 @@ impl ComponentNameContext { } } + if let Some(_) = version_suffix { + require_feature::cm_canon_names( + *features, + "the `cm-canon-names` feature is not active", + offset, + )?; + match ty { + ComponentEntityType::Instance(_) => {} + _ => bail!(offset, "only instances can have an `versionsuffix`"), + } + } + + if let Some(_) = external_id { + require_feature::cm_implements( + *features, + "the `cm-implements` feature is not active", + offset, + )?; + match ty { + ComponentEntityType::Instance(_) => {} + _ => bail!(offset, "only instances can have an `external-id`"), + } + } + // Validate that the kebab name, if it has structure such as // `[method]a.b`, is indeed valid with respect to known resources. - self.validate(&kebab, ty, types, offset) + self.validate(&kebab, version_suffix, ty, types, offset) .with_context(|| format!("{} name `{kebab}` is not valid", kind.desc()))?; // Top-level kebab-names must all be unique, even between both imports @@ -4728,12 +4750,11 @@ impl ComponentNameContext { // Otherwise all strings must be unique, regardless of their name, so // consult the `items` set to ensure that we're not for example // importing the same interface ID twice. - match items.entry(name.name.to_string()) { + match items.entry(name.to_string()) { Entry::Occupied(e) => { bail!( offset, "{kind} name `{name}` conflicts with previous name `{prev}`", - name = name.name, kind = kind.desc(), prev = e.key(), ); @@ -4741,7 +4762,9 @@ impl ComponentNameContext { Entry::Vacant(e) => { e.insert(ComponentItem { ty: *ty, - implements: name.implements.map(|s| s.to_string()), + implements: implements.map(|s| s.to_string()), + version_suffix: version_suffix.map(|s| s.to_string()), + external_id: external_id.map(|s| s.to_string()), }); info.combine(ty.info(types), offset)?; } @@ -4753,6 +4776,7 @@ impl ComponentNameContext { fn validate( &self, name: &ComponentName, + version_suffix: Option<&str>, ty: &ComponentEntityType, types: &TypeAlloc, offset: usize, @@ -4768,10 +4792,17 @@ impl ComponentNameContext { match name.kind() { // No validation necessary for these styles of names ComponentNameKind::Label(_) - | ComponentNameKind::Interface(_) | ComponentNameKind::Url(_) - | ComponentNameKind::Dependency(_) - | ComponentNameKind::Hash(_) => {} + | ComponentNameKind::Hash(_) + | ComponentNameKind::Dependency(_) => {} + + // Validate the `version_suffix` field in the context of interface + // names. + ComponentNameKind::Interface(name) => { + if let Err(e) = name.version(version_suffix) { + bail!(offset, "invalid interface version: {e}"); + } + } // Constructors must return `(own $resource)` or // `(result (own $Tresource))` and the `$resource` must be named diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index e968fbcd40..d85851716f 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1032,6 +1032,10 @@ pub struct ComponentItem { pub ty: ComponentEntityType, /// The optional `(implements "...")` metadata, if specified. pub implements: Option, + /// The optional `(versionsuffix "...")` metadata, if specified. + pub version_suffix: Option, + /// The optional `(external_id "...")` metadata, if specified. + pub external_id: Option, } impl TypeData for ComponentType { diff --git a/crates/wasmparser/src/validator/names.rs b/crates/wasmparser/src/validator/names.rs index 0b77e65a8a..0148d566c4 100644 --- a/crates/wasmparser/src/validator/names.rs +++ b/crates/wasmparser/src/validator/names.rs @@ -524,9 +524,19 @@ impl<'a> InterfaceName<'a> { } /// Returns the `1.2.3` in `a:b:c/d/e@1.2.3` - pub fn version(&self) -> Option { - let at = self.0.find('@')?; - Some(Version::parse(&self.0[at + 1..]).unwrap()) + pub fn version(&self, suffix: Option<&str>) -> Result, semver::Error> { + let Some(at) = self.0.find('@') else { + return Ok(None); + }; + let prefix = &self.0[at + 1..]; + match suffix { + // FIXME: this should perform full validation of the version + // suffix/prefix, notably that "prefix" is indeed the "semver track" + // that is expected. For example "1.2.3" means that prefix must be + // "1", nothing else. This validation is deferred to a future PR. + Some(suffix) => Ok(Some(Version::parse(&format!("{prefix}{suffix}"))?)), + None => Ok(Some(Version::parse(prefix)?)), + } } } @@ -659,8 +669,8 @@ impl<'a> ComponentNameParser<'a> { } // pkgname ::= ? - fn pkg_name(&mut self, require_projection: bool) -> Result<()> { - self.pkg_path(require_projection)?; + fn pkg_name(&mut self, is_interface_name: bool) -> Result<()> { + self.pkg_path(is_interface_name)?; if self.eat_str("@") { let version = match self.eat_up_to('>') { @@ -668,7 +678,11 @@ impl<'a> ComponentNameParser<'a> { None => self.take_rest(), }; - self.semver(version)?; + // Validation of the semver of interface names is deferred to full + // component validation with access to the `version_suffix` field. + if !is_interface_name { + self.semver(version)?; + } } Ok(()) diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 2f48a0596b..4ceed09fcc 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -393,13 +393,31 @@ impl Printer<'_, '_> { } fn print_component_extern_name(&mut self, name: &ComponentExternName<'_>) -> Result<()> { - self.print_str(name.name)?; - if let Some(implements) = name.implements { + let ComponentExternName { + name, + implements, + version_suffix, + external_id, + } = name; + self.print_str(name)?; + if let Some(implements) = implements { self.result.write_str(" ")?; self.start_group("implements ")?; self.print_str(implements)?; self.end_group()?; } + if let Some(version_suffix) = version_suffix { + self.result.write_str(" ")?; + self.start_group("versionsuffix ")?; + self.print_str(version_suffix)?; + self.end_group()?; + } + if let Some(external_id) = external_id { + self.result.write_str(" ")?; + self.start_group("external-id ")?; + self.print_str(external_id)?; + self.end_group()?; + } Ok(()) } diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index 4344d55e30..c58c0ce777 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -1052,6 +1052,8 @@ impl<'a> From> for wasm_encoder::ComponentExternName<'a> wasm_encoder::ComponentExternName { name: name.name.into(), implements: name.implements.map(|i| i.into()), + version_suffix: name.version_suffix.map(|i| i.into()), + external_id: name.external_id.map(|i| i.into()), } } } diff --git a/crates/wast/src/component/component.rs b/crates/wast/src/component/component.rs index b0d69dfaeb..04fa4ab2a9 100644 --- a/crates/wast/src/component/component.rs +++ b/crates/wast/src/component/component.rs @@ -161,54 +161,54 @@ impl<'a> Parse<'a> for ComponentField<'a> { fn parse(parser: Parser<'a>) -> Result { if parser.peek::()? { if parser.peek2::()? { - return Ok(Self::CoreModule(parser.parse()?)); + return parser.parse().map(Self::CoreModule); } if parser.peek2::()? { - return Ok(Self::CoreInstance(parser.parse()?)); + return parser.parse().map(Self::CoreInstance); } if parser.peek2::()? { - return Ok(Self::CoreType(parser.parse()?)); + return parser.parse().map(Self::CoreType); } if parser.peek2::()? { - return Ok(Self::CoreFunc(parser.parse()?)); + return parser.parse().map(Self::CoreFunc); } if parser.peek2::()? { parser.parse::()?; - return Ok(Self::CoreRec(parser.parse()?)); + return parser.parse().map(Self::CoreRec); } } else { if parser.peek::()? { - return Ok(Self::Component(parser.parse()?)); + return parser.parse().map(Self::Component); } if parser.peek::()? { - return Ok(Self::Instance(parser.parse()?)); + return parser.parse().map(Self::Instance); } if parser.peek::()? { - return Ok(Self::Alias(parser.parse()?)); + return parser.parse().map(Self::Alias); } if parser.peek::()? { - return Ok(Self::Type(Type::parse_maybe_with_inline_exports(parser)?)); + return Type::parse_maybe_with_inline_exports(parser).map(Self::Type); } if parser.peek::()? { - return Ok(Self::Import(parser.parse()?)); + return parser.parse().map(Self::Import); } if parser.peek::()? { - return Ok(Self::Func(parser.parse()?)); + return parser.parse().map(Self::Func); } if parser.peek::()? { - return Ok(Self::Export(parser.parse()?)); + return parser.parse().map(Self::Export); } if parser.peek::()? { - return Ok(Self::Start(parser.parse()?)); + return parser.parse().map(Self::Start); } if parser.peek::()? { - return Ok(Self::CanonicalFunc(parser.parse()?)); + return parser.parse().map(Self::CanonicalFunc); } if parser.peek::()? { - return Ok(Self::Custom(parser.parse()?)); + return parser.parse().map(Self::Custom); } if parser.peek::()? { - return Ok(Self::Producers(parser.parse()?)); + return parser.parse().map(Self::Producers); } } Err(parser.error("expected valid component field")) diff --git a/crates/wast/src/component/import.rs b/crates/wast/src/component/import.rs index cf2a9f5b1a..dad95152a0 100644 --- a/crates/wast/src/component/import.rs +++ b/crates/wast/src/component/import.rs @@ -30,6 +30,10 @@ pub struct ComponentExternName<'a> { pub name: &'a str, /// For imports, an optional `(implements "...")` directive. pub implements: Option<&'a str>, + /// For imports, an optional `(versionsuffix "...")` directive. + pub version_suffix: Option<&'a str>, + /// For imports, an optional `(external-id "...")` directive. + pub external_id: Option<&'a str>, } impl<'a> Parse<'a> for ComponentExternName<'a> { @@ -56,7 +60,28 @@ impl<'a> Parse<'a> for ComponentExternName<'a> { } else { None }; - Ok(ComponentExternName { name, implements }) + let version_suffix = if parser.peek2::()? { + Some(parser.parens(|p| { + p.parse::()?; + p.parse() + })?) + } else { + None + }; + let external_id = if parser.peek2::()? { + Some(parser.parens(|p| { + p.parse::()?; + p.parse() + })?) + } else { + None + }; + Ok(ComponentExternName { + name, + implements, + version_suffix, + external_id, + }) } } diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index 458610bcff..8dbfeea87d 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -613,6 +613,8 @@ pub mod kw { custom_keyword!(thread_suspend_then_promote = "thread.suspend-then-promote"); custom_keyword!(thread_yield_then_promote = "thread.yield-then-promote"); custom_keyword!(cancellable); + custom_keyword!(versionsuffix); + custom_keyword!(external_id = "external-id"); } /// Common annotations used to parse WebAssembly text files. diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 00543cf852..83d1f894c7 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -565,6 +565,8 @@ impl<'a> EncodingState<'a> { wasm_encoder::ComponentExternName { name: name.into(), implements: info.implements.as_deref().map(|s| s.into()), + external_id: info.external_id.as_deref().map(|s| s.into()), + version_suffix: None, }, ComponentTypeRef::Instance(instance_type_idx), ); @@ -950,6 +952,8 @@ impl<'a> EncodingState<'a> { wasm_encoder::ComponentExternName { name: export_name.into(), implements: resolve.implements_value(key, item).map(|s| s.into()), + external_id: resolve.external_id_value(key, item).map(|s| s.into()), + version_suffix: None, }, ComponentExportKind::Instance, instance_index, diff --git a/crates/wit-component/src/encoding/wit.rs b/crates/wit-component/src/encoding/wit.rs index 42171ea7dc..3fb5969fda 100644 --- a/crates/wit-component/src/encoding/wit.rs +++ b/crates/wit-component/src/encoding/wit.rs @@ -129,6 +129,8 @@ fn component_extern_name( ComponentExternName { name: resolve.name_world_key(key).into(), implements: resolve.implements_value(key, item).map(|s| s.into()), + external_id: resolve.external_id_value(key, item).map(|s| s.into()), + version_suffix: None, } } diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index c9a699b0b0..fa01a5c391 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -50,6 +50,7 @@ pub struct ImportedInterface { pub lowerings: IndexMap<(String, AbiVariant), Lowering>, pub interface: Option, pub implements: Option, + pub external_id: Option, } #[derive(Debug)] @@ -260,15 +261,18 @@ impl<'a> ComponentWorld<'a> { WorldItem::Interface { id, .. } => Some(*id), }; let implements = resolve.implements_value(key, item); + let external_id = resolve.external_id_value(key, item); let interface = import_map .entry(import_map_key) .or_insert_with(|| ImportedInterface { interface: interface_id, lowerings: Default::default(), implements: implements.clone(), + external_id: external_id.clone(), }); assert_eq!(interface.interface, interface_id); assert_eq!(interface.implements, implements); + assert_eq!(interface.external_id, external_id); match item { WorldItem::Function(func) => { interface.add_func(required, resolve, func); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index c29ab79737..d06e4565a6 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -457,7 +457,15 @@ impl WitPrinter { // `docs`); for an inline `import x: interface { .. }` with no statement // docs fall back to the interface definition's docs. let docs = match item { - WorldItem::Interface { id, docs, .. } => { + WorldItem::Interface { + id, + docs, + external_id, + .. + } => { + if let Some(id) = external_id { + self.print_external_id(id); + } if docs.contents.is_some() { Some(docs) } else if matches!(name, WorldKey::Name(_)) { @@ -1176,6 +1184,37 @@ impl WitPrinter { } } } + + fn print_external_id(&mut self, id: &str) { + self.output.keyword("@external-id"); + self.output.str("(\""); + let mut buf = [0; 4]; + for c in id.chars() { + if c.is_ascii_alphanumeric() + || c == '-' + || c == '_' + || c == '.' + || c == '/' + || c == ':' + || c == ' ' + { + self.output.push_str(c.encode_utf8(&mut buf)); + continue; + } + match c { + '\\' => self.output.push_str("\\\\"), + '"' => self.output.push_str("\\\""), + '\n' => self.output.push_str("\\n"), + '\r' => self.output.push_str("\\r"), + '\t' => self.output.push_str("\\t"), + _ => { + self.output.push_str(&format!("\\u{{{:x}}}", c as u32)); + } + } + } + self.output.str("\")"); + self.output.newline(); + } } fn is_keyword(name: &str) -> bool { diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index d45594396e..76114b6aa5 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -2417,11 +2417,16 @@ impl NameMangling for Legacy { _ => bail!("module requires an import interface named `{module}`"), }; + // FIXME: this prevents core wasm from importing from `@1` or + // `@0.1`, for example. More refactoring will be necessary to enable + // that. + let version = name.version(None)?; + // Prioritize an exact match based on versions, so try that first. let pkgname = PackageName { namespace: name.namespace().to_string(), name: name.package().to_string(), - version: name.version(), + version: version.clone(), }; if let Some(pkg) = resolve.package_names.get(&pkgname) { if let Some(id) = resolve.packages[*pkg] @@ -2473,7 +2478,7 @@ impl NameMangling for Legacy { continue; } - let module_version = match name.version() { + let module_version = match &version { Some(version) => version, None => continue, }; diff --git a/crates/wit-dylib/tests/roundtrip.rs b/crates/wit-dylib/tests/roundtrip.rs index 01238dc327..52691531f3 100644 --- a/crates/wit-dylib/tests/roundtrip.rs +++ b/crates/wit-dylib/tests/roundtrip.rs @@ -89,6 +89,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { stability: Default::default(), docs: Default::default(), span: Default::default(), + external_id: Default::default(), }, ) }) @@ -203,6 +204,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { stability: Default::default(), docs: Default::default(), span: Default::default(), + external_id: Default::default(), }, ); resolve.worlds[caller].imports.insert( @@ -212,6 +214,7 @@ fn run_one(u: &mut Unstructured<'_>) -> Result<()> { stability: Default::default(), docs: Default::default(), span: Default::default(), + external_id: Default::default(), }, ); diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml index aab53e80d1..830ac0ff5b 100644 --- a/crates/wit-parser/Cargo.toml +++ b/crates/wit-parser/Cargo.toml @@ -34,7 +34,7 @@ wat = { workspace = true, optional = true, features = ['component-model'] } default = ['std', 'serde', 'decoding'] # Enables use of std::path::Path and filesystem-related APIs. -std = [] +std = ['semver/std'] # Enables support for `derive(Serialize, Deserialize)` on many structures, such # as `Resolve`, which can assist when encoding `Resolve` as JSON for example. diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 929e36e5ac..d5a087554e 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -1651,6 +1651,7 @@ enum Attribute<'a> { Since { span: Span, version: Version }, Unstable { span: Span, feature: Id<'a> }, Deprecated { span: Span, version: Version }, + ExternalId { span: Span, id: String }, } impl<'a> Attribute<'a> { @@ -1692,6 +1693,15 @@ impl<'a> Attribute<'a> { version, } } + "external-id" => { + tokens.expect(Token::LeftParen)?; + let span = tokens.expect(Token::StringLiteral)?; + tokens.expect(Token::RightParen)?; + Attribute::ExternalId { + span: id.span, + id: tokens.string_literal(span)?, + } + } other => { return Err(ParseError::new_syntax( id.span, @@ -1703,14 +1713,6 @@ impl<'a> Attribute<'a> { } Ok(ret) } - - fn span(&self) -> Span { - match self { - Attribute::Since { span, .. } - | Attribute::Unstable { span, .. } - | Attribute::Deprecated { span, .. } => *span, - } - } } fn eat_id(tokens: &mut Tokenizer<'_>, expected: &str) -> ParseResult { diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index eaa74b57e7..6f38c27dd8 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -1,5 +1,7 @@ +use alloc::string::String; #[cfg(test)] -use alloc::{vec, vec::Vec}; +use alloc::vec; +use alloc::vec::Vec; use core::char; use core::fmt; use core::result::Result; @@ -109,6 +111,7 @@ pub enum Token { Slash, Plus, Minus, + StringLiteral, Use, Type, @@ -170,7 +173,6 @@ pub enum Error { ForbiddenCodepoint(u32, char), InvalidCharInId(u32, char), IdPartEmpty(u32), - InvalidEscape(u32, char), Unexpected(u32, char), UnterminatedComment(u32), Wanted { @@ -178,6 +180,15 @@ pub enum Error { expected: &'static str, found: &'static str, }, + InvalidUnicodeValue(u32, u32), + InvalidStringElement(u32, char), + InvalidStringEscape(u32, char), + WantedChar(u32, char), + UnexpectedEof(u32), + InvalidUtf8(u32, core::str::Utf8Error), + NumberTooBig(u32), + LoneUnderscore(u32), + InvalidHexDigit(u32, char), } impl<'a> Tokenizer<'a> { @@ -308,6 +319,10 @@ impl<'a> Tokenizer<'a> { } ExplicitId } + '"' => { + self.expect_string_literal(start)?; + StringLiteral + } ch if is_keylike_start(ch) => { let remaining = self.chars.chars.as_str().len(); let mut iter = self.chars.clone(); @@ -435,10 +450,119 @@ impl<'a> Tokenizer<'a> { } } + fn expect_any_char(&mut self) -> Result<(u32, char), Error> { + let end = self.eof_span().end; + let (pos, c) = self.chars.next().ok_or(Error::UnexpectedEof(end))?; + let pos = u32::try_from(pos).unwrap(); + Ok((pos, c)) + } + + fn expect_char(&mut self, expected: char) -> Result<(), Error> { + let (pos, actual) = self.expect_any_char()?; + if actual == expected { + Ok(()) + } else { + Err(Error::WantedChar(pos, expected)) + } + } + + pub fn string_literal(&mut self, span: Span) -> Result { + let input = self.get_span(span); + let input = &input[1..]; + Tokenizer::new(input, span.start + 1)?.expect_string_literal(span.start + 1) + } + + fn expect_string_literal(&mut self, start: u32) -> Result { + let mut buf = Vec::new(); + loop { + let (pos, c) = self.expect_any_char()?; + match c { + '"' => break, + '\\' => { + let (pos, c) = self.expect_any_char()?; + match c { + '"' => buf.push(b'"'), + '\'' => buf.push(b'\''), + 't' => buf.push(b'\t'), + 'n' => buf.push(b'\n'), + 'r' => buf.push(b'\r'), + '\\' => buf.push(b'\\'), + 'u' => { + self.expect_char('{')?; + let n = self.eat_hexnum()?; + let c = char::from_u32(n).ok_or(Error::InvalidUnicodeValue(pos, n))?; + buf.extend(c.encode_utf8(&mut [0; 4]).as_bytes()); + self.expect_char('}')?; + } + c1 if c1.is_ascii_hexdigit() => { + let (_, c2) = self.eat_hexdigit()?; + buf.push(to_hex(c1) * 16 + c2); + } + c => return Err(Error::InvalidStringEscape(pos, c)), + } + } + c if (c as u32) < 0x20 || c as u32 == 0x7f => { + return Err(Error::InvalidStringElement(pos, c)); + } + c => buf.extend(c.encode_utf8(&mut [0; 4]).as_bytes()), + } + } + match String::from_utf8(buf) { + Ok(s) => Ok(s), + Err(e) => Err(Error::InvalidUtf8(start, e.utf8_error())), + } + } + pub fn eof_span(&self) -> Span { let end = self.span_offset + u32::try_from(self.input.len()).unwrap(); Span::new(end, end) } + + fn eat_hexnum(&mut self) -> Result { + let (pos, n) = self.eat_hexdigit()?; + let mut last_underscore = false; + let mut n = n as u32; + loop { + if self.eatc('_') { + last_underscore = true; + continue; + } + let (pos, c) = self.clone().expect_any_char()?; + if !c.is_ascii_hexdigit() { + break; + } + last_underscore = false; + self.chars.next(); + n = n + .checked_mul(16) + .and_then(|n| n.checked_add(to_hex(c) as u32)) + .ok_or(Error::NumberTooBig(pos))?; + } + if last_underscore { + return Err(Error::LoneUnderscore(pos)); + } + Ok(n) + } + + /// Reads a hexadecimal digit from the input stream, returning where it's + /// defined and the hex value. Returns an error on EOF or an invalid hex + /// digit. + fn eat_hexdigit(&mut self) -> Result<(u32, u8), Error> { + let (pos, ch) = self.expect_any_char()?; + if ch.is_ascii_hexdigit() { + Ok((pos, to_hex(ch))) + } else { + Err(Error::InvalidHexDigit(pos, ch)) + } + } +} + +fn to_hex(c: char) -> u8 { + match c { + 'a'..='f' => c as u8 - b'a' + 10, + 'A'..='F' => c as u8 - b'A' + 10, + _ => c as u8 - b'0', + } } impl<'a> Iterator for CrlfFold<'a> { @@ -621,6 +745,7 @@ impl Token { Include => "keyword `include`", With => "keyword `with`", Async => "keyword `async`", + StringLiteral => "a string literal", } } } @@ -636,10 +761,18 @@ impl Error { | Error::ForbiddenCodepoint(at, _) | Error::InvalidCharInId(at, _) | Error::IdPartEmpty(at) - | Error::InvalidEscape(at, _) | Error::Unexpected(at, _) - | Error::UnterminatedComment(at) => *at, - Error::Wanted { at, .. } => *at, + | Error::UnterminatedComment(at) + | Error::InvalidUnicodeValue(at, _) + | Error::InvalidStringElement(at, _) + | Error::InvalidStringEscape(at, _) + | Error::WantedChar(at, _) + | Error::UnexpectedEof(at) + | Error::InvalidUtf8(at, _) + | Error::NumberTooBig(at) + | Error::LoneUnderscore(at) + | Error::InvalidHexDigit(at, _) + | Error::Wanted { at, .. } => *at, } } } @@ -669,7 +802,15 @@ impl fmt::Display for Error { } => write!(f, "expected {expected}, found {found}"), Error::InvalidCharInId(_, ch) => write!(f, "invalid character in identifier {ch:?}"), Error::IdPartEmpty(_) => write!(f, "identifiers must have characters between '-'s"), - Error::InvalidEscape(_, ch) => write!(f, "invalid escape in string {ch:?}"), + Error::InvalidUnicodeValue(_, val) => write!(f, "invalid unicode value {val:#x}"), + Error::InvalidStringElement(_, c) => write!(f, "invalid string character {c:?}"), + Error::InvalidStringEscape(_, c) => write!(f, "invalid string escape {c:?}"), + Error::WantedChar(_, c) => write!(f, "expected character {c:?}"), + Error::UnexpectedEof(_) => write!(f, "unexpected end of file"), + Error::InvalidUtf8(_, err) => write!(f, "invalid UTF-8: {err}"), + Error::NumberTooBig(_) => write!(f, "number is too big to fit in a u32"), + Error::LoneUnderscore(_) => write!(f, "trailing underscore in number"), + Error::InvalidHexDigit(_, c) => write!(f, "invalid hex digit {c:?}"), } } } @@ -818,3 +959,72 @@ fn test_tokenizer() { assert!(collect("\u{c}").is_err(), "control code"); assert!(collect("\u{85}").is_err(), "control code"); } + +#[test] +fn test_strings() { + #[track_caller] + fn test(s: &str, expected: Result<&str, Error>) { + let actual = (|| { + let mut t = Tokenizer::new(s, 0)?; + let next = t.next()?; + assert!( + matches!(next, Some((_, Token::StringLiteral))), + "{s:?} didn't tokenize as a string" + ); + assert!(t.next()?.is_none(), "extra tokens after string: {s:?}"); + let (span, _) = next.unwrap(); + t.string_literal(span) + })(); + match (&actual, &expected) { + (Ok(actual), Ok(expected)) => assert_eq!(actual, expected), + (Err(actual), Err(expected)) => assert_eq!(actual, expected), + (Ok(_) | Err(_), _) => panic!("expected error {expected:?}, but got Ok({actual:?})"), + } + } + + // smoke test + test("\"\"", Ok("")); + test("\"a\"", Ok("a")); + test("\"a b c\"", Ok("a b c")); + + // single-char-escapes + test("\"\\\"\"", Ok("\"")); + test("\"\\t\"", Ok("\t")); + test("\"\\n\"", Ok("\n")); + test("\"\\r\"", Ok("\r")); + test("\"\\\\\"", Ok("\\")); + test("\"\\h\"", Err(Error::InvalidStringEscape(2, 'h'))); + + // double-hex-digit + test("\"\\00\"", Ok("\0")); + test("\"\\01\"", Ok("\x01")); + test("\"\\0f\"", Ok("\x0f")); + test("\"\\0_1\"", Err(Error::InvalidHexDigit(3, '_'))); + test("\"\\0g\"", Err(Error::InvalidHexDigit(3, 'g'))); + #[allow(invalid_from_utf8)] + test( + "\"\\ff\"", + Err(Error::InvalidUtf8( + 0, + core::str::from_utf8(&[0xff]).unwrap_err(), + )), + ); + + // unicode escape + test("\"\\u{0}\"", Ok("\0")); + test("\"\\u{1}\"", Ok("\x01")); + test("\"\\u{1_2_3_f}\"", Ok("\u{123f}")); + test("\"\\u0000\"", Err(Error::WantedChar(3, '{'))); + test("\"\\u{0h\"", Err(Error::WantedChar(5, '}'))); + test("\"\\u{1_}\"", Err(Error::LoneUnderscore(4))); + test( + "\"\\u{fffffffffffffffffffff}\"", + Err(Error::NumberTooBig(12)), + ); + test( + "\"\\u{ffff_ffff}\"", + Err(Error::InvalidUnicodeValue(2, u32::MAX)), + ); + + test("\"\t\"", Err(Error::InvalidStringElement(1, '\t'))); +} diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 665bca644a..c42f0191df 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -755,33 +755,39 @@ impl<'a> Resolver<'a> { self.resolve_interface(id, items, docs, attrs)?; self.type_lookup = prev; let stability = self.interfaces[id].stability.clone(); + let external_id = self.external_id(attrs)?; Ok(WorldItem::Interface { id, stability, docs: Default::default(), span: name.span, + external_id, }) } ast::ExternKind::Path(path) => { let stability = self.stability(attrs)?; + let external_id = self.external_id(attrs)?; let docs = self.docs(docs); let (item, name, item_span) = self.resolve_ast_item_path(path)?; let id = self.extract_iface_from_item(&item, &name, item_span)?; Ok(WorldItem::Interface { id, stability, + external_id, docs, span: item_span, }) } ast::ExternKind::NamedPath(name, path) => { let stability = self.stability(attrs)?; + let external_id = self.external_id(attrs)?; let docs = self.docs(docs); let (item, iface_name, item_span) = self.resolve_ast_item_path(path)?; let id = self.extract_iface_from_item(&item, &iface_name, item_span)?; Ok(WorldItem::Interface { id, stability, + external_id, docs, span: name.span, }) @@ -1592,61 +1598,84 @@ impl<'a> Resolver<'a> { } fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> ParseResult { - match attrs { - [] => Ok(Stability::Unknown), - - [ast::Attribute::Since { version, .. }] => Ok(Stability::Stable { - since: version.clone(), - deprecated: None, - }), - - [ - ast::Attribute::Since { version, .. }, - ast::Attribute::Deprecated { - version: deprecated, - .. - }, - ] - | [ - ast::Attribute::Deprecated { - version: deprecated, - .. - }, - ast::Attribute::Since { version, .. }, - ] => Ok(Stability::Stable { - since: version.clone(), - deprecated: Some(deprecated.clone()), - }), - - [ast::Attribute::Unstable { feature, .. }] => Ok(Stability::Unstable { - feature: feature.name.to_string(), - deprecated: None, - }), - - [ - ast::Attribute::Unstable { feature, .. }, - ast::Attribute::Deprecated { version, .. }, - ] - | [ - ast::Attribute::Deprecated { version, .. }, - ast::Attribute::Unstable { feature, .. }, - ] => Ok(Stability::Unstable { - feature: feature.name.to_string(), - deprecated: Some(version.clone()), + let mut since = None; + let mut since_span = Span::default(); + let mut deprecated = None; + let mut deprecated_span = Span::default(); + let mut unstable = None; + for attr in attrs { + match attr { + ast::Attribute::Since { version, span } => { + if since.is_some() { + return Err(ParseError::new_syntax( + *span, + "cannot specify @since twice".to_owned(), + )); + } + since = Some(version.clone()); + since_span = *span; + } + ast::Attribute::Deprecated { version, span } => { + if deprecated.is_some() { + return Err(ParseError::new_syntax( + *span, + "cannot specify @deprecated twice".to_owned(), + )); + } + deprecated = Some(version.clone()); + deprecated_span = *span; + } + ast::Attribute::Unstable { feature, span } => { + if unstable.is_some() { + return Err(ParseError::new_syntax( + *span, + "cannot specify @unstable twice".to_owned(), + )); + } + unstable = Some(feature.name.to_string()); + } + _ => {} + } + } + match (since, deprecated, unstable) { + (Some(since), deprecated, None) => Ok(Stability::Stable { since, deprecated }), + (None, deprecated, Some(feature)) => Ok(Stability::Unstable { + feature, + deprecated, }), - [ast::Attribute::Deprecated { span, .. }] => { + (Some(_), _deprecated, Some(_)) => { return Err(ParseError::new_syntax( - *span, - "must pair @deprecated with either @since or @unstable".to_owned(), + since_span, + "cannot specify both @since and @unstable".to_owned(), )); } - [_, b, ..] => { + (None, Some(_), None) => { return Err(ParseError::new_syntax( - b.span(), - "unsupported combination of attributes".to_owned(), + deprecated_span, + "cannot specify both @deprecated without @since or @unstable".to_owned(), )); } + (None, None, None) => Ok(Stability::Unknown), + } + } + + fn external_id(&mut self, attrs: &[ast::Attribute<'_>]) -> ParseResult> { + let mut external_id = None; + for attr in attrs { + match attr { + ast::Attribute::ExternalId { span, id } => { + if external_id.is_some() { + return Err(ParseError::new_syntax( + *span, + "cannot specify @external-id twice".to_owned(), + )); + } + external_id = Some(id.clone()) + } + _ => {} + } } + Ok(external_id) } fn resolve_params( diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index ca865ab757..4b2d949345 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -12,7 +12,7 @@ use wasmparser::{ WasmFeatures, component_types::{ ComponentAnyTypeId, ComponentDefinedType, ComponentEntityType, ComponentFuncType, - ComponentInstanceType, ComponentItem, ComponentType, ComponentValType, + ComponentItem, ComponentType, ComponentValType, }, names::{ComponentName, ComponentNameKind}, types, @@ -30,15 +30,9 @@ struct ComponentInfo { package_metadata: Option, } -struct DecodingExport { - name: String, - kind: ComponentExternalKind, - index: u32, -} - enum Extern { - Import(String), - Export(DecodingExport), + Import(ComponentItem), + Export(ComponentItem), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -97,23 +91,23 @@ impl ComponentInfo { Payload::ComponentImportSection(s) if depth == 1 => { for import in s { let import = import?; - externs.push(( - import.name.name.to_string(), - Extern::Import(import.name.name.to_string()), - )); + let ty = validator + .types(0) + .unwrap() + .component_item_for_import(import.name.name) + .unwrap(); + externs.push((import.name.name.to_string(), Extern::Import(ty.clone()))); } } Payload::ComponentExportSection(s) if depth == 1 => { for export in s { let export = export?; - externs.push(( - export.name.name.to_string(), - Extern::Export(DecodingExport { - name: export.name.name.to_string(), - kind: export.kind, - index: export.index, - }), - )); + let ty = validator + .types(0) + .unwrap() + .component_item_for_export(export.name.name) + .unwrap(); + externs.push((export.name.name.to_string(), Extern::Export(ty.clone()))); } } #[cfg(feature = "serde")] @@ -162,11 +156,10 @@ impl ComponentInfo { Extern::Export(e) => e, _ => return false, }; - match export.kind { - ComponentExternalKind::Type => matches!( - self.types.as_ref().component_any_type_at(export.index), - ComponentAnyTypeId::Component(_) - ), + match export.ty { + ComponentEntityType::Type { created, .. } => { + matches!(created, ComponentAnyTypeId::Component(_)) + } _ => false, } }) { @@ -195,15 +188,13 @@ impl ComponentInfo { Extern::Export(e) => e, _ => unreachable!(), }; - let id = self.types.as_ref().component_type_at(export.index); - let ty = &self.types[id]; if pkg.is_some() { bail!("more than one top-level exported component type found"); } let name = ComponentName::new(name, 0).unwrap(); pkg = Some( decoder - .decode_v1_package(&name, ty) + .decode_v1_package(&name, export) .with_context(|| format!("failed to decode document `{name}`"))?, ); } @@ -233,10 +224,13 @@ impl ComponentInfo { Extern::Export(e) => e, _ => unreachable!(), }; - - let index = export.index; - let id = self.types.as_ref().component_type_at(index); - let component = &self.types[id]; + let component = match export.ty { + ComponentEntityType::Type { + created: ComponentAnyTypeId::Component(id), + .. + } => &self.types[id], + _ => unreachable!(), + }; // The single export of this component will determine if it's a world or an interface: // worlds export a component, while interfaces export an instance. @@ -249,17 +243,17 @@ impl ComponentInfo { let name = component.exports.keys().nth(0).unwrap(); - let name = match component.exports[name].ty { - ComponentEntityType::Component(ty) => { - let package_name = - decoder.decode_world(name.as_str(), &self.types[ty], &mut fields)?; + let item = &component.exports[name]; + let name = match item.ty { + ComponentEntityType::Component(_) => { + let package_name = decoder.decode_world(name.as_str(), item, &mut fields)?; package_name } - ComponentEntityType::Instance(ty) => { + ComponentEntityType::Instance(_) => { let package_name = decoder.decode_interface( name.as_str(), &component.imports, - &self.types[ty], + item, &mut fields, )?; package_name @@ -332,13 +326,13 @@ impl ComponentInfo { interfaces: &mut package.interfaces, }; - for (_name, item) in self.externs.iter() { + for (name, item) in self.externs.iter() { match item { - Extern::Import(name) => { - decoder.decode_component_import(name, world, &mut fields)? + Extern::Import(i) => { + decoder.decode_component_import(name, i, world, &mut fields)? } - Extern::Export(export) => { - decoder.decode_component_export(export, world, &mut fields)? + Extern::Export(e) => { + decoder.decode_component_export(name, e, world, &mut fields)? } } } @@ -477,13 +471,9 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { assert_eq!(ty.imports.len(), 0); assert_eq!(ty.exports.len(), 1); let name = ty.exports.keys().nth(0).unwrap(); - let ty = match ty.exports[0].ty { - ComponentEntityType::Component(ty) => ty, - _ => unreachable!(), - }; let name = decoder.decode_world( name, - &types[ty], + &ty.exports[0], &mut PackageFields { interfaces: &mut interfaces, worlds: &mut worlds, @@ -539,15 +529,19 @@ impl WitPackageDecoder<'_> { } } - fn decode_v1_package(&mut self, name: &ComponentName, ty: &ComponentType) -> Result { + fn decode_v1_package(&mut self, name: &ComponentName, item: &ComponentItem) -> Result { + let ty = match item.ty { + ComponentEntityType::Type { + created: ComponentAnyTypeId::Component(id), + .. + } => &self.types[id], + _ => unreachable!(), + }; + // Process all imports for this package first, where imports are // importing from remote packages. - for (name, ty) in ty.imports.iter() { - let ty = match ty.ty { - ComponentEntityType::Instance(idx) => &self.types[idx], - _ => bail!("import `{name}` is not an instance"), - }; - self.register_import(name, ty) + for (name, item) in ty.imports.iter() { + self.register_import(name, item) .with_context(|| format!("failed to process import `{name}`"))?; } @@ -557,7 +551,7 @@ impl WitPackageDecoder<'_> { // this case would be `foo:bar`. name: match name.kind() { ComponentNameKind::Interface(name) if name.interface().as_str() == "wit" => { - name.to_package_name() + name.to_package_name(item)? } _ => bail!("package name is not a valid id: {name}"), }, @@ -573,8 +567,7 @@ impl WitPackageDecoder<'_> { for (name, ty) in ty.exports.iter() { match ty.ty { - ComponentEntityType::Instance(idx) => { - let ty = &self.types[idx]; + ComponentEntityType::Instance(_) => { self.register_interface(name.as_str(), ty, &mut fields) .with_context(|| format!("failed to process export `{name}`"))?; } @@ -593,7 +586,7 @@ impl WitPackageDecoder<'_> { &mut self, name: &str, imports: &wasmparser::collections::IndexMap, - ty: &ComponentInstanceType, + item: &ComponentItem, fields: &mut PackageFields<'a>, ) -> Result { let component_name = self @@ -601,20 +594,16 @@ impl WitPackageDecoder<'_> { .context("expected world name to have an ID form")?; let package = match component_name.kind() { - ComponentNameKind::Interface(name) => name.to_package_name(), + ComponentNameKind::Interface(name) => name.to_package_name(item)?, _ => bail!("expected world name to be fully qualified"), }; for (name, ty) in imports.iter() { - let ty = match ty.ty { - ComponentEntityType::Instance(idx) => &self.types[idx], - _ => bail!("import `{name}` is not an instance"), - }; self.register_import(name, ty) .with_context(|| format!("failed to process import `{name}`"))?; } - let _ = self.register_interface(name, ty, fields)?; + let _ = self.register_interface(name, item, fields)?; Ok(package) } @@ -622,15 +611,19 @@ impl WitPackageDecoder<'_> { fn decode_world<'a>( &mut self, name: &str, - ty: &ComponentType, + item: &ComponentItem, fields: &mut PackageFields<'a>, ) -> Result { + let ty = match item.ty { + ComponentEntityType::Component(ty) => &self.types[ty], + _ => unreachable!(), + }; let kebab_name = self .parse_component_name(name) .context("expected world name to have an ID form")?; let package = match kebab_name.kind() { - ComponentNameKind::Interface(name) => name.to_package_name(), + ComponentNameKind::Interface(name) => name.to_package_name(item)?, _ => bail!("expected world name to be fully qualified"), }; @@ -642,18 +635,16 @@ impl WitPackageDecoder<'_> { fn decode_component_import<'a>( &mut self, name: &str, + item: &ComponentItem, world: WorldId, package: &mut PackageFields<'a>, ) -> Result<()> { log::debug!("decoding component import `{name}`"); - let item = self.types.as_ref().component_item_for_import(name).unwrap(); let owner = TypeOwner::World(world); let (name, item) = match item.ty { - ComponentEntityType::Instance(i) => { - let ty = &self.types[i]; - self.decode_world_instance(name, item.implements.as_deref(), ty, package) - .with_context(|| format!("failed to decode WIT from import `{name}`"))? - } + ComponentEntityType::Instance(_) => self + .decode_world_instance(name, item, package) + .with_context(|| format!("failed to decode WIT from import `{name}`"))?, ComponentEntityType::Func(i) => { let ty = &self.types[i]; let func = self @@ -685,28 +676,24 @@ impl WitPackageDecoder<'_> { fn decode_component_export<'a>( &mut self, - export: &DecodingExport, + name: &str, + item: &ComponentItem, world: WorldId, package: &mut PackageFields<'a>, ) -> Result<()> { - let name = &export.name; log::debug!("decoding component export `{name}`"); - let types = self.types.as_ref(); - let item = types.component_item_for_export(name).unwrap(); let (name, item) = match item.ty { ComponentEntityType::Func(i) => { - let ty = &types[i]; + let ty = &self.types[i]; let func = self .convert_function(name, ty, TypeOwner::World(world)) .with_context(|| format!("failed to decode function from export `{name}`"))?; (WorldKey::Name(name.to_string()), WorldItem::Function(func)) } - ComponentEntityType::Instance(i) => { - let ty = &types[i]; - self.decode_world_instance(name, item.implements.as_deref(), ty, package) - .with_context(|| format!("failed to decode WIT from export `{name}`"))? - } + ComponentEntityType::Instance(_) => self + .decode_world_instance(name, item, package) + .with_context(|| format!("failed to decode WIT from export `{name}`"))?, _ => { bail!("component export `{name}` was not a function or instance") } @@ -726,21 +713,20 @@ impl WitPackageDecoder<'_> { fn decode_world_instance<'a>( &mut self, name: &str, - implements: Option<&str>, - ty: &ComponentInstanceType, + item: &ComponentItem, package: &mut PackageFields<'a>, ) -> Result<(WorldKey, WorldItem)> { - let (key, id) = match implements { + let (key, id) = match &item.implements { Some(i) => { - let id = self.register_import(i, ty)?; + let id = self.register_import(i, item)?; (WorldKey::Name(name.to_string()), id) } None => match self.parse_component_name(name)?.kind() { ComponentNameKind::Interface(i) => { - let id = self.register_import(i.as_str(), ty)?; + let id = self.register_import(i.as_str(), item)?; (WorldKey::Interface(id), id) } - _ => self.register_interface(name, ty, package)?, + _ => self.register_interface(name, item, package)?, }, }; Ok(( @@ -750,6 +736,7 @@ impl WitPackageDecoder<'_> { stability: Default::default(), docs: Default::default(), span: Default::default(), + external_id: item.external_id.clone(), }, )) } @@ -763,10 +750,14 @@ impl WitPackageDecoder<'_> { /// dependency the foreign package structure, types, etc, all need to be /// created. For a local dependency it's instead ensured that all the types /// line up with the previous definitions. - fn register_import(&mut self, name: &str, ty: &ComponentInstanceType) -> Result { + fn register_import(&mut self, name: &str, item: &ComponentItem) -> Result { + let ty = match item.ty { + ComponentEntityType::Instance(idx) => &self.types[idx], + _ => bail!("import `{name}` is not an instance"), + }; let (is_local, interface) = match self.named_interfaces.get(name) { Some(id) => (true, *id), - None => (false, self.extract_dep_interface(name)?), + None => (false, self.extract_dep_interface(name, item)?), }; let owner = TypeOwner::Interface(interface); for (name, ty) in ty.exports.iter() { @@ -893,13 +884,17 @@ impl WitPackageDecoder<'_> { /// This will parse the `name_string` as a component model ID string and /// ensure that there's an `InterfaceId` corresponding to its components. - fn extract_dep_interface(&mut self, name_string: &str) -> Result { + fn extract_dep_interface( + &mut self, + name_string: &str, + item: &ComponentItem, + ) -> Result { let name = ComponentName::new(name_string, 0).unwrap(); let name = match name.kind() { ComponentNameKind::Interface(name) => name, _ => bail!("package name is not a valid id: {name_string}"), }; - let package_name = name.to_package_name(); + let package_name = name.to_package_name(item)?; // Lazily create a `Package` as necessary, along with the interface. let package = self .foreign_packages @@ -955,14 +950,18 @@ impl WitPackageDecoder<'_> { fn register_interface<'a>( &mut self, name: &str, - ty: &ComponentInstanceType, + item: &ComponentItem, package: &mut PackageFields<'a>, ) -> Result<(WorldKey, InterfaceId)> { + let ty = match item.ty { + ComponentEntityType::Instance(i) => &self.types[i], + _ => unreachable!(), + }; // If this interface's name is already known then that means this is an // interface that's both imported and exported. Use `register_import` // to draw connections between types and this interface's types. if self.named_interfaces.contains_key(name) { - let id = self.register_import(name, ty)?; + let id = self.register_import(name, item)?; return Ok((WorldKey::Interface(id), id)); } @@ -1128,9 +1127,8 @@ impl WitPackageDecoder<'_> { let owner = TypeOwner::World(self.resolve.worlds.next_id()); for (name, ty) in ty.imports.iter() { let (name, item) = match ty.ty { - ComponentEntityType::Instance(idx) => { - let t = &self.types[idx]; - self.decode_world_instance(name, ty.implements.as_deref(), t, package)? + ComponentEntityType::Instance(_) => { + self.decode_world_instance(name, ty, package)? } ComponentEntityType::Type { created, @@ -1158,9 +1156,8 @@ impl WitPackageDecoder<'_> { for (name, item) in ty.exports.iter() { let (name, item) = match item.ty { - ComponentEntityType::Instance(idx) => { - let ty = &self.types[idx]; - self.decode_world_instance(name, item.implements.as_deref(), ty, package)? + ComponentEntityType::Instance(_) => { + self.decode_world_instance(name, item, package)? } ComponentEntityType::Func(idx) => { @@ -1870,15 +1867,15 @@ impl Registrar<'_> { } pub(crate) trait InterfaceNameExt { - fn to_package_name(&self) -> PackageName; + fn to_package_name(&self, item: &ComponentItem) -> Result; } impl InterfaceNameExt for wasmparser::names::InterfaceName<'_> { - fn to_package_name(&self) -> PackageName { - PackageName { + fn to_package_name(&self, item: &ComponentItem) -> Result { + Ok(PackageName { namespace: self.namespace().to_string(), name: self.package().to_string(), - version: self.version(), - } + version: self.version(item.version_suffix.as_deref())?, + }) } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index f67f48f0fd..589991c53e 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -523,6 +523,8 @@ pub enum WorldItem { serde(skip_serializing_if = "Stability::is_unknown") )] stability: Stability, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + external_id: Option, /// Documentation attached to the `import`/`export` statement. #[cfg_attr( feature = "serde", diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index 20bbac02e2..adfa023f57 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -1547,6 +1547,18 @@ impl Resolve { None } + /// Returns the component model `external-id` value for the world import of + /// `key` and `item`. + /// + /// See the component model explainer and 🏷️ for more information on this feature. + pub fn external_id_value(&self, key: &WorldKey, item: &WorldItem) -> Option { + let _ = key; + if let WorldItem::Interface { external_id, .. } = item { + return external_id.clone(); + } + None + } + /// Returns the interface that `id` uses a type from, if it uses a type from /// a different interface than `id` is defined within. /// @@ -2087,6 +2099,7 @@ impl Resolve { id, stability, docs, + external_id, .. } => { self.elaborate_world_import( @@ -2095,6 +2108,7 @@ impl Resolve { *id, &stability, docs, + external_id.as_deref(), ); } @@ -2116,6 +2130,7 @@ impl Resolve { dep, &self.types[*id].stability, &Docs::default(), + None, ); } let prev = new_imports.insert(name.clone(), item.clone()); @@ -2170,6 +2185,7 @@ impl Resolve { id: InterfaceId, stability: &Stability, docs: &Docs, + external_id: Option<&str>, ) { if imports.contains_key(&key) { return; @@ -2183,6 +2199,7 @@ impl Resolve { dep, stability, &Docs::default(), + None, ); } let prev = imports.insert( @@ -2192,6 +2209,7 @@ impl Resolve { stability: stability.clone(), docs: docs.clone(), span: Default::default(), + external_id: external_id.map(|s| s.to_string()), }, ); assert!(prev.is_none()); @@ -2305,8 +2323,13 @@ impl Resolve { return false; } } - let (id, stability) = match &item { - WorldItem::Interface { id, stability, .. } => (*id, stability), + let (id, stability, external_id) = match &item { + WorldItem::Interface { + id, + stability, + external_id, + .. + } => (*id, stability, external_id), _ => unreachable!(), }; // If this is an import and it's already in the `imports` set then @@ -2320,6 +2343,7 @@ impl Resolve { stability: stability.clone(), docs: Default::default(), span: Default::default(), + external_id: external_id.clone(), }; let key = WorldKey::Interface(dep); let add_export = add_export && export_interfaces.contains_key(&key); @@ -2433,12 +2457,14 @@ impl Resolve { stability: Default::default(), docs: Default::default(), span: Default::default(), + external_id: Default::default(), }, &WorldItem::Interface { id: *replace_with, stability: Default::default(), docs: Default::default(), span: Default::default(), + external_id: Default::default(), }, ) .with_context(|| { diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index cb9d02c1ca..10fc176c63 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -123,6 +123,11 @@ impl Runner { }; if env::var_os("BLESS").is_some() { let normalized = normalize(&result, extension); + if let Ok(prev) = fs::read(&result_file) { + if normalized.as_bytes() == prev { + return Ok(()); + } + } fs::write(&result_file, normalized)?; } else { let expected = fs::read_to_string(&result_file).context(format!( diff --git a/crates/wit-smith/src/generate.rs b/crates/wit-smith/src/generate.rs index 461c8672c7..7a1a3bceb0 100644 --- a/crates/wit-smith/src/generate.rs +++ b/crates/wit-smith/src/generate.rs @@ -691,6 +691,17 @@ impl<'a> InterfaceGenerator<'a> { part.push_str("\n"); } + match kind { + ItemKind::AnonInterface(_) + | ItemKind::Interface(_) + | ItemKind::ImplementsInterface(_) => { + if u.arbitrary()? { + part.push_str("@external-id(\"hi\")\n"); + } + } + ItemKind::Func(_) | ItemKind::Type | ItemKind::Use | ItemKind::Include => {} + } + if let Some(dir) = direction { part.push_str(match dir { Direction::Import => "import ", diff --git a/tests/cli/component-model/canon-names.wast b/tests/cli/component-model/canon-names.wast new file mode 100644 index 0000000000..aa2d6712df --- /dev/null +++ b/tests/cli/component-model/canon-names.wast @@ -0,0 +1,24 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-canon-names + +(component + (component + (import "a:b/c@1" (versionsuffix ".2.3") (instance)) + (import "a:b/c@0.2" (versionsuffix ".3") (instance)) + (import "a:b/c@0.0.3" (instance)) + (import "a:b/c@1.2.3-rc.1" (instance)) + (import "a:b/c@0.2.3-rc.1" (instance)) + (import "a:b/c@0.0.3-rc.1" (instance)) + ) +) + +(assert_invalid + (component (import "a:b/c@1" (versionsuffix "2.3") (instance))) + "invalid interface version") + +(assert_invalid + (component (import "a:b/c@1" (versionsuffix ".2") (instance))) + "invalid interface version") + +(assert_invalid + (component (import "a:b/c@1" (versionsuffix ".2.3") (func))) + "only instances can have") diff --git a/tests/cli/component-model/implements.wast b/tests/cli/component-model/implements.wast index 3bf689ed6e..f848102839 100644 --- a/tests/cli/component-model/implements.wast +++ b/tests/cli/component-model/implements.wast @@ -72,7 +72,7 @@ (component (import "a" (implements "a:b/c") (func)) ) - "only instance names can have an `implements`") + "only instances can have an `implements`") (assert_invalid (component @@ -90,10 +90,63 @@ "not a valid name") (assert_invalid (component (type (instance (export "a" (implements "a:b/c") (func))))) - "only instance names") + "only instances") (assert_invalid (component (instance) (instance (export "x" (implements "a") (instance 0))) ) "must be an interface") + +;; ---------------------------------- external-id ---------------------------- + +;; goes on various things +(component + (component + (import "a" (external-id "") (instance)) + (import "b" (external-id "") (instance)) + (import "c" (external-id "") (instance)) + (import "a:b/c" (external-id "") (instance)) + + (instance $a) + + (export "a" (external-id "") (instance $a)) + (export "b" (external-id "") (instance $a)) + (export "c" (external-id "") (instance $a)) + (export "a:b/c" (external-id "") (instance $a)) + ) + + (type (instance + (export "a" (external-id "") (instance)) + )) + (type (component + (import "a" (external-id "") (instance)) + (export "a" (external-id "") (instance)) + )) + + (instance $a) + (instance + (export "a" (external-id "") (instance $a)) + ) +) + +;; totally unstructured +(component + (component + (import "a" (external-id "") (instance)) + (import "b" (external-id "hello, general kenobi") (instance)) + (import "c" (external-id "https://this.is.a/url") (instance)) + (import "d" (external-id "look\tma\nwhitespace\r\n") (instance)) + (import "e" (external-id "\00") (instance)) + ) +) + +;; must be utf-8 +(assert_malformed + (component quote "(import \"a\" (external-id \"\\ff\") (instance))") + "malformed UTF-8 encoding") + +;; only on instances +(assert_invalid + (component (import "a" (external-id "") (func))) + "only instances") diff --git a/tests/cli/dummy-external-id.wit b/tests/cli/dummy-external-id.wit new file mode 100644 index 0000000000..ec515f4cbf --- /dev/null +++ b/tests/cli/dummy-external-id.wit @@ -0,0 +1,14 @@ +// RUN: component embed % --dummy-names legacy | \ +// component new | \ +// validate -f cm-implements + +package x:name; + +interface i { + x: func(); +} + +world w { + @external-id("hi") + import i; +} diff --git a/tests/cli/dummy-external-id2.wit b/tests/cli/dummy-external-id2.wit new file mode 100644 index 0000000000..64625ea2f4 --- /dev/null +++ b/tests/cli/dummy-external-id2.wit @@ -0,0 +1,12 @@ +// RUN: component wit % + +package x:name; + +interface i { + x: func(); +} + +world w { + @external-id("hi \t") + import i; +} diff --git a/tests/cli/dummy-external-id2.wit.stdout b/tests/cli/dummy-external-id2.wit.stdout new file mode 100644 index 0000000000..e15a5b6c5d --- /dev/null +++ b/tests/cli/dummy-external-id2.wit.stdout @@ -0,0 +1,11 @@ +/// RUN: component wit % +package x:name; + +interface i { + x: func(); +} + +world w { + @external-id("hi /t") + import i; +} diff --git a/tests/cli/dump/alias.wat.stdout b/tests/cli/dump/alias.wat.stdout index a26f4f7100..8e08913a8a 100644 --- a/tests/cli/dump/alias.wat.stdout +++ b/tests/cli/dump/alias.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 1f | component type section 0xa | 01 | 1 count - 0xb | 42 04 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "f1", implements: None }, ty: Func(0) }, Type(Func(ComponentFuncType { async_: false, params: [("p1", Primitive(String))], result: None })), Export { name: ComponentExternName { name: "f2", implements: None }, ty: Func(1) }]) + 0xb | 42 04 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "f1", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }, Type(Func(ComponentFuncType { async_: false, params: [("p1", Primitive(String))], result: None })), Export { name: ComponentExternName { name: "f2", implements: None, version_suffix: None, external_id: None }, ty: Func(1) }]) | 00 01 00 04 | 00 02 66 31 | 01 00 01 40 @@ -12,7 +12,7 @@ | 01 01 0x29 | 0a 06 | component import section 0x2b | 01 | 1 count - 0x2c | 00 01 69 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "i", implements: None }, ty: Instance(0) } + 0x2c | 00 01 69 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "i", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) } | 00 0x31 | 06 13 | component alias section 0x33 | 03 | 3 count diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout index c5ff04c981..894c2aeeee 100644 --- a/tests/cli/dump/alias2.wat.stdout +++ b/tests/cli/dump/alias2.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) }, Export { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) }, Type(Component([])), Export { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) }]) + 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }, Export { name: ComponentExternName { name: "c", implements: None, version_suffix: None, external_id: None }, ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExternName { name: "d", implements: None, version_suffix: None, external_id: None }, ty: Instance(1) }, Type(Component([])), Export { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, ty: Component(2) }]) | 00 04 00 01 | 61 00 11 00 | 01 40 00 01 @@ -16,7 +16,7 @@ | 65 04 02 0x3a | 0a 06 | component import section 0x3c | 01 | 1 count - 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } + 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) } | 00 0x42 | 04 59 | [component 0] inline size 0x44 | 00 61 73 6d | version 13 (Component) @@ -26,30 +26,30 @@ 0x4f | 50 00 | [core type 0] Module([]) 0x51 | 0a 07 | component import section 0x53 | 01 | 1 count - 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } + 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) } | 11 00 0x5a | 07 05 | component type section 0x5c | 01 | 1 count 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x61 | 0a 0b | component import section 0x63 | 02 | 2 count - 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) } + 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, ty: Func(0) } | 00 - 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) } + 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentExternName { name: "c", implements: None, version_suffix: None, external_id: None }, ty: Value(Primitive(String)) } | 73 0x6e | 07 03 | component type section 0x70 | 01 | 1 count 0x71 | 42 00 | [type 1] Instance([]) 0x73 | 0a 06 | component import section 0x75 | 01 | 1 count - 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) } + 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "d", implements: None, version_suffix: None, external_id: None }, ty: Instance(1) } | 01 0x7b | 07 03 | component type section 0x7d | 01 | 1 count 0x7e | 41 00 | [type 2] Component([]) 0x80 | 0a 06 | component import section 0x82 | 01 | 1 count - 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) } + 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, ty: Component(2) } | 02 0x88 | 00 13 | custom section 0x8a | 0e 63 6f 6d | name: "component-name" @@ -86,7 +86,7 @@ 0xe2 | 03 02 01 00 | alias [type 0] Outer { kind: Type, count: 1, index: 0 } 0xe6 | 0a 06 | component import section 0xe8 | 01 | 1 count - 0xe9 | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } + 0xe9 | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) } | 00 0xee | 00 1b | custom section 0xf0 | 0e 63 6f 6d | name: "component-name" @@ -112,7 +112,7 @@ | 65 0x128 | 05 24 | component instance section 0x12a | 02 | 2 count - 0x12b | 01 05 00 01 | [instance 4] FromExports([ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Module, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "b", implements: None }, kind: Func, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "c", implements: None }, kind: Value, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "d", implements: None }, kind: Instance, index: 3, ty: None }, ComponentExport { name: ComponentExternName { name: "e", implements: None }, kind: Component, index: 3, ty: None }]) + 0x12b | 01 05 00 01 | [instance 4] FromExports([ComponentExport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, kind: Module, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, kind: Func, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "c", implements: None, version_suffix: None, external_id: None }, kind: Value, index: 1, ty: None }, ComponentExport { name: ComponentExternName { name: "d", implements: None, version_suffix: None, external_id: None }, kind: Instance, index: 3, ty: None }, ComponentExport { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, kind: Component, index: 3, ty: None }]) | 61 00 11 01 | 00 01 62 01 | 01 00 01 63 diff --git a/tests/cli/dump/bundled.wat.stdout b/tests/cli/dump/bundled.wat.stdout index 461507aa10..16072bbc3c 100644 --- a/tests/cli/dump/bundled.wat.stdout +++ b/tests/cli/dump/bundled.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 06 01 70 | [type 0] Instance([Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("len", Primitive(U32))], result: Some(Type(0)) })), Export { name: ComponentExternName { name: "read", implements: None }, ty: Func(1) }, Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("buf", Type(2))], result: Some(Primitive(U32)) })), Export { name: ComponentExternName { name: "write", implements: None }, ty: Func(3) }]) + 0xb | 42 06 01 70 | [type 0] Instance([Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("len", Primitive(U32))], result: Some(Type(0)) })), Export { name: ComponentExternName { name: "read", implements: None, version_suffix: None, external_id: None }, ty: Func(1) }, Type(Defined(List(Primitive(U8)))), Type(Func(ComponentFuncType { async_: false, params: [("buf", Type(2))], result: Some(Primitive(U32)) })), Export { name: ComponentExternName { name: "write", implements: None, version_suffix: None, external_id: None }, ty: Func(3) }]) | 7d 01 40 01 | 03 6c 65 6e | 79 00 00 04 @@ -16,7 +16,7 @@ | 65 01 03 0x3a | 0a 0e | component import section 0x3c | 01 | 1 count - 0x3d | 00 09 77 61 | [instance 0] ComponentImport { name: ComponentExternName { name: "wasi-file", implements: None }, ty: Instance(0) } + 0x3d | 00 09 77 61 | [instance 0] ComponentImport { name: ComponentExternName { name: "wasi-file", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) } | 73 69 2d 66 | 69 6c 65 05 | 00 @@ -194,7 +194,7 @@ | 01 0x1e0 | 0b 0a | component export section 0x1e2 | 01 | 1 count - 0x1e3 | 00 04 77 6f | export ComponentExport { name: ComponentExternName { name: "work", implements: None }, kind: Func, index: 1, ty: None } + 0x1e3 | 00 04 77 6f | export ComponentExport { name: ComponentExternName { name: "work", implements: None, version_suffix: None, external_id: None }, kind: Func, index: 1, ty: None } | 72 6b 01 01 | 00 0x1ec | 00 7c | custom section diff --git a/tests/cli/dump/component-expand-bundle2.wat.stdout b/tests/cli/dump/component-expand-bundle2.wat.stdout index 8ad680abe9..2fcbdc2165 100644 --- a/tests/cli/dump/component-expand-bundle2.wat.stdout +++ b/tests/cli/dump/component-expand-bundle2.wat.stdout @@ -8,7 +8,7 @@ | 01 00 00 00 0x1c | 0b 08 | component export section 0x1e | 01 | 1 count - 0x1f | 00 01 65 00 | export ComponentExport { name: ComponentExternName { name: "e", implements: None }, kind: Module, index: 0, ty: None } + 0x1f | 00 01 65 00 | export ComponentExport { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, kind: Module, index: 0, ty: None } | 11 00 00 0x26 | 00 13 | custom section 0x28 | 0e 63 6f 6d | name: "component-name" @@ -22,12 +22,12 @@ | 0d 00 01 00 0x45 | 07 0d | component type section 0x47 | 01 | 1 count - 0x48 | 41 02 00 50 | [type 0] Component([CoreType(Module([])), Import(ComponentImport { name: ComponentExternName { name: "i", implements: None }, ty: Module(0) })]) + 0x48 | 41 02 00 50 | [type 0] Component([CoreType(Module([])), Import(ComponentImport { name: ComponentExternName { name: "i", implements: None, version_suffix: None, external_id: None }, ty: Module(0) })]) | 00 03 00 01 | 69 00 11 00 0x54 | 0a 06 | component import section 0x56 | 01 | 1 count - 0x57 | 00 01 69 04 | [component 0] ComponentImport { name: ComponentExternName { name: "i", implements: None }, ty: Component(0) } + 0x57 | 00 01 69 04 | [component 0] ComponentImport { name: ComponentExternName { name: "i", implements: None, version_suffix: None, external_id: None }, ty: Component(0) } | 00 0x5c | 00 14 | custom section 0x5e | 0e 63 6f 6d | name: "component-name" @@ -45,7 +45,7 @@ | 01 65 0x81 | 05 10 | component instance section 0x83 | 02 | 2 count - 0x84 | 01 01 00 01 | [instance 1] FromExports([ComponentExport { name: ComponentExternName { name: "e", implements: None }, kind: Module, index: 0, ty: None }]) + 0x84 | 01 01 00 01 | [instance 1] FromExports([ComponentExport { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, kind: Module, index: 0, ty: None }]) | 65 00 11 00 0x8c | 00 01 01 01 | [instance 2] Instantiate { component_index: 1, args: [ComponentInstantiationArg { name: "i", kind: Instance, index: 1 }] } | 69 05 01 diff --git a/tests/cli/dump/component-inline-export-import.wat.stdout b/tests/cli/dump/component-inline-export-import.wat.stdout index 99b4e14aba..caba9bab7e 100644 --- a/tests/cli/dump/component-inline-export-import.wat.stdout +++ b/tests/cli/dump/component-inline-export-import.wat.stdout @@ -5,17 +5,17 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 05 | component import section 0xf | 01 | 1 count - 0x10 | 00 00 04 00 | [component 0] ComponentImport { name: ComponentExternName { name: "", implements: None }, ty: Component(0) } + 0x10 | 00 00 04 00 | [component 0] ComponentImport { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, ty: Component(0) } 0x14 | 03 03 | core type section 0x16 | 01 | 1 count 0x17 | 50 00 | [core type 0] Module([]) 0x19 | 0a 07 | component import section 0x1b | 01 | 1 count - 0x1c | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } + 0x1c | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) } | 11 00 0x22 | 0b 0d | component export section 0x24 | 02 | 2 count - 0x25 | 00 00 04 00 | export ComponentExport { name: ComponentExternName { name: "", implements: None }, kind: Component, index: 0, ty: None } + 0x25 | 00 00 04 00 | export ComponentExport { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, kind: Component, index: 0, ty: None } | 00 - 0x2a | 00 01 61 00 | export ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Module, index: 0, ty: None } + 0x2a | 00 01 61 00 | export ComponentExport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, kind: Module, index: 0, ty: None } | 11 00 00 diff --git a/tests/cli/dump/component-inline-type.wat.stdout b/tests/cli/dump/component-inline-type.wat.stdout index 492bd15fe1..2c8157f840 100644 --- a/tests/cli/dump/component-inline-type.wat.stdout +++ b/tests/cli/dump/component-inline-type.wat.stdout @@ -5,26 +5,26 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 06 | component import section 0xf | 01 | 1 count - 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } + 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Component(0) } | 00 0x15 | 03 03 | core type section 0x17 | 01 | 1 count 0x18 | 50 00 | [core type 0] Module([]) 0x1a | 0a 07 | component import section 0x1c | 01 | 1 count - 0x1d | 00 01 62 00 | [module 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Module(0) } + 0x1d | 00 01 62 00 | [module 0] ComponentImport { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, ty: Module(0) } | 11 00 0x23 | 07 03 | component type section 0x25 | 01 | 1 count 0x26 | 42 00 | [type 1] Instance([]) 0x28 | 0a 06 | component import section 0x2a | 01 | 1 count - 0x2b | 00 01 63 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "c", implements: None }, ty: Instance(1) } + 0x2b | 00 01 63 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "c", implements: None, version_suffix: None, external_id: None }, ty: Instance(1) } | 01 0x30 | 07 05 | component type section 0x32 | 01 | 1 count 0x33 | 40 00 01 00 | [type 2] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x37 | 0a 06 | component import section 0x39 | 01 | 1 count - 0x3a | 00 01 64 01 | [func 0] ComponentImport { name: ComponentExternName { name: "d", implements: None }, ty: Func(2) } + 0x3a | 00 01 64 01 | [func 0] ComponentImport { name: ComponentExternName { name: "d", implements: None, version_suffix: None, external_id: None }, ty: Func(2) } | 02 diff --git a/tests/cli/dump/component-instance-type.wat.stdout b/tests/cli/dump/component-instance-type.wat.stdout index a661a28422..d03ab61626 100644 --- a/tests/cli/dump/component-instance-type.wat.stdout +++ b/tests/cli/dump/component-instance-type.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 28 | component type section 0xa | 01 | 1 count - 0xb | 42 03 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Type(Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Alias(Outer { kind: Type, count: 1, index: 0 }), Import(ComponentImport { name: ComponentExternName { name: "1", implements: None }, ty: Func(0) }), Export { name: ComponentExternName { name: "1", implements: None }, ty: Func(1) }])), Export { name: ComponentExternName { name: "c5", implements: None }, ty: Component(1) }]) + 0xb | 42 03 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Type(Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Alias(Outer { kind: Type, count: 1, index: 0 }), Import(ComponentImport { name: ComponentExternName { name: "1", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }), Export { name: ComponentExternName { name: "1", implements: None, version_suffix: None, external_id: None }, ty: Func(1) }])), Export { name: ComponentExternName { name: "c5", implements: None, version_suffix: None, external_id: None }, ty: Component(1) }]) | 00 01 00 01 | 41 04 01 40 | 00 01 00 02 diff --git a/tests/cli/dump/component-linking.wat.stdout b/tests/cli/dump/component-linking.wat.stdout index 91eed3ce30..f7485176f1 100644 --- a/tests/cli/dump/component-linking.wat.stdout +++ b/tests/cli/dump/component-linking.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 07 30 | component type section 0xa | 01 | 1 count - 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) }, Export { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) }, Type(Component([])), Export { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) }]) + 0xb | 42 09 00 50 | [type 0] Instance([CoreType(Module([])), Export { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) }, Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }, Export { name: ComponentExternName { name: "c", implements: None, version_suffix: None, external_id: None }, ty: Value(Primitive(String)) }, Type(Instance([])), Export { name: ComponentExternName { name: "d", implements: None, version_suffix: None, external_id: None }, ty: Instance(1) }, Type(Component([])), Export { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, ty: Component(2) }]) | 00 04 00 01 | 61 00 11 00 | 01 40 00 01 @@ -16,7 +16,7 @@ | 65 04 02 0x3a | 0a 06 | component import section 0x3c | 01 | 1 count - 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } + 0x3d | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) } | 00 0x42 | 04 59 | [component 0] inline size 0x44 | 00 61 73 6d | version 13 (Component) @@ -26,30 +26,30 @@ 0x4f | 50 00 | [core type 0] Module([]) 0x51 | 0a 07 | component import section 0x53 | 01 | 1 count - 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } + 0x54 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) } | 11 00 0x5a | 07 05 | component type section 0x5c | 01 | 1 count 0x5d | 40 00 01 00 | [type 0] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x61 | 0a 0b | component import section 0x63 | 02 | 2 count - 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Func(0) } + 0x64 | 00 01 62 01 | [func 0] ComponentImport { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, ty: Func(0) } | 00 - 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentExternName { name: "c", implements: None }, ty: Value(Primitive(String)) } + 0x69 | 00 01 63 02 | [value 0] ComponentImport { name: ComponentExternName { name: "c", implements: None, version_suffix: None, external_id: None }, ty: Value(Primitive(String)) } | 73 0x6e | 07 03 | component type section 0x70 | 01 | 1 count 0x71 | 42 00 | [type 1] Instance([]) 0x73 | 0a 06 | component import section 0x75 | 01 | 1 count - 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "d", implements: None }, ty: Instance(1) } + 0x76 | 00 01 64 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "d", implements: None, version_suffix: None, external_id: None }, ty: Instance(1) } | 01 0x7b | 07 03 | component type section 0x7d | 01 | 1 count 0x7e | 41 00 | [type 2] Component([]) 0x80 | 0a 06 | component import section 0x82 | 01 | 1 count - 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentExternName { name: "e", implements: None }, ty: Component(2) } + 0x83 | 00 01 65 04 | [component 0] ComponentImport { name: ComponentExternName { name: "e", implements: None, version_suffix: None, external_id: None }, ty: Component(2) } | 02 0x88 | 00 13 | custom section 0x8a | 0e 63 6f 6d | name: "component-name" diff --git a/tests/cli/dump/import-modules.wat.stdout b/tests/cli/dump/import-modules.wat.stdout index 2ff8fde16b..cd990b435f 100644 --- a/tests/cli/dump/import-modules.wat.stdout +++ b/tests/cli/dump/import-modules.wat.stdout @@ -7,7 +7,7 @@ | 01 66 00 00 0x17 | 0a 07 | component import section 0x19 | 01 | 1 count - 0x1a | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } + 0x1a | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) } | 11 00 0x20 | 01 2b | [core module 1] inline size 0x22 | 00 61 73 6d | version 1 (Module) diff --git a/tests/cli/dump/instance-expand.wat.stdout b/tests/cli/dump/instance-expand.wat.stdout index 7178765803..7088e2a7c3 100644 --- a/tests/cli/dump/instance-expand.wat.stdout +++ b/tests/cli/dump/instance-expand.wat.stdout @@ -2,12 +2,12 @@ | 0d 00 01 00 0x8 | 07 0d | component type section 0xa | 01 | 1 count - 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "", implements: None }, ty: Func(0) }]) + 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }]) | 00 01 00 04 | 00 00 01 00 0x17 | 0a 06 | component import section 0x19 | 01 | 1 count - 0x1a | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Instance(0) } + 0x1a | 00 01 61 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) } | 00 0x1f | 00 16 | custom section 0x21 | 0e 63 6f 6d | name: "component-name" diff --git a/tests/cli/dump/instance-type.wat.stdout b/tests/cli/dump/instance-type.wat.stdout index cb29c01998..752a52dfcb 100644 --- a/tests/cli/dump/instance-type.wat.stdout +++ b/tests/cli/dump/instance-type.wat.stdout @@ -2,9 +2,9 @@ | 0d 00 01 00 0x8 | 07 17 | component type section 0xa | 02 | 2 count - 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "", implements: None }, ty: Func(0) }]) + 0xb | 42 02 01 40 | [type 0] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }]) | 00 01 00 04 | 00 00 01 00 - 0x17 | 42 02 01 42 | [type 1] Instance([Type(Instance([])), Export { name: ComponentExternName { name: "", implements: None }, ty: Instance(0) }]) + 0x17 | 42 02 01 42 | [type 1] Instance([Type(Instance([])), Export { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) }]) | 00 04 00 00 | 05 00 diff --git a/tests/cli/dump/instance-type2.wat.stdout b/tests/cli/dump/instance-type2.wat.stdout index 4189197cab..f6913cc737 100644 --- a/tests/cli/dump/instance-type2.wat.stdout +++ b/tests/cli/dump/instance-type2.wat.stdout @@ -3,10 +3,10 @@ 0x8 | 07 18 | component type section 0xa | 04 | 4 count 0xb | 42 00 | [type 0] Instance([]) - 0xd | 42 01 04 00 | [type 1] Instance([Export { name: ComponentExternName { name: "", implements: None }, ty: Instance(0) }]) + 0xd | 42 01 04 00 | [type 1] Instance([Export { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) }]) | 00 05 00 0x14 | 42 00 | [type 2] Instance([]) - 0x16 | 42 02 02 03 | [type 3] Instance([Alias(Outer { kind: Type, count: 1, index: 2 }), Export { name: ComponentExternName { name: "", implements: None }, ty: Instance(0) }]) + 0x16 | 42 02 02 03 | [type 3] Instance([Alias(Outer { kind: Type, count: 1, index: 2 }), Export { name: ComponentExternName { name: "", implements: None, version_suffix: None, external_id: None }, ty: Instance(0) }]) | 02 01 02 04 | 00 00 05 00 0x22 | 00 16 | custom section diff --git a/tests/cli/dump/instantiate.wat.stdout b/tests/cli/dump/instantiate.wat.stdout index 846be32015..6492905dee 100644 --- a/tests/cli/dump/instantiate.wat.stdout +++ b/tests/cli/dump/instantiate.wat.stdout @@ -5,14 +5,14 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 06 | component import section 0xf | 01 | 1 count - 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } + 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Component(0) } | 00 0x15 | 07 05 | component type section 0x17 | 01 | 1 count 0x18 | 40 00 01 00 | [type 1] Func(ComponentFuncType { async_: false, params: [], result: None }) 0x1c | 0a 06 | component import section 0x1e | 01 | 1 count - 0x1f | 00 01 66 01 | [func 0] ComponentImport { name: ComponentExternName { name: "f", implements: None }, ty: Func(1) } + 0x1f | 00 01 66 01 | [func 0] ComponentImport { name: ComponentExternName { name: "f", implements: None, version_suffix: None, external_id: None }, ty: Func(1) } | 01 0x24 | 05 08 | component instance section 0x26 | 01 | 1 count diff --git a/tests/cli/dump/instantiate2.wat.stdout b/tests/cli/dump/instantiate2.wat.stdout index c3f80961d9..862893578a 100644 --- a/tests/cli/dump/instantiate2.wat.stdout +++ b/tests/cli/dump/instantiate2.wat.stdout @@ -2,13 +2,13 @@ | 0d 00 01 00 0x8 | 07 0e | component type section 0xa | 01 | 1 count - 0xb | 41 02 01 40 | [type 0] Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Import(ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Func(0) })]) + 0xb | 41 02 01 40 | [type 0] Component([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Import(ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Func(0) })]) | 00 01 00 03 | 00 01 61 01 | 00 0x18 | 0a 06 | component import section 0x1a | 01 | 1 count - 0x1b | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } + 0x1b | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Component(0) } | 00 0x20 | 05 04 | component instance section 0x22 | 01 | 1 count diff --git a/tests/cli/dump/module-types.wat.stdout b/tests/cli/dump/module-types.wat.stdout index a4ce4f1b53..2dbec33212 100644 --- a/tests/cli/dump/module-types.wat.stdout +++ b/tests/cli/dump/module-types.wat.stdout @@ -13,7 +13,7 @@ | 00 01 0x2d | 0a 07 | component import section 0x2f | 01 | 1 count - 0x30 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Module(0) } + 0x30 | 00 01 61 00 | [module 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Module(0) } | 11 00 0x36 | 07 03 | component type section 0x38 | 01 | 1 count diff --git a/tests/cli/dump/nested-component.wat.stdout b/tests/cli/dump/nested-component.wat.stdout index b690270821..a389f8bd2c 100644 --- a/tests/cli/dump/nested-component.wat.stdout +++ b/tests/cli/dump/nested-component.wat.stdout @@ -5,7 +5,7 @@ 0xb | 41 00 | [type 0] Component([]) 0xd | 0a 06 | component import section 0xf | 01 | 1 count - 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Component(0) } + 0x10 | 00 01 61 04 | [component 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Component(0) } | 00 0x15 | 04 08 | [component 1] inline size 0x17 | 00 61 73 6d | version 13 (Component) @@ -39,25 +39,25 @@ | 73 01 00 0x70 | 0a 06 | component import section 0x72 | 01 | 1 count - 0x73 | 00 01 61 01 | [func 0] ComponentImport { name: ComponentExternName { name: "a", implements: None }, ty: Func(0) } + 0x73 | 00 01 61 01 | [func 0] ComponentImport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Func(0) } | 00 0x78 | 0b 08 | component export section 0x7a | 01 | 1 count - 0x7b | 00 01 61 00 | export ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Module, index: 0, ty: None } + 0x7b | 00 01 61 00 | export ComponentExport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, kind: Module, index: 0, ty: None } | 11 00 00 0x82 | 07 0e | component type section 0x84 | 01 | 1 count - 0x85 | 42 02 01 40 | [type 1] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "a", implements: None }, ty: Func(0) }]) + 0x85 | 42 02 01 40 | [type 1] Instance([Type(Func(ComponentFuncType { async_: false, params: [], result: None })), Export { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, ty: Func(0) }]) | 00 01 00 04 | 00 01 61 01 | 00 0x92 | 0a 06 | component import section 0x94 | 01 | 1 count - 0x95 | 00 01 62 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "b", implements: None }, ty: Instance(1) } + 0x95 | 00 01 62 05 | [instance 0] ComponentImport { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, ty: Instance(1) } | 01 0x9a | 0b 07 | component export section 0x9c | 01 | 1 count - 0x9d | 00 01 62 05 | export ComponentExport { name: ComponentExternName { name: "b", implements: None }, kind: Instance, index: 0, ty: None } + 0x9d | 00 01 62 05 | export ComponentExport { name: ComponentExternName { name: "b", implements: None, version_suffix: None, external_id: None }, kind: Instance, index: 0, ty: None } | 00 00 0xa3 | 00 17 | custom section 0xa5 | 0e 63 6f 6d | name: "component-name" @@ -69,5 +69,5 @@ 0xb9 | 00 01 6d | Naming { index: 0, name: "m" } 0xbc | 0b 07 | component export section 0xbe | 01 | 1 count - 0xbf | 00 01 61 04 | export ComponentExport { name: ComponentExternName { name: "a", implements: None }, kind: Component, index: 3, ty: None } + 0xbf | 00 01 61 04 | export ComponentExport { name: ComponentExternName { name: "a", implements: None, version_suffix: None, external_id: None }, kind: Component, index: 3, ty: None } | 03 00 diff --git a/tests/cli/missing-features/component-model/implements.wast b/tests/cli/missing-features/component-model/implements.wast index cb133d712d..36f0104ed0 100644 --- a/tests/cli/missing-features/component-model/implements.wast +++ b/tests/cli/missing-features/component-model/implements.wast @@ -7,6 +7,12 @@ ) "the `cm-implements` feature is not active") +(assert_malformed + (component + (import "a" (external-id "") (instance)) + ) + "the `cm-implements` feature is not active") + ;; binary usage, even if it's not used, is disallowed without the feature (assert_malformed (component binary diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr index 169c535ae0..e7a4d12380 100644 --- a/tests/cli/validate-unknown-features.wat.stderr +++ b/tests/cli/validate-unknown-features.wat.stderr @@ -1,4 +1,4 @@ error: invalid value 'unknown' for '--features ': unknown feature `unknown` -Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-more-async-builtins, cm-threading, cm-error-context, cm-fixed-length-lists, cm-gc, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, cm-map, cm64, cm-implements, mvp, wasm1, wasm2, wasm3, lime1, all +Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-more-async-builtins, cm-threading, cm-error-context, cm-fixed-length-lists, cm-gc, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, cm-map, cm64, cm-implements, cm-canon-names, mvp, wasm1, wasm2, wasm3, lime1, all For more information, try '--help'. diff --git a/tests/snapshots/cli/component-model/canon-names.wast.json b/tests/snapshots/cli/component-model/canon-names.wast.json new file mode 100644 index 0000000000..5d53b1d3ef --- /dev/null +++ b/tests/snapshots/cli/component-model/canon-names.wast.json @@ -0,0 +1,32 @@ +{ + "source_filename": "tests/cli/component-model/canon-names.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "canon-names.0.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 15, + "filename": "canon-names.1.wasm", + "module_type": "binary", + "text": "invalid interface version" + }, + { + "type": "assert_invalid", + "line": 19, + "filename": "canon-names.2.wasm", + "module_type": "binary", + "text": "invalid interface version" + }, + { + "type": "assert_invalid", + "line": 23, + "filename": "canon-names.3.wasm", + "module_type": "binary", + "text": "only instances can have" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/cli/component-model/canon-names.wast/0.print b/tests/snapshots/cli/component-model/canon-names.wast/0.print new file mode 100644 index 0000000000..00c36becb8 --- /dev/null +++ b/tests/snapshots/cli/component-model/canon-names.wast/0.print @@ -0,0 +1,28 @@ +(component + (component (;0;) + (type (;0;) + (instance) + ) + (import "a:b/c@1" (versionsuffix ".2.3") (instance (;0;) (type 0))) + (type (;1;) + (instance) + ) + (import "a:b/c@0.2" (versionsuffix ".3") (instance (;1;) (type 1))) + (type (;2;) + (instance) + ) + (import "a:b/c@0.0.3" (instance (;2;) (type 2))) + (type (;3;) + (instance) + ) + (import "a:b/c@1.2.3-rc.1" (instance (;3;) (type 3))) + (type (;4;) + (instance) + ) + (import "a:b/c@0.2.3-rc.1" (instance (;4;) (type 4))) + (type (;5;) + (instance) + ) + (import "a:b/c@0.0.3-rc.1" (instance (;5;) (type 5))) + ) +) diff --git a/tests/snapshots/cli/component-model/implements.wast.json b/tests/snapshots/cli/component-model/implements.wast.json index 4fb762c050..0bfc11f910 100644 --- a/tests/snapshots/cli/component-model/implements.wast.json +++ b/tests/snapshots/cli/component-model/implements.wast.json @@ -54,7 +54,7 @@ "line": 72, "filename": "implements.7.wasm", "module_type": "binary", - "text": "only instance names can have an `implements`" + "text": "only instances can have an `implements`" }, { "type": "assert_invalid", @@ -82,7 +82,7 @@ "line": 92, "filename": "implements.11.wasm", "module_type": "binary", - "text": "only instance names" + "text": "only instances" }, { "type": "assert_invalid", @@ -90,6 +90,32 @@ "filename": "implements.12.wasm", "module_type": "binary", "text": "must be an interface" + }, + { + "type": "module", + "line": 104, + "filename": "implements.13.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 134, + "filename": "implements.14.wasm", + "module_type": "binary" + }, + { + "type": "assert_malformed", + "line": 146, + "filename": "implements.15.wat", + "module_type": "text", + "text": "malformed UTF-8 encoding" + }, + { + "type": "assert_invalid", + "line": 151, + "filename": "implements.16.wasm", + "module_type": "binary", + "text": "only instances" } ] } \ No newline at end of file diff --git a/tests/snapshots/cli/component-model/implements.wast/13.print b/tests/snapshots/cli/component-model/implements.wast/13.print new file mode 100644 index 0000000000..b8988ea588 --- /dev/null +++ b/tests/snapshots/cli/component-model/implements.wast/13.print @@ -0,0 +1,49 @@ +(component + (component (;0;) + (type (;0;) + (instance) + ) + (import "a" (external-id "") (instance (;0;) (type 0))) + (type (;1;) + (instance) + ) + (import "b" (external-id "") (instance (;1;) (type 1))) + (type (;2;) + (instance) + ) + (import "c" (external-id "") (instance (;2;) (type 2))) + (type (;3;) + (instance) + ) + (import "a:b/c" (external-id "") (instance (;3;) (type 3))) + (instance $a (;4;)) + (export (;5;) "a" (external-id "") (instance $a)) + (export (;6;) "b" (external-id "") (instance $a)) + (export (;7;) "c" (external-id "") (instance $a)) + (export (;8;) "a:b/c" (external-id "") (instance $a)) + ) + (type (;0;) + (instance + (type (;0;) + (instance) + ) + (export (;0;) "a" (external-id "") (instance (type 0))) + ) + ) + (type (;1;) + (component + (type (;0;) + (instance) + ) + (import "a" (external-id "") (instance (;0;) (type 0))) + (type (;1;) + (instance) + ) + (export (;1;) "a" (external-id "") (instance (type 1))) + ) + ) + (instance $a (;0;)) + (instance (;1;) + (export "a" (external-id "") (instance $a)) + ) +) diff --git a/tests/snapshots/cli/component-model/implements.wast/14.print b/tests/snapshots/cli/component-model/implements.wast/14.print new file mode 100644 index 0000000000..53c46a53c9 --- /dev/null +++ b/tests/snapshots/cli/component-model/implements.wast/14.print @@ -0,0 +1,24 @@ +(component + (component (;0;) + (type (;0;) + (instance) + ) + (import "a" (external-id "") (instance (;0;) (type 0))) + (type (;1;) + (instance) + ) + (import "b" (external-id "hello, general kenobi") (instance (;1;) (type 1))) + (type (;2;) + (instance) + ) + (import "c" (external-id "https://this.is.a/url") (instance (;2;) (type 2))) + (type (;3;) + (instance) + ) + (import "d" (external-id "look\u{9}ma\u{a}whitespace\u{d}\u{a}") (instance (;3;) (type 3))) + (type (;4;) + (instance) + ) + (import "e" (external-id "\u{0}") (instance (;4;) (type 4))) + ) +) diff --git a/tests/snapshots/cli/missing-features/component-model/implements.wast.json b/tests/snapshots/cli/missing-features/component-model/implements.wast.json index 9ec6d00b3e..7ee104539e 100644 --- a/tests/snapshots/cli/missing-features/component-model/implements.wast.json +++ b/tests/snapshots/cli/missing-features/component-model/implements.wast.json @@ -10,10 +10,17 @@ }, { "type": "assert_malformed", - "line": 12, + "line": 11, "filename": "implements.1.wasm", "module_type": "binary", "text": "the `cm-implements` feature is not active" + }, + { + "type": "assert_malformed", + "line": 18, + "filename": "implements.2.wasm", + "module_type": "binary", + "text": "the `cm-implements` feature is not active" } ] } \ No newline at end of file