diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index ee1c1fb9b7..94788e7716 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -607,6 +607,7 @@ impl WitPackageDecoder<'_> { types: IndexMap::default(), functions: IndexMap::new(), document: doc, + wildcard: None, }) }); Ok(interface) @@ -624,6 +625,7 @@ impl WitPackageDecoder<'_> { types: IndexMap::default(), functions: IndexMap::new(), document: doc, + wildcard: None, }; for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) { diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index c2a93ae2a1..c1f440e6be 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -8,6 +8,9 @@ use std::path::{Path, PathBuf}; pub mod lex; +pub use expand::expand; +mod expand; + pub use resolve::Resolver; mod resolve; pub mod toposort; @@ -464,10 +467,31 @@ struct Stream<'a> { pub struct Value<'a> { docs: Docs<'a>, - name: Id<'a>, + name: VariableId<'a>, kind: ValueKind<'a>, } +impl<'a> Value<'a> { + pub(crate) fn name(&self) -> &'a str { + match &self.name { + VariableId::Id(id) => id.name, + VariableId::Wildcard(_) => "*", + } + } + + pub(crate) fn span(&self) -> Span { + match self.name { + VariableId::Id(Id { span, .. }) => span, + VariableId::Wildcard(span) => span, + } + } +} + +pub enum VariableId<'a> { + Wildcard(Span), // `*` + Id(Id<'a>), +} + struct Union<'a> { span: Span, cases: Vec>, @@ -552,7 +576,7 @@ impl<'a> InterfaceItem<'a> { Some((_span, Token::Union)) => { TypeDef::parse_union(tokens, docs).map(InterfaceItem::TypeDef) } - Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => { + Some((_span, Token::Star | Token::Id | Token::ExplicitId)) => { Value::parse(tokens, docs).map(InterfaceItem::Value) } Some((_span, Token::Use)) => Use::parse(tokens).map(InterfaceItem::Use), @@ -670,13 +694,24 @@ impl<'a> TypeDef<'a> { impl<'a> Value<'a> { fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { - let name = parse_id(tokens)?; + let name = parse_variable_id(tokens)?; tokens.expect(Token::Colon)?; let kind = ValueKind::parse(tokens)?; Ok(Value { docs, name, kind }) } } +fn parse_variable_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { + match tokens.clone().next()? { + Some((_span, Token::Id | Token::ExplicitId)) => parse_id(tokens).map(VariableId::Id), + Some((span, Token::Star)) => { + tokens.expect(Token::Star)?; + Ok(VariableId::Wildcard(span)) + } + other => Err(err_expected(tokens, "an identifier or `*`", other).into()), + } +} + fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { match tokens.next()? { Some((span, Token::Id)) => Ok(Id { diff --git a/crates/wit-parser/src/ast/expand.rs b/crates/wit-parser/src/ast/expand.rs new file mode 100644 index 0000000000..b2c2225a9a --- /dev/null +++ b/crates/wit-parser/src/ast/expand.rs @@ -0,0 +1,90 @@ +use crate::{Interface, InterfaceId, Resolve, WorldItem}; +use anyhow::{anyhow, Result}; +use id_arena::Arena; +use indexmap::IndexMap; +use std::collections::{HashMap, HashSet}; + +pub type Substitutions = HashMap>>; + +pub fn expand(resolve: &mut Resolve, mut substitutions: Substitutions) -> Result<()> { + let mut new_interfaces = resolve.interfaces.clone(); + for (_, world) in &mut resolve.worlds { + let mut subs = substitutions.remove(&world.name).unwrap_or_default(); + expand_interfaces( + &world.name, + "imports", + &mut world.imports, + &mut new_interfaces, + &mut subs, + )?; + expand_interfaces( + &world.name, + "exports", + &mut world.exports, + &mut new_interfaces, + &mut subs, + )?; + } + + if !substitutions.is_empty() { + log::warn!("unused substitutions were provided: {substitutions:?}",); + } + + resolve.interfaces = new_interfaces; + + Ok(()) +} + +fn expand_interfaces( + world_name: &str, + desc: &str, + items: &mut IndexMap, + new_interfaces: &mut Arena, + substitutions: &mut HashMap>, +) -> Result<()> { + for (name, item) in items { + if let WorldItem::Interface(interface) = item { + if new_interfaces[*interface].wildcard.is_some() { + let new_interface = expand_interface( + *interface, + new_interfaces, + substitutions.remove(name).ok_or_else(|| { + anyhow!( + "world {world_name} {desc} item {name} contains wildcards \ + but no substitutions were provided", + ) + })?, + ); + *interface = new_interfaces.alloc(new_interface); + } + } + } + + if !substitutions.is_empty() { + log::warn!("unused substitutions were provided for world {world_name}: {substitutions:?}",); + } + + Ok(()) +} + +fn expand_interface( + interface: InterfaceId, + new_interfaces: &Arena, + substitutions: HashSet, +) -> Interface { + let mut new_interface = new_interfaces[interface].clone(); + // Make the expanded interface anonymous; otherwise the generated component type will fail to validate due to + // the existence of multiple interfaces with the same name. + // + // TODO: implement something like + // https://github.com/WebAssembly/component-model/issues/172#issuecomment-1466939890, which may entail changes + // to how interfaces are modeled in WIT, `Resolve`, and the component model. + new_interface.name = None; + let function = new_interface.wildcard.take().unwrap(); + for var_name in substitutions { + let mut new_func = function.clone(); + new_func.name = var_name.clone(); + assert!(new_interface.functions.insert(var_name, new_func).is_none()); + } + new_interface +} diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index ba499cb532..a415290d8b 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,4 +1,4 @@ -use super::{Error, ParamList, ResultList, ValueKind}; +use super::{Error, ParamList, ResultList, ValueKind, VariableId}; use crate::ast::toposort::toposort; use crate::*; use anyhow::{anyhow, bail, Result}; @@ -186,6 +186,7 @@ impl<'a> Resolver<'a> { docs: Docs::default(), document: doc, functions: IndexMap::new(), + wildcard: None, }); DocumentItem::Interface(id) }); @@ -205,6 +206,7 @@ impl<'a> Resolver<'a> { docs: Docs::default(), document: doc, functions: IndexMap::new(), + wildcard: None, }) }), }; @@ -531,6 +533,7 @@ impl<'a> Resolver<'a> { name: name.map(|s| s.to_string()), functions: IndexMap::new(), types: IndexMap::new(), + wildcard: None, }); if let Some(name) = name { self.document_interfaces[document.index()] @@ -563,11 +566,21 @@ impl<'a> Resolver<'a> { match field { ast::InterfaceItem::Value(value) => match &value.kind { ValueKind::Func(func) => { - self.define_interface_name(&value.name, TypeOrItem::Item("function"))?; - let func = self.resolve_function(&value.docs, value.name.name, func)?; - let prev = self.interfaces[interface_id] - .functions - .insert(value.name.name.to_string(), func); + if !matches!(value.name, VariableId::Wildcard(_)) { + self.define_interface_name( + value.name(), + value.span(), + TypeOrItem::Item("function"), + )?; + } + let func = self.resolve_function(&value.docs, value.name(), func)?; + let prev = if matches!(value.name, VariableId::Wildcard(_)) { + self.interfaces[interface_id].wildcard.replace(func) + } else { + self.interfaces[interface_id] + .functions + .insert(value.name().to_string(), func) + }; assert!(prev.is_none()); } }, @@ -646,7 +659,9 @@ impl<'a> Resolver<'a> { name: Some(def.name.name.to_string()), owner, }); - self.define_interface_name(&def.name, TypeOrItem::Type(id))?; + let name = def.name.name; + let span = def.name.span; + self.define_interface_name(name, span, TypeOrItem::Type(id))?; } Ok(()) } @@ -675,7 +690,7 @@ impl<'a> Resolver<'a> { name: Some(name.name.to_string()), owner, }); - self.define_interface_name(name, TypeOrItem::Type(id))?; + self.define_interface_name(name.name, name.span, TypeOrItem::Type(id))?; } Ok(()) } @@ -758,12 +773,12 @@ impl<'a> Resolver<'a> { } } - fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> Result<()> { - let prev = self.type_lookup.insert(name.name, (item, name.span)); + fn define_interface_name(&mut self, name: &'a str, span: Span, item: TypeOrItem) -> Result<()> { + let prev = self.type_lookup.insert(name, (item, span)); if prev.is_some() { Err(Error { - span: name.span, - msg: format!("name `{}` is defined more than once", name.name), + span, + msg: format!("name `{}` is defined more than once", name), } .into()) } else { diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index e477a18093..fafb8aaf01 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -8,7 +8,7 @@ use std::path::Path; pub mod abi; mod ast; use ast::lex::Span; -pub use ast::SourceMap; +pub use ast::{expand, SourceMap}; mod sizealign; pub use sizealign::*; mod resolve; @@ -289,6 +289,9 @@ pub struct Interface { /// The document that this interface belongs to. pub document: DocumentId, + + /// TODO: A templated function. + pub wildcard: Option, } #[derive(Debug, Clone, PartialEq)] diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 4bdca9899f..20b26bad94 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -742,6 +742,9 @@ impl Remap { for (_, func) in iface.functions.iter_mut() { self.update_function(func); } + if let Some(func) = &mut iface.wildcard { + self.update_function(func); + } } fn update_function(&self, func: &mut Function) {