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

Nested interfaces in wit #1624

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions crates/wit-component/src/encoding/wit/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ impl InterfaceEncoder<'_> {
let iface = &self.resolve.interfaces[interface];
let mut type_order = IndexSet::new();
for (_, id) in iface.types.iter() {
let ty = &self.resolve.types[*id];
if let TypeOwner::Interface(iface_id) = ty.owner {
self.interface = Some(iface_id);
}
self.encode_valtype(self.resolve, &Type::Id(*id))?;
type_order.insert(*id);
}
Expand Down
60 changes: 59 additions & 1 deletion crates/wit-component/src/encoding/wit/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ impl InterfaceEncoder<'_> {
let iface = &self.resolve.interfaces[interface];
let mut type_order = IndexSet::new();
for (_, id) in iface.types.iter() {
let ty = &self.resolve.types[*id];
if let TypeOwner::Interface(iface_id) = ty.owner {
self.interface = Some(iface_id);
}
self.encode_valtype(self.resolve, &Type::Id(*id))?;
type_order.insert(*id);
}
Expand Down Expand Up @@ -218,14 +222,68 @@ impl InterfaceEncoder<'_> {
.unwrap()
.export(name, ComponentTypeRef::Func(ty));
}
let instance = self.pop_instance();
let mut instance = self.pop_instance();
self.encode_nested(iface, &mut instance)?;

let idx = self.outer.type_count();
self.outer.ty().instance(&instance);
self.import_map.insert(interface, self.instances);
self.instances += 1;
Ok(idx)
}

fn encode_nested<'a>(
&'a mut self,
iface: &Interface,
instance: &'a mut InstanceType,
) -> Result<&mut InstanceType> {
for (nest_name, nest_item) in &iface.nested {
let package_id = self
.resolve
.package_names
.get(&nest_item.package_name)
.unwrap();
let package = &self.resolve.packages[*package_id];
let nested = package.interfaces.get(&nest_item.iface_name).unwrap();
let nested_iface = &self.resolve.interfaces[*nested];
let mut inst = InterfaceEncoder::new(&self.resolve);
inst.push_instance();
for (_, id) in &nested_iface.types {
let ty = &self.resolve.types[*id];
if let TypeOwner::Interface(iface_id) = ty.owner {
inst.interface = Some(iface_id);
}
inst.encode_valtype(self.resolve, &Type::Id(*id))?;
}
let ty = instance.ty();
let nested_instance = &mut inst.pop_instance();
for (nest_name, deep_nest) in &nested_iface.nested {
let deep_pkg_id = self
.resolve
.package_names
.get(&deep_nest.package_name)
.unwrap();
let deep_package = &self.resolve.packages[*deep_pkg_id];
let deep_iface_id = deep_package.interfaces.get(&deep_nest.iface_name).unwrap();
let deep_nest = &self.resolve.interfaces[*deep_iface_id];
let mut clone = nested_instance.clone();
let deep_instance = self.encode_nested(deep_nest, &mut clone)?;
let deep_ty = nested_instance.ty();
deep_ty.instance(&deep_instance);
nested_instance.export(
nest_name,
ComponentTypeRef::Instance(deep_instance.type_count()),
);
}
ty.instance(&nested_instance);
instance.export(
nest_name,
ComponentTypeRef::Instance(instance.type_count() - 1),
);
}
Ok(instance)
}

fn push_instance(&mut self) {
assert!(self.ty.is_none());
assert!(self.saved_types.is_none());
Expand Down
10 changes: 9 additions & 1 deletion crates/wit-component/src/printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl WitPrinter {
}

if has_multiple_packages {
self.output.push_str("{");
self.output.push_str("{\n");
self.output.indent += 1
} else {
self.print_semicolon();
Expand All @@ -82,6 +82,14 @@ impl WitPrinter {
self.output.push_str("interface ");
self.print_name(name);
self.output.push_str(" {\n");
let nested = &resolve.interfaces[*id].nested;
for item in nested {
self.print_stability(&item.1.stability);
self.print_docs(&item.1.docs);
self.output.push_str("nest ");
self.print_name(item.0);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that this is quite right because this should print nest a:b/c; or similar so I think an interface id needs to be printed here rather than just the string name

self.output.push_str(";\n")
}
self.print_interface(resolve, *id)?;
writeln!(&mut self.output, "}}\n")?;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/wit-component/tests/interfaces/doc-comments.wat
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
)
)
(export (;5;) "coverage-world" (type 4))
(@custom "package-docs" "\00{\22docs\22:\22package docs;\22,\22worlds\22:{\22coverage-world\22:{\22docs\22:\22world docs\22,\22interfaces\22:{\22i\22:{\22docs\22:\22world inline interface docs\22,\22funcs\22:{\22f\22:\22inline interface func docs\22},\22types\22:{\22t\22:{\22docs\22:\22inline interface typedef docs\22}}}},\22types\22:{\22t\22:{\22docs\22:\22world typedef docs\22}},\22funcs\22:{\22imp\22:\22world func import docs\22,\22exp\22:\22world func export docs\22}}},\22interfaces\22:{\22coverage-iface\22:{\22docs\22:\22interface docs\22,\22funcs\22:{\22[constructor]res\22:\22constructor docs\22,\22[method]res.m\22:\22method docs\22,\22[static]res.s\22:\22static func docs\22,\22f\22:\22interface func docs\22},\22types\22:{\22t\22:{\22docs\22:\22basic typedef docs\22},\22r\22:{\22docs\22:\22record typedef docs\22,\22items\22:{\22f1\22:\22record field docs\22}},\22fl\22:{\22items\22:{\22f1\22:\22flag docs\22}},\22v\22:{\22items\22:{\22c1\22:\22variant case docs\22}},\22e\22:{\22items\22:{\22c1\22:\22enum case docs\22}}}},\22other-comment-forms\22:{\22docs\22:\22other comment forms\5cn multi-line block\22,\22funcs\22:{\22multiple-lines-split\22:\22one doc line\5cnnon-doc in the middle\5cnanother doc line\22,\22mixed-forms\22:\22mixed forms; line doc\5cnplus block doc\5cn multi-line\22}}}}")
(@custom "package-docs" "\00{\22docs\22:\22package docs;\22,\22worlds\22:{\22coverage-world\22:{\22docs\22:\22world docs\22,\22interfaces\22:{\22i\22:{\22docs\22:\22world inline interface docs\22,\22funcs\22:{\22f\22:\22inline interface func docs\22},\22types\22:{\22t\22:{\22docs\22:\22inline interface typedef docs\22}},\22nested\22:{}}},\22types\22:{\22t\22:{\22docs\22:\22world typedef docs\22}},\22funcs\22:{\22imp\22:\22world func import docs\22,\22exp\22:\22world func export docs\22}}},\22interfaces\22:{\22coverage-iface\22:{\22docs\22:\22interface docs\22,\22funcs\22:{\22[constructor]res\22:\22constructor docs\22,\22[method]res.m\22:\22method docs\22,\22[static]res.s\22:\22static func docs\22,\22f\22:\22interface func docs\22},\22types\22:{\22t\22:{\22docs\22:\22basic typedef docs\22},\22r\22:{\22docs\22:\22record typedef docs\22,\22items\22:{\22f1\22:\22record field docs\22}},\22fl\22:{\22items\22:{\22f1\22:\22flag docs\22}},\22v\22:{\22items\22:{\22c1\22:\22variant case docs\22}},\22e\22:{\22items\22:{\22c1\22:\22enum case docs\22}}},\22nested\22:{}},\22other-comment-forms\22:{\22docs\22:\22other comment forms\5cn multi-line block\22,\22funcs\22:{\22multiple-lines-split\22:\22one doc line\5cnnon-doc in the middle\5cnanother doc line\22,\22mixed-forms\22:\22mixed forms; line doc\5cnplus block doc\5cn multi-line\22},\22nested\22:{}}}}")
(@producers
(processed-by "wit-component" "$CARGO_PKG_VERSION")
)
Expand Down
40 changes: 40 additions & 0 deletions crates/wit-component/tests/interfaces/nested.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
(component
(type (;0;)
(component
(type (;0;)
(instance
(type (;0;) (record (field "foo" string)))
(export (;1;) "my-record" (type (eq 0)))
(type (;2;) (func (result string)))
(export (;0;) "hello" (func (type 2)))
(type (;3;)
(instance
(type (;0;) (record (field "foo" string)))
(export (;1;) "nest-record" (type (eq 0)))
(type (;2;)
(instance
(type (;0;) (record (field "foo" string)))
(export (;1;) "nest-record" (type (eq 0)))
)
)
(export (;0;) "foo:nestnest/deep" (instance (type 2)))
)
)
(export (;0;) "foo:nestee/things@1.0.0" (instance (type 3)))
(type (;4;)
(instance
(export (;0;) "foo" (type (sub resource)))
)
)
(export (;1;) "foo:nestee/more@1.0.0" (instance (type 4)))
)
)
(export (;0;) "foo:thing/something" (instance (type 0)))
)
)
(export (;1;) "something" (type 0))
(@custom "package-docs" "\01{\22interfaces\22:{\22something\22:{\22types\22:{\22my-record\22:{\22stability\22:{\22stable\22:{\22since\22:\221.0.0\22}}}},\22nested\22:{\22foo:nestee/things@1.0.0\22:{\22docs\22:{\22contents\22:\22nesting can be documented\22},\22stability\22:\22unknown\22},\22foo:nestee/more@1.0.0\22:{\22docs\22:{\22contents\22:null},\22stability\22:{\22stable\22:{\22since\22:\221.0.0\22}}}}}}}")
(@producers
(processed-by "wit-component" "$CARGO_PKG_VERSION")
)
)
17 changes: 17 additions & 0 deletions crates/wit-component/tests/interfaces/nested/deps/nestee.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package foo:nestee@1.0.0;

interface things {
//nesting can be documented
@since(version = 1.0.0)
nest foo:nestnest/deep;
record nest-record {
foo: string
}
hello: func() -> string;
}

interface more {
resource foo {
bar: func() -> option<string>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo:nestnest;

interface deep {
record deep-record {
foo: string
}
}
14 changes: 14 additions & 0 deletions crates/wit-component/tests/interfaces/nested/nested.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package foo:thing;

interface something {
//nesting can be documented
nest foo:nestee/things@1.0.0;
@since(version = 1.0.0)
nest foo:nestee/more@1.0.0;
@since(version = 1.0.0)
record my-record {
foo: string
}

hello: func() -> string;
}
15 changes: 15 additions & 0 deletions crates/wit-component/tests/interfaces/nested/thing.wit.print
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package foo:thing;

interface something {
/// nesting can be documented
nest foo:nestee/things@1.0.0;
@since(version = 1.0.0)
nest foo:nestee/more@1.0.0;
@since(version = 1.0.0)
record my-record {
foo: string,
}

hello: func() -> string;
}

2 changes: 1 addition & 1 deletion crates/wit-component/tests/interfaces/resources.wat
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@
)
)
(export (;11;) "implicit-own-handles3" (type 10))
(@custom "package-docs" "\00{\22worlds\22:{\22implicit-own-handles3\22:{\22types\22:{\22a\22:{\22docs\22:\22there should only be one `list` type despite there looking like two\5cnlist types here\22}}}},\22interfaces\22:{\22implicit-own-handles2\22:{\22types\22:{\22a\22:{\22docs\22:\22the `own` return and list param should be the same `own`\22},\22b\22:{\22docs\22:\22same as above, even when the `list<b>` implicitly-defined `own` comes\5cnbefore an explicitly defined `own`\22},\22c\22:{\22docs\22:\22same as the above, the `own` argument should have the same type as the\5cnreturn value\22}}}}}")
(@custom "package-docs" "\00{\22worlds\22:{\22implicit-own-handles3\22:{\22types\22:{\22a\22:{\22docs\22:\22there should only be one `list` type despite there looking like two\5cnlist types here\22}}}},\22interfaces\22:{\22implicit-own-handles2\22:{\22types\22:{\22a\22:{\22docs\22:\22the `own` return and list param should be the same `own`\22},\22b\22:{\22docs\22:\22same as above, even when the `list<b>` implicitly-defined `own` comes\5cnbefore an explicitly defined `own`\22},\22c\22:{\22docs\22:\22same as the above, the `own` argument should have the same type as the\5cnreturn value\22}},\22nested\22:{}}}}")
(@producers
(processed-by "wit-component" "$CARGO_PKG_VERSION")
)
Expand Down
2 changes: 1 addition & 1 deletion crates/wit-component/tests/interfaces/wasi-http.wat

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions crates/wit-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,19 @@ impl<'a> DeclList<'a> {
}
Ok(())
}

fn for_each_nest<'b>(&'b self, mut f: impl FnMut(&'b Nest<'a>) -> Result<()>) -> Result<()> {
for item in self.items.iter() {
if let AstItem::Interface(i) = item {
for item in i.items.iter() {
if let InterfaceItem::Nest(n) = item {
f(n)?;
}
}
}
}
Ok(())
}
}

enum AstItem<'a> {
Expand Down Expand Up @@ -545,6 +558,48 @@ enum InterfaceItem<'a> {
TypeDef(TypeDef<'a>),
Func(NamedFunc<'a>),
Use(Use<'a>),
Nest(Nest<'a>),
}

struct Nest<'a> {
docs: Docs<'a>,
id: PackageName<'a>,
name: Id<'a>,
attributes: Vec<Attribute<'a>>,
}

impl<'a> Nest<'a> {
fn parse(
tokens: &mut Tokenizer<'a>,
docs: Docs<'a>,
attributes: Vec<Attribute<'a>>,
) -> Result<Self> {
tokens.eat(Token::Nest)?;
let id = parse_id(tokens)?;
tokens.expect(Token::Colon)?;
// `foo:bar/baz@1.0`
let namespace = id;
let pkg_name = parse_id(tokens)?;
tokens.expect(Token::Slash)?;
let name = parse_id(tokens)?;
let version = parse_opt_version(tokens)?;
Comment on lines +578 to +585
Copy link
Member

Choose a reason for hiding this comment

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

I think that this is a duplication of UseName's internal parsing, but could UseName be used here instead? That would enable consistently being able to refer to both foreign and local interfaces with nest

tokens.expect_semicolon()?;
Ok(Self {
id: PackageName {
docs: Default::default(),
span: Span {
start: namespace.span.start,
end: pkg_name.span.end,
},
namespace,
name: pkg_name,
version,
},
docs,
name,
attributes,
})
}
}

struct Use<'a> {
Expand Down Expand Up @@ -983,6 +1038,9 @@ impl<'a> InterfaceItem<'a> {
NamedFunc::parse(tokens, docs, attributes).map(InterfaceItem::Func)
}
Some((_span, Token::Use)) => Use::parse(tokens, attributes).map(InterfaceItem::Use),
Some((_span, Token::Nest)) => {
Nest::parse(tokens, docs, attributes).map(InterfaceItem::Nest)
}
other => Err(err_expected(tokens, "`type`, `resource` or `func`", other).into()),
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/wit-parser/src/ast/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub enum Token {
As,
From_,
Static,
Nest,
Interface,
Tuple,
Import,
Expand Down Expand Up @@ -315,6 +316,7 @@ impl<'a> Tokenizer<'a> {
"as" => As,
"from" => From_,
"static" => Static,
"nest" => Nest,
"interface" => Interface,
"tuple" => Tuple,
"world" => World,
Expand Down Expand Up @@ -576,6 +578,7 @@ impl Token {
As => "keyword `as`",
From_ => "keyword `from`",
Static => "keyword `static`",
Nest => "keyword `nest`",
Interface => "keyword `interface`",
Tuple => "keyword `tuple`",
Import => "keyword `import`",
Expand Down
Loading