Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: preliminary support for WIT templates #964

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/wit-component/src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ impl WitPackageDecoder<'_> {
types: IndexMap::default(),
functions: IndexMap::new(),
document: doc,
wildcard: None,
})
});
Ok(interface)
Expand All @@ -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()) {
Expand Down
41 changes: 38 additions & 3 deletions crates/wit-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<UnionCase<'a>>,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -670,13 +694,24 @@ impl<'a> TypeDef<'a> {

impl<'a> Value<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
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<VariableId<'a>> {
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<Id<'a>> {
match tokens.next()? {
Some((span, Token::Id)) => Ok(Id {
Expand Down
90 changes: 90 additions & 0 deletions crates/wit-parser/src/ast/expand.rs
Original file line number Diff line number Diff line change
@@ -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<String, HashMap<String, HashSet<String>>>;

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<String, WorldItem>,
new_interfaces: &mut Arena<Interface>,
substitutions: &mut HashMap<String, HashSet<String>>,
) -> 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<Interface>,
substitutions: HashSet<String>,
) -> 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
}
39 changes: 27 additions & 12 deletions crates/wit-parser/src/ast/resolve.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -186,6 +186,7 @@ impl<'a> Resolver<'a> {
docs: Docs::default(),
document: doc,
functions: IndexMap::new(),
wildcard: None,
});
DocumentItem::Interface(id)
});
Expand All @@ -205,6 +206,7 @@ impl<'a> Resolver<'a> {
docs: Docs::default(),
document: doc,
functions: IndexMap::new(),
wildcard: None,
})
}),
};
Expand Down Expand Up @@ -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()]
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll want to handle prev.is_some() (indicating multiple * appears textually) here and return a concrete syntax error positioned at the span of the wildcard.

} else {
self.interfaces[interface_id]
.functions
.insert(value.name().to_string(), func)
};
assert!(prev.is_none());
}
},
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion crates/wit-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -289,6 +289,9 @@ pub struct Interface {

/// The document that this interface belongs to.
pub document: DocumentId,

/// TODO: A templated function.
pub wildcard: Option<Function>,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down
3 changes: 3 additions & 0 deletions crates/wit-parser/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down