Skip to content

Commit 34bd111

Browse files
fibonacci1729dicej
authored andcommitted
feat: preliminary support for WIT templates
This is a minimum viable implementation of WIT templates per WebAssembly/component-model#172. It supports interfaces with at most one wildcard function, which may be expanded (i.e. monomorphized) with a set of substitutions provided by the application developer when generating guest bindings. I will be posting separate PRs to the `wit-bindgen` and `wasmtime` repos to implement and test guest and host binding generation, respectively. Signed-off-by: Joel Dice <[email protected]>
1 parent 7108f55 commit 34bd111

File tree

7 files changed

+165
-17
lines changed

7 files changed

+165
-17
lines changed

crates/wit-component/src/decoding.rs

+2
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ impl WitPackageDecoder<'_> {
607607
types: IndexMap::default(),
608608
functions: IndexMap::new(),
609609
document: doc,
610+
wildcard: None,
610611
})
611612
});
612613
Ok(interface)
@@ -624,6 +625,7 @@ impl WitPackageDecoder<'_> {
624625
types: IndexMap::default(),
625626
functions: IndexMap::new(),
626627
document: doc,
628+
wildcard: None,
627629
};
628630

629631
for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) {

crates/wit-parser/src/ast.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::path::{Path, PathBuf};
88

99
pub mod lex;
1010

11+
pub use expand::expand;
12+
mod expand;
13+
1114
pub use resolve::Resolver;
1215
mod resolve;
1316
pub mod toposort;
@@ -464,10 +467,31 @@ struct Stream<'a> {
464467

465468
pub struct Value<'a> {
466469
docs: Docs<'a>,
467-
name: Id<'a>,
470+
name: VariableId<'a>,
468471
kind: ValueKind<'a>,
469472
}
470473

474+
impl<'a> Value<'a> {
475+
pub(crate) fn name(&self) -> &'a str {
476+
match &self.name {
477+
VariableId::Id(id) => id.name,
478+
VariableId::Wildcard(_) => "*",
479+
}
480+
}
481+
482+
pub(crate) fn span(&self) -> Span {
483+
match self.name {
484+
VariableId::Id(Id { span, .. }) => span,
485+
VariableId::Wildcard(span) => span,
486+
}
487+
}
488+
}
489+
490+
pub enum VariableId<'a> {
491+
Wildcard(Span), // `*`
492+
Id(Id<'a>),
493+
}
494+
471495
struct Union<'a> {
472496
span: Span,
473497
cases: Vec<UnionCase<'a>>,
@@ -552,7 +576,7 @@ impl<'a> InterfaceItem<'a> {
552576
Some((_span, Token::Union)) => {
553577
TypeDef::parse_union(tokens, docs).map(InterfaceItem::TypeDef)
554578
}
555-
Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => {
579+
Some((_span, Token::Star | Token::Id | Token::ExplicitId)) => {
556580
Value::parse(tokens, docs).map(InterfaceItem::Value)
557581
}
558582
Some((_span, Token::Use)) => Use::parse(tokens).map(InterfaceItem::Use),
@@ -670,13 +694,24 @@ impl<'a> TypeDef<'a> {
670694

671695
impl<'a> Value<'a> {
672696
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
673-
let name = parse_id(tokens)?;
697+
let name = parse_variable_id(tokens)?;
674698
tokens.expect(Token::Colon)?;
675699
let kind = ValueKind::parse(tokens)?;
676700
Ok(Value { docs, name, kind })
677701
}
678702
}
679703

704+
fn parse_variable_id<'a>(tokens: &mut Tokenizer<'a>) -> Result<VariableId<'a>> {
705+
match tokens.clone().next()? {
706+
Some((_span, Token::Id | Token::ExplicitId)) => parse_id(tokens).map(VariableId::Id),
707+
Some((span, Token::Star)) => {
708+
tokens.expect(Token::Star)?;
709+
Ok(VariableId::Wildcard(span))
710+
}
711+
other => Err(err_expected(tokens, "an identifier or `*`", other).into()),
712+
}
713+
}
714+
680715
fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result<Id<'a>> {
681716
match tokens.next()? {
682717
Some((span, Token::Id)) => Ok(Id {

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

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::{Interface, InterfaceId, Resolve, WorldItem};
2+
use anyhow::{anyhow, Result};
3+
use id_arena::Arena;
4+
use indexmap::IndexMap;
5+
use std::collections::{HashMap, HashSet};
6+
7+
pub type Substitutions = HashMap<String, HashMap<String, HashSet<String>>>;
8+
9+
pub fn expand(resolve: &mut Resolve, mut substitutions: Substitutions) -> Result<()> {
10+
let mut new_interfaces = resolve.interfaces.clone();
11+
for (_, world) in &mut resolve.worlds {
12+
let mut subs = substitutions.remove(&world.name).unwrap_or_default();
13+
expand_interfaces(
14+
&world.name,
15+
"imports",
16+
&mut world.imports,
17+
&mut new_interfaces,
18+
&mut subs,
19+
)?;
20+
expand_interfaces(
21+
&world.name,
22+
"exports",
23+
&mut world.exports,
24+
&mut new_interfaces,
25+
&mut subs,
26+
)?;
27+
}
28+
29+
if !substitutions.is_empty() {
30+
log::warn!("unused substitutions were provided: {substitutions:?}",);
31+
}
32+
33+
resolve.interfaces = new_interfaces;
34+
35+
Ok(())
36+
}
37+
38+
fn expand_interfaces(
39+
world_name: &str,
40+
desc: &str,
41+
items: &mut IndexMap<String, WorldItem>,
42+
new_interfaces: &mut Arena<Interface>,
43+
substitutions: &mut HashMap<String, HashSet<String>>,
44+
) -> Result<()> {
45+
for (name, item) in items {
46+
if let WorldItem::Interface(interface) = item {
47+
if new_interfaces[*interface].wildcard.is_some() {
48+
let new_interface = expand_interface(
49+
*interface,
50+
new_interfaces,
51+
substitutions.remove(name).ok_or_else(|| {
52+
anyhow!(
53+
"world {world_name} {desc} item {name} contains wildcards \
54+
but no substitutions were provided",
55+
)
56+
})?,
57+
);
58+
*interface = new_interfaces.alloc(new_interface);
59+
}
60+
}
61+
}
62+
63+
if !substitutions.is_empty() {
64+
log::warn!("unused substitutions were provided for world {world_name}: {substitutions:?}",);
65+
}
66+
67+
Ok(())
68+
}
69+
70+
fn expand_interface(
71+
interface: InterfaceId,
72+
new_interfaces: &Arena<Interface>,
73+
substitutions: HashSet<String>,
74+
) -> Interface {
75+
let mut new_interface = new_interfaces[interface].clone();
76+
// Make the expanded interface anonymous; otherwise the generated component type will fail to validate due to
77+
// the existence of multiple interfaces with the same name.
78+
//
79+
// TODO: implement something like
80+
// https://github.com/WebAssembly/component-model/issues/172#issuecomment-1466939890, which may entail changes
81+
// to how interfaces are modeled in WIT, `Resolve`, and the component model.
82+
new_interface.name = None;
83+
let function = new_interface.wildcard.take().unwrap();
84+
for var_name in substitutions {
85+
let mut new_func = function.clone();
86+
new_func.name = var_name.clone();
87+
assert!(new_interface.functions.insert(var_name, new_func).is_none());
88+
}
89+
new_interface
90+
}

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

+27-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Error, ParamList, ResultList, ValueKind};
1+
use super::{Error, ParamList, ResultList, ValueKind, VariableId};
22
use crate::ast::toposort::toposort;
33
use crate::*;
44
use anyhow::{anyhow, bail, Result};
@@ -186,6 +186,7 @@ impl<'a> Resolver<'a> {
186186
docs: Docs::default(),
187187
document: doc,
188188
functions: IndexMap::new(),
189+
wildcard: None,
189190
});
190191
DocumentItem::Interface(id)
191192
});
@@ -205,6 +206,7 @@ impl<'a> Resolver<'a> {
205206
docs: Docs::default(),
206207
document: doc,
207208
functions: IndexMap::new(),
209+
wildcard: None,
208210
})
209211
}),
210212
};
@@ -531,6 +533,7 @@ impl<'a> Resolver<'a> {
531533
name: name.map(|s| s.to_string()),
532534
functions: IndexMap::new(),
533535
types: IndexMap::new(),
536+
wildcard: None,
534537
});
535538
if let Some(name) = name {
536539
self.document_interfaces[document.index()]
@@ -563,11 +566,21 @@ impl<'a> Resolver<'a> {
563566
match field {
564567
ast::InterfaceItem::Value(value) => match &value.kind {
565568
ValueKind::Func(func) => {
566-
self.define_interface_name(&value.name, TypeOrItem::Item("function"))?;
567-
let func = self.resolve_function(&value.docs, value.name.name, func)?;
568-
let prev = self.interfaces[interface_id]
569-
.functions
570-
.insert(value.name.name.to_string(), func);
569+
if !matches!(value.name, VariableId::Wildcard(_)) {
570+
self.define_interface_name(
571+
value.name(),
572+
value.span(),
573+
TypeOrItem::Item("function"),
574+
)?;
575+
}
576+
let func = self.resolve_function(&value.docs, value.name(), func)?;
577+
let prev = if matches!(value.name, VariableId::Wildcard(_)) {
578+
self.interfaces[interface_id].wildcard.replace(func)
579+
} else {
580+
self.interfaces[interface_id]
581+
.functions
582+
.insert(value.name().to_string(), func)
583+
};
571584
assert!(prev.is_none());
572585
}
573586
},
@@ -646,7 +659,9 @@ impl<'a> Resolver<'a> {
646659
name: Some(def.name.name.to_string()),
647660
owner,
648661
});
649-
self.define_interface_name(&def.name, TypeOrItem::Type(id))?;
662+
let name = def.name.name;
663+
let span = def.name.span;
664+
self.define_interface_name(name, span, TypeOrItem::Type(id))?;
650665
}
651666
Ok(())
652667
}
@@ -675,7 +690,7 @@ impl<'a> Resolver<'a> {
675690
name: Some(name.name.to_string()),
676691
owner,
677692
});
678-
self.define_interface_name(name, TypeOrItem::Type(id))?;
693+
self.define_interface_name(name.name, name.span, TypeOrItem::Type(id))?;
679694
}
680695
Ok(())
681696
}
@@ -758,12 +773,12 @@ impl<'a> Resolver<'a> {
758773
}
759774
}
760775

761-
fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> Result<()> {
762-
let prev = self.type_lookup.insert(name.name, (item, name.span));
776+
fn define_interface_name(&mut self, name: &'a str, span: Span, item: TypeOrItem) -> Result<()> {
777+
let prev = self.type_lookup.insert(name, (item, span));
763778
if prev.is_some() {
764779
Err(Error {
765-
span: name.span,
766-
msg: format!("name `{}` is defined more than once", name.name),
780+
span,
781+
msg: format!("name `{}` is defined more than once", name),
767782
}
768783
.into())
769784
} else {

crates/wit-parser/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::Path;
88
pub mod abi;
99
mod ast;
1010
use ast::lex::Span;
11-
pub use ast::SourceMap;
11+
pub use ast::{SourceMap, expand};
1212
mod sizealign;
1313
pub use sizealign::*;
1414
mod resolve;
@@ -289,6 +289,9 @@ pub struct Interface {
289289

290290
/// The document that this interface belongs to.
291291
pub document: DocumentId,
292+
293+
/// TODO: A templated function.
294+
pub wildcard: Option<Function>,
292295
}
293296

294297
#[derive(Debug, Clone, PartialEq)]

crates/wit-parser/src/resolve.rs

+3
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,9 @@ impl Remap {
742742
for (_, func) in iface.functions.iter_mut() {
743743
self.update_function(func);
744744
}
745+
if let Some(func) = &mut iface.wildcard {
746+
self.update_function(func);
747+
}
745748
}
746749

747750
fn update_function(&self, func: &mut Function) {

tests/testsuite

Submodule testsuite updated 65 files

0 commit comments

Comments
 (0)