-
Notifications
You must be signed in to change notification settings - Fork 246
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
Nested interfaces in wit #1624
base: main
Are you sure you want to change the base?
Nested interfaces in wit #1624
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done a bit of a cursory review to start off with but I think that this still has a few missing pieces. For example src/resolve.rs
in the wit-parser
care will need to be updated to handle this new features of interfaces in a number of locations.
At a high level though I think it would be best to propose this feature on the component-model repository first. There's a number of high-level questions about how to design this feature that I think should be thought through such as:
- Is the syntax
nest a:b/c;
what everyone agrees on? - Should
nest a;
be allowed? - Should
nest a { /* ... */ }
be allowed? - How will code generators for guest langauges map this?
Those are at least the questions off the top of my head which I think would be best to settle first before finishing up the implementation here. A PR to the component model repository would also help flesh out the wasm-encoding of this WIT construct.
crates/wit-parser/src/ast/resolve.rs
Outdated
for nest_item in i.items.iter() { | ||
if let InterfaceItem::Nest(n) = nest_item { | ||
if package_items.insert(n.name.name, n.name.span).is_some() { | ||
bail!(Error::new( | ||
n.name.span, | ||
format!("duplicate item named `{}`", n.name.name), | ||
)) | ||
} | ||
let prev = decl_list_ns.insert(n.name.name, ()); | ||
assert!(prev.is_none()); | ||
let prev = order.insert(n.name.name, Vec::new()); | ||
assert!(prev.is_none()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is quite right because this seems like it would be an interface
-level validation rather than a package-level validation which I think this loop is doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these changes got removed as a result of implementing some of the other comments made. While part of the logic was for validation, I think that some of the inserts here were needed to properly resolve interfaces, whereas now that's not the case. Given that these lines have been removed, I'm not sure that any validation is needed now? Unless there's a place that you'd expect to see a validation that isn't present.
Sounds good! I can work on opening something up over there. |
3d45221
to
9906c2e
Compare
I think the issues re: encoding seem like they've mostly been hashed out here. While there seems to be a plan for the non-foreign interface nesting, I think it's ok to start with what this PR originally scoped (only |
fn interface_path(&self, iface: InterfaceId) -> String { | ||
let iface = &self.resolve.interfaces[iface]; | ||
let pkg_id = iface.package.unwrap(); | ||
let pkg = &self.resolve.packages[pkg_id]; | ||
if let Some(v) = &pkg.name.version { | ||
format!( | ||
"{}:{}/{}@{}", | ||
pkg.name.namespace, | ||
pkg.name.name, | ||
iface.name.as_ref().unwrap(), | ||
v.to_string() | ||
) | ||
} else { | ||
format!("{}/{}", pkg.name.to_string(), iface.name.as_ref().unwrap()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this I think Resolve::id_of
might be what you want here? (in that I think it's equivalent to this function)
let ty = &self.resolve.types[*id]; | ||
if let TypeOwner::Interface(iface_id) = ty.owner { | ||
self.interface = Some(iface_id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this might be best done a little differently than updating self.interface
here. What I'm worried about is that this updates self.interface
but doesn't restore it what it previously was.
Looking throughout this code though push_instance
and pop_instance
might be a good place to hook into? I think it should be ok to move most modifications of self.interface
into those functions where push takes an InterfaceId
and sets self.interface
and then pop restores the previous value saved during push.
docs: Docs<'a>, | ||
attributes: Vec<Attribute<'a>>, | ||
) -> Result<Self> { | ||
tokens.eat(Token::Nest)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll want to expect
here instead of eat
(as eat
has a boolean return)
_ => bail!("expected interface name"), | ||
} | ||
} | ||
_ => bail!("instance type export `{name}` is not a type, function or instance"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this name
needs to be updated to exp_name
stability: Default::default(), | ||
}); | ||
} | ||
_ => bail!("expected interface name"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind adding in the exp_name
to the error message here?
@@ -726,6 +727,40 @@ impl WitPackageDecoder<'_> { | |||
Ok(()) | |||
} | |||
|
|||
fn register_export( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function may want to get folded into register_import
perhaps? (maybe coupled with a function rename). The internal structure looks quite similar and I think it's needed for importing an interface with nest
as well?
One thing I'd also recommend for testing this is fuzzing. For example here's a small patch to enable some fuzzing of this commit: diff --git a/crates/wit-smith/src/generate.rs b/crates/wit-smith/src/generate.rs
index 41c5c5c1..82db5fdd 100644
--- a/crates/wit-smith/src/generate.rs
+++ b/crates/wit-smith/src/generate.rs
@@ -344,6 +344,7 @@ impl<'a> InterfaceGenerator<'a> {
Use,
Type,
Function,
+ Nest,
}
let mut parts = Vec::new();
@@ -374,6 +375,11 @@ impl<'a> InterfaceGenerator<'a> {
Generate::Function => {
parts.push(self.gen_func(u)?);
}
+ Generate::Nest => {
+ if let Some(part) = self.gen_nest(u)? {
+ parts.push(part);
+ }
+ }
}
}
@@ -882,6 +888,15 @@ impl<'a> InterfaceGenerator<'a> {
fn gen_unique_name(&mut self, u: &mut Unstructured<'_>) -> Result<String> {
gen_unique_name(u, &mut self.unique_names)
}
+
+ fn gen_nest(&mut self, u: &mut Unstructured<'_>) -> Result<Option<String>> {
+ let mut path = String::new();
+ if self.gen.gen_path(u, self.file, &mut path)?.is_none() {
+ return Ok(None);
+ }
+
+ Ok(Some(format!("nest {path};")))
+ }
}
fn gen_unique_name(u: &mut Unstructured<'_>, set: &mut HashSet<String>) -> Result<String> { which is executed via
(you can pass That'll help weed out issues dealing with round-tripping interfaces and such. For example that quickly finds this locally for me:
which I think means two things:
|
This PR enables the ability to describe nested interfaces in wit via the
nest
keyword.This would indicate that that the instance associated with baz would export the instance associated with interface
qux
from packageother:pkg
.As I understand it, most use cases most immediately benefit from nesting interfaces from foreign packages, so I started with that, though we could certainly extend this PR or have follow ups to support any combo of locally defined/inlined/anonymous interfaces being nested.
This issue points out that wit currently is not able to express nested instances that can be expressed in wat/binary, so wit-bindgen can't be used by language toolchains to generate that binary. This syntax can be leveraged so that the unlocked dependency syntax referenced in the linked issue can specify the exports it expects in the imports it's describing.