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
106 changes: 70 additions & 36 deletions crates/wit-component/src/encoding/wit/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,52 @@ pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result<Compone
Ok(encoder.component)
}

fn encode_instance_contents(enc: &mut InterfaceEncoder, interface: InterfaceId) -> Result<()> {
macovedj marked this conversation as resolved.
Show resolved Hide resolved
enc.push_instance();
let iface = &enc.resolve.interfaces[interface];
let mut type_order = IndexSet::new();
for (_, id) in iface.types.iter() {
let ty = &enc.resolve.types[*id];
if let TypeOwner::Interface(iface_id) = ty.owner {
enc.interface = Some(iface_id);
}
enc.encode_valtype(enc.resolve, &Type::Id(*id))?;
type_order.insert(*id);
}

// Sort functions based on whether or not they're associated with
// resources.
//
// This is done here to ensure that when a WIT package is printed as WIT
// then decoded, or if it's printed as Wasm then decoded, the final
// result is the same. When printing via WIT resource methods are
// attached to the resource types themselves meaning that they'll appear
// intermingled with the rest of the types, namely first before all
// other functions. The purpose of this sort is to perform a stable sort
// over all functions by shuffling the resource-related functions first,
// in order of when their associated resource was encoded, and putting
// freestanding functions last.
//
// Note that this is not actually required for correctness, it's
// basically here to make fuzzing happy.
let mut funcs = iface.functions.iter().collect::<Vec<_>>();
funcs.sort_by_key(|(_name, func)| match func.kind {
FunctionKind::Freestanding => type_order.len(),
FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => {
type_order.get_index_of(&id).unwrap()
}
});

for (name, func) in funcs {
let ty = enc.encode_func_type(enc.resolve, func)?;
enc.ty
.as_mut()
.unwrap()
.export(name, ComponentTypeRef::Func(ty));
}
Ok(())
}

struct Encoder<'a> {
component: ComponentBuilder,
resolve: &'a Resolve,
Expand Down Expand Up @@ -180,52 +226,40 @@ impl InterfaceEncoder<'_> {
}

fn encode_instance(&mut self, interface: InterfaceId) -> Result<u32> {
self.push_instance();
encode_instance_contents(self, interface)?;
let iface = &self.resolve.interfaces[interface];
let mut type_order = IndexSet::new();
for (_, id) in iface.types.iter() {
self.encode_valtype(self.resolve, &Type::Id(*id))?;
type_order.insert(*id);
let mut instance = self.pop_instance();
for (name, nest) in &iface.nested {
self.encode_nested(name, nest.id, &mut instance)?;
}
macovedj marked this conversation as resolved.
Show resolved Hide resolved

// Sort functions based on whether or not they're associated with
// resources.
//
// This is done here to ensure that when a WIT package is printed as WIT
// then decoded, or if it's printed as Wasm then decoded, the final
// result is the same. When printing via WIT resource methods are
// attached to the resource types themselves meaning that they'll appear
// intermingled with the rest of the types, namely first before all
// other functions. The purpose of this sort is to perform a stable sort
// over all functions by shuffling the resource-related functions first,
// in order of when their associated resource was encoded, and putting
// freestanding functions last.
//
// Note that this is not actually required for correctness, it's
// basically here to make fuzzing happy.
let mut funcs = iface.functions.iter().collect::<Vec<_>>();
funcs.sort_by_key(|(_name, func)| match func.kind {
FunctionKind::Freestanding => type_order.len(),
FunctionKind::Method(id) | FunctionKind::Constructor(id) | FunctionKind::Static(id) => {
type_order.get_index_of(&id).unwrap()
}
});

for (name, func) in funcs {
let ty = self.encode_func_type(self.resolve, func)?;
self.ty
.as_mut()
.unwrap()
.export(name, ComponentTypeRef::Func(ty));
}
let instance = self.pop_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>(
&mut self,
name: &str,
iface_id: InterfaceId,
instance: &'a mut InstanceType,
) -> Result<()> {
let mut inst = InterfaceEncoder::new(&self.resolve);
encode_instance_contents(&mut inst, iface_id)?;
let ty = instance.ty();
let nested_instance = &mut inst.pop_instance();
let iface = &self.resolve.interfaces[iface_id];
for (deep_name, deep_nest) in &iface.nested {
let mut inst = InterfaceEncoder::new(&self.resolve);
inst.encode_nested(deep_name, deep_nest.id, nested_instance)?;
}
ty.instance(&nested_instance);
instance.export(name, ComponentTypeRef::Instance(instance.type_count() - 1));
Ok(())
}

fn push_instance(&mut self) {
assert!(self.ty.is_none());
assert!(self.saved_types.is_none());
Expand Down
7 changes: 7 additions & 0 deletions crates/wit-component/src/printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ impl WitPrinter {
fn print_interface(&mut self, resolve: &Resolve, id: InterfaceId) -> Result<()> {
let prev_items = mem::replace(&mut self.any_items, false);
let interface = &resolve.interfaces[id];
for (_, nest) in &interface.nested {
self.print_stability(&nest.stability);
self.print_docs(&nest.docs);
self.output.push_str("nest ");
self.print_path_to_interface(resolve, nest.id, interface.package.unwrap())?;
self.output.push_str(";\n");
}

let mut resource_funcs = HashMap::new();
let mut freestanding = Vec::new();
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
48 changes: 48 additions & 0 deletions crates/wit-component/tests/interfaces/nested.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(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;) (func (result 1)))
(export (;0;) "goodbye" (func (type 2)))
(type (;3;)
(instance
(type (;0;) (record (field "foo" string)))
(export (;1;) "deep-record" (type (eq 0)))
(type (;2;) string)
(export (;3;) "anything" (type (eq 2)))
)
)
(export (;0;) "foo:nestnest/deep" (instance (type 3)))
)
)
(export (;0;) "foo:nestee/[email protected]" (instance (type 3)))
(type (;4;)
(instance
(export (;0;) "foo" (type (sub resource)))
(type (;1;) (borrow 0))
(type (;2;) (option string))
(type (;3;) (func (param "self" 1) (result 2)))
(export (;0;) "[method]foo.bar" (func (type 3)))
)
)
(export (;1;) "foo:nestee/[email protected]" (instance (type 4)))
)
)
(export (;0;) "foo:thing/[email protected]" (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
}
goodbye: func() -> nest-record;
}

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

interface deep {
record deep-record {
foo: string
}
type anything = 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:[email protected];

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:[email protected];

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.

20 changes: 20 additions & 0 deletions crates/wit-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ impl<'a> DeclList<'a> {
Some(&u.names),
WorldOrInterface::Interface,
)?,
InterfaceItem::Nest(n) => {
f(Some(&i.name), &n.from, None, WorldOrInterface::Interface)?
}
_ => {}
}
}
Expand Down Expand Up @@ -549,6 +552,13 @@ enum InterfaceItem<'a> {
TypeDef(TypeDef<'a>),
Func(NamedFunc<'a>),
Use(Use<'a>),
Nest(Nest<'a>),
}

struct Nest<'a> {
docs: Docs<'a>,
attributes: Vec<Attribute<'a>>,
from: UsePath<'a>,
}

struct Use<'a> {
Expand Down Expand Up @@ -987,6 +997,16 @@ 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)) => {
tokens.eat(Token::Nest)?;
let path = UsePath::parse(tokens)?;
tokens.expect_semicolon()?;
Ok(InterfaceItem::Nest(Nest {
docs,
attributes,
from: path,
}))
macovedj marked this conversation as resolved.
Show resolved Hide resolved
}
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