Skip to content

Commit ca2cbe8

Browse files
committed
[wasmparser] Add [implements=<I>]L component name support
Add parsing, validation, and uniqueness rules for the new `[implements=<interface>]label` extern name form from the component model `implements` proposal. An implements name labels an instance import/export that implements a named interface. Uniqueness: `[implements=<I>]L` conflicts with bare label `L` and with `[method]L.L` / `[static]L.L` (the existing l.l edge case), but is strongly unique from interface names, constructors, and normal method/static names. See implements feature: WebAssembly/component-model#613
1 parent 76927bf commit ca2cbe8

File tree

2 files changed

+218
-2
lines changed

2 files changed

+218
-2
lines changed

crates/wasmparser/src/validator/component.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4538,6 +4538,7 @@ impl ComponentNameContext {
45384538
| ComponentNameKind::Method(_)
45394539
| ComponentNameKind::Static(_)
45404540
| ComponentNameKind::Constructor(_)
4541+
| ComponentNameKind::Implements(_)
45414542
| ComponentNameKind::Interface(_) => {}
45424543

45434544
ComponentNameKind::Hash(_)
@@ -4608,6 +4609,17 @@ impl ComponentNameContext {
46084609
| ComponentNameKind::Dependency(_)
46094610
| ComponentNameKind::Hash(_) => {}
46104611

4612+
// `[implements=<I>]L` may only appear on instance imports/exports.
4613+
ComponentNameKind::Implements(imp) => match ty {
4614+
ComponentEntityType::Instance(_) => {}
4615+
_ => bail!(
4616+
offset,
4617+
"`implements` name `[implements=<{}>]{}` can only be used with instance types",
4618+
imp.interface(),
4619+
imp.label(),
4620+
),
4621+
},
4622+
46114623
// Constructors must return `(own $resource)` or
46124624
// `(result (own $Tresource))` and the `$resource` must be named
46134625
// within this context to match `rname`.

crates/wasmparser/src/validator/names.rs

Lines changed: 206 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ impl From<KebabString> for String {
242242
/// * a plain method name : `[method]a-b.c-d`
243243
/// * a plain static method name : `[static]a-b.c-d`
244244
/// * a plain constructor: `[constructor]a-b`
245+
/// * an implements name: `[implements=<a:b/c>]label`
245246
/// * an interface name: `wasi:cli/reactor@0.1.0`
246247
/// * a dependency name: `locked-dep=foo:bar/baz`
247248
/// * a URL name: `url=https://..`
@@ -252,6 +253,10 @@ impl From<KebabString> for String {
252253
/// Note that this type the `[method]...` and `[static]...` variants are
253254
/// considered equal and hash to the same value. This enables disallowing
254255
/// clashes between the two where method name overlap cannot happen.
256+
///
257+
/// Similarly, `[implements=<I>]L` is considered equal to a bare label `L`
258+
/// (they conflict on the same label). Two `[implements=<...>]` names with
259+
/// the same label also conflict, regardless of the interface name.
255260
#[derive(Clone)]
256261
pub struct ComponentName {
257262
raw: String,
@@ -264,6 +269,7 @@ enum ParsedComponentNameKind {
264269
Constructor,
265270
Method,
266271
Static,
272+
Implements,
267273
Interface,
268274
Dependency,
269275
Url,
@@ -283,6 +289,9 @@ pub enum ComponentNameKind<'a> {
283289
/// `[static]a-b.c-d`
284290
#[allow(missing_docs)]
285291
Static(ResourceFunc<'a>),
292+
/// A plain-named instance that implements a named interface, e.g.
293+
/// `[implements=<a:b/c>]label`.
294+
Implements(ImplementsName<'a>),
286295
/// `wasi:http/types@2.0`
287296
#[allow(missing_docs)]
288297
Interface(InterfaceName<'a>),
@@ -300,6 +309,7 @@ pub enum ComponentNameKind<'a> {
300309
const CONSTRUCTOR: &str = "[constructor]";
301310
const METHOD: &str = "[method]";
302311
const STATIC: &str = "[static]";
312+
const IMPLEMENTS_PREFIX: &str = "[implements=<";
303313

304314
impl ComponentName {
305315
/// Attempts to parse `name` as a valid component name, returning `Err` if
@@ -338,6 +348,7 @@ impl ComponentName {
338348
PK::Constructor => Constructor(KebabStr::new_unchecked(&self.raw[CONSTRUCTOR.len()..])),
339349
PK::Method => Method(ResourceFunc(&self.raw[METHOD.len()..])),
340350
PK::Static => Static(ResourceFunc(&self.raw[STATIC.len()..])),
351+
PK::Implements => Implements(ImplementsName(&self.raw[IMPLEMENTS_PREFIX.len()..])),
341352
PK::Interface => Interface(InterfaceName(&self.raw)),
342353
PK::Dependency => Dependency(DependencyName(&self.raw)),
343354
PK::Url => Url(UrlName(&self.raw)),
@@ -379,7 +390,7 @@ impl Ord for ComponentName {
379390

380391
impl PartialOrd for ComponentName {
381392
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
382-
self.kind.partial_cmp(&other.kind)
393+
Some(self.kind().cmp(&other.kind()))
383394
}
384395
}
385396

@@ -397,9 +408,13 @@ impl fmt::Debug for ComponentName {
397408

398409
impl ComponentNameKind<'_> {
399410
/// Returns the [`ParsedComponentNameKind`] of the [`ComponentNameKind`].
411+
///
412+
/// Note that `Implements` maps to `Label` because they share the same
413+
/// uniqueness namespace (an `[implements=<I>]L` name conflicts with a
414+
/// bare label `L`).
400415
fn kind(&self) -> ParsedComponentNameKind {
401416
match self {
402-
Self::Label(_) => ParsedComponentNameKind::Label,
417+
Self::Label(_) | Self::Implements(_) => ParsedComponentNameKind::Label,
403418
Self::Constructor(_) => ParsedComponentNameKind::Constructor,
404419
Self::Method(_) => ParsedComponentNameKind::Method,
405420
Self::Static(_) => ParsedComponentNameKind::Static,
@@ -428,12 +443,29 @@ impl Ord for ComponentNameKind<'_> {
428443
Ordering::Equal
429444
}
430445

446+
// `[implements=<I>]L` compares by label only, and is equivalent
447+
// to a bare label `L`.
448+
(Implements(lhs), Implements(rhs)) => lhs.label().cmp(rhs.label()),
449+
(Label(lhs), Implements(rhs)) => (*lhs).cmp(rhs.label()),
450+
(Implements(lhs), Label(rhs)) => lhs.label().cmp(*rhs),
451+
452+
// `[implements=<I>]l` is equivalent to `[method]l.l` / `[static]l.l`
453+
// when resource == method (the `l.l` edge case that also equals
454+
// bare label `l`).
455+
(Implements(imp), Method(method) | Static(method))
456+
| (Method(method) | Static(method), Implements(imp))
457+
if imp.label() == method.resource() && imp.label() == method.method() =>
458+
{
459+
Ordering::Equal
460+
}
461+
431462
(Interface(lhs), Interface(rhs)) => lhs.cmp(rhs),
432463
(Dependency(lhs), Dependency(rhs)) => lhs.cmp(rhs),
433464
(Url(lhs), Url(rhs)) => lhs.cmp(rhs),
434465
(Hash(lhs), Hash(rhs)) => lhs.cmp(rhs),
435466

436467
(Label(_), _)
468+
| (Implements(_), _)
437469
| (Constructor(_), _)
438470
| (Method(_), _)
439471
| (Static(_), _)
@@ -455,7 +487,11 @@ impl Hash for ComponentNameKind<'_> {
455487
fn hash<H: Hasher>(&self, hasher: &mut H) {
456488
use ComponentNameKind::*;
457489
match self {
490+
// `[implements=<I>]L` hashes the same as bare label `L` since
491+
// they conflict on the same label.
458492
Label(name) => (0u8, name).hash(hasher),
493+
Implements(name) => (0u8, name.label()).hash(hasher),
494+
459495
Constructor(name) => (1u8, name).hash(hasher),
460496

461497
Method(name) | Static(name) => {
@@ -508,6 +544,37 @@ impl<'a> ResourceFunc<'a> {
508544
}
509545
}
510546

547+
/// An implements name, representing `[implements=<I>]L`.
548+
///
549+
/// The internal string starts after `[implements=<` and contains
550+
/// `interface_name>]label`.
551+
#[derive(Debug, Clone)]
552+
pub struct ImplementsName<'a>(&'a str);
553+
554+
impl<'a> ImplementsName<'a> {
555+
/// Returns the full raw string of the implements name (everything after
556+
/// the `[implements=<` prefix).
557+
pub fn as_str(&self) -> &'a str {
558+
self.0
559+
}
560+
561+
/// Returns the index of `>]` in the internal string, which separates the
562+
/// interface name from the label.
563+
fn split_point(&self) -> usize {
564+
self.0.find(">]").unwrap()
565+
}
566+
567+
/// Returns the interface name (the `I` in `[implements=<I>]L`).
568+
pub fn interface(&self) -> &'a str {
569+
&self.0[..self.split_point()]
570+
}
571+
572+
/// Returns the label (the `L` in `[implements=<I>]L`).
573+
pub fn label(&self) -> &'a KebabStr {
574+
KebabStr::new_unchecked(&self.0[self.split_point() + 2..])
575+
}
576+
}
577+
511578
/// An interface name, stored as `a:b/c@1.2.3`
512579
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
513580
pub struct InterfaceName<'a>(&'a str);
@@ -616,6 +683,29 @@ impl<'a> ComponentNameParser<'a> {
616683
return Ok(ParsedComponentNameKind::Static);
617684
}
618685

686+
// '[implements=<' <interfacename> '>]' <label>
687+
if self.eat_str(IMPLEMENTS_PREFIX) {
688+
let iface_str = self.take_up_to('>')?;
689+
// Validate the interface name by parsing it as a package name
690+
// with a required projection (e.g. `ns:pkg/iface`).
691+
let mut iface_parser = ComponentNameParser {
692+
next: iface_str,
693+
offset: self.offset,
694+
features: self.features,
695+
};
696+
iface_parser.pkg_name(true)?;
697+
if !iface_parser.next.is_empty() {
698+
bail!(
699+
self.offset,
700+
"trailing content after interface name in implements annotation"
701+
);
702+
}
703+
self.expect_str(">")?;
704+
self.expect_str("]")?;
705+
self.expect_kebab()?;
706+
return Ok(ParsedComponentNameKind::Implements);
707+
}
708+
619709
// 'unlocked-dep=<' <pkgnamequery> '>'
620710
if self.eat_str("unlocked-dep=") {
621711
self.expect_str("<")?;
@@ -962,6 +1052,19 @@ mod tests {
9621052
assert!(parse_kebab_name("[method]a.b.c").is_none());
9631053
assert!(parse_kebab_name("[static]a.b").is_some());
9641054
assert!(parse_kebab_name("[static]a").is_none());
1055+
1056+
// implements names
1057+
assert!(parse_kebab_name("[implements=<a:b/c>]name").is_some());
1058+
assert!(parse_kebab_name("[implements=<a:b/c@1.0.0>]name").is_some());
1059+
assert!(parse_kebab_name("[implements=<ns:pkg/iface>]my-label").is_some());
1060+
// invalid: not a valid interface name (no colon/slash)
1061+
assert!(parse_kebab_name("[implements=<not-valid>]name").is_none());
1062+
// invalid: empty interface name
1063+
assert!(parse_kebab_name("[implements=<>]name").is_none());
1064+
// invalid: missing label
1065+
assert!(parse_kebab_name("[implements=<a:b/c>]").is_none());
1066+
// invalid: label not kebab
1067+
assert!(parse_kebab_name("[implements=<a:b/c>]NOT_KEBAB").is_none());
9651068
}
9661069

9671070
#[test]
@@ -1014,4 +1117,105 @@ mod tests {
10141117
assert!(!s.insert(parse_kebab_name("[static]a.b")));
10151118
assert!(s.insert(parse_kebab_name("[static]b.b")));
10161119
}
1120+
1121+
#[test]
1122+
fn implements_name_parts() {
1123+
let name = parse_kebab_name("[implements=<a:b/c>]my-label").unwrap();
1124+
match name.kind() {
1125+
ComponentNameKind::Implements(imp) => {
1126+
assert_eq!(imp.interface(), "a:b/c");
1127+
assert_eq!(imp.label().as_str(), "my-label");
1128+
}
1129+
other => panic!("expected Implements, got {other:?}"),
1130+
}
1131+
1132+
let name = parse_kebab_name("[implements=<ns:pkg/iface@1.2.3>]the-name").unwrap();
1133+
match name.kind() {
1134+
ComponentNameKind::Implements(imp) => {
1135+
assert_eq!(imp.interface(), "ns:pkg/iface@1.2.3");
1136+
assert_eq!(imp.label().as_str(), "the-name");
1137+
}
1138+
other => panic!("expected Implements, got {other:?}"),
1139+
}
1140+
}
1141+
1142+
#[test]
1143+
fn implements_name_equality() {
1144+
// Same label conflicts, even with different interfaces
1145+
assert_eq!(
1146+
parse_kebab_name("[implements=<a:b/c>]name"),
1147+
parse_kebab_name("[implements=<x:y/z>]name"),
1148+
);
1149+
1150+
// Implements conflicts with bare label
1151+
assert_eq!(
1152+
parse_kebab_name("[implements=<a:b/c>]name"),
1153+
parse_kebab_name("name"),
1154+
);
1155+
1156+
// Different labels are strongly unique
1157+
assert_ne!(
1158+
parse_kebab_name("[implements=<a:b/c>]one"),
1159+
parse_kebab_name("[implements=<a:b/c>]two"),
1160+
);
1161+
1162+
// Implements is strongly unique from interface names
1163+
assert_ne!(
1164+
parse_kebab_name("[implements=<a:b/c>]name"),
1165+
parse_kebab_name("a:b/c"),
1166+
);
1167+
1168+
// HashSet uniqueness
1169+
let mut s = HashSet::new();
1170+
assert!(s.insert(parse_kebab_name("[implements=<a:b/c>]one")));
1171+
assert!(s.insert(parse_kebab_name("[implements=<a:b/c>]two")));
1172+
// same label conflicts
1173+
assert!(!s.insert(parse_kebab_name("[implements=<x:y/z>]one")));
1174+
// bare label conflicts with implements label
1175+
assert!(!s.insert(parse_kebab_name("one")));
1176+
// interface name is a different kind, no conflict
1177+
assert!(s.insert(parse_kebab_name("a:b/c")));
1178+
}
1179+
1180+
#[test]
1181+
fn implements_cross_kind_uniqueness() {
1182+
// Implements is strongly unique from constructor
1183+
assert_ne!(
1184+
parse_kebab_name("[implements=<a:b/c>]name"),
1185+
parse_kebab_name("[constructor]name"),
1186+
);
1187+
1188+
// Implements is strongly unique from method (different resource.method)
1189+
assert_ne!(
1190+
parse_kebab_name("[implements=<a:b/c>]name"),
1191+
parse_kebab_name("[method]name.other"),
1192+
);
1193+
1194+
// Implements is strongly unique from static (different resource.method)
1195+
assert_ne!(
1196+
parse_kebab_name("[implements=<a:b/c>]name"),
1197+
parse_kebab_name("[static]name.other"),
1198+
);
1199+
1200+
// The l.l edge case: [method]name.name equals bare "name",
1201+
// and [implements=<I>]name also equals bare "name",
1202+
// so they must be equal to each other.
1203+
assert_eq!(
1204+
parse_kebab_name("[implements=<a:b/c>]name"),
1205+
parse_kebab_name("[method]name.name"),
1206+
);
1207+
assert_eq!(
1208+
parse_kebab_name("[implements=<a:b/c>]name"),
1209+
parse_kebab_name("[static]name.name"),
1210+
);
1211+
1212+
// HashSet: implements, constructor, and method with different
1213+
// resource.method should all coexist
1214+
let mut s = HashSet::new();
1215+
assert!(s.insert(parse_kebab_name("[implements=<a:b/c>]name")));
1216+
assert!(s.insert(parse_kebab_name("[constructor]name")));
1217+
assert!(s.insert(parse_kebab_name("[method]name.other")));
1218+
// But [method]name.name conflicts (l.l edge case)
1219+
assert!(!s.insert(parse_kebab_name("[method]name.name")));
1220+
}
10171221
}

0 commit comments

Comments
 (0)