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 15 commits into
base: main
Choose a base branch
from
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);
}
macovedj marked this conversation as resolved.
Show resolved Hide resolved
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);
macovedj marked this conversation as resolved.
Show resolved Hide resolved
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/[email protected]" (instance (type 3)))
(type (;4;)
(instance
(export (;0;) "foo" (type (sub resource)))
)
)
(export (;1;) "foo:nestee/[email protected]" (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/[email protected]\22:{\22docs\22:{\22contents\22:\22nesting can be documented\22},\22stability\22:\22unknown\22},\22foo:nestee/[email protected]\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:[email protected];

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/[email protected];
@since(version = 1.0.0)
nest foo:nestee/[email protected];
@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/[email protected];
@since(version = 1.0.0)
nest foo:nestee/[email protected];
@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)?;
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 you'll want to expect here instead of eat (as eat has a boolean return)

let id = parse_id(tokens)?;
tokens.expect(Token::Colon)?;
// `foo:bar/[email protected]`
let namespace = id;
let pkg_name = parse_id(tokens)?;
tokens.expect(Token::Slash)?;
let name = parse_id(tokens)?;
let version = parse_opt_version(tokens)?;
macovedj marked this conversation as resolved.
Show resolved Hide resolved
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