Skip to content

Commit 5f195a7

Browse files
committed
[wit-parser] Add implements syntax for named interface imports/exports
Support the `import label: iface;` and `export label: iface;` WIT syntax, which encodes as `[implements=<I>]label` in the binary format. This allows importing or exporting the same interface multiple times under different names. Changes include: - Add `implements: Option<InterfaceId>` field to `WorldItem::Interface` - Parse `NamedPath` variant in the WIT AST with disambiguation against fully-qualified `namespace:package/interface` paths - Decode `[implements=<I>]label` names in all binary decoding paths via a new `decode_world_instance` helper - Thread `implements` through world elaboration and ID remapping - Add `name_world_key_with_item` for binary encoding On the API change for `name_world_key_with_item`, I opted to introduce this new fn that is used only at the few call sites that need it vs updating the ~50 sites for name_world_key.
1 parent ca2cbe8 commit 5f195a7

13 files changed

+269
-91
lines changed

crates/wit-parser/src/ast.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ impl<'a> DeclList<'a> {
205205
}
206206
Ok(())
207207
}
208-
ExternKind::Path(path) => {
208+
ExternKind::Path(path) | ExternKind::NamedPath(_, path) => {
209209
f(None, attrs, path, None, WorldOrInterface::Interface)
210210
}
211211
ExternKind::Func(..) => Ok(()),
@@ -480,6 +480,8 @@ enum ExternKind<'a> {
480480
Interface(Id<'a>, Vec<InterfaceItem<'a>>),
481481
Path(UsePath<'a>),
482482
Func(Id<'a>, Func<'a>),
483+
/// `label: use-path` — a named import/export that implements an interface.
484+
NamedPath(Id<'a>, UsePath<'a>),
483485
}
484486

485487
impl<'a> ExternKind<'a> {
@@ -508,6 +510,26 @@ impl<'a> ExternKind<'a> {
508510
*tokens = clone;
509511
return Ok(ExternKind::Interface(id, Interface::parse_items(tokens)?));
510512
}
513+
514+
// import label: use-path
515+
// At this point we consumed `id:` on the clone but the next token
516+
// is not `func`, `async`, or `interface`. This could be either:
517+
// import label: local-iface; (NamedPath)
518+
// import label: pkg:name/iface; (NamedPath with package path)
519+
// import ns:pkg/iface; (regular fully-qualified Path)
520+
//
521+
// Disambiguate: if the next tokens are `id /`, then the colon was
522+
// part of a fully-qualified `namespace:package/interface` name, not
523+
// a label separator. Fall through to the Path parser in that case.
524+
let mut peek = clone.clone();
525+
let is_qualified_path =
526+
parse_id(&mut peek).is_ok() && peek.clone().eat(Token::Slash).unwrap_or(false);
527+
if !is_qualified_path {
528+
*tokens = clone;
529+
let path = UsePath::parse(tokens)?;
530+
tokens.expect_semicolon()?;
531+
return Ok(ExternKind::NamedPath(id, path));
532+
}
511533
}
512534

513535
// import foo
@@ -524,6 +546,7 @@ impl<'a> ExternKind<'a> {
524546
ExternKind::Path(UsePath::Id(id)) => id.span,
525547
ExternKind::Path(UsePath::Package { name, .. }) => name.span,
526548
ExternKind::Func(id, _) => id.span,
549+
ExternKind::NamedPath(id, _) => id.span,
527550
}
528551
}
529552
}

crates/wit-parser/src/ast/resolve.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,8 +701,18 @@ impl<'a> Resolver<'a> {
701701
let id = self.extract_iface_from_item(&item, &name, span)?;
702702
WorldKey::Interface(id)
703703
}
704+
705+
// Named paths use the label as the key.
706+
ast::ExternKind::NamedPath(name, _) => WorldKey::Name(name.name.to_string()),
704707
};
705-
if let WorldItem::Interface { id, .. } = world_item {
708+
if let WorldItem::Interface {
709+
id,
710+
implements: None,
711+
..
712+
} = world_item
713+
{
714+
// Only enforce the single-import restriction for bare
715+
// `ExternKind::Path` imports (not named/implements imports).
706716
if !interfaces.insert(id) {
707717
bail!(Error::new(
708718
kind.span(),
@@ -753,6 +763,7 @@ impl<'a> Resolver<'a> {
753763
Ok(WorldItem::Interface {
754764
id,
755765
stability,
766+
implements: None,
756767
span: name.span,
757768
})
758769
}
@@ -763,9 +774,21 @@ impl<'a> Resolver<'a> {
763774
Ok(WorldItem::Interface {
764775
id,
765776
stability,
777+
implements: None,
766778
span: item_span,
767779
})
768780
}
781+
ast::ExternKind::NamedPath(name, path) => {
782+
let stability = self.stability(attrs)?;
783+
let (item, iface_name, item_span) = self.resolve_ast_item_path(path)?;
784+
let id = self.extract_iface_from_item(&item, &iface_name, item_span)?;
785+
Ok(WorldItem::Interface {
786+
id,
787+
stability,
788+
implements: Some(id),
789+
span: name.span,
790+
})
791+
}
769792
ast::ExternKind::Func(name, func) => {
770793
let func = self.resolve_function(
771794
docs,

crates/wit-parser/src/decoding.rs

Lines changed: 70 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ use wasmparser::{
1818
types::Types,
1919
};
2020

21+
/// Parses an `[implements=<interface_name>]label` name, returning
22+
/// the interface name and label if the name matches this pattern.
23+
fn parse_implements_name(name: &str) -> Option<(&str, &str)> {
24+
let rest = name.strip_prefix("[implements=<")?;
25+
let end = rest.find(">]")?;
26+
let interface = &rest[..end];
27+
let label = &rest[end + 2..];
28+
if interface.is_empty() || label.is_empty() {
29+
return None;
30+
}
31+
Some((interface, label))
32+
}
33+
2134
/// Represents information about a decoded WebAssembly component.
2235
struct ComponentInfo {
2336
/// Wasmparser-defined type information learned after a component is fully
@@ -654,21 +667,8 @@ impl WitPackageDecoder<'_> {
654667
let (name, item) = match ty {
655668
ComponentEntityType::Instance(i) => {
656669
let ty = &self.types[i];
657-
let (name, id) = if name.contains('/') {
658-
let id = self.register_import(name, ty)?;
659-
(WorldKey::Interface(id), id)
660-
} else {
661-
self.register_interface(name, ty, package)
662-
.with_context(|| format!("failed to decode WIT from import `{name}`"))?
663-
};
664-
(
665-
name,
666-
WorldItem::Interface {
667-
id,
668-
stability: Default::default(),
669-
span: Default::default(),
670-
},
671-
)
670+
self.decode_world_instance(name, ty, package)
671+
.with_context(|| format!("failed to decode WIT from import `{name}`"))?
672672
}
673673
ComponentEntityType::Func(i) => {
674674
let ty = &self.types[i];
@@ -720,21 +720,8 @@ impl WitPackageDecoder<'_> {
720720
}
721721
ComponentEntityType::Instance(i) => {
722722
let ty = &types[i];
723-
let (name, id) = if name.contains('/') {
724-
let id = self.register_import(name, ty)?;
725-
(WorldKey::Interface(id), id)
726-
} else {
727-
self.register_interface(name, ty, package)
728-
.with_context(|| format!("failed to decode WIT from export `{name}`"))?
729-
};
730-
(
731-
name,
732-
WorldItem::Interface {
733-
id,
734-
stability: Default::default(),
735-
span: Default::default(),
736-
},
737-
)
723+
self.decode_world_instance(name, ty, package)
724+
.with_context(|| format!("failed to decode WIT from export `{name}`"))?
738725
}
739726
_ => {
740727
bail!("component export `{name}` was not a function or instance")
@@ -744,6 +731,55 @@ impl WitPackageDecoder<'_> {
744731
Ok(())
745732
}
746733

734+
/// Decodes a component instance import/export name into a
735+
/// `(WorldKey, WorldItem)` pair.
736+
///
737+
/// Handles three name forms:
738+
/// - `[implements=<I>]label` — named import/export implementing interface I
739+
/// - `ns:pkg/iface` — qualified interface name, keyed by `InterfaceId`
740+
/// - `plain-name` — unqualified name for an inline or local interface
741+
fn decode_world_instance<'a>(
742+
&mut self,
743+
name: &str,
744+
ty: &ComponentInstanceType,
745+
package: &mut PackageFields<'a>,
746+
) -> Result<(WorldKey, WorldItem)> {
747+
if let Some((iface_name, label)) = parse_implements_name(name) {
748+
let id = self.register_import(iface_name, ty)?;
749+
Ok((
750+
WorldKey::Name(label.to_string()),
751+
WorldItem::Interface {
752+
id,
753+
stability: Default::default(),
754+
implements: Some(id),
755+
span: Default::default(),
756+
},
757+
))
758+
} else if name.contains('/') {
759+
let id = self.register_import(name, ty)?;
760+
Ok((
761+
WorldKey::Interface(id),
762+
WorldItem::Interface {
763+
id,
764+
stability: Default::default(),
765+
implements: None,
766+
span: Default::default(),
767+
},
768+
))
769+
} else {
770+
let (key, id) = self.register_interface(name, ty, package)?;
771+
Ok((
772+
key,
773+
WorldItem::Interface {
774+
id,
775+
stability: Default::default(),
776+
implements: None,
777+
span: Default::default(),
778+
},
779+
))
780+
}
781+
}
782+
747783
/// Registers that the `name` provided is either imported interface from a
748784
/// foreign package or referencing a previously defined interface in this
749785
/// package.
@@ -1111,27 +1147,7 @@ impl WitPackageDecoder<'_> {
11111147
let (name, item) = match ty {
11121148
ComponentEntityType::Instance(idx) => {
11131149
let ty = &self.types[*idx];
1114-
let (name, id) = if name.contains('/') {
1115-
// If a name is an interface import then it is either to
1116-
// a package-local or foreign interface, and both
1117-
// situations are handled in `register_import`.
1118-
let id = self.register_import(name, ty)?;
1119-
(WorldKey::Interface(id), id)
1120-
} else {
1121-
// A plain kebab-name indicates an inline interface that
1122-
// wasn't declared explicitly elsewhere with a name, and
1123-
// `register_interface` will create a new `Interface`
1124-
// with no name.
1125-
self.register_interface(name, ty, package)?
1126-
};
1127-
(
1128-
name,
1129-
WorldItem::Interface {
1130-
id,
1131-
stability: Default::default(),
1132-
span: Default::default(),
1133-
},
1134-
)
1150+
self.decode_world_instance(name, ty, package)?
11351151
}
11361152
ComponentEntityType::Type {
11371153
created,
@@ -1161,26 +1177,7 @@ impl WitPackageDecoder<'_> {
11611177
let (name, item) = match ty {
11621178
ComponentEntityType::Instance(idx) => {
11631179
let ty = &self.types[*idx];
1164-
let (name, id) = if name.contains('/') {
1165-
// Note that despite this being an export this is
1166-
// calling `register_import`. With a URL this interface
1167-
// must have been previously defined so this will
1168-
// trigger the logic of either filling in a remotely
1169-
// defined interface or connecting items to local
1170-
// definitions of our own interface.
1171-
let id = self.register_import(name, ty)?;
1172-
(WorldKey::Interface(id), id)
1173-
} else {
1174-
self.register_interface(name, ty, package)?
1175-
};
1176-
(
1177-
name,
1178-
WorldItem::Interface {
1179-
id,
1180-
stability: Default::default(),
1181-
span: Default::default(),
1182-
},
1183-
)
1180+
self.decode_world_instance(name, ty, package)?
11841181
}
11851182

11861183
ComponentEntityType::Func(idx) => {
@@ -1254,8 +1251,9 @@ impl WitPackageDecoder<'_> {
12541251
}
12551252
}
12561253

1257-
// Functions shouldn't have ID-based names at this time.
1254+
// Functions shouldn't have ID-based or implements names.
12581255
ComponentNameKind::Interface(_)
1256+
| ComponentNameKind::Implements(_)
12591257
| ComponentNameKind::Url(_)
12601258
| ComponentNameKind::Hash(_)
12611259
| ComponentNameKind::Dependency(_) => unreachable!(),

crates/wit-parser/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,18 @@ pub enum WorldItem {
614614
serde(skip_serializing_if = "Stability::is_unknown")
615615
)]
616616
stability: Stability,
617+
/// When `Some`, the interface is imported/exported under a plain name
618+
/// using the `[implements=<I>]label` encoding. The `InterfaceId` here
619+
/// identifies the named interface `I` that this import/export
620+
/// implements.
621+
#[cfg_attr(
622+
feature = "serde",
623+
serde(
624+
skip_serializing_if = "Option::is_none",
625+
serialize_with = "serialize_optional_id"
626+
)
627+
)]
628+
implements: Option<InterfaceId>,
617629
#[cfg_attr(feature = "serde", serde(skip))]
618630
span: Span,
619631
},

0 commit comments

Comments
 (0)