From 7dfee651f1271f7c56a9ac54709cd9e04ad96d6c Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Mon, 29 May 2023 10:47:58 -0700 Subject: [PATCH 01/14] Add ast parser and resolver for include Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast.rs | 19 ++- crates/wit-parser/src/ast/lex.rs | 7 + crates/wit-parser/src/ast/resolve.rs | 161 ++++++++++++------ crates/wit-parser/tests/all.rs | 5 + .../deps/another-pkg/other-doc.wit | 3 + .../ui/foreign-deps-union/deps/corp/saas.wit | 4 + .../deps/different-pkg/the-doc.wit | 2 + .../deps/foreign-pkg/the-doc.wit | 5 + .../deps/some-pkg/some-doc.wit | 13 ++ .../foreign-deps-union/deps/wasi/clocks.wit | 5 + .../deps/wasi/filesystem.wit | 7 + .../tests/ui/foreign-deps-union/root.wit | 50 ++++++ crates/wit-parser/tests/ui/worlds-union.wit | 44 +++++ 13 files changed, 268 insertions(+), 57 deletions(-) create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/another-pkg/other-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/corp/saas.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/different-pkg/the-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/foreign-pkg/the-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/some-pkg/some-doc.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/clocks.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/filesystem.wit create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/root.wit create mode 100644 crates/wit-parser/tests/ui/worlds-union.wit diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 329440a789..4d19679670 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -53,6 +53,7 @@ impl<'a> Ast<'a> { WorldItem::Type(_) => {} WorldItem::Import(Import { kind, .. }) => imports.push(kind), WorldItem::Export(Export { kind, .. }) => exports.push(kind), + WorldItem::Include(i) => f(Some(&world.name), &i.from, None)?, } } @@ -106,7 +107,7 @@ impl<'a> AstItem<'a> { Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Self::Interface), Some((_span, Token::World)) => World::parse(tokens, docs).map(Self::World), Some((_span, Token::Use)) => ToplevelUse::parse(tokens).map(Self::Use), - other => Err(err_expected(tokens, "`world`, `interface` or `use`", other).into()), + other => Err(err_expected(tokens, "`world`, `interface`or `use`", other).into()), } } } @@ -199,6 +200,7 @@ enum WorldItem<'a> { Export(Export<'a>), Use(Use<'a>), Type(TypeDef<'a>), + Include(Include<'a>), } impl<'a> WorldItem<'a> { @@ -217,9 +219,10 @@ impl<'a> WorldItem<'a> { } Some((_span, Token::Union)) => TypeDef::parse_union(tokens, docs).map(WorldItem::Type), Some((_span, Token::Enum)) => TypeDef::parse_enum(tokens, docs).map(WorldItem::Type), + Some((_span, Token::Include)) => Include::parse(tokens).map(WorldItem::Include), other => Err(err_expected( tokens, - "`import`, `export`, `use`, or type definition", + "`import`, `export`, `include`, `use`, or type definition", other, ) .into()), @@ -413,6 +416,18 @@ impl<'a> Use<'a> { } } +struct Include<'a> { + from: UsePath<'a>, +} + +impl<'a> Include<'a> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { + tokens.expect(Token::Include)?; + let from = UsePath::parse(tokens)?; + Ok(Include { from }) + } +} + #[derive(Debug, Clone)] pub struct Id<'a> { name: &'a str, diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index 4c79c37bc8..e69a5954d2 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -92,6 +92,9 @@ pub enum Token { ExplicitId, Integer, + + Include, + With, } #[derive(Eq, PartialEq, Debug)] @@ -281,6 +284,8 @@ impl<'a> Tokenizer<'a> { "import" => Import, "export" => Export, "package" => Package, + "include" => Include, + "with" => With, _ => Id, } } @@ -533,6 +538,8 @@ impl Token { World => "keyword `world`", Package => "keyword `package`", Integer => "an integer", + Include => "keyword `include`", + With => "keyword `with`", } } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 08505fa645..8c5c5343f2 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -62,7 +62,10 @@ pub struct Resolver<'a> { /// Spans for each world in `self.world` to assign for each import/export /// for later error reporting. - world_spans: Vec<(Vec, Vec)>, + world_item_spans: Vec<(Vec, Vec)>, + + /// Spans for each world in `self.world` + world_spans: Vec, /// The span of each interface's definition which is used for error /// reporting during the final `Resolve` phase. @@ -141,44 +144,46 @@ impl<'a> Resolver<'a> { // all interfaces in the package to visit. let asts = mem::take(&mut self.asts); self.populate_foreign_interfaces(&asts); - let order = self.populate_ast_items(&asts)?; + let (iface_order, world_order) = self.populate_ast_items(&asts)?; self.populate_foreign_types(&asts)?; // Use the topological ordering of all interfaces to resolve all // interfaces in-order. Note that a reverse-mapping from ID to AST is // generated here to assist with this. - let mut id_to_ast = IndexMap::new(); + let mut iface_id_to_ast = IndexMap::new(); + let mut world_id_to_ast = IndexMap::new(); for (i, ast) in asts.iter().enumerate() { for item in ast.items.iter() { - if let ast::AstItem::Interface(interface) = item { - let id = match self.ast_items[i][interface.name.name] { - AstItem::Interface(id) => id, - AstItem::World(_) => unreachable!(), - }; - id_to_ast.insert(id, (interface, i)); + match item { + ast::AstItem::Interface(iface) => { + let id = match self.ast_items[i][iface.name.name] { + AstItem::Interface(id) => id, + AstItem::World(_) => unreachable!(), + }; + iface_id_to_ast.insert(id, (iface, i)); + } + ast::AstItem::World(world) => { + let id = match self.ast_items[i][world.name.name] { + AstItem::World(id) => id, + AstItem::Interface(_) => unreachable!(), + }; + world_id_to_ast.insert(id, (world, i)); + } + ast::AstItem::Use(_) => {} } } } - for id in order { - let (interface, i) = &id_to_ast[&id]; + + for id in iface_order { + let (interface, i) = &iface_id_to_ast[&id]; self.cur_ast_index = *i; self.resolve_interface(id, &interface.items, &interface.docs)?; } - // With all interfaces out of the way the next order of business is to - // take care of all the worlds. Worlds only depend on interfaces so no - // topological ordering is necessary here. - for (i, ast) in asts.iter().enumerate() { - self.cur_ast_index = i; - for item in ast.items.iter() { - if let ast::AstItem::World(world) = item { - let id = match self.ast_items[i][world.name.name] { - AstItem::World(id) => id, - AstItem::Interface(_) => unreachable!(), - }; - self.resolve_world(id, world)?; - } - } + for id in world_order { + let (workld, i) = &world_id_to_ast[&id]; + self.cur_ast_index = *i; + self.resolve_world(id, workld)?; } Ok(UnresolvedPackage { @@ -199,7 +204,7 @@ impl<'a> Resolver<'a> { }) .collect(), unknown_type_spans: mem::take(&mut self.unknown_type_spans), - world_spans: mem::take(&mut self.world_spans), + world_spans: mem::take(&mut self.world_item_spans), interface_spans: mem::take(&mut self.interface_spans), foreign_dep_spans: mem::take(&mut self.foreign_dep_spans), source_map: SourceMap::default(), @@ -255,14 +260,29 @@ impl<'a> Resolver<'a> { }) } + fn alloc_world(&mut self, span: Span) -> WorldId { + self.world_spans.push(span); + self.worlds.alloc(World { + name: String::new(), + docs: Docs::default(), + exports: IndexMap::new(), + imports: IndexMap::new(), + package: None, + }) + } + /// This method will create a `World` and an `Interface` for all items /// present in the specified set of ASTs. Additionally maps for each AST are /// generated for resolving use-paths later on. - fn populate_ast_items(&mut self, asts: &[ast::Ast<'a>]) -> Result> { + fn populate_ast_items( + &mut self, + asts: &[ast::Ast<'a>], + ) -> Result<(Vec, Vec)> { let mut package_items = IndexMap::new(); // Validate that all worlds and interfaces have unique names within this // package across all ASTs which make up the package. + let mut names = HashMap::new(); let mut ast_namespaces = Vec::new(); let mut order = IndexMap::new(); for ast in asts { @@ -280,6 +300,8 @@ impl<'a> Resolver<'a> { assert!(prev.is_none()); let prev = order.insert(i.name.name, Vec::new()); assert!(prev.is_none()); + let prev = names.insert(i.name.name, item); + assert!(prev.is_none()); } ast::AstItem::World(w) => { if package_items.insert(w.name.name, w.name.span).is_some() { @@ -290,6 +312,10 @@ impl<'a> Resolver<'a> { } let prev = ast_ns.insert(w.name.name, ()); assert!(prev.is_none()); + let prev = order.insert(w.name.name, Vec::new()); + assert!(prev.is_none()); + let prev = names.insert(w.name.name, item); + assert!(prev.is_none()); } // These are processed down below. ast::AstItem::Use(_) => {} @@ -372,19 +398,34 @@ impl<'a> Resolver<'a> { })?; } - let order = toposort("interface", &order)?; + let order = toposort("interface or world", &order)?; + log::debug!("toposort for interfaces and worlds in order: {:?}", order); // Allocate interfaces in-order now that the ordering is defined. This // is then used to build up internal maps for each AST which are stored // in `self.ast_items`. let mut interface_ids = IndexMap::new(); - let mut id_order = Vec::new(); + let mut world_ids = IndexMap::new(); + let mut iface_id_order = Vec::new(); + let mut world_id_order = Vec::new(); for name in order { - let id = self.alloc_interface(package_items[name]); - self.interfaces[id].name = Some(name.to_string()); - let prev = interface_ids.insert(name, id); - assert!(prev.is_none()); - id_order.push(id); + match names.get(name).unwrap() { + ast::AstItem::Interface(_) => { + let id = self.alloc_interface(package_items[name]); + self.interfaces[id].name = Some(name.to_string()); + let prev = interface_ids.insert(name, id); + assert!(prev.is_none()); + iface_id_order.push(id); + } + ast::AstItem::World(_) => { + let id = self.alloc_world(package_items[name]); + self.worlds[id].name = name.to_string(); + let prev = world_ids.insert(name, id); + assert!(prev.is_none()); + world_id_order.push(id); + } + ast::AstItem::Use(_) => unreachable!(), + }; } for ast in asts { let mut items = IndexMap::new(); @@ -394,33 +435,39 @@ impl<'a> Resolver<'a> { let name = u.as_.as_ref().unwrap_or(u.item.name()); let item = match &u.item { ast::UsePath::Id(name) => { - *interface_ids.get(name.name).ok_or_else(|| Error { - span: name.span, - msg: format!( - "interface `{name}` does not exist", - name = name.name + match names.get(name.name).unwrap() { + ast::AstItem::Interface(_) => AstItem::Interface( + *interface_ids.get(name.name).ok_or_else(|| Error { + span: name.span, + msg: format!( + "interface `{name}` does not exist", + name = name.name + ), + })?, ), - })? + ast::AstItem::World(_) => AstItem::World( + *world_ids.get(name.name).ok_or_else(|| Error { + span: name.span, + msg: format!( + "world `{name}` does not exist", + name = name.name + ), + })?, + ), + ast::AstItem::Use(_) => unreachable!(), + } } ast::UsePath::Package { id, name } => { - self.foreign_deps[&id.package_name()][name.name] + AstItem::Interface(self.foreign_deps[&id.package_name()][name.name]) + // TODO: change this to interface and world } }; - (name.name, AstItem::Interface(item)) + (name.name, item) } ast::AstItem::Interface(i) => { (i.name.name, AstItem::Interface(interface_ids[i.name.name])) } - ast::AstItem::World(w) => { - let id = self.worlds.alloc(World { - name: w.name.name.to_string(), - docs: Docs::default(), - exports: IndexMap::new(), - imports: IndexMap::new(), - package: None, - }); - (w.name.name, AstItem::World(id)) - } + ast::AstItem::World(w) => (w.name.name, AstItem::World(world_ids[w.name.name])), }; let prev = items.insert(name, ast_item); assert!(prev.is_none()); @@ -434,7 +481,7 @@ impl<'a> Resolver<'a> { } self.ast_items.push(items); } - Ok(id_order) + Ok((iface_id_order, world_id_order)) } /// Generate a `Type::Unknown` entry for all types imported from foreign @@ -493,6 +540,8 @@ impl<'a> Resolver<'a> { ast::WorldItem::Use(u) => Some(TypeItem::Use(u)), ast::WorldItem::Type(t) => Some(TypeItem::Def(t)), ast::WorldItem::Import(_) | ast::WorldItem::Export(_) => None, + // should be handled in `wit-parser::resolve` + ast::WorldItem::Include(_) => None, }), )?; @@ -526,7 +575,9 @@ impl<'a> Resolver<'a> { for item in world.items.iter() { let (docs, kind, desc, spans, interfaces) = match item { // handled in `resolve_types` - ast::WorldItem::Use(_) | ast::WorldItem::Type(_) => continue, + ast::WorldItem::Use(_) | ast::WorldItem::Type(_) | ast::WorldItem::Include(_) => { + continue + } ast::WorldItem::Import(import) => ( &import.docs, @@ -578,7 +629,7 @@ impl<'a> Resolver<'a> { assert!(prev.is_none()); spans.push(kind.span()); } - self.world_spans.push((import_spans, export_spans)); + self.world_item_spans.push((import_spans, export_spans)); self.type_lookup.clear(); Ok(world_id) diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index c1c1132c66..8776afc88a 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -20,6 +20,11 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wit_parser::*; fn main() { + // print process id + println!("process id: {}", std::process::id()); + // sleep for 10 seconds + std::thread::sleep(std::time::Duration::from_secs(10)); + env_logger::init(); let tests = find_tests(); let filter = std::env::args().nth(1); diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/another-pkg/other-doc.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/another-pkg/other-doc.wit new file mode 100644 index 0000000000..dc584c6302 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/another-pkg/other-doc.wit @@ -0,0 +1,3 @@ +package foo:another-pkg + +interface other-interface {} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/corp/saas.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/corp/saas.wit new file mode 100644 index 0000000000..f476b526d3 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/corp/saas.wit @@ -0,0 +1,4 @@ +package foo:corp + +interface saas { +} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/different-pkg/the-doc.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/different-pkg/the-doc.wit new file mode 100644 index 0000000000..ddeeab873f --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/different-pkg/the-doc.wit @@ -0,0 +1,2 @@ +package foo:different-pkg +interface i {} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/foreign-pkg/the-doc.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/foreign-pkg/the-doc.wit new file mode 100644 index 0000000000..cba7a70e9f --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/foreign-pkg/the-doc.wit @@ -0,0 +1,5 @@ +package foo:foreign-pkg + +interface the-default { + type some-type = u32 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/some-pkg/some-doc.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/some-pkg/some-doc.wit new file mode 100644 index 0000000000..3bad202e2a --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/some-pkg/some-doc.wit @@ -0,0 +1,13 @@ +package foo:some-pkg + +interface the-default { + type from-default = string +} + +interface some-interface { + type another-type = u32 +} + +interface another-interface { + type yet-another-type = u8 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/clocks.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/clocks.wit new file mode 100644 index 0000000000..0ea67674ad --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/clocks.wit @@ -0,0 +1,5 @@ +package foo:wasi + +interface clocks { + type timestamp = u64 +} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/filesystem.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/filesystem.wit new file mode 100644 index 0000000000..1ed63e3da3 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/filesystem.wit @@ -0,0 +1,7 @@ +package foo:wasi + +interface filesystem { + record stat { + ino: u64 + } +} diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/root.wit b/crates/wit-parser/tests/ui/foreign-deps-union/root.wit new file mode 100644 index 0000000000..262a8a7262 --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/root.wit @@ -0,0 +1,50 @@ +package foo:root + +interface foo { + use foo:wasi/clocks.{timestamp} + use foo:wasi/filesystem.{stat} +} + +world my-world { + import foo:wasi/filesystem + import foo:wasi/clocks + + export foo:corp/saas +} + +use foo:wasi/filesystem as filesystem +use foo:wasi/clocks as clocks + +world my-world2 { + import filesystem + import clocks + export foo + export foo:corp/saas +} + +interface bar { + use filesystem.{} + use foo:some-pkg/the-default.{from-default} + use foo:some-pkg/some-interface.{another-type} + use foo:some-pkg/some-interface.{} + use foo:some-pkg/another-interface.{yet-another-type} + use foo:different-pkg/i.{} +} + +world bars-world { + import foo:some-pkg/the-default + import foo:another-pkg/other-interface +} + +interface use1 { + use foo:foreign-pkg/the-default.{some-type} +} +interface use2 { + use foo:foreign-pkg/the-default.{some-type} +} + +world unionw-world { + include my-world + include my-world2 + include bars-world +} diff --git a/crates/wit-parser/tests/ui/worlds-union.wit b/crates/wit-parser/tests/ui/worlds-union.wit new file mode 100644 index 0000000000..f4bcfa0ecf --- /dev/null +++ b/crates/wit-parser/tests/ui/worlds-union.wit @@ -0,0 +1,44 @@ +package foo:foo + + +world union-world { + include test + include a-different-world + include the-world +} + +interface foo {} +interface bar {} + +world the-world { + import foo + import bar + import baz: interface { + foo: func() + } + export foo + export bar + export baz2: interface { + foo: func() + } +} + +world a-different-world { + import foo +} + +interface i1 { + type t = u32 +} +interface i2 { + use i1.{t} +} +interface i3 { + use i2.{t} +} + +world test { + import i3 + export i1 + export i3 +} \ No newline at end of file From e9629d3e47de09dfab67f6c6b9fd5e1b382d85f4 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Mon, 29 May 2023 12:03:15 -0700 Subject: [PATCH 02/14] Implemented resolver for foreign world deps Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-component/src/decoding.rs | 2 + crates/wit-component/src/metadata.rs | 1 + crates/wit-parser/src/ast.rs | 41 +++- crates/wit-parser/src/ast/resolve.rs | 209 +++++++++++++++--- crates/wit-parser/src/lib.rs | 12 +- crates/wit-parser/src/resolve.rs | 107 ++++++++- crates/wit-parser/tests/all.rs | 5 - .../ui/foreign-deps-union/deps/wasi/wasi.wit | 6 + .../tests/ui/foreign-deps-union/root.wit | 2 +- .../tests/ui/parse-fail/use-cycle1.wit.result | 2 +- .../tests/ui/parse-fail/use-cycle4.wit.result | 2 +- 11 files changed, 331 insertions(+), 58 deletions(-) create mode 100644 crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/wasi.wit diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index 109b19cb5b..be60f9f043 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -140,6 +140,7 @@ impl<'a> ComponentInfo<'a> { imports: Default::default(), exports: Default::default(), package: None, + includes: Default::default(), }); let mut decoder = WitPackageDecoder { resolve, @@ -752,6 +753,7 @@ impl WitPackageDecoder<'_> { docs: Default::default(), imports: Default::default(), exports: Default::default(), + includes: Default::default(), package: None, }; diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 2fe9035ccc..796cd00afd 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -75,6 +75,7 @@ impl Default for Bindgen { docs: Default::default(), imports: Default::default(), exports: Default::default(), + includes: Default::default(), package: Some(package), }); resolve.packages[package] diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index 4d19679670..f11f5ae350 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -36,7 +36,12 @@ impl<'a> Ast<'a> { fn for_each_path<'b>( &'b self, - mut f: impl FnMut(Option<&'b Id<'a>>, &'b UsePath<'a>, Option<&'b [UseName<'a>]>) -> Result<()>, + mut f: impl FnMut( + Option<&'b Id<'a>>, + &'b UsePath<'a>, + Option<&'b [UseName<'a>]>, + WorldOrInterface, + ) -> Result<()>, ) -> Result<()> { for item in self.items.iter() { match item { @@ -49,11 +54,15 @@ impl<'a> Ast<'a> { let mut exports = Vec::new(); for item in world.items.iter() { match item { - WorldItem::Use(u) => f(None, &u.from, Some(&u.names))?, + WorldItem::Use(u) => { + f(None, &u.from, Some(&u.names), WorldOrInterface::Interface)? + } + WorldItem::Include(i) => { + f(Some(&world.name), &i.from, None, WorldOrInterface::World)? + } WorldItem::Type(_) => {} WorldItem::Import(Import { kind, .. }) => imports.push(kind), WorldItem::Export(Export { kind, .. }) => exports.push(kind), - WorldItem::Include(i) => f(Some(&world.name), &i.from, None)?, } } @@ -61,13 +70,18 @@ impl<'a> Ast<'a> { ExternKind::Interface(_, items) => { for item in items { match item { - InterfaceItem::Use(u) => f(None, &u.from, Some(&u.names))?, + InterfaceItem::Use(u) => f( + None, + &u.from, + Some(&u.names), + WorldOrInterface::Interface, + )?, _ => {} } } Ok(()) } - ExternKind::Path(path) => f(None, path, None), + ExternKind::Path(path) => f(None, path, None, WorldOrInterface::Interface), ExternKind::Func(..) => Ok(()), }; @@ -81,13 +95,18 @@ impl<'a> Ast<'a> { AstItem::Interface(i) => { for item in i.items.iter() { match item { - InterfaceItem::Use(u) => f(Some(&i.name), &u.from, Some(&u.names))?, + InterfaceItem::Use(u) => f( + Some(&i.name), + &u.from, + Some(&u.names), + WorldOrInterface::Interface, + )?, _ => {} } } } AstItem::Use(u) => { - f(None, &u.item, None)?; + f(None, &u.item, None, WorldOrInterface::Interface)?; } } } @@ -107,7 +126,7 @@ impl<'a> AstItem<'a> { Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Self::Interface), Some((_span, Token::World)) => World::parse(tokens, docs).map(Self::World), Some((_span, Token::Use)) => ToplevelUse::parse(tokens).map(Self::Use), - other => Err(err_expected(tokens, "`world`, `interface`or `use`", other).into()), + other => Err(err_expected(tokens, "`world`, `interface` or `use`", other).into()), } } } @@ -332,6 +351,12 @@ impl<'a> Interface<'a> { } } +#[derive(Debug)] +pub enum WorldOrInterface { + World, + Interface, +} + enum InterfaceItem<'a> { TypeDef(TypeDef<'a>), Value(Value<'a>), diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 8c5c5343f2..e226b68418 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,8 +1,10 @@ -use super::{Error, ParamList, ResultList, ValueKind}; +use super::{Error, ParamList, ResultList, ValueKind, WorldOrInterface}; use crate::ast::toposort::toposort; +use crate::ast::Ast; use crate::*; use anyhow::{bail, Result}; use indexmap::IndexMap; +use log::debug; use std::collections::{HashMap, HashSet}; use std::mem; @@ -45,11 +47,15 @@ pub struct Resolver<'a> { /// package. This map is keyed by the name of the package being imported /// from. The next level of key is the name of the interface being imported /// from, and the final value is the assigned ID of the interface. - foreign_deps: IndexMap>, + foreign_iface_deps: IndexMap>, + + foreign_world_deps: IndexMap>, /// All interfaces that are present within `self.foreign_deps`. foreign_interfaces: HashSet, + foreign_worlds: HashSet, + /// The current type lookup scope which will eventually make its way into /// `self.interface_types`. type_lookup: IndexMap<&'a str, (TypeOrItem, Span)>, @@ -74,6 +80,8 @@ pub struct Resolver<'a> { /// Spans per entry in `self.foreign_deps` for where the dependency was /// introduced to print an error message if necessary. foreign_dep_spans: Vec, + + foreign_world_spans: Vec, } #[derive(Debug, Copy, Clone)] @@ -144,6 +152,7 @@ impl<'a> Resolver<'a> { // all interfaces in the package to visit. let asts = mem::take(&mut self.asts); self.populate_foreign_interfaces(&asts); + self.populate_foreign_worlds(&asts); let (iface_order, world_order) = self.populate_ast_items(&asts)?; self.populate_foreign_types(&asts)?; @@ -191,8 +200,8 @@ impl<'a> Resolver<'a> { worlds: mem::take(&mut self.worlds), types: mem::take(&mut self.types), interfaces: mem::take(&mut self.interfaces), - foreign_deps: self - .foreign_deps + foreign_iface_deps: self + .foreign_iface_deps .iter() .map(|(name, deps)| { ( @@ -204,10 +213,24 @@ impl<'a> Resolver<'a> { }) .collect(), unknown_type_spans: mem::take(&mut self.unknown_type_spans), - world_spans: mem::take(&mut self.world_item_spans), + world_item_spans: mem::take(&mut self.world_item_spans), interface_spans: mem::take(&mut self.interface_spans), + world_spans: mem::take(&mut self.world_spans), foreign_dep_spans: mem::take(&mut self.foreign_dep_spans), source_map: SourceMap::default(), + foreign_world_deps: self + .foreign_world_deps + .iter() + .map(|(name, deps)| { + ( + name.clone(), + deps.iter() + .map(|(name, id)| (name.to_string(), *id)) + .collect(), + ) + }) + .collect(), + foreign_world_spans: mem::take(&mut self.foreign_world_spans), }) } @@ -216,14 +239,18 @@ impl<'a> Resolver<'a> { /// This will populate the `self.foreign_{deps,interfaces}` maps with all /// `UsePath::Package` entries. fn populate_foreign_interfaces(&mut self, asts: &[ast::Ast<'a>]) { - let mut foreign_deps = mem::take(&mut self.foreign_deps); + let mut foreign_deps = mem::take(&mut self.foreign_iface_deps); let mut foreign_interfaces = mem::take(&mut self.foreign_interfaces); for ast in asts { - ast.for_each_path(|_, path, _names| { + ast.for_each_path(|_, path, _names, world_or_iface| { let (id, name) = match path { ast::UsePath::Package { id, name } => (id, name), _ => return Ok(()), }; + // skip worlds + if !matches!(world_or_iface, WorldOrInterface::Interface) { + return Ok(()); + } let deps = foreign_deps.entry(id.package_name()).or_insert_with(|| { self.foreign_dep_spans.push(id.span); @@ -244,10 +271,49 @@ impl<'a> Resolver<'a> { }) .unwrap(); } - self.foreign_deps = foreign_deps; + self.foreign_iface_deps = foreign_deps; self.foreign_interfaces = foreign_interfaces; } + /// Registers all foreign worlds made within the ASTs provided. + /// + /// This will populate teh `self.foreign{deps, worlds}` maps iwht all + /// `UsePath::Package` entries. + fn populate_foreign_worlds(&mut self, asts: &[ast::Ast<'a>]) { + let mut foreign_deps = mem::take(&mut self.foreign_world_deps); + let mut foreign_worlds = mem::take(&mut self.foreign_worlds); + for ast in asts { + ast.for_each_path(|_: Option<&ast::Id>, path, _names, world_or_iface| { + let (id, name) = match path { + ast::UsePath::Package { id, name } => (id, name), + _ => return Ok(()), + }; + // skip interfaces + if !matches!(world_or_iface, WorldOrInterface::World) { + return Ok(()); + } + let deps = foreign_deps + .entry(id.package_name()) + .or_insert_with(|| IndexMap::new()); + let id = *deps.entry(name.name).or_insert_with(|| { + log::trace!( + "creating a world for foreign dep: {}/{}", + id.package_name(), + name.name + ); + self.alloc_world(name.span, true) + }); + + foreign_worlds.insert(id); + + Ok(()) + }) + .unwrap(); + } + self.foreign_world_deps = foreign_deps; + self.foreign_worlds = foreign_worlds; + } + fn alloc_interface(&mut self, span: Span) -> InterfaceId { self.interface_types.push(IndexMap::new()); self.interface_spans.push(span); @@ -260,14 +326,18 @@ impl<'a> Resolver<'a> { }) } - fn alloc_world(&mut self, span: Span) -> WorldId { + fn alloc_world(&mut self, span: Span, dummy_span: bool) -> WorldId { self.world_spans.push(span); + if dummy_span { + self.world_item_spans.push((Vec::new(), Vec::new())); + } self.worlds.alloc(World { name: String::new(), docs: Docs::default(), exports: IndexMap::new(), imports: IndexMap::new(), package: None, + includes: Vec::new(), }) } @@ -362,7 +432,7 @@ impl<'a> Resolver<'a> { // With this file's namespace information look at all `use` paths // and record dependencies between interfaces. - ast.for_each_path(|iface, path, _names| { + ast.for_each_path(|iface, path, _names, _| { // If this import isn't contained within an interface then it's // in a world and it doesn't need to participate in our // topo-sort. @@ -418,7 +488,8 @@ impl<'a> Resolver<'a> { iface_id_order.push(id); } ast::AstItem::World(_) => { - let id = self.alloc_world(package_items[name]); + // No dummy span needs to be created because they will be created at `resolve_world` + let id = self.alloc_world(package_items[name], false); self.worlds[id].name = name.to_string(); let prev = world_ids.insert(name, id); assert!(prev.is_none()); @@ -435,31 +506,45 @@ impl<'a> Resolver<'a> { let name = u.as_.as_ref().unwrap_or(u.item.name()); let item = match &u.item { ast::UsePath::Id(name) => { - match names.get(name.name).unwrap() { - ast::AstItem::Interface(_) => AstItem::Interface( - *interface_ids.get(name.name).ok_or_else(|| Error { + if let Some(ast_item) = names.get(name.name) { + match ast_item { + ast::AstItem::Interface(_) => AstItem::Interface( + *interface_ids.get(name.name).ok_or_else(|| Error { + span: name.span, + msg: format!( + "interface `{name}` does not exist", + name = name.name + ), + })?, + ), + ast::AstItem::World(_) => AstItem::World( + *world_ids.get(name.name).ok_or_else(|| Error { + span: name.span, + msg: format!( + "world `{name}` does not exist", + name = name.name + ), + })?, + ), + ast::AstItem::Use(_) => unreachable!(), + } + } else { + AstItem::Interface(*interface_ids.get(name.name).ok_or_else( + || Error { span: name.span, msg: format!( "interface `{name}` does not exist", name = name.name ), - })?, - ), - ast::AstItem::World(_) => AstItem::World( - *world_ids.get(name.name).ok_or_else(|| Error { - span: name.span, - msg: format!( - "world `{name}` does not exist", - name = name.name - ), - })?, - ), - ast::AstItem::Use(_) => unreachable!(), + }, + )?) } } ast::UsePath::Package { id, name } => { - AstItem::Interface(self.foreign_deps[&id.package_name()][name.name]) - // TODO: change this to interface and world + // TODO: check if name belongs to a iface or world + AstItem::Interface( + self.foreign_iface_deps[&id.package_name()][name.name], + ) } }; (name.name, item) @@ -493,12 +578,12 @@ impl<'a> Resolver<'a> { fn populate_foreign_types(&mut self, asts: &[ast::Ast<'a>]) -> Result<()> { for (i, ast) in asts.iter().enumerate() { self.cur_ast_index = i; - ast.for_each_path(|_, path, names| { + ast.for_each_path(|_, path, names, _| { let names = match names { Some(names) => names, None => return Ok(()), }; - let iface = self.resolve_path(path)?; + let iface = self.resolve_iface_path(path)?; if !self.foreign_interfaces.contains(&iface) { return Ok(()); } @@ -545,6 +630,15 @@ impl<'a> Resolver<'a> { }), )?; + // resolve include items + let items = world.items.iter().filter_map(|i| match i { + ast::WorldItem::Include(i) => Some(i), + _ => None, + }); + for include in items { + self.resolve_include(TypeOwner::World(world_id), include)?; + } + let mut export_spans = Vec::new(); let mut import_spans = Vec::new(); let mut used_names = HashMap::new(); @@ -609,7 +703,7 @@ impl<'a> Resolver<'a> { } WorldKey::Name(name.name.to_string()) } - ast::ExternKind::Path(path) => WorldKey::Interface(self.resolve_path(path)?), + ast::ExternKind::Path(path) => WorldKey::Interface(self.resolve_iface_path(path)?), }; let world_item = self.resolve_world_item(docs, kind)?; if let WorldItem::Interface(id) = world_item { @@ -649,7 +743,7 @@ impl<'a> Resolver<'a> { Ok(WorldItem::Interface(id)) } ast::ExternKind::Path(path) => { - let id = self.resolve_path(path)?; + let id = self.resolve_iface_path(path)?; Ok(WorldItem::Interface(id)) } ast::ExternKind::Func(name, func) => { @@ -782,7 +876,7 @@ impl<'a> Resolver<'a> { } fn resolve_use(&mut self, owner: TypeOwner, u: &ast::Use<'a>) -> Result<()> { - let use_from = self.resolve_path(&u.from)?; + let use_from = self.resolve_iface_path(&u.from)?; for name in u.names.iter() { let lookup = &self.interface_types[use_from.index()]; let id = match lookup.get(name.name.name) { @@ -810,6 +904,18 @@ impl<'a> Resolver<'a> { Ok(()) } + /// For each name in the `include`, resolve the path of the include, add it to the self.includes + fn resolve_include(&mut self, owner: TypeOwner, i: &ast::Include<'a>) -> Result<()> { + let (include_from, span) = self.resolve_world_path(&i.from)?; + self.foreign_world_spans.push(span); + let world_id = match owner { + TypeOwner::World(id) => id, + _ => unreachable!(), + }; + self.worlds[world_id].includes.push(include_from); + Ok(()) + } + fn resolve_function( &mut self, docs: &ast::Docs<'_>, @@ -828,7 +934,7 @@ impl<'a> Resolver<'a> { }) } - fn resolve_path(&self, path: &ast::UsePath<'a>) -> Result { + fn resolve_iface_path(&self, path: &ast::UsePath<'a>) -> Result { match path { ast::UsePath::Id(id) => { let item = self.ast_items[self.cur_ast_index] @@ -854,8 +960,41 @@ impl<'a> Resolver<'a> { } } ast::UsePath::Package { id, name } => { - Ok(self.foreign_deps[&id.package_name()][name.name]) + Ok(self.foreign_iface_deps[&id.package_name()][name.name]) + } + } + } + + fn resolve_world_path(&self, path: &ast::UsePath<'a>) -> Result<(WorldId, Span)> { + match path { + ast::UsePath::Id(id) => { + let span = id.span; + let item = self.ast_items[self.cur_ast_index] + .get(id.name) + .or_else(|| self.package_items.get(id.name)); + match item { + Some(AstItem::World(id)) => Ok((*id, span)), + Some(AstItem::Interface(_)) => { + bail!(Error { + span: id.span, + msg: format!( + "name `{}` is defined as an interface, not a world", + id.name + ), + }) + } + None => { + bail!(Error { + span: id.span, + msg: format!("world `{name}` does not exist", name = id.name), + }) + } + } } + ast::UsePath::Package { id, name } => Ok(( + self.foreign_world_deps[&id.package_name()][name.name], + name.span, + )), } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index d5d2fb2cb0..50187efc4a 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -86,12 +86,15 @@ pub struct UnresolvedPackage { /// interface name followed by the identifier within `self.interfaces`. The /// fields of `self.interfaces` describes the required types that are from /// each foreign interface. - pub foreign_deps: IndexMap>, + pub foreign_iface_deps: IndexMap>, + pub foreign_world_deps: IndexMap>, unknown_type_spans: Vec, - world_spans: Vec<(Vec, Vec)>, + world_item_spans: Vec<(Vec, Vec)>, interface_spans: Vec, + world_spans: Vec, foreign_dep_spans: Vec, + foreign_world_spans: Vec, source_map: SourceMap, } @@ -235,6 +238,9 @@ pub struct World { /// The package that owns this world. pub package: Option, + + /// All the included worlds from this world. + pub includes: Vec, } /// The key to the import/export maps of a world. Either a kebab-name or a @@ -258,7 +264,7 @@ impl WorldKey { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum WorldItem { /// An interface is being imported or exported from a world, indicating that /// it's a namespace of functions. diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index d5f5cdfba1..554e6fe03b 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -132,7 +132,7 @@ impl Resolve { return Ok(()); } pkg.source_map.rewrite_error(|| { - for (i, (dep, _)) in pkg.foreign_deps.iter().enumerate() { + for (i, (dep, _)) in pkg.foreign_iface_deps.iter().enumerate() { let span = pkg.foreign_dep_spans[i]; if !visiting.insert(dep) { bail!(Error { @@ -625,14 +625,23 @@ impl Remap { // This is done after types/interfaces are fully settled so the // transitive relation between interfaces, through types, is understood // here. - assert_eq!(unresolved.worlds.len(), unresolved.world_spans.len()); + assert_eq!(unresolved.worlds.len(), unresolved.world_item_spans.len()); + let unresolved_world_spans = unresolved.foreign_world_spans; for ((id, mut world), (import_spans, export_spans)) in unresolved .worlds .into_iter() + .zip(unresolved.world_item_spans) .skip(foreign_worlds) - .zip(unresolved.world_spans) { self.update_world(&mut world, resolve, &import_spans, &export_spans)?; + + // Resolve all includes of the world + let includes = mem::take(&mut world.includes); + for include_world in includes.into_iter() { + let span = unresolved_world_spans[include_world.index()]; + self.resolve_include(&mut world, include_world, span, resolve)?; + } + let new_id = resolve.worlds.alloc(world); assert_eq!(self.worlds.len(), id.index()); self.worlds.push(new_id); @@ -678,10 +687,47 @@ impl Remap { resolve: &mut Resolve, unresolved: &UnresolvedPackage, ) -> Result<()> { + // Invert the `foreign_deps` map to be keyed by world id to get + // used in the loops below. + let mut world_to_package = HashMap::new(); + for (i, (pkg_name, worlds)) in unresolved.foreign_world_deps.iter().enumerate() { + for (world, unresolved_world_id) in worlds { + let prev = world_to_package.insert( + *unresolved_world_id, + (pkg_name, world, unresolved.foreign_dep_spans[i]), + ); + assert!(prev.is_none()); + } + } + + for (unresolved_world_id, _) in unresolved.worlds.iter() { + let (pkg_name, world, span) = match world_to_package.get(&unresolved_world_id) { + Some(items) => *items, + None => break, + }; + + let pkgid = resolve + .package_names + .get(pkg_name) + .copied() + .ok_or_else(|| Error { + span, + msg: format!("package not found"), + })?; + let pkg = &resolve.packages[pkgid]; + let span = unresolved.world_spans[unresolved_world_id.index()]; + let world_id = pkg.worlds.get(world).copied().ok_or_else(|| Error { + span, + msg: format!("world not found in package"), + })?; + assert_eq!(self.worlds.len(), unresolved_world_id.index()); + self.worlds.push(world_id); + } + // Invert the `foreign_deps` map to be keyed by interface id to get // used in the loops below. let mut interface_to_package = HashMap::new(); - for (i, (pkg_name, interfaces)) in unresolved.foreign_deps.iter().enumerate() { + for (i, (pkg_name, interfaces)) in unresolved.foreign_iface_deps.iter().enumerate() { for (interface, unresolved_interface_id) in interfaces { let prev = interface_to_package.insert( *unresolved_interface_id, @@ -1025,6 +1071,59 @@ impl Remap { } }); } + + fn resolve_include( + &self, + world: &mut World, + include_world: WorldId, + span: Span, + resolve: &Resolve, + ) -> Result<()> { + let include_world_id = self.worlds[include_world.index()]; + let include_world = &resolve.worlds[include_world_id]; + + // copy the imports and exports from the included world into the current world + for import in include_world.imports.iter() { + let name = import.0.clone(); + + // check if world.imports already has the same world item + // let item = world.imports.iter().find(|i| i.1 == import.1); + // if item.is_some() && item.unwrap().0 != &name { + // bail!(Error { + // msg: format!("include of world `{}` has imports `{}`, which shadows the previous imports", include_world.name, name), + // span: span + // }) + // } + + let prev = world.imports.insert(name.clone(), import.1.clone()); + // if prev.is_some() && &prev.unwrap() != import.1 { + // bail!(Error { + // msg: format!("include of world `{}` has imports `{}`, which shadows the previous imports", include_world.name, name), + // span: span + // }) + // } + } + + for export in include_world.exports.iter() { + let name = export.0.clone(); + // check if world.exports already has the same world item + let item = world.exports.iter().find(|i| i.1 == export.1); + // if item.is_some() && item.unwrap().0 != &name { + // bail!(Error { + // msg: format!("include of world `{}` has exports `{}`, which shadows the previous exports", include_world.name, name), + // span: span + // }) + // } + let prev: Option = world.exports.insert(name.clone(), export.1.clone()); + // if prev.is_some() && &prev.unwrap() != export.1 { + // bail!(Error { + // msg: format!("include of world `{}` has exports `{}`, which shadows the previous exports", include_world.name, name), + // span: span + // }) + // } + } + Ok(()) + } } fn foreach_interface_dep( diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs index 8776afc88a..c1c1132c66 100644 --- a/crates/wit-parser/tests/all.rs +++ b/crates/wit-parser/tests/all.rs @@ -20,11 +20,6 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wit_parser::*; fn main() { - // print process id - println!("process id: {}", std::process::id()); - // sleep for 10 seconds - std::thread::sleep(std::time::Duration::from_secs(10)); - env_logger::init(); let tests = find_tests(); let filter = std::env::args().nth(1); diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/wasi.wit b/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/wasi.wit new file mode 100644 index 0000000000..09bcd3ca1c --- /dev/null +++ b/crates/wit-parser/tests/ui/foreign-deps-union/deps/wasi/wasi.wit @@ -0,0 +1,6 @@ +package foo:wasi + +world wasi { + import filesystem + import clocks +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/foreign-deps-union/root.wit b/crates/wit-parser/tests/ui/foreign-deps-union/root.wit index 262a8a7262..787cff092d 100644 --- a/crates/wit-parser/tests/ui/foreign-deps-union/root.wit +++ b/crates/wit-parser/tests/ui/foreign-deps-union/root.wit @@ -46,5 +46,5 @@ interface use2 { world unionw-world { include my-world include my-world2 - include bars-world + include foo:wasi/wasi } diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result index c0150a6008..984ecb59d6 100644 --- a/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle1.wit.result @@ -1,4 +1,4 @@ -interface `foo` depends on itself +interface or world `foo` depends on itself --> tests/ui/parse-fail/use-cycle1.wit:5:11 | 5 | interface foo { diff --git a/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result index 79b327045d..11ece35ad4 100644 --- a/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/use-cycle4.wit.result @@ -1,4 +1,4 @@ -interface `bar` depends on itself +interface or world `bar` depends on itself --> tests/ui/parse-fail/use-cycle4.wit:10:11 | 10 | interface bar { From f366966c4b685fee304b8011c0cc19c3556fe3d9 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Mon, 29 May 2023 13:03:05 -0700 Subject: [PATCH 03/14] clean up and add more tests Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast.rs | 3 +- crates/wit-parser/src/ast/resolve.rs | 178 ++++++------------ crates/wit-parser/src/lib.rs | 11 +- crates/wit-parser/src/resolve.rs | 79 +++----- .../ui/complex-include/deps/bar/root.wit | 9 + .../ui/complex-include/deps/baz/root.wit | 9 + .../tests/ui/complex-include/root.wit | 26 +++ crates/wit-parser/tests/ui/include-reps.wit | 15 ++ .../tests/ui/parse-fail/bad-include1.wit | 5 + .../ui/parse-fail/bad-include1.wit.result | 5 + .../tests/ui/parse-fail/bad-include2.wit | 9 + .../ui/parse-fail/bad-include2.wit.result | 5 + .../tests/ui/parse-fail/bad-include3.wit | 5 + .../ui/parse-fail/bad-include3.wit.result | 5 + .../tests/ui/parse-fail/include-cycle.wit | 9 + .../ui/parse-fail/include-cycle.wit.result | 5 + .../no-access-to-sibling-use.wit.result | 2 +- .../unresolved-interface3.wit.result | 2 +- .../tests/ui/worlds-union-dedup.wit | 23 +++ 19 files changed, 221 insertions(+), 184 deletions(-) create mode 100644 crates/wit-parser/tests/ui/complex-include/deps/bar/root.wit create mode 100644 crates/wit-parser/tests/ui/complex-include/deps/baz/root.wit create mode 100644 crates/wit-parser/tests/ui/complex-include/root.wit create mode 100644 crates/wit-parser/tests/ui/include-reps.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-include1.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-include2.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-include2.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-include3.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/bad-include3.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/include-cycle.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/include-cycle.wit.result create mode 100644 crates/wit-parser/tests/ui/worlds-union-dedup.wit diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index f11f5ae350..e4dd0dde47 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -106,7 +106,7 @@ impl<'a> Ast<'a> { } } AstItem::Use(u) => { - f(None, &u.item, None, WorldOrInterface::Interface)?; + f(None, &u.item, None, WorldOrInterface::Unknown)?; } } } @@ -355,6 +355,7 @@ impl<'a> Interface<'a> { pub enum WorldOrInterface { World, Interface, + Unknown, } enum InterfaceItem<'a> { diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index e226b68418..50b5f1e457 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -47,9 +47,7 @@ pub struct Resolver<'a> { /// package. This map is keyed by the name of the package being imported /// from. The next level of key is the name of the interface being imported /// from, and the final value is the assigned ID of the interface. - foreign_iface_deps: IndexMap>, - - foreign_world_deps: IndexMap>, + foreign_deps: IndexMap>, /// All interfaces that are present within `self.foreign_deps`. foreign_interfaces: HashSet, @@ -84,12 +82,6 @@ pub struct Resolver<'a> { foreign_world_spans: Vec, } -#[derive(Debug, Copy, Clone)] -enum AstItem { - Interface(InterfaceId), - World(WorldId), -} - #[derive(PartialEq, Eq, Hash)] enum Key { Variant(Vec<(String, Option)>), @@ -151,8 +143,7 @@ impl<'a> Resolver<'a> { // `use` statements and additionally generate a topological ordering of // all interfaces in the package to visit. let asts = mem::take(&mut self.asts); - self.populate_foreign_interfaces(&asts); - self.populate_foreign_worlds(&asts); + self.populate_foreign_deps(&asts); let (iface_order, world_order) = self.populate_ast_items(&asts)?; self.populate_foreign_types(&asts)?; @@ -200,8 +191,8 @@ impl<'a> Resolver<'a> { worlds: mem::take(&mut self.worlds), types: mem::take(&mut self.types), interfaces: mem::take(&mut self.interfaces), - foreign_iface_deps: self - .foreign_iface_deps + foreign_deps: self + .foreign_deps .iter() .map(|(name, deps)| { ( @@ -218,39 +209,24 @@ impl<'a> Resolver<'a> { world_spans: mem::take(&mut self.world_spans), foreign_dep_spans: mem::take(&mut self.foreign_dep_spans), source_map: SourceMap::default(), - foreign_world_deps: self - .foreign_world_deps - .iter() - .map(|(name, deps)| { - ( - name.clone(), - deps.iter() - .map(|(name, id)| (name.to_string(), *id)) - .collect(), - ) - }) - .collect(), foreign_world_spans: mem::take(&mut self.foreign_world_spans), }) } /// Registers all foreign dependencies made within the ASTs provided. /// - /// This will populate the `self.foreign_{deps,interfaces}` maps with all + /// This will populate the `self.foreign_{deps,interfaces,worlds}` maps with all /// `UsePath::Package` entries. - fn populate_foreign_interfaces(&mut self, asts: &[ast::Ast<'a>]) { - let mut foreign_deps = mem::take(&mut self.foreign_iface_deps); + fn populate_foreign_deps(&mut self, asts: &[ast::Ast<'a>]) { + let mut foreign_deps = mem::take(&mut self.foreign_deps); let mut foreign_interfaces = mem::take(&mut self.foreign_interfaces); + let mut foreign_worlds = mem::take(&mut self.foreign_worlds); for ast in asts { ast.for_each_path(|_, path, _names, world_or_iface| { let (id, name) = match path { ast::UsePath::Package { id, name } => (id, name), _ => return Ok(()), }; - // skip worlds - if !matches!(world_or_iface, WorldOrInterface::Interface) { - return Ok(()); - } let deps = foreign_deps.entry(id.package_name()).or_insert_with(|| { self.foreign_dep_spans.push(id.span); @@ -262,55 +238,31 @@ impl<'a> Resolver<'a> { id.package_name(), name.name ); - self.alloc_interface(name.span) + match world_or_iface { + WorldOrInterface::World => { + AstItem::World(self.alloc_world(name.span, true)) + } + WorldOrInterface::Interface => { + AstItem::Interface(self.alloc_interface(name.span)) + } + WorldOrInterface::Unknown => { + // TODO: fix me + AstItem::Interface(self.alloc_interface(name.span)) + } + } }); - foreign_interfaces.insert(id); - - Ok(()) - }) - .unwrap(); - } - self.foreign_iface_deps = foreign_deps; - self.foreign_interfaces = foreign_interfaces; - } - - /// Registers all foreign worlds made within the ASTs provided. - /// - /// This will populate teh `self.foreign{deps, worlds}` maps iwht all - /// `UsePath::Package` entries. - fn populate_foreign_worlds(&mut self, asts: &[ast::Ast<'a>]) { - let mut foreign_deps = mem::take(&mut self.foreign_world_deps); - let mut foreign_worlds = mem::take(&mut self.foreign_worlds); - for ast in asts { - ast.for_each_path(|_: Option<&ast::Id>, path, _names, world_or_iface| { - let (id, name) = match path { - ast::UsePath::Package { id, name } => (id, name), - _ => return Ok(()), + let _ = match id { + AstItem::Interface(id) => foreign_interfaces.insert(id), + AstItem::World(id) => foreign_worlds.insert(id), }; - // skip interfaces - if !matches!(world_or_iface, WorldOrInterface::World) { - return Ok(()); - } - let deps = foreign_deps - .entry(id.package_name()) - .or_insert_with(|| IndexMap::new()); - let id = *deps.entry(name.name).or_insert_with(|| { - log::trace!( - "creating a world for foreign dep: {}/{}", - id.package_name(), - name.name - ); - self.alloc_world(name.span, true) - }); - - foreign_worlds.insert(id); Ok(()) }) .unwrap(); } - self.foreign_world_deps = foreign_deps; + self.foreign_deps = foreign_deps; + self.foreign_interfaces = foreign_interfaces; self.foreign_worlds = foreign_worlds; } @@ -474,8 +426,7 @@ impl<'a> Resolver<'a> { // Allocate interfaces in-order now that the ordering is defined. This // is then used to build up internal maps for each AST which are stored // in `self.ast_items`. - let mut interface_ids = IndexMap::new(); - let mut world_ids = IndexMap::new(); + let mut ids = IndexMap::new(); let mut iface_id_order = Vec::new(); let mut world_id_order = Vec::new(); for name in order { @@ -483,7 +434,7 @@ impl<'a> Resolver<'a> { ast::AstItem::Interface(_) => { let id = self.alloc_interface(package_items[name]); self.interfaces[id].name = Some(name.to_string()); - let prev = interface_ids.insert(name, id); + let prev = ids.insert(name, AstItem::Interface(id)); assert!(prev.is_none()); iface_id_order.push(id); } @@ -491,7 +442,7 @@ impl<'a> Resolver<'a> { // No dummy span needs to be created because they will be created at `resolve_world` let id = self.alloc_world(package_items[name], false); self.worlds[id].name = name.to_string(); - let prev = world_ids.insert(name, id); + let prev = ids.insert(name, AstItem::World(id)); assert!(prev.is_none()); world_id_order.push(id); } @@ -505,54 +456,29 @@ impl<'a> Resolver<'a> { ast::AstItem::Use(u) => { let name = u.as_.as_ref().unwrap_or(u.item.name()); let item = match &u.item { - ast::UsePath::Id(name) => { - if let Some(ast_item) = names.get(name.name) { - match ast_item { - ast::AstItem::Interface(_) => AstItem::Interface( - *interface_ids.get(name.name).ok_or_else(|| Error { - span: name.span, - msg: format!( - "interface `{name}` does not exist", - name = name.name - ), - })?, - ), - ast::AstItem::World(_) => AstItem::World( - *world_ids.get(name.name).ok_or_else(|| Error { - span: name.span, - msg: format!( - "world `{name}` does not exist", - name = name.name - ), - })?, - ), - ast::AstItem::Use(_) => unreachable!(), - } - } else { - AstItem::Interface(*interface_ids.get(name.name).ok_or_else( - || Error { - span: name.span, - msg: format!( - "interface `{name}` does not exist", - name = name.name - ), - }, - )?) - } - } + ast::UsePath::Id(name) => *ids.get(name.name).ok_or_else(|| Error { + span: name.span, + msg: format!( + "interface or world `{name}` does not exist", + name = name.name + ), + })?, ast::UsePath::Package { id, name } => { - // TODO: check if name belongs to a iface or world - AstItem::Interface( - self.foreign_iface_deps[&id.package_name()][name.name], - ) + self.foreign_deps[&id.package_name()][name.name] } }; (name.name, item) } ast::AstItem::Interface(i) => { - (i.name.name, AstItem::Interface(interface_ids[i.name.name])) + let iface_item = ids[i.name.name]; + assert!(matches!(iface_item, AstItem::Interface(_))); + (i.name.name, iface_item) + } + ast::AstItem::World(w) => { + let world_item = ids[w.name.name]; + assert!(matches!(world_item, AstItem::World(_))); + (w.name.name, world_item) } - ast::AstItem::World(w) => (w.name.name, AstItem::World(world_ids[w.name.name])), }; let prev = items.insert(name, ast_item); assert!(prev.is_none()); @@ -907,6 +833,7 @@ impl<'a> Resolver<'a> { /// For each name in the `include`, resolve the path of the include, add it to the self.includes fn resolve_include(&mut self, owner: TypeOwner, i: &ast::Include<'a>) -> Result<()> { let (include_from, span) = self.resolve_world_path(&i.from)?; + self.foreign_dep_spans.push(span); self.foreign_world_spans.push(span); let world_id = match owner { TypeOwner::World(id) => id, @@ -960,7 +887,10 @@ impl<'a> Resolver<'a> { } } ast::UsePath::Package { id, name } => { - Ok(self.foreign_iface_deps[&id.package_name()][name.name]) + match self.foreign_deps[&id.package_name()][name.name] { + AstItem::Interface(i) => Ok(i), + AstItem::World(_) => unreachable!(), + } } } } @@ -991,10 +921,12 @@ impl<'a> Resolver<'a> { } } } - ast::UsePath::Package { id, name } => Ok(( - self.foreign_world_deps[&id.package_name()][name.name], - name.span, - )), + ast::UsePath::Package { id, name } => { + match self.foreign_deps[&id.package_name()][name.name] { + AstItem::World(w) => Ok((w, name.span)), + AstItem::Interface(_) => unreachable!(), + } + } } } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 50187efc4a..144129364d 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -86,16 +86,21 @@ pub struct UnresolvedPackage { /// interface name followed by the identifier within `self.interfaces`. The /// fields of `self.interfaces` describes the required types that are from /// each foreign interface. - pub foreign_iface_deps: IndexMap>, - pub foreign_world_deps: IndexMap>, + pub foreign_deps: IndexMap>, unknown_type_spans: Vec, world_item_spans: Vec<(Vec, Vec)>, interface_spans: Vec, world_spans: Vec, foreign_dep_spans: Vec, - foreign_world_spans: Vec, source_map: SourceMap, + foreign_world_spans: Vec, +} + +#[derive(Debug, Copy, Clone)] +pub enum AstItem { + Interface(InterfaceId), + World(WorldId), } /// A structure used to keep track of the name of a package, containing optional diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 554e6fe03b..5a840219dd 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1,8 +1,8 @@ use crate::ast::lex::Span; use crate::ast::{parse_use_path, AstUsePath}; use crate::{ - Error, Function, Interface, InterfaceId, PackageName, Results, Type, TypeDef, TypeDefKind, - TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, WorldKey, + AstItem, Error, Function, Interface, InterfaceId, PackageName, Results, Type, TypeDef, + TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, WorldKey, }; use anyhow::{anyhow, bail, Context, Result}; use id_arena::{Arena, Id}; @@ -132,7 +132,7 @@ impl Resolve { return Ok(()); } pkg.source_map.rewrite_error(|| { - for (i, (dep, _)) in pkg.foreign_iface_deps.iter().enumerate() { + for (i, (dep, _)) in pkg.foreign_deps.iter().enumerate() { let span = pkg.foreign_dep_spans[i]; if !visiting.insert(dep) { bail!(Error { @@ -690,13 +690,25 @@ impl Remap { // Invert the `foreign_deps` map to be keyed by world id to get // used in the loops below. let mut world_to_package = HashMap::new(); - for (i, (pkg_name, worlds)) in unresolved.foreign_world_deps.iter().enumerate() { - for (world, unresolved_world_id) in worlds { - let prev = world_to_package.insert( - *unresolved_world_id, - (pkg_name, world, unresolved.foreign_dep_spans[i]), - ); - assert!(prev.is_none()); + let mut interface_to_package = HashMap::new(); + for (i, (pkg_name, worlds_or_ifaces)) in unresolved.foreign_deps.iter().enumerate() { + for (name, item) in worlds_or_ifaces { + match item { + AstItem::Interface(unresolved_interface_id) => { + let prev = interface_to_package.insert( + *unresolved_interface_id, + (pkg_name, name, unresolved.foreign_dep_spans[i]), + ); + assert!(prev.is_none()); + } + AstItem::World(unresolved_world_id) => { + let prev = world_to_package.insert( + *unresolved_world_id, + (pkg_name, name, unresolved.foreign_dep_spans[i]), + ); + assert!(prev.is_none()); + } + } } } @@ -724,19 +736,6 @@ impl Remap { self.worlds.push(world_id); } - // Invert the `foreign_deps` map to be keyed by interface id to get - // used in the loops below. - let mut interface_to_package = HashMap::new(); - for (i, (pkg_name, interfaces)) in unresolved.foreign_iface_deps.iter().enumerate() { - for (interface, unresolved_interface_id) in interfaces { - let prev = interface_to_package.insert( - *unresolved_interface_id, - (pkg_name, interface, unresolved.foreign_dep_spans[i]), - ); - assert!(prev.is_none()); - } - } - // Connect all interfaces referred to in `interface_to_package`, which // are at the front of `unresolved.interfaces`, to interfaces already // contained within `resolve`. @@ -1085,42 +1084,12 @@ impl Remap { // copy the imports and exports from the included world into the current world for import in include_world.imports.iter() { let name = import.0.clone(); - - // check if world.imports already has the same world item - // let item = world.imports.iter().find(|i| i.1 == import.1); - // if item.is_some() && item.unwrap().0 != &name { - // bail!(Error { - // msg: format!("include of world `{}` has imports `{}`, which shadows the previous imports", include_world.name, name), - // span: span - // }) - // } - - let prev = world.imports.insert(name.clone(), import.1.clone()); - // if prev.is_some() && &prev.unwrap() != import.1 { - // bail!(Error { - // msg: format!("include of world `{}` has imports `{}`, which shadows the previous imports", include_world.name, name), - // span: span - // }) - // } + world.imports.insert(name.clone(), import.1.clone()); } for export in include_world.exports.iter() { let name = export.0.clone(); - // check if world.exports already has the same world item - let item = world.exports.iter().find(|i| i.1 == export.1); - // if item.is_some() && item.unwrap().0 != &name { - // bail!(Error { - // msg: format!("include of world `{}` has exports `{}`, which shadows the previous exports", include_world.name, name), - // span: span - // }) - // } - let prev: Option = world.exports.insert(name.clone(), export.1.clone()); - // if prev.is_some() && &prev.unwrap() != export.1 { - // bail!(Error { - // msg: format!("include of world `{}` has exports `{}`, which shadows the previous exports", include_world.name, name), - // span: span - // }) - // } + world.exports.insert(name.clone(), export.1.clone()); } Ok(()) } diff --git a/crates/wit-parser/tests/ui/complex-include/deps/bar/root.wit b/crates/wit-parser/tests/ui/complex-include/deps/bar/root.wit new file mode 100644 index 0000000000..1fa78730de --- /dev/null +++ b/crates/wit-parser/tests/ui/complex-include/deps/bar/root.wit @@ -0,0 +1,9 @@ +package foo:bar + +interface a {} +interface b {} + +world bar-a { + import a + import b +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/complex-include/deps/baz/root.wit b/crates/wit-parser/tests/ui/complex-include/deps/baz/root.wit new file mode 100644 index 0000000000..08662ebb28 --- /dev/null +++ b/crates/wit-parser/tests/ui/complex-include/deps/baz/root.wit @@ -0,0 +1,9 @@ +package foo:baz + +interface a {} +interface b {} + +world baz-a { + import a + import b +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/complex-include/root.wit b/crates/wit-parser/tests/ui/complex-include/root.wit new file mode 100644 index 0000000000..4acf114509 --- /dev/null +++ b/crates/wit-parser/tests/ui/complex-include/root.wit @@ -0,0 +1,26 @@ +package foo:root + +interface ai {} +interface bi {} + +world a { + import ai + import bi +} + +world b { + include foo:bar/bar-a +} + +world c { + include b + include foo:bar/bar-a +} + +world union-world { + include a + include b + include c + include foo:bar/bar-a + include foo:baz/baz-a +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/include-reps.wit b/crates/wit-parser/tests/ui/include-reps.wit new file mode 100644 index 0000000000..207173021f --- /dev/null +++ b/crates/wit-parser/tests/ui/include-reps.wit @@ -0,0 +1,15 @@ +package foo:foo + +interface a {} +interface b {} + +world bar { + import a + export b +} + +world foo { + include bar + include bar + include bar +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit new file mode 100644 index 0000000000..555ee0fdee --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit @@ -0,0 +1,5 @@ +package foo:foo + +world foo { + include non-existance +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result new file mode 100644 index 0000000000..49a56efbf3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result @@ -0,0 +1,5 @@ +interface `non-existance` not found in package + --> tests/ui/parse-fail/bad-include1.wit:4:11 + | + 4 | include non-existance + | ^------------ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include2.wit b/crates/wit-parser/tests/ui/parse-fail/bad-include2.wit new file mode 100644 index 0000000000..5b5aff80d3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include2.wit @@ -0,0 +1,9 @@ +package foo:foo + +world bar { + include foo +} + +world foo { + include bar +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include2.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-include2.wit.result new file mode 100644 index 0000000000..7ccd018090 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include2.wit.result @@ -0,0 +1,5 @@ +interface or world `foo` depends on itself + --> tests/ui/parse-fail/bad-include2.wit:7:7 + | + 7 | world foo { + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include3.wit b/crates/wit-parser/tests/ui/parse-fail/bad-include3.wit new file mode 100644 index 0000000000..3503ac5f09 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include3.wit @@ -0,0 +1,5 @@ +package foo:foo + +world foo { + include foo +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include3.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-include3.wit.result new file mode 100644 index 0000000000..2ced2f6788 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include3.wit.result @@ -0,0 +1,5 @@ +interface or world `foo` depends on itself + --> tests/ui/parse-fail/bad-include3.wit:3:7 + | + 3 | world foo { + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/include-cycle.wit b/crates/wit-parser/tests/ui/parse-fail/include-cycle.wit new file mode 100644 index 0000000000..b11971b0ba --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/include-cycle.wit @@ -0,0 +1,9 @@ +package foo:foo + +world a { + include b +} + +world b { + include a +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/include-cycle.wit.result b/crates/wit-parser/tests/ui/parse-fail/include-cycle.wit.result new file mode 100644 index 0000000000..a89a1fc99c --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/include-cycle.wit.result @@ -0,0 +1,5 @@ +interface or world `b` depends on itself + --> tests/ui/parse-fail/include-cycle.wit:7:7 + | + 7 | world b { + | ^ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result b/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result index e938b272fe..fa2974802f 100644 --- a/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/no-access-to-sibling-use.wit.result @@ -1,7 +1,7 @@ failed to parse package: tests/ui/parse-fail/no-access-to-sibling-use Caused by: - interface `bar-renamed` does not exist + interface or world `bar-renamed` does not exist --> tests/ui/parse-fail/no-access-to-sibling-use/foo.wit:3:5 | 3 | use bar-renamed diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result index 716eab1237..828f9c20cd 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface3.wit.result @@ -1,4 +1,4 @@ -interface `bar` does not exist +interface or world `bar` does not exist --> tests/ui/parse-fail/unresolved-interface3.wit:5:5 | 5 | use bar as foo diff --git a/crates/wit-parser/tests/ui/worlds-union-dedup.wit b/crates/wit-parser/tests/ui/worlds-union-dedup.wit new file mode 100644 index 0000000000..88e2d0e30c --- /dev/null +++ b/crates/wit-parser/tests/ui/worlds-union-dedup.wit @@ -0,0 +1,23 @@ +package foo:foo + +interface a1 { } +interface a2 { } +interface b1 { } +interface b2 { } +interface c { } +interface d { } + +world my-world-a { + import a1 + import b1 +} + +world my-world-b { + import a1 + import b1 +} + +world union-my-world { + include my-world-a + include my-world-b +} From de432261992ec7ee6b938e76b9c03fa95f346eea Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Mon, 29 May 2023 13:19:07 -0700 Subject: [PATCH 04/14] fixed all the wit-component tests Signed-off-by: Jiaxiao Zhou (Mossaka) --- .../tests/interfaces/diamond.wat | 22 +++++++------- .../tests/interfaces/diamond.wit.print | 14 ++++----- .../tests/interfaces/world-top-level.wat | 30 +++++++++---------- .../interfaces/world-top-level.wit.print | 12 ++++---- .../tests/interfaces/worlds-with-types.wat | 24 +++++++-------- .../interfaces/worlds-with-types.wit.print | 12 ++++---- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/crates/wit-component/tests/interfaces/diamond.wat b/crates/wit-component/tests/interfaces/diamond.wat index 38b3dba0cb..c17392e7a9 100644 --- a/crates/wit-component/tests/interfaces/diamond.wat +++ b/crates/wit-component/tests/interfaces/diamond.wat @@ -24,17 +24,10 @@ (export (;1;) "the-enum" (type (eq 0))) ) ) - (import "foo" (instance (;1;) (type 2))) - (type (;3;) - (instance - (alias outer 1 1 (type (;0;))) - (export (;1;) "the-enum" (type (eq 0))) - ) - ) - (import "bar" (instance (;2;) (type 3))) + (export (;1;) "bar" (instance (type 2))) ) ) - (export (;0;) (interface "foo:foo/w1") (component (type 1))) + (export (;0;) (interface "foo:foo/w3") (component (type 1))) (type (;2;) (component (type (;0;) @@ -78,10 +71,17 @@ (export (;1;) "the-enum" (type (eq 0))) ) ) - (export (;1;) "bar" (instance (type 2))) + (import "foo" (instance (;1;) (type 2))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "the-enum" (type (eq 0))) + ) + ) + (import "bar" (instance (;2;) (type 3))) ) ) - (export (;2;) (interface "foo:foo/w3") (component (type 3))) + (export (;2;) (interface "foo:foo/w1") (component (type 3))) ) ) (@producers diff --git a/crates/wit-component/tests/interfaces/diamond.wit.print b/crates/wit-component/tests/interfaces/diamond.wit.print index 2b686a10e1..1b34e4df30 100644 --- a/crates/wit-component/tests/interfaces/diamond.wit.print +++ b/crates/wit-component/tests/interfaces/diamond.wit.print @@ -7,12 +7,9 @@ interface shared { } -world w1 { +world w3 { import shared - import foo: interface { - use shared.{the-enum} - } - import bar: interface { + export bar: interface { use shared.{the-enum} } } @@ -25,9 +22,12 @@ world w2 { use shared.{the-enum} } } -world w3 { +world w1 { import shared - export bar: interface { + import foo: interface { + use shared.{the-enum} + } + import bar: interface { use shared.{the-enum} } } diff --git a/crates/wit-component/tests/interfaces/world-top-level.wat b/crates/wit-component/tests/interfaces/world-top-level.wat index 39fe9a9d01..2a966d9296 100644 --- a/crates/wit-component/tests/interfaces/world-top-level.wat +++ b/crates/wit-component/tests/interfaces/world-top-level.wat @@ -2,6 +2,20 @@ (type (;0;) (component (type (;0;) + (component + (type (;0;) (func)) + (import "foo" (func (;0;) (type 0))) + ) + ) + (export (;0;) (interface "foo:foo/just-import") (component (type 0))) + (type (;1;) + (component + (type (;0;) (func)) + (export (;0;) "foo" (func (type 0))) + ) + ) + (export (;1;) (interface "foo:foo/just-export") (component (type 1))) + (type (;2;) (component (type (;0;) (instance) @@ -20,21 +34,7 @@ (export (;3;) "bar2" (func (type 4))) ) ) - (export (;0;) (interface "foo:foo/foo") (component (type 0))) - (type (;1;) - (component - (type (;0;) (func)) - (import "foo" (func (;0;) (type 0))) - ) - ) - (export (;1;) (interface "foo:foo/just-import") (component (type 1))) - (type (;2;) - (component - (type (;0;) (func)) - (export (;0;) "foo" (func (type 0))) - ) - ) - (export (;2;) (interface "foo:foo/just-export") (component (type 2))) + (export (;2;) (interface "foo:foo/foo") (component (type 2))) ) ) (@producers diff --git a/crates/wit-component/tests/interfaces/world-top-level.wit.print b/crates/wit-component/tests/interfaces/world-top-level.wit.print index d602d16218..40fd90e71d 100644 --- a/crates/wit-component/tests/interfaces/world-top-level.wit.print +++ b/crates/wit-component/tests/interfaces/world-top-level.wit.print @@ -1,5 +1,11 @@ package foo:foo +world just-import { + import foo: func() +} +world just-export { + export foo: func() +} world foo { import some-interface: interface { } @@ -10,9 +16,3 @@ world foo { export foo2: func() export bar2: func() -> u32 } -world just-import { - import foo: func() -} -world just-export { - export foo: func() -} diff --git a/crates/wit-component/tests/interfaces/worlds-with-types.wat b/crates/wit-component/tests/interfaces/worlds-with-types.wat index 8b8d3bdd9d..2ddb45a971 100644 --- a/crates/wit-component/tests/interfaces/worlds-with-types.wat +++ b/crates/wit-component/tests/interfaces/worlds-with-types.wat @@ -9,17 +9,6 @@ ) (export (;0;) (interface "foo:foo/import-me") (instance (type 0))) (type (;1;) - (component - (type (;0;) (record)) - (import "foo" (type (;1;) (eq 0))) - (import "bar" (type (;2;) (eq 1))) - (type (;3;) (func (param "a" 1) (result 2))) - (import "a" (func (;0;) (type 3))) - (export (;1;) "b" (func (type 3))) - ) - ) - (export (;0;) (interface "foo:foo/simple") (component (type 1))) - (type (;2;) (component (type (;0;) (instance @@ -35,7 +24,18 @@ (export (;1;) "b" (func (type 3))) ) ) - (export (;1;) (interface "foo:foo/with-imports") (component (type 2))) + (export (;0;) (interface "foo:foo/with-imports") (component (type 1))) + (type (;2;) + (component + (type (;0;) (record)) + (import "foo" (type (;1;) (eq 0))) + (import "bar" (type (;2;) (eq 1))) + (type (;3;) (func (param "a" 1) (result 2))) + (import "a" (func (;0;) (type 3))) + (export (;1;) "b" (func (type 3))) + ) + ) + (export (;1;) (interface "foo:foo/simple") (component (type 2))) ) ) (@producers diff --git a/crates/wit-component/tests/interfaces/worlds-with-types.wit.print b/crates/wit-component/tests/interfaces/worlds-with-types.wit.print index 683f72935b..f71ab16292 100644 --- a/crates/wit-component/tests/interfaces/worlds-with-types.wit.print +++ b/crates/wit-component/tests/interfaces/worlds-with-types.wit.print @@ -5,6 +5,12 @@ interface import-me { } +world with-imports { + import import-me + import a: func(a: foo) + use import-me.{foo} + export b: func(a: foo) +} world simple { import a: func(a: foo) -> bar record foo { @@ -14,9 +20,3 @@ world simple { export b: func(a: foo) -> bar } -world with-imports { - import import-me - import a: func(a: foo) - use import-me.{foo} - export b: func(a: foo) -} From 9890369a17ecf0c15c10f14fea743342743fcef7 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 09:53:00 -0700 Subject: [PATCH 05/14] clean up Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast/resolve.rs | 2 -- crates/wit-parser/src/resolve.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 50b5f1e457..2b528b8ff7 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1,10 +1,8 @@ use super::{Error, ParamList, ResultList, ValueKind, WorldOrInterface}; use crate::ast::toposort::toposort; -use crate::ast::Ast; use crate::*; use anyhow::{bail, Result}; use indexmap::IndexMap; -use log::debug; use std::collections::{HashMap, HashSet}; use std::mem; diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 5a840219dd..5f5e4cb9e1 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1075,7 +1075,7 @@ impl Remap { &self, world: &mut World, include_world: WorldId, - span: Span, + _span: Span, resolve: &Resolve, ) -> Result<()> { let include_world_id = self.worlds[include_world.index()]; From e7a4444deb5f293d987eaa19f52ed3abbc202893 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 10:10:24 -0700 Subject: [PATCH 06/14] deduplicate the resolve_world_path method Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast/resolve.rs | 99 +++++++++---------- .../parse-fail/unknown-interface.wit.result | 2 +- .../unresolved-interface2.wit.result | 2 +- .../ui/parse-fail/unresolved-use9.wit.result | 2 +- 4 files changed, 49 insertions(+), 56 deletions(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 2b528b8ff7..be7d745af4 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -507,7 +507,8 @@ impl<'a> Resolver<'a> { Some(names) => names, None => return Ok(()), }; - let iface = self.resolve_iface_path(path)?; + let (item, name, span) = self.resolve_ast_item_path(path)?; + let iface = self.extract_iface_from_item(&item, &name, span)?; if !self.foreign_interfaces.contains(&iface) { return Ok(()); } @@ -627,7 +628,11 @@ impl<'a> Resolver<'a> { } WorldKey::Name(name.name.to_string()) } - ast::ExternKind::Path(path) => WorldKey::Interface(self.resolve_iface_path(path)?), + ast::ExternKind::Path(path) => { + let (item, name, span) = self.resolve_ast_item_path(path)?; + let id = self.extract_iface_from_item(&item, &name, span)?; + WorldKey::Interface(id) + } }; let world_item = self.resolve_world_item(docs, kind)?; if let WorldItem::Interface(id) = world_item { @@ -667,7 +672,8 @@ impl<'a> Resolver<'a> { Ok(WorldItem::Interface(id)) } ast::ExternKind::Path(path) => { - let id = self.resolve_iface_path(path)?; + let (item, name, span) = self.resolve_ast_item_path(path)?; + let id = self.extract_iface_from_item(&item, &name, span)?; Ok(WorldItem::Interface(id)) } ast::ExternKind::Func(name, func) => { @@ -800,7 +806,9 @@ impl<'a> Resolver<'a> { } fn resolve_use(&mut self, owner: TypeOwner, u: &ast::Use<'a>) -> Result<()> { - let use_from = self.resolve_iface_path(&u.from)?; + let (item, name, span) = self.resolve_ast_item_path(&u.from)?; + let use_from = self.extract_iface_from_item(&item, &name, span)?; + for name in u.names.iter() { let lookup = &self.interface_types[use_from.index()]; let id = match lookup.get(name.name.name) { @@ -830,7 +838,8 @@ impl<'a> Resolver<'a> { /// For each name in the `include`, resolve the path of the include, add it to the self.includes fn resolve_include(&mut self, owner: TypeOwner, i: &ast::Include<'a>) -> Result<()> { - let (include_from, span) = self.resolve_world_path(&i.from)?; + let (item, name, span) = self.resolve_ast_item_path(&i.from)?; + let include_from = self.extract_world_from_item(&item, &name, span)?; self.foreign_dep_spans.push(span); self.foreign_world_spans.push(span); let world_id = match owner { @@ -859,71 +868,55 @@ impl<'a> Resolver<'a> { }) } - fn resolve_iface_path(&self, path: &ast::UsePath<'a>) -> Result { + fn resolve_ast_item_path(&self, path: &ast::UsePath<'a>) -> Result<(AstItem, String, Span)> { match path { ast::UsePath::Id(id) => { let item = self.ast_items[self.cur_ast_index] .get(id.name) .or_else(|| self.package_items.get(id.name)); match item { - Some(AstItem::Interface(id)) => Ok(*id), - Some(AstItem::World(_)) => { - bail!(Error { - span: id.span, - msg: format!( - "name `{}` is defined as a world, not an interface", - id.name - ), - }) - } + Some(item) => Ok((*item, id.name.into(), id.span)), None => { bail!(Error { span: id.span, - msg: format!("interface `{name}` does not exist", name = id.name), + msg: format!("interface or world `{}` does not exist", id.name), }) } } } - ast::UsePath::Package { id, name } => { - match self.foreign_deps[&id.package_name()][name.name] { - AstItem::Interface(i) => Ok(i), - AstItem::World(_) => unreachable!(), - } - } + ast::UsePath::Package { id, name } => Ok(( + self.foreign_deps[&id.package_name()][name.name], + name.name.into(), + name.span, + )), } } - fn resolve_world_path(&self, path: &ast::UsePath<'a>) -> Result<(WorldId, Span)> { - match path { - ast::UsePath::Id(id) => { - let span = id.span; - let item = self.ast_items[self.cur_ast_index] - .get(id.name) - .or_else(|| self.package_items.get(id.name)); - match item { - Some(AstItem::World(id)) => Ok((*id, span)), - Some(AstItem::Interface(_)) => { - bail!(Error { - span: id.span, - msg: format!( - "name `{}` is defined as an interface, not a world", - id.name - ), - }) - } - None => { - bail!(Error { - span: id.span, - msg: format!("world `{name}` does not exist", name = id.name), - }) - } - } + fn extract_iface_from_item( + &self, + item: &AstItem, + name: &str, + span: Span, + ) -> Result { + match item { + AstItem::Interface(id) => Ok(*id), + AstItem::World(id) => { + bail!(Error { + span: span, + msg: format!("name `{}` is defined as a world, not an interface", name), + }) } - ast::UsePath::Package { id, name } => { - match self.foreign_deps[&id.package_name()][name.name] { - AstItem::World(w) => Ok((w, name.span)), - AstItem::Interface(_) => unreachable!(), - } + } + } + + fn extract_world_from_item(&self, item: &AstItem, name: &str, span: Span) -> Result { + match item { + AstItem::World(id) => Ok(*id), + AstItem::Interface(id) => { + bail!(Error { + span: span, + msg: format!("name `{}` is defined as an interface, not a world", name), + }) } } } diff --git a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result index 4ae873138c..bb72a5b5c2 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unknown-interface.wit.result @@ -1,4 +1,4 @@ -interface `bar` does not exist +interface or world `bar` does not exist --> tests/ui/parse-fail/unknown-interface.wit:6:10 | 6 | import bar diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result index 2a89904c2b..448a9775b6 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-interface2.wit.result @@ -1,4 +1,4 @@ -interface `bar` does not exist +interface or world `bar` does not exist --> tests/ui/parse-fail/unresolved-interface2.wit:6:10 | 6 | import bar diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result index 5e8ee39982..f72d33d8d9 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use9.wit.result @@ -1,4 +1,4 @@ -interface `bar` does not exist +interface or world `bar` does not exist --> tests/ui/parse-fail/unresolved-use9.wit:7:9 | 7 | use bar.{i32} From 80492dec6171c285d999c707419ba7531ff22811 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 10:48:34 -0700 Subject: [PATCH 07/14] fail on kebab-name duplicates Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast/resolve.rs | 6 +-- crates/wit-parser/src/resolve.rs | 47 +++++++++++++++++-- .../ui/parse-fail/bad-include1.wit.result | 2 +- .../tests/ui/parse-fail/bad-pkg1.wit.result | 2 +- .../ui/parse-fail/kebab-name-include.wit | 8 ++++ .../parse-fail/kebab-name-include.wit.result | 5 ++ .../ui/parse-fail/unresolved-use1.wit.result | 2 +- 7 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit.result diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index be7d745af4..0b767698c6 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -407,7 +407,7 @@ impl<'a> Resolver<'a> { bail!(Error { span: used_name.span, msg: format!( - "interface `{name}` not found in package", + "interface or world `{name}` not found in package", name = used_name.name ), }) @@ -900,7 +900,7 @@ impl<'a> Resolver<'a> { ) -> Result { match item { AstItem::Interface(id) => Ok(*id), - AstItem::World(id) => { + AstItem::World(_) => { bail!(Error { span: span, msg: format!("name `{}` is defined as a world, not an interface", name), @@ -912,7 +912,7 @@ impl<'a> Resolver<'a> { fn extract_world_from_item(&self, item: &AstItem, name: &str, span: Span) -> Result { match item { AstItem::World(id) => Ok(*id), - AstItem::Interface(id) => { + AstItem::Interface(_) => { bail!(Error { span: span, msg: format!("name `{}` is defined as an interface, not a world", name), diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 5f5e4cb9e1..888f324bf0 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -736,6 +736,13 @@ impl Remap { self.worlds.push(world_id); } + for (id, _) in unresolved.worlds.iter().skip(self.worlds.len()) { + assert!( + world_to_package.get(&id).is_none(), + "found foreign world after local world" + ); + } + // Connect all interfaces referred to in `interface_to_package`, which // are at the front of `unresolved.interfaces`, to interfaces already // contained within `resolve`. @@ -1075,7 +1082,7 @@ impl Remap { &self, world: &mut World, include_world: WorldId, - _span: Span, + span: Span, resolve: &Resolve, ) -> Result<()> { let include_world_id = self.worlds[include_world.index()]; @@ -1083,13 +1090,43 @@ impl Remap { // copy the imports and exports from the included world into the current world for import in include_world.imports.iter() { - let name = import.0.clone(); - world.imports.insert(name.clone(), import.1.clone()); + let name = import.0; + match name { + WorldKey::Name(n) => { + let prev = world + .imports + .insert(WorldKey::Name(n.clone()), import.1.clone()); + if prev.is_some() { + bail!(Error { + msg: format!("import of `{}` shadows previously imported items", n), + span, + }) + } + } + i => { + world.imports.insert(i.clone(), import.1.clone()); + } + }; } for export in include_world.exports.iter() { - let name = export.0.clone(); - world.exports.insert(name.clone(), export.1.clone()); + let name = export.0; + match name { + WorldKey::Name(n) => { + let prev = world + .exports + .insert(WorldKey::Name(n.clone()), export.1.clone()); + if prev.is_some() { + bail!(Error { + msg: format!("export of `{}` shadows previously exported items", n), + span, + }) + } + } + i => { + world.exports.insert(i.clone(), export.1.clone()); + } + } } Ok(()) } diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result index 49a56efbf3..76aff05001 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-include1.wit.result @@ -1,4 +1,4 @@ -interface `non-existance` not found in package +interface or world `non-existance` not found in package --> tests/ui/parse-fail/bad-include1.wit:4:11 | 4 | include non-existance diff --git a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result index e664619234..04611d285d 100644 --- a/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/bad-pkg1.wit.result @@ -1,7 +1,7 @@ failed to parse package: tests/ui/parse-fail/bad-pkg1 Caused by: - interface `nonexistent` not found in package + interface or world `nonexistent` not found in package --> tests/ui/parse-fail/bad-pkg1/root.wit:4:7 | 4 | use nonexistent.{} diff --git a/crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit b/crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit new file mode 100644 index 0000000000..59c2fae5cc --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit @@ -0,0 +1,8 @@ +package foo:foo + +world foo { import a: func() } +world bar { import a: func() } +world baz { + include foo + include bar +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit.result b/crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit.result new file mode 100644 index 0000000000..d9f901c707 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/kebab-name-include.wit.result @@ -0,0 +1,5 @@ +import of `a` shadows previously imported items + --> tests/ui/parse-fail/kebab-name-include.wit:7:13 + | + 7 | include bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result index c539ca41d9..adfee891aa 100644 --- a/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result +++ b/crates/wit-parser/tests/ui/parse-fail/unresolved-use1.wit.result @@ -1,4 +1,4 @@ -interface `bar` not found in package +interface or world `bar` not found in package --> tests/ui/parse-fail/unresolved-use1.wit:6:7 | 6 | use bar.{x} From e5933ca7d73ef61dbde1b7053ac44cd233a2741b Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 11:48:12 -0700 Subject: [PATCH 08/14] impl include syntax Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-component/src/decoding.rs | 2 ++ crates/wit-component/src/metadata.rs | 1 + crates/wit-parser/src/ast.rs | 29 ++++++++++++++++++- crates/wit-parser/src/ast/resolve.rs | 12 +++++++- crates/wit-parser/src/lib.rs | 12 ++++++++ crates/wit-parser/src/resolve.rs | 29 ++++++++++++++++--- .../ui/kebab-name-include-with-no-effect.wit | 8 +++++ .../tests/ui/kebab-name-include-with.wit | 8 +++++ 8 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 crates/wit-parser/tests/ui/kebab-name-include-with-no-effect.wit create mode 100644 crates/wit-parser/tests/ui/kebab-name-include-with.wit diff --git a/crates/wit-component/src/decoding.rs b/crates/wit-component/src/decoding.rs index be60f9f043..bec98c5256 100644 --- a/crates/wit-component/src/decoding.rs +++ b/crates/wit-component/src/decoding.rs @@ -141,6 +141,7 @@ impl<'a> ComponentInfo<'a> { exports: Default::default(), package: None, includes: Default::default(), + include_names: Default::default(), }); let mut decoder = WitPackageDecoder { resolve, @@ -754,6 +755,7 @@ impl WitPackageDecoder<'_> { imports: Default::default(), exports: Default::default(), includes: Default::default(), + include_names: Default::default(), package: None, }; diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 796cd00afd..bc7035885e 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -76,6 +76,7 @@ impl Default for Bindgen { imports: Default::default(), exports: Default::default(), includes: Default::default(), + include_names: Default::default(), package: Some(package), }); resolve.packages[package] diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index e4dd0dde47..e2a5a2dfbe 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -444,13 +444,40 @@ impl<'a> Use<'a> { struct Include<'a> { from: UsePath<'a>, + names: Vec>, +} + +struct IncludeName<'a> { + name: Id<'a>, + as_: Id<'a>, } impl<'a> Include<'a> { fn parse(tokens: &mut Tokenizer<'a>) -> Result { tokens.expect(Token::Include)?; let from = UsePath::parse(tokens)?; - Ok(Include { from }) + + let mut names = Vec::new(); + + match tokens.clone().next()? { + Some((_span, Token::With)) => { + tokens.expect(Token::With)?; + tokens.expect(Token::LeftBrace)?; + while !tokens.eat(Token::RightBrace)? { + let name = parse_id(tokens)?; + tokens.eat(Token::As)?; + let as_ = parse_id(tokens)?; + let name = IncludeName { name, as_ }; + names.push(name); + if !tokens.eat(Token::Comma)? { + tokens.expect(Token::RightBrace)?; + break; + } + } + } + _ => {} + } + Ok(Include { from, names }) } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 0b767698c6..73a538e597 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -287,7 +287,8 @@ impl<'a> Resolver<'a> { exports: IndexMap::new(), imports: IndexMap::new(), package: None, - includes: Vec::new(), + includes: Default::default(), + include_names: Default::default(), }) } @@ -847,6 +848,15 @@ impl<'a> Resolver<'a> { _ => unreachable!(), }; self.worlds[world_id].includes.push(include_from); + self.worlds[world_id].include_names.push( + i.names + .iter() + .map(|n| IncludeName { + name: n.name.name.to_string(), + as_: n.as_.name.to_string(), + }) + .collect(), + ); Ok(()) } diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 144129364d..b5529d3492 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -246,6 +246,18 @@ pub struct World { /// All the included worlds from this world. pub includes: Vec, + + /// All the included worlds names + pub include_names: Vec>, +} + +#[derive(Debug, Clone)] +pub struct IncludeName { + /// The name of the item + pub name: String, + + /// The name to be replaced with + pub as_: String, } /// The key to the import/export maps of a world. Either a kebab-name or a diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 888f324bf0..35f0f59568 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1,8 +1,9 @@ use crate::ast::lex::Span; use crate::ast::{parse_use_path, AstUsePath}; use crate::{ - AstItem, Error, Function, Interface, InterfaceId, PackageName, Results, Type, TypeDef, - TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, WorldKey, + AstItem, Error, Function, IncludeName, Interface, InterfaceId, PackageName, Results, Type, + TypeDef, TypeDefKind, TypeId, TypeOwner, UnresolvedPackage, World, WorldId, WorldItem, + WorldKey, }; use anyhow::{anyhow, bail, Context, Result}; use id_arena::{Arena, Id}; @@ -637,9 +638,11 @@ impl Remap { // Resolve all includes of the world let includes = mem::take(&mut world.includes); - for include_world in includes.into_iter() { + let include_names = mem::take(&mut world.include_names); + for (index, include_world) in includes.into_iter().enumerate() { let span = unresolved_world_spans[include_world.index()]; - self.resolve_include(&mut world, include_world, span, resolve)?; + let names = &include_names[index]; + self.resolve_include(&mut world, include_world, names, span, resolve)?; } let new_id = resolve.worlds.alloc(world); @@ -1082,6 +1085,7 @@ impl Remap { &self, world: &mut World, include_world: WorldId, + names: &[IncludeName], span: Span, resolve: &Resolve, ) -> Result<()> { @@ -1093,6 +1097,15 @@ impl Remap { let name = import.0; match name { WorldKey::Name(n) => { + let n = if let Some(found) = names + .into_iter() + .find(|include_name| include_name.name == n.clone()) + { + found.as_.clone() + } else { + n.clone() + }; + let prev = world .imports .insert(WorldKey::Name(n.clone()), import.1.clone()); @@ -1113,6 +1126,14 @@ impl Remap { let name = export.0; match name { WorldKey::Name(n) => { + let n = if let Some(found) = names + .into_iter() + .find(|include_name| include_name.name == n.clone()) + { + found.as_.clone() + } else { + n.clone() + }; let prev = world .exports .insert(WorldKey::Name(n.clone()), export.1.clone()); diff --git a/crates/wit-parser/tests/ui/kebab-name-include-with-no-effect.wit b/crates/wit-parser/tests/ui/kebab-name-include-with-no-effect.wit new file mode 100644 index 0000000000..506d37beca --- /dev/null +++ b/crates/wit-parser/tests/ui/kebab-name-include-with-no-effect.wit @@ -0,0 +1,8 @@ +package foo:foo + +world foo { import a: func() } +world bar { import a: func() } +world baz { + include foo with { b1 as b2 } + include bar with { a as b } +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/kebab-name-include-with.wit b/crates/wit-parser/tests/ui/kebab-name-include-with.wit new file mode 100644 index 0000000000..08a35f0250 --- /dev/null +++ b/crates/wit-parser/tests/ui/kebab-name-include-with.wit @@ -0,0 +1,8 @@ +package foo:foo + +world foo { import a: func() } +world bar { import a: func() } +world baz { + include foo with { a as b } + include bar +} \ No newline at end of file From abee913119f9bc37c7ba6d40c2fa122bd3a1e98d Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 12:18:14 -0700 Subject: [PATCH 09/14] add more parse failure tests Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast.rs | 2 ++ crates/wit-parser/src/ast/resolve.rs | 22 ++++++++++++++----- .../ui/parse-fail/include-foreign.wit.result | 5 +++++ .../include-foreign/deps/bar/empty.wit | 3 +++ .../ui/parse-fail/include-foreign/root.wit | 5 +++++ .../non-existance-world-include.wit.result | 5 +++++ .../deps/bar/.gitkeep | 0 .../deps/bar/baz.wit | 4 ++++ .../non-existance-world-include/root.wit | 5 +++++ .../use-and-include-world.wit.result | 8 +++++++ .../use-and-include-world/deps/bar/.gitkeep | 0 .../use-and-include-world/deps/bar/baz.wit | 4 ++++ .../parse-fail/use-and-include-world/root.wit | 7 ++++++ .../tests/ui/parse-fail/use-world.wit.result | 5 +++++ .../ui/parse-fail/use-world/deps/bar/.gitkeep | 0 .../ui/parse-fail/use-world/deps/bar/baz.wit | 4 ++++ .../tests/ui/parse-fail/use-world/root.wit | 7 ++++++ 17 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/include-foreign/deps/bar/empty.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/include-foreign/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-and-include-world/root.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-world.wit.result create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/.gitkeep create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/baz.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/use-world/root.wit diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index e2a5a2dfbe..e647f80f35 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -106,6 +106,8 @@ impl<'a> Ast<'a> { } } AstItem::Use(u) => { + // At the top-level, we don't know if this is a world or an interface + // It is up to the resolver to decides how to handle this ambiguity. f(None, &u.item, None, WorldOrInterface::Unknown)?; } } diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 73a538e597..597a540ec1 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -231,20 +231,30 @@ impl<'a> Resolver<'a> { IndexMap::new() }); let id = *deps.entry(name.name).or_insert_with(|| { - log::trace!( - "creating an interface for foreign dep: {}/{}", - id.package_name(), - name.name - ); match world_or_iface { WorldOrInterface::World => { + log::trace!( + "creating a world for foreign dep: {}/{}", + id.package_name(), + name.name + ); AstItem::World(self.alloc_world(name.span, true)) } WorldOrInterface::Interface => { + log::trace!( + "creating an interface for foreign dep: {}/{}", + id.package_name(), + name.name + ); AstItem::Interface(self.alloc_interface(name.span)) } WorldOrInterface::Unknown => { - // TODO: fix me + log::trace!( + "creating an interface for foreign dep: {}/{}", + id.package_name(), + name.name + ); + // Currently top-level `use` always assumes an interface AstItem::Interface(self.alloc_interface(name.span)) } } diff --git a/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result b/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result new file mode 100644 index 0000000000..eac7974f1b --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/include-foreign.wit.result @@ -0,0 +1,5 @@ +world not found in package + --> tests/ui/parse-fail/include-foreign/root.wit:4:19 + | + 4 | include foo:bar/bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/include-foreign/deps/bar/empty.wit b/crates/wit-parser/tests/ui/parse-fail/include-foreign/deps/bar/empty.wit new file mode 100644 index 0000000000..b6da2120bb --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/include-foreign/deps/bar/empty.wit @@ -0,0 +1,3 @@ +package foo:bar + +interface bar {} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/include-foreign/root.wit b/crates/wit-parser/tests/ui/parse-fail/include-foreign/root.wit new file mode 100644 index 0000000000..739532fae3 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/include-foreign/root.wit @@ -0,0 +1,5 @@ +package foo:foo + +world foo { + include foo:bar/bar +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result new file mode 100644 index 0000000000..756ac8f4a7 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include.wit.result @@ -0,0 +1,5 @@ +world not found in package + --> tests/ui/parse-fail/non-existance-world-include/root.wit:4:19 + | + 4 | include foo:baz/non-existance + | ^------------ \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/baz.wit new file mode 100644 index 0000000000..195539f4c0 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/deps/bar/baz.wit @@ -0,0 +1,4 @@ +package foo:baz + +interface bar {} + diff --git a/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/root.wit b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/root.wit new file mode 100644 index 0000000000..6bc8e5f9ed --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/non-existance-world-include/root.wit @@ -0,0 +1,5 @@ +package foo:foo + +world foo { + include foo:baz/non-existance +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result new file mode 100644 index 0000000000..b1de64f6c7 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world.wit.result @@ -0,0 +1,8 @@ +failed to parse package: tests/ui/parse-fail/use-and-include-world + +Caused by: + name `bar` is defined as an interface, not a world + --> tests/ui/parse-fail/use-and-include-world/root.wit:6:21 + | + 6 | include foo:baz/bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/baz.wit new file mode 100644 index 0000000000..501609101d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/deps/bar/baz.wit @@ -0,0 +1,4 @@ +package foo:baz + +world bar {} + diff --git a/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/root.wit b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/root.wit new file mode 100644 index 0000000000..19c318bee5 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-and-include-world/root.wit @@ -0,0 +1,7 @@ +package foo:foo + +use foo:baz/bar + +world foo { + include foo:baz/bar +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result b/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result new file mode 100644 index 0000000000..a3a57d46f8 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-world.wit.result @@ -0,0 +1,5 @@ +interface not found in package + --> tests/ui/parse-fail/use-world/root.wit:3:13 + | + 3 | use foo:baz/bar + | ^-- \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/.gitkeep b/crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/baz.wit b/crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/baz.wit new file mode 100644 index 0000000000..501609101d --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-world/deps/bar/baz.wit @@ -0,0 +1,4 @@ +package foo:baz + +world bar {} + diff --git a/crates/wit-parser/tests/ui/parse-fail/use-world/root.wit b/crates/wit-parser/tests/ui/parse-fail/use-world/root.wit new file mode 100644 index 0000000000..e11dcae587 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/use-world/root.wit @@ -0,0 +1,7 @@ +package foo:foo + +use foo:baz/bar + +world foo { + +} \ No newline at end of file From 73dae09b4a0a3e57abe98b3e176dc4074b258989 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 15:02:25 -0700 Subject: [PATCH 10/14] add note to world.includes Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index b5529d3492..18b4c72e21 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -244,10 +244,10 @@ pub struct World { /// The package that owns this world. pub package: Option, - /// All the included worlds from this world. + /// All the included worlds from this world. Empty if this is fully resolved pub includes: Vec, - /// All the included worlds names + /// All the included worlds names. Empty if this is fully resolved pub include_names: Vec>, } From fdca14b81d9b646aba91d818bbb9c0a422198600 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 15:11:10 -0700 Subject: [PATCH 11/14] refactored process_foreign_deps method Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/resolve.rs | 103 ++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 35f0f59568..a4fefd1796 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -715,40 +715,29 @@ impl Remap { } } - for (unresolved_world_id, _) in unresolved.worlds.iter() { - let (pkg_name, world, span) = match world_to_package.get(&unresolved_world_id) { - Some(items) => *items, - None => break, - }; - - let pkgid = resolve - .package_names - .get(pkg_name) - .copied() - .ok_or_else(|| Error { - span, - msg: format!("package not found"), - })?; - let pkg = &resolve.packages[pkgid]; - let span = unresolved.world_spans[unresolved_world_id.index()]; - let world_id = pkg.worlds.get(world).copied().ok_or_else(|| Error { - span, - msg: format!("world not found in package"), - })?; - assert_eq!(self.worlds.len(), unresolved_world_id.index()); - self.worlds.push(world_id); - } - - for (id, _) in unresolved.worlds.iter().skip(self.worlds.len()) { - assert!( - world_to_package.get(&id).is_none(), - "found foreign world after local world" - ); - } - // Connect all interfaces referred to in `interface_to_package`, which // are at the front of `unresolved.interfaces`, to interfaces already // contained within `resolve`. + self.process_foreign_interfaces(unresolved, &interface_to_package, resolve)?; + + // Connect all worlds referred to in `world_to_package`, which + // are at the front of `unresolved.worlds`, to worlds already + // contained within `resolve`. + self.process_foreign_worlds(unresolved, &world_to_package, resolve)?; + + // Finally, iterate over all foreign-defined types and determine + // what they map to. + self.process_foreign_types(unresolved, resolve)?; + + Ok(()) + } + + fn process_foreign_interfaces( + &mut self, + unresolved: &UnresolvedPackage, + interface_to_package: &HashMap, (&PackageName, &String, Span)>, + resolve: &mut Resolve, + ) -> Result<(), anyhow::Error> { for (unresolved_iface_id, unresolved_iface) in unresolved.interfaces.iter() { let (pkg_name, interface, span) = match interface_to_package.get(&unresolved_iface_id) { Some(items) => *items, @@ -782,16 +771,60 @@ impl Remap { assert_eq!(self.interfaces.len(), unresolved_iface_id.index()); self.interfaces.push(iface_id); } - for (id, _) in unresolved.interfaces.iter().skip(self.interfaces.len()) { assert!( interface_to_package.get(&id).is_none(), "found foreign interface after local interface" ); } + Ok(()) + } - // And finally iterate over all foreign-defined types and determine - // what they map to. + fn process_foreign_worlds( + &mut self, + unresolved: &UnresolvedPackage, + world_to_package: &HashMap, (&PackageName, &String, Span)>, + resolve: &mut Resolve, + ) -> Result<(), anyhow::Error> { + for (unresolved_world_id, _) in unresolved.worlds.iter() { + let (pkg_name, world, span) = match world_to_package.get(&unresolved_world_id) { + Some(items) => *items, + // Same as above, all worlds are foreign until we find a + // non-foreign one. + None => break, + }; + + let pkgid = resolve + .package_names + .get(pkg_name) + .copied() + .ok_or_else(|| Error { + span, + msg: format!("package not found"), + })?; + let pkg = &resolve.packages[pkgid]; + let span = unresolved.world_spans[unresolved_world_id.index()]; + let world_id = pkg.worlds.get(world).copied().ok_or_else(|| Error { + span, + msg: format!("world not found in package"), + })?; + assert_eq!(self.worlds.len(), unresolved_world_id.index()); + self.worlds.push(world_id); + } + for (id, _) in unresolved.worlds.iter().skip(self.worlds.len()) { + assert!( + world_to_package.get(&id).is_none(), + "found foreign world after local world" + ); + } + Ok(()) + } + + fn process_foreign_types( + &mut self, + unresolved: &UnresolvedPackage, + resolve: &mut Resolve, + ) -> Result<(), anyhow::Error> { for (unresolved_type_id, unresolved_ty) in unresolved.types.iter() { // All "Unknown" types should appear first so once we're no longer // in unknown territory it's package-defined types so break out of @@ -817,13 +850,11 @@ impl Remap { assert_eq!(self.types.len(), unresolved_type_id.index()); self.types.push(type_id); } - for (_, ty) in unresolved.types.iter().skip(self.types.len()) { if let TypeDefKind::Unknown = ty.kind { panic!("unknown type after defined type"); } } - Ok(()) } From 76a39ba7485f83d7a585d460192b9360a2702580 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Wed, 31 May 2023 15:19:27 -0700 Subject: [PATCH 12/14] removed foreign_dep_span in resolve_include Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast/resolve.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 597a540ec1..5e36269833 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -851,7 +851,6 @@ impl<'a> Resolver<'a> { fn resolve_include(&mut self, owner: TypeOwner, i: &ast::Include<'a>) -> Result<()> { let (item, name, span) = self.resolve_ast_item_path(&i.from)?; let include_from = self.extract_world_from_item(&item, &name, span)?; - self.foreign_dep_spans.push(span); self.foreign_world_spans.push(span); let world_id = match owner { TypeOwner::World(id) => id, From c6b279534a01f1234bee74f364fae5739c545904 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Fri, 2 Jun 2023 13:58:57 -0700 Subject: [PATCH 13/14] resolved some comments Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-parser/src/ast/resolve.rs | 13 +---- crates/wit-parser/src/resolve.rs | 86 ++++++++++++---------------- 2 files changed, 40 insertions(+), 59 deletions(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index c9557ec3a4..0ec683885c 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -241,7 +241,9 @@ impl<'a> Resolver<'a> { ); AstItem::World(self.alloc_world(name.span, true)) } - WorldOrInterface::Interface => { + WorldOrInterface::Interface | WorldOrInterface::Unknown => { + // Currently top-level `use` always assumes an interface, so the + // `Unknown` case is the same as `Interface`. log::trace!( "creating an interface for foreign dep: {}/{}", id.package_name(), @@ -249,15 +251,6 @@ impl<'a> Resolver<'a> { ); AstItem::Interface(self.alloc_interface(name.span)) } - WorldOrInterface::Unknown => { - log::trace!( - "creating an interface for foreign dep: {}/{}", - id.package_name(), - name.name - ); - // Currently top-level `use` always assumes an interface - AstItem::Interface(self.alloc_interface(name.span)) - } } }); diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index ac4ceba805..211cb7d976 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1151,61 +1151,49 @@ impl Remap { // copy the imports and exports from the included world into the current world for import in include_world.imports.iter() { - let name = import.0; - match name { - WorldKey::Name(n) => { - let n = if let Some(found) = names - .into_iter() - .find(|include_name| include_name.name == n.clone()) - { - found.as_.clone() - } else { - n.clone() - }; - - let prev = world - .imports - .insert(WorldKey::Name(n.clone()), import.1.clone()); - if prev.is_some() { - bail!(Error { - msg: format!("import of `{}` shadows previously imported items", n), - span, - }) - } - } - i => { - world.imports.insert(i.clone(), import.1.clone()); - } - }; + self.resolve_include_item(names, &mut world.imports, import, span, "import")?; } for export in include_world.exports.iter() { - let name = export.0; - match name { - WorldKey::Name(n) => { - let n = if let Some(found) = names - .into_iter() - .find(|include_name| include_name.name == n.clone()) - { - found.as_.clone() - } else { - n.clone() - }; - let prev = world - .exports - .insert(WorldKey::Name(n.clone()), export.1.clone()); - if prev.is_some() { - bail!(Error { - msg: format!("export of `{}` shadows previously exported items", n), - span, - }) - } + self.resolve_include_item(names, &mut world.exports, export, span, "export")?; + } + Ok(()) + } + + fn resolve_include_item( + &self, + names: &[IncludeName], + items: &mut IndexMap, + item: (&WorldKey, &WorldItem), + span: Span, + item_type: &str, + ) -> Result<()> { + match item.0 { + WorldKey::Name(n) => { + let n = if let Some(found) = names + .into_iter() + .find(|include_name| include_name.name == n.clone()) + { + found.as_.clone() + } else { + n.clone() + }; + + let prev = items.insert(WorldKey::Name(n.clone()), item.1.clone()); + if prev.is_some() { + bail!(Error { + msg: format!("{item_type} of `{n}` shadows previously {item_type}ed items"), + span, + }) } - i => { - world.exports.insert(i.clone(), export.1.clone()); + } + key => { + let prev = items.insert(key.clone(), item.1.clone()); + if let Some(prev) = prev { + assert_eq!(prev, item.1.clone()); } } - } + }; Ok(()) } } From a31dcf7e5ad43bca18e390c41f6d4c75c1f529e9 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Fri, 2 Jun 2023 15:34:27 -0700 Subject: [PATCH 14/14] Implemented wit-smith to do fuzz tests. Did not finish the include with part because the current impl has an issue: in the roundtrip test, the list of worlds before-and-after the roundtrip is different in terms of the ordering. Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/wit-smith/src/generate.rs | 124 +++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 13 deletions(-) diff --git a/crates/wit-smith/src/generate.rs b/crates/wit-smith/src/generate.rs index e11e04ab60..b60c2db652 100644 --- a/crates/wit-smith/src/generate.rs +++ b/crates/wit-smith/src/generate.rs @@ -11,13 +11,16 @@ use wit_parser::*; pub struct Generator { config: Config, - packages: PackageList, + packages_lists: PackageIfaceList, + package_worlds: PackageWorldList, next_interface_id: u32, } type TypeList = Vec<(String, usize)>; type InterfaceList = IndexMap; -type PackageList = Vec<(PackageName, InterfaceList)>; +type WorldList = IndexMap; +type PackageIfaceList = Vec<(PackageName, InterfaceList)>; +type PackageWorldList = Vec<(PackageName, WorldList)>; struct InterfaceGenerator<'a> { gen: &'a Generator, @@ -43,7 +46,8 @@ impl Generator { pub fn new(config: Config) -> Generator { Generator { config, - packages: Default::default(), + packages_lists: Default::default(), + package_worlds: Default::default(), next_interface_id: 0, } } @@ -52,9 +56,12 @@ impl Generator { let mut packages = Vec::new(); let mut names = HashSet::new(); while packages.len() < self.config.max_packages && (packages.is_empty() || u.arbitrary()?) { - let (pkg, interfaces) = self.gen_package(u, &mut names)?; + let (pkg, interfaces, worlds) = self.gen_package(u, &mut names)?; if interfaces.len() > 0 { - self.packages.push((pkg.name.clone(), interfaces)); + self.packages_lists.push((pkg.name.clone(), interfaces)); + } + if worlds.len() > 0 { + self.package_worlds.push((pkg.name.clone(), worlds)); } packages.push(pkg); } @@ -65,7 +72,7 @@ impl Generator { &mut self, u: &mut Unstructured<'_>, names: &mut HashSet, - ) -> Result<(Package, InterfaceList)> { + ) -> Result<(Package, InterfaceList, WorldList)> { let namespace = gen_unique_name(u, names)?; let name = gen_unique_name(u, names)?; let version = if u.arbitrary()? { @@ -114,6 +121,7 @@ impl Generator { .map(|(k, _)| (k.clone(), Definition::Package)) .collect(), interfaces: package.interfaces.clone(), + worlds: package.worlds.clone(), }); files.last_mut().unwrap() } @@ -124,6 +132,20 @@ impl Generator { log::debug!("new world `{name}` in {i}"); let world = self.gen_world(u, &name, file)?; file.items.push(world); + let world = FileWorld { + name, + }; + // this world is defined at the package level, and it must be unique. + let prev = package + .worlds + .insert(world.name.clone(), world.clone()); + assert!(prev.is_none()); + + // this is also defined at the file level, and it must be unique here too. + let prev = file + .worlds + .insert(world.name.clone(), world.clone()); + assert!(prev.is_none()); } Generate::Interface => { let name = file.gen_unique_package_name(u, &mut package_names)?; @@ -163,7 +185,7 @@ impl Generator { Generate::Use => { let mut piece = String::new(); piece.push_str("use "); - let (name, id, types) = match self.gen_path(u, &mut package, &mut piece)? { + let (name, id, types) = match self.gen_interface_path(u, &mut package, &mut piece)? { Some(i) => i, None => continue, }; @@ -218,7 +240,7 @@ impl Generator { log::trace!("{s}"); ret.sources.push(format!("wit{i}.wit").as_ref(), &s); } - Ok((ret, package.interfaces)) + Ok((ret, package.interfaces, package.worlds)) } fn gen_world( @@ -241,7 +263,56 @@ impl Generator { Ok((ret, gen.types_in_interface)) } - fn gen_path<'a>( + fn gen_world_path<'a>( + &'a self, + u: &mut Unstructured<'_>, + file: &'a mut File, + dst: &mut String, + ) -> Result> { + enum Choice { + Worlds, + Packages, + } + let mut choices = Vec::new(); + if !file.worlds.is_empty() { + choices.push(Choice::Worlds); + } + if !self.package_worlds.is_empty() { + choices.push(Choice::Packages); + } + if choices.is_empty() { + return Ok(None); + } + match u.choose(&choices)? { + Choice::Worlds => { + let i = u.int_in_range(0..=file.worlds.len() - 1)?; + let (name, _) = file.worlds.iter().nth(i).unwrap(); + file.namespace.insert(name.clone(), Definition::File); + dst.push_str("%"); + dst.push_str(name); + Ok(Some(name)) + } + Choice::Packages => { + let (pkg, worlds) = u.choose(&self.package_worlds)?; + dst.push_str("%"); + dst.push_str(&pkg.namespace); + dst.push_str(":"); + dst.push_str("%"); + dst.push_str(&pkg.name); + dst.push_str("/"); + let i = u.int_in_range(0..=worlds.len() - 1)?; + let w = &worlds[i]; + dst.push_str("%"); + dst.push_str(&w.name); + if let Some(version) = &pkg.version { + dst.push_str(&format!("@{version}")); + } + Ok(Some(&w.name)) + } + } + } + + fn gen_interface_path<'a>( &'a self, u: &mut Unstructured<'_>, file: &'a mut File, @@ -255,7 +326,7 @@ impl Generator { if !file.interfaces.is_empty() { choices.push(Choice::Interfaces); } - if !self.packages.is_empty() { + if !self.packages_lists.is_empty() { choices.push(Choice::Packages); } if choices.is_empty() { @@ -274,7 +345,7 @@ impl Generator { Some((&i.name, i.id, &i.types)) } Choice::Packages => { - let (pkg, ifaces) = u.choose(&self.packages)?; + let (pkg, ifaces) = u.choose(&self.packages_lists)?; dst.push_str("%"); dst.push_str(&pkg.namespace); dst.push_str(":"); @@ -376,6 +447,7 @@ impl<'a> InterfaceGenerator<'a> { AnonInterface(Direction), Type, Use, + Include, } let mut parts = Vec::new(); @@ -389,6 +461,7 @@ impl<'a> InterfaceGenerator<'a> { ItemKind::Interface(dir) => (Some(dir), false), ItemKind::Type => (None, true), ItemKind::Use => (None, false), + ItemKind::Include => (None, false), }; let mut part = String::new(); @@ -416,7 +489,7 @@ impl<'a> InterfaceGenerator<'a> { self.gen_func_sig(u, &mut part)?; } ItemKind::Interface(dir) => { - let id = match self.gen.gen_path(u, self.file, &mut part)? { + let id = match self.gen.gen_interface_path(u, self.file, &mut part)? { Some((_name, id, _types)) => id, // If an interface couldn't be chosen or wasn't // chosen then skip this import. A unique name was @@ -456,6 +529,12 @@ impl<'a> InterfaceGenerator<'a> { continue; } } + + ItemKind::Include => { + if !self.gen_include(u, &mut part)? { + continue; + } + } } parts.push(part); } @@ -474,7 +553,7 @@ impl<'a> InterfaceGenerator<'a> { fn gen_use(&mut self, u: &mut Unstructured<'_>, part: &mut String) -> Result { let mut path = String::new(); - let (_name, _id, types) = match self.gen.gen_path(u, self.file, &mut path)? { + let (_name, _id, types) = match self.gen.gen_interface_path(u, self.file, &mut path)? { Some(types) => types, None => return Ok(false), }; @@ -499,6 +578,19 @@ impl<'a> InterfaceGenerator<'a> { Ok(true) } + fn gen_include(&mut self, u: &mut Unstructured<'_>, part: &mut String) -> Result { + let mut path = String::new(); + let _ = match self.gen.gen_world_path(u, self.file, &mut path)? { + Some(name) => name, + None => return Ok(false), + }; + part.push_str("include "); + part.push_str(&path); + + // TODO: add `with` + Ok(true) + } + fn gen_typedef(&mut self, u: &mut Unstructured<'_>, name: &str) -> Result<(usize, String)> { #[derive(Arbitrary)] pub enum Kind { @@ -806,6 +898,12 @@ struct File { items: Vec, namespace: HashMap, interfaces: IndexMap, + worlds: IndexMap, +} + +#[derive(Clone)] +struct FileWorld { + name: String, } #[derive(Clone)]