diff --git a/crates/wit-component/tests/merge.rs b/crates/wit-component/tests/merge.rs index 2ff7739eef..e1cca07c56 100644 --- a/crates/wit-component/tests/merge.rs +++ b/crates/wit-component/tests/merge.rs @@ -34,9 +34,6 @@ fn merging() -> Result<()> { from.assert_valid(); into.assert_valid(); - let from_clone = from.clone(); - let into_clone = into.clone(); - match into.merge(from) { Ok(_) => { assert!( @@ -54,13 +51,6 @@ fn merging() -> Result<()> { let output = printer.output.to_string(); assert_output(&expected, &output)?; } - - // Also assert merging in the reverse direction succeeds. - let mut reverse = from_clone; - reverse - .merge(into_clone) - .context("merge succeeded in one direction but failed in reverse")?; - reverse.assert_valid(); } Err(e) => { assert!(test_case.starts_with("bad-"), "failed to merge with {e:?}"); diff --git a/crates/wit-component/tests/merge/bad-interface1/error.txt b/crates/wit-component/tests/merge/bad-interface1/error.txt new file mode 100644 index 0000000000..0558df13d3 --- /dev/null +++ b/crates/wit-component/tests/merge/bad-interface1/error.txt @@ -0,0 +1 @@ +failed to merge package `foo:foo` into existing copy: failed to merge interface `a`: expected type `a` to be present \ No newline at end of file diff --git a/crates/wit-component/tests/merge/disjoint-types/from/a.wit b/crates/wit-component/tests/merge/bad-interface1/from/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-types/from/a.wit rename to crates/wit-component/tests/merge/bad-interface1/from/a.wit diff --git a/crates/wit-component/tests/merge/disjoint-types/from/deps/foo/a.wit b/crates/wit-component/tests/merge/bad-interface1/from/deps/foo/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-types/from/deps/foo/a.wit rename to crates/wit-component/tests/merge/bad-interface1/from/deps/foo/a.wit diff --git a/crates/wit-component/tests/merge/disjoint-types/into/a.wit b/crates/wit-component/tests/merge/bad-interface1/into/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-types/into/a.wit rename to crates/wit-component/tests/merge/bad-interface1/into/a.wit diff --git a/crates/wit-component/tests/merge/disjoint-types/into/deps/foo/a.wit b/crates/wit-component/tests/merge/bad-interface1/into/deps/foo/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-types/into/deps/foo/a.wit rename to crates/wit-component/tests/merge/bad-interface1/into/deps/foo/a.wit diff --git a/crates/wit-component/tests/merge/bad-interface2/error.txt b/crates/wit-component/tests/merge/bad-interface2/error.txt new file mode 100644 index 0000000000..0b3f674bf8 --- /dev/null +++ b/crates/wit-component/tests/merge/bad-interface2/error.txt @@ -0,0 +1 @@ +failed to merge package `foo:foo` into existing copy: failed to merge interface `a`: expected function `a` to be present \ No newline at end of file diff --git a/crates/wit-component/tests/merge/disjoint-funcs/from/a.wit b/crates/wit-component/tests/merge/bad-interface2/from/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-funcs/from/a.wit rename to crates/wit-component/tests/merge/bad-interface2/from/a.wit diff --git a/crates/wit-component/tests/merge/disjoint-funcs/from/deps/foo/a.wit b/crates/wit-component/tests/merge/bad-interface2/from/deps/foo/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-funcs/from/deps/foo/a.wit rename to crates/wit-component/tests/merge/bad-interface2/from/deps/foo/a.wit diff --git a/crates/wit-component/tests/merge/disjoint-funcs/into/a.wit b/crates/wit-component/tests/merge/bad-interface2/into/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-funcs/into/a.wit rename to crates/wit-component/tests/merge/bad-interface2/into/a.wit diff --git a/crates/wit-component/tests/merge/disjoint-funcs/into/deps/foo/a.wit b/crates/wit-component/tests/merge/bad-interface2/into/deps/foo/a.wit similarity index 100% rename from crates/wit-component/tests/merge/disjoint-funcs/into/deps/foo/a.wit rename to crates/wit-component/tests/merge/bad-interface2/into/deps/foo/a.wit diff --git a/crates/wit-component/tests/merge/commutative-disjoint-funcs/from/a.wit b/crates/wit-component/tests/merge/commutative-disjoint-funcs/from/a.wit deleted file mode 100644 index 1e150dfdc2..0000000000 --- a/crates/wit-component/tests/merge/commutative-disjoint-funcs/from/a.wit +++ /dev/null @@ -1,5 +0,0 @@ -package a:b; - -interface a { - f1: func(); -} diff --git a/crates/wit-component/tests/merge/commutative-disjoint-funcs/into/a.wit b/crates/wit-component/tests/merge/commutative-disjoint-funcs/into/a.wit deleted file mode 100644 index 80d8f11d24..0000000000 --- a/crates/wit-component/tests/merge/commutative-disjoint-funcs/into/a.wit +++ /dev/null @@ -1,5 +0,0 @@ -package a:b; - -interface a { - f2: func(); -} diff --git a/crates/wit-component/tests/merge/commutative-disjoint-funcs/merge/b.wit b/crates/wit-component/tests/merge/commutative-disjoint-funcs/merge/b.wit deleted file mode 100644 index 98dd08b029..0000000000 --- a/crates/wit-component/tests/merge/commutative-disjoint-funcs/merge/b.wit +++ /dev/null @@ -1,8 +0,0 @@ -package a:b; - -interface a { - f2: func(); - - f1: func(); -} - diff --git a/crates/wit-component/tests/merge/commutative/from/a.wit b/crates/wit-component/tests/merge/commutative/from/a.wit deleted file mode 100644 index bfd8c8f297..0000000000 --- a/crates/wit-component/tests/merge/commutative/from/a.wit +++ /dev/null @@ -1,23 +0,0 @@ -package wasi:io@0.2.0; - -interface error { - resource error; -} - -interface streams { - use error.{error}; - - resource output-stream { - check-write: func() -> result; - write: func(contents: list) -> result<_, stream-error>; - blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; - blocking-flush: func() -> result<_, stream-error>; - } - - variant stream-error { - last-operation-failed(error), - closed, - } - - resource input-stream; -} diff --git a/crates/wit-component/tests/merge/commutative/into/a.wit b/crates/wit-component/tests/merge/commutative/into/a.wit deleted file mode 100644 index 2a95e70eff..0000000000 --- a/crates/wit-component/tests/merge/commutative/into/a.wit +++ /dev/null @@ -1,53 +0,0 @@ -package wasi:io@0.2.0; - -interface error { - resource error { - to-debug-string: func() -> string; - } -} - -interface poll { - resource pollable { - ready: func() -> bool; - block: func(); - } - - poll: func(in: list>) -> list; -} - -interface streams { - use error.{error}; - use poll.{pollable}; - - variant stream-error { - last-operation-failed(error), - closed, - } - - resource input-stream { - read: func(len: u64) -> result, stream-error>; - blocking-read: func(len: u64) -> result, stream-error>; - skip: func(len: u64) -> result; - blocking-skip: func(len: u64) -> result; - subscribe: func() -> pollable; - } - - resource output-stream { - check-write: func() -> result; - write: func(contents: list) -> result<_, stream-error>; - blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; - flush: func() -> result<_, stream-error>; - blocking-flush: func() -> result<_, stream-error>; - subscribe: func() -> pollable; - write-zeroes: func(len: u64) -> result<_, stream-error>; - blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; - splice: func(src: borrow, len: u64) -> result; - blocking-splice: func(src: borrow, len: u64) -> result; - } -} - -world imports { - import error; - import poll; - import streams; -} diff --git a/crates/wit-component/tests/merge/commutative/merge/io.wit b/crates/wit-component/tests/merge/commutative/merge/io.wit deleted file mode 100644 index 2a95e70eff..0000000000 --- a/crates/wit-component/tests/merge/commutative/merge/io.wit +++ /dev/null @@ -1,53 +0,0 @@ -package wasi:io@0.2.0; - -interface error { - resource error { - to-debug-string: func() -> string; - } -} - -interface poll { - resource pollable { - ready: func() -> bool; - block: func(); - } - - poll: func(in: list>) -> list; -} - -interface streams { - use error.{error}; - use poll.{pollable}; - - variant stream-error { - last-operation-failed(error), - closed, - } - - resource input-stream { - read: func(len: u64) -> result, stream-error>; - blocking-read: func(len: u64) -> result, stream-error>; - skip: func(len: u64) -> result; - blocking-skip: func(len: u64) -> result; - subscribe: func() -> pollable; - } - - resource output-stream { - check-write: func() -> result; - write: func(contents: list) -> result<_, stream-error>; - blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; - flush: func() -> result<_, stream-error>; - blocking-flush: func() -> result<_, stream-error>; - subscribe: func() -> pollable; - write-zeroes: func(len: u64) -> result<_, stream-error>; - blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; - splice: func(src: borrow, len: u64) -> result; - blocking-splice: func(src: borrow, len: u64) -> result; - } -} - -world imports { - import error; - import poll; - import streams; -} diff --git a/crates/wit-component/tests/merge/disjoint-funcs/merge/foo.wit b/crates/wit-component/tests/merge/disjoint-funcs/merge/foo.wit deleted file mode 100644 index c3fc6683f6..0000000000 --- a/crates/wit-component/tests/merge/disjoint-funcs/merge/foo.wit +++ /dev/null @@ -1,10 +0,0 @@ -package foo:foo; - -interface a { - type t = u32; - - b: func(); - - a: func(); -} - diff --git a/crates/wit-component/tests/merge/disjoint-funcs/merge/from.wit b/crates/wit-component/tests/merge/disjoint-funcs/merge/from.wit deleted file mode 100644 index c8b6f48811..0000000000 --- a/crates/wit-component/tests/merge/disjoint-funcs/merge/from.wit +++ /dev/null @@ -1,6 +0,0 @@ -package foo:%from; - -interface a { - use foo:foo/a.{t}; -} - diff --git a/crates/wit-component/tests/merge/disjoint-funcs/merge/into.wit b/crates/wit-component/tests/merge/disjoint-funcs/merge/into.wit deleted file mode 100644 index ab8bb5c657..0000000000 --- a/crates/wit-component/tests/merge/disjoint-funcs/merge/into.wit +++ /dev/null @@ -1,6 +0,0 @@ -package foo:into; - -interface a { - use foo:foo/a.{t}; -} - diff --git a/crates/wit-component/tests/merge/disjoint-types/merge/foo.wit b/crates/wit-component/tests/merge/disjoint-types/merge/foo.wit deleted file mode 100644 index 8527263d2a..0000000000 --- a/crates/wit-component/tests/merge/disjoint-types/merge/foo.wit +++ /dev/null @@ -1,8 +0,0 @@ -package foo:foo; - -interface a { - type b = s32; - - type a = u32; -} - diff --git a/crates/wit-component/tests/merge/disjoint-types/merge/from.wit b/crates/wit-component/tests/merge/disjoint-types/merge/from.wit deleted file mode 100644 index 96ed27f59a..0000000000 --- a/crates/wit-component/tests/merge/disjoint-types/merge/from.wit +++ /dev/null @@ -1,6 +0,0 @@ -package foo:%from; - -interface a { - use foo:foo/a.{a}; -} - diff --git a/crates/wit-component/tests/merge/disjoint-types/merge/into.wit b/crates/wit-component/tests/merge/disjoint-types/merge/into.wit deleted file mode 100644 index 604327da70..0000000000 --- a/crates/wit-component/tests/merge/disjoint-types/merge/into.wit +++ /dev/null @@ -1,6 +0,0 @@ -package foo:into; - -interface a { - use foo:foo/a.{b}; -} - diff --git a/crates/wit-component/tests/merge/topo-sort-needed/README.md b/crates/wit-component/tests/merge/topo-sort-needed/README.md deleted file mode 100644 index 660ef4123d..0000000000 --- a/crates/wit-component/tests/merge/topo-sort-needed/README.md +++ /dev/null @@ -1,14 +0,0 @@ -During a merge, when `from` contributes a **new** interface that doesn't exist in `into`, that interface gets appended to the **end** of the arena. If an existing interface in `into` now depends on that newly-appended interface (because extra types/functions referencing it were also added), the ordering invariant is violated — the dependency comes *after* its dependent. - -The `topo-sort-needed` test sets up exactly this scenario: - -- **`from`** has two interfaces: `dep` (defines `type t = u32`) and `user` (has `use dep.{t}`, shared function `get`, and extra function `get-dep` that returns `t`). -- **`into`** has only `user` with just the `get` function — no `dep` at all. - -When merging `from` into `into`: - -1. `user` is matched between both sides (same name, same package). The shared function `get` checks out structurally. -2. `dep` has no counterpart in `into`, so it's **appended to the end** of the arena — after `user`. -3. The extra function `get-dep` and the `use dep.{t}` type from `from`'s `user` are added to `into`'s `user`, creating a dependency from `user` → `dep`. - -Now the arena has `[user, dep]` but the dependency goes `user → dep` — `dep` should come first. Without the topological sort, `assert_topologically_sorted` panics with `assertion failed: other_interface_pos <= my_interface_pos`. With the sort, the arena is reordered to `[dep, user]` and the invariant holds. \ No newline at end of file diff --git a/crates/wit-component/tests/merge/topo-sort-needed/from/a.wit b/crates/wit-component/tests/merge/topo-sort-needed/from/a.wit deleted file mode 100644 index 46e5627824..0000000000 --- a/crates/wit-component/tests/merge/topo-sort-needed/from/a.wit +++ /dev/null @@ -1,19 +0,0 @@ -package foo:bar; - -/// `from` has `dep` and `user`. `user` has a function `get` shared with -/// `into`, and an extra function `get-dep` that references `dep.{t}`. -/// During the merge, `dep` is added to the arena after `user` (because -/// it's a brand-new interface). The extra `use dep.{t}` type in `user` -/// then creates a dependency user->dep where dep comes later in the -/// arena. The topological sort corrects this. - -interface dep { - type t = u32; -} - -interface user { - use dep.{t}; - - get: func() -> u32; - get-dep: func() -> t; -} diff --git a/crates/wit-component/tests/merge/topo-sort-needed/into/a.wit b/crates/wit-component/tests/merge/topo-sort-needed/into/a.wit deleted file mode 100644 index 35b9d0814b..0000000000 --- a/crates/wit-component/tests/merge/topo-sort-needed/into/a.wit +++ /dev/null @@ -1,7 +0,0 @@ -package foo:bar; - -/// `into` only has `user` with `get` — no `dep` interface at all. - -interface user { - get: func() -> u32; -} diff --git a/crates/wit-component/tests/merge/topo-sort-needed/merge/bar.wit b/crates/wit-component/tests/merge/topo-sort-needed/merge/bar.wit deleted file mode 100644 index 9f1309de76..0000000000 --- a/crates/wit-component/tests/merge/topo-sort-needed/merge/bar.wit +++ /dev/null @@ -1,21 +0,0 @@ -package foo:bar; - -/// `into` only has `user` with `get` — no `dep` interface at all. -interface user { - use dep.{t}; - - get: func() -> u32; - - get-dep: func() -> t; -} - -/// `from` has `dep` and `user`. `user` has a function `get` shared with -/// `into`, and an extra function `get-dep` that references `dep.{t}`. -/// During the merge, `dep` is added to the arena after `user` (because -/// it's a brand-new interface). The extra `use dep.{t}` type in `user` -/// then creates a dependency user->dep where dep comes later in the -/// arena. The topological sort corrects this. -interface dep { - type t = u32; -} - diff --git a/crates/wit-parser/src/resolve/mod.rs b/crates/wit-parser/src/resolve/mod.rs index 596839457e..9d6ff1946e 100644 --- a/crates/wit-parser/src/resolve/mod.rs +++ b/crates/wit-parser/src/resolve/mod.rs @@ -541,43 +541,9 @@ package {name} is defined in two different locations:\n\ let mut moved_interfaces = Vec::new(); for (id, mut iface) in interfaces { let new_id = match interface_map.get(&id).copied() { - Some(into_id) => { - update_stability(&iface.stability, &mut self.interfaces[into_id].stability)?; - - // Add any extra types from `from`'s interface that - // don't exist in `into`'s interface. These types were - // already moved as new types above (since they weren't - // in `type_map`), but they still need to be registered - // in the target interface's `types` map. - for (name, from_type_id) in iface.types.iter() { - if self.interfaces[into_id].types.contains_key(name) { - continue; - } - let new_type_id = remap.map_type(*from_type_id, Default::default())?; - self.interfaces[into_id] - .types - .insert(name.clone(), new_type_id); - } - - // Add any extra functions from `from`'s interface that - // don't exist in `into`'s interface. These need their - // type references remapped and spans adjusted. - let extra_funcs: Vec<_> = iface - .functions - .into_iter() - .filter(|(name, _)| { - !self.interfaces[into_id] - .functions - .contains_key(name.as_str()) - }) - .collect(); - for (name, mut func) in extra_funcs { - remap.update_function(self, &mut func, Default::default())?; - func.adjust_spans(span_offset); - self.interfaces[into_id].functions.insert(name, func); - } - - into_id + Some(id) => { + update_stability(&iface.stability, &mut self.interfaces[id].stability)?; + id } None => { log::debug!("moving interface {:?}", iface.name); @@ -714,19 +680,6 @@ package {name} is defined in two different locations:\n\ log::trace!("now have {} packages", self.packages.len()); - // Ensure the interfaces arena is in topological order after the - // merge. Newly-added interfaces may have been appended after - // existing interfaces that now depend on them. - self.topologically_sort_interfaces(Some(&mut remap)); - - // Re-elaborate all worlds after the merge. Merging may have added - // extra types to existing interfaces that introduce new interface - // dependencies not yet present in the world's imports. - let world_ids: Vec<_> = self.worlds.iter().map(|(id, _)| id).collect(); - for world_id in world_ids { - self.elaborate_world(world_id)?; - } - #[cfg(debug_assertions)] self.assert_valid(); Ok(remap) @@ -1598,149 +1551,6 @@ package {name} is defined in two different locations:\n\ pushed[id.index()] = true; } - /// Rebuilds the interfaces arena in topological order and updates all - /// InterfaceId references throughout this `Resolve`. - /// - /// After a merge, newly-added interfaces may appear after existing - /// interfaces that now depend on them. This method re-orders the - /// interfaces arena so that dependencies always precede dependents - /// within each package. - /// - /// The optional `remap` is updated so that its interface entries reflect - /// the new interface IDs. - fn topologically_sort_interfaces(&mut self, remap: Option<&mut Remap>) { - // Compute a global topological order: iterate packages in topological - // order, and within each package topologically sort interfaces based - // on their `use`-type dependencies. - let mut order = Vec::with_capacity(self.interfaces.len()); - let mut visited = vec![false; self.interfaces.len()]; - - for pkg_id in self.topological_packages() { - let pkg = &self.packages[pkg_id]; - for (_, &iface_id) in pkg.interfaces.iter() { - self.visit_interface_topo(iface_id, pkg_id, &mut visited, &mut order); - } - } - - // Also include interfaces that don't belong to any package (shouldn't - // normally happen, but be safe). - for (id, _) in self.interfaces.iter() { - if !visited[id.index()] { - order.push(id); - } - } - - // Check if already in order — if so, skip the rebuild. - let already_sorted = order - .iter() - .zip(order.iter().skip(1)) - .all(|(a, b)| a.index() < b.index()); - if already_sorted { - return; - } - - // Build old-to-new mapping. - let mut old_to_new: Vec> = vec![None; self.interfaces.len()]; - - // Consume the old arena and put items into a vec for random access. - let old_arena = mem::take(&mut self.interfaces); - let mut items: Vec> = old_arena - .into_iter() - .map(|(_, iface)| Some(iface)) - .collect(); - - // Rebuild the arena in topological order. - for &old_id in &order { - let iface = items[old_id.index()].take().unwrap(); - let new_id = self.interfaces.alloc(iface); - old_to_new[old_id.index()] = Some(new_id); - } - - // Helper closure to map an old InterfaceId to the new one. - let map_iface = |id: InterfaceId| -> InterfaceId { old_to_new[id.index()].unwrap() }; - - // Update all InterfaceId references throughout the Resolve. - - // 1. Package::interfaces - for (_, pkg) in self.packages.iter_mut() { - for (_, id) in pkg.interfaces.iter_mut() { - *id = map_iface(*id); - } - } - - // 2. Types: TypeOwner::Interface - for (_, ty) in self.types.iter_mut() { - if let TypeOwner::Interface(id) = &mut ty.owner { - *id = map_iface(*id); - } - } - - // 3. Worlds: imports/exports with WorldKey::Interface and WorldItem::Interface - for (_, world) in self.worlds.iter_mut() { - let imports = mem::take(&mut world.imports); - for (key, mut item) in imports { - let new_key = match key { - WorldKey::Interface(id) => WorldKey::Interface(map_iface(id)), - other => other, - }; - if let WorldItem::Interface { id, .. } = &mut item { - *id = map_iface(*id); - } - world.imports.insert(new_key, item); - } - let exports = mem::take(&mut world.exports); - for (key, mut item) in exports { - let new_key = match key { - WorldKey::Interface(id) => WorldKey::Interface(map_iface(id)), - other => other, - }; - if let WorldItem::Interface { id, .. } = &mut item { - *id = map_iface(*id); - } - world.exports.insert(new_key, item); - } - } - - // 4. Interface::clone_of - for (_, iface) in self.interfaces.iter_mut() { - if let Some(id) = &mut iface.clone_of { - *id = map_iface(*id); - } - } - - // 5. Update the Remap if provided. - if let Some(remap) = remap { - for entry in remap.interfaces.iter_mut() { - if let Some(id) = entry { - *id = map_iface(*id); - } - } - } - } - - /// Depth-first visit for topological sorting of interfaces within a package. - fn visit_interface_topo( - &self, - id: InterfaceId, - pkg_id: PackageId, - visited: &mut Vec, - order: &mut Vec, - ) { - if visited[id.index()] { - return; - } - visited[id.index()] = true; - - // Visit same-package dependencies first. - for dep in self.interface_direct_deps(id) { - if self.interfaces[dep].package == Some(pkg_id) { - self.visit_interface_topo(dep, pkg_id, visited, order); - } - } - - order.push(id); - } - #[doc(hidden)] pub fn assert_valid(&self) { let mut package_interfaces = Vec::new(); @@ -4400,20 +4210,24 @@ impl<'a> MergeMap<'a> { let from_interface = &self.from.interfaces[from_id]; let into_interface = &self.into.interfaces[into_id]; - // When merging interfaces, types and functions that exist in both - // `from` and `into` must match structurally. Either side is allowed - // to have extra types or functions not present in the other, which - // enables commutative merging of partial views of the same - // interface. The only requirement is that the intersection of the - // two interfaces is compatible. + // Unlike documents/interfaces above if an interface in `from` + // differs from the interface in `into` then that's considered an + // error. Changing interfaces can reflect changes in imports/exports + // which may not be expected so it's currently required that all + // interfaces, when merged, exactly match. + // + // One case to consider here, for example, is that if a world in + // `into` exports the interface `into_id` then if `from_id` were to + // add more items into `into` then it would unexpectedly require more + // items to be exported which may not work. In an import context this + // might work since it's "just more items available for import", but + // for now a conservative route of "interfaces must match" is taken. for (name, from_type_id) in from_interface.types.iter() { - let into_type_id = match into_interface.types.get(name) { - Some(id) => *id, - // Extra type in `from` not present in `into`; it will be - // moved as a new type and added to the interface later. - None => continue, - }; + let into_type_id = *into_interface + .types + .get(name) + .ok_or_else(|| anyhow!("expected type `{name}` to be present"))?; let prev = self.type_map.insert(*from_type_id, into_type_id); assert!(prev.is_none()); @@ -4424,9 +4238,7 @@ impl<'a> MergeMap<'a> { for (name, from_func) in from_interface.functions.iter() { let into_func = match into_interface.functions.get(name) { Some(func) => func, - // Extra function in `from` not present in `into`; it will - // be added to the interface during the merge phase. - None => continue, + None => bail!("expected function `{name}` to be present"), }; self.build_function(from_func, into_func) .with_context(|| format!("mismatch in function `{name}`"))?; diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index 500c19ddc7..480093bb64 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -79,11 +79,8 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { // Decode what was just created and record it later for testing merging // worlds together. - let (dresolve, dworldid) = match wit_component::decode(&wasm).unwrap() { - wit_component::DecodedWasm::Component(r, w) => (r, w), - _ => unreachable!(), - }; - decoded_bindgens.push((dresolve, dworldid, dummy, world.name.clone())); + let (_, decoded) = wit_component::metadata::decode(&dummy).unwrap(); + decoded_bindgens.push((decoded, dummy, world.name.clone())); log::debug!("... decoding the component itself"); wit_component::decode(&wasm).unwrap(); @@ -105,47 +102,27 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { } let i = u.choose_index(decoded_bindgens.len())?; - let (mut b1, worldid1, wasm1, world1) = decoded_bindgens.swap_remove(i); + let (mut b1, wasm1, world1) = decoded_bindgens.swap_remove(i); if u.arbitrary()? { let i = u.choose_index(decoded_bindgens.len())?; - let (mut b2, worldid2, wasm2, world2) = decoded_bindgens.swap_remove(i); + let (b2, wasm2, world2) = decoded_bindgens.swap_remove(i); log::debug!("merging bindgens world {world1} <- world {world2}"); write_file("bindgen1.wasm", &wasm1); write_file("bindgen2.wasm", &wasm2); - let pkg2 = b2.worlds[worldid2].package.unwrap(); - let mut name = b2.packages[pkg2].name.clone(); - b2.package_names.swap_remove(&name).unwrap(); - name.name.push_str("2"); - let prev = b2.package_names.insert(name.clone(), pkg2); - assert!(prev.is_none()); - b2.packages[pkg2].name = name; - - let only_interfaces = b1.worlds[worldid1] - .imports - .iter() - .chain(b1.worlds[worldid1].exports.iter()) - .chain(b2.worlds[worldid2].imports.iter()) - .chain(b2.worlds[worldid2].exports.iter()) - .all(|(id, _)| matches!(id, wit_parser::WorldKey::Interface(_))); - // Merging worlds may fail but if successful then a `Resolve` is asserted // to be valid which is what we're interested in here. Note that failure // here can be due to the structure of worlds which aren't reasonable to // control in this generator, so it's just done to see what happens and try // to trigger panics in `Resolve::assert_valid`. - let merge_result = b1.merge(b2); - - if only_interfaces { - merge_result.unwrap(); - } + let _ = b1.merge(b2); } else { log::debug!("merging world imports based on semver {world1}"); write_file("bindgen1.wasm", &wasm1); - let _ = b1.merge_world_imports_based_on_semver(worldid1); + let _ = b1.resolve.merge_world_imports_based_on_semver(b1.world); } Ok(()) } diff --git a/tests/cli/merge-with-similar-versions.wit b/tests/cli/merge-with-similar-versions.wit index 44d8686e15..cf3b4085e7 100644 --- a/tests/cli/merge-with-similar-versions.wit +++ b/tests/cli/merge-with-similar-versions.wit @@ -2,7 +2,7 @@ // RUN[exports]: component wit % --merge-world-imports-based-on-semver exports // RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps // RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver // RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver // RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive diff --git a/tests/cli/merge-with-similar-versions.wit.exports.stdout b/tests/cli/merge-with-similar-versions.wit.exports.stdout index c9ed724099..0b754fbe02 100644 --- a/tests/cli/merge-with-similar-versions.wit.exports.stdout +++ b/tests/cli/merge-with-similar-versions.wit.exports.stdout @@ -2,7 +2,7 @@ /// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports /// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps /// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +/// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver /// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver /// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive package foo:bar; diff --git a/tests/cli/merge-with-similar-versions.wit.fix-transitive.stdout b/tests/cli/merge-with-similar-versions.wit.fix-transitive.stdout index 6cd668ef6a..592dfe5a83 100644 --- a/tests/cli/merge-with-similar-versions.wit.fix-transitive.stdout +++ b/tests/cli/merge-with-similar-versions.wit.fix-transitive.stdout @@ -2,7 +2,7 @@ /// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports /// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps /// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +/// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver /// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver /// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive package foo:bar; diff --git a/tests/cli/merge-with-similar-versions.wit.imports-deps.stdout b/tests/cli/merge-with-similar-versions.wit.imports-deps.stdout index 996c951f16..43ded819a8 100644 --- a/tests/cli/merge-with-similar-versions.wit.imports-deps.stdout +++ b/tests/cli/merge-with-similar-versions.wit.imports-deps.stdout @@ -2,7 +2,7 @@ /// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports /// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps /// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +/// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver /// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver /// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive package foo:bar; diff --git a/tests/cli/merge-with-similar-versions.wit.imports-deps2.stdout b/tests/cli/merge-with-similar-versions.wit.imports-deps2.stdout index dc62c2b35b..786ea17da5 100644 --- a/tests/cli/merge-with-similar-versions.wit.imports-deps2.stdout +++ b/tests/cli/merge-with-similar-versions.wit.imports-deps2.stdout @@ -2,7 +2,7 @@ /// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports /// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps /// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +/// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver /// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver /// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive package foo:bar; diff --git a/tests/cli/merge-with-similar-versions.wit.imports.stdout b/tests/cli/merge-with-similar-versions.wit.imports.stdout index 0151bd53c9..e5c716fe44 100644 --- a/tests/cli/merge-with-similar-versions.wit.imports.stdout +++ b/tests/cli/merge-with-similar-versions.wit.imports.stdout @@ -2,7 +2,7 @@ /// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports /// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps /// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +/// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver /// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver /// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive package foo:bar; diff --git a/tests/cli/merge-with-similar-versions.wit.invalid-semver.stderr b/tests/cli/merge-with-similar-versions.wit.invalid-semver.stderr new file mode 100644 index 0000000000..fa9de15642 --- /dev/null +++ b/tests/cli/merge-with-similar-versions.wit.invalid-semver.stderr @@ -0,0 +1,6 @@ +error: failed to merge world imports based on semver + +Caused by: + 0: failed to upgrade `a:b/invalid-semver@0.1.0` to `a:b/invalid-semver@0.1.1`, was this semver-compatible update not semver compatible? + 1: failed to merge interfaces + 2: expected function `x` to be present diff --git a/tests/cli/merge-with-similar-versions.wit.invalid-semver.stdout b/tests/cli/merge-with-similar-versions.wit.invalid-semver.stdout deleted file mode 100644 index b6a9da879b..0000000000 --- a/tests/cli/merge-with-similar-versions.wit.invalid-semver.stdout +++ /dev/null @@ -1,96 +0,0 @@ -/// RUN[imports]: component wit % --merge-world-imports-based-on-semver imports -/// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports -/// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps -/// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver -/// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver -/// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive -package foo:bar; - -world imports { - import a:b/foo@0.1.0; - import a:b/foo@0.1.1; -} -world exports { - export a:b/foo@0.1.0; - export a:b/foo@0.1.1; -} -world imports-deps { - import a:b/foo@0.1.0; - import a:b/depend-on-foo@0.1.0; - import a:b/foo@0.1.1; - import a:b/depend-on-foo@0.1.1; -} -world imports-deps2 { - import a:b/foo@0.1.1; - import a:b/depend-on-foo@0.1.1; - import a:b/foo@0.1.0; - import a:b/depend-on-foo@0.1.0; -} -world invalid-semver { - import a:b/invalid-semver@0.1.1; -} -world valid-semver { - import a:b/valid-semver@0.1.0; - import a:b/valid-semver@0.1.1; -} -world fix-transitive { - import a:b/foo@0.1.0; - import a:b/use-foo1@0.1.0; - import a:b/foo@0.1.1; - import a:b/use-foo2@0.1.1; - use a:b/foo@0.1.0.{t as t1}; - use a:b/use-foo1@0.1.0.{t as t2}; - use a:b/foo@0.1.1.{t as t3}; - use a:b/use-foo2@0.1.1.{t as t4}; -} -package a:b@0.1.0 { - interface foo { - type t = u32; - - x: func(); - } - interface depend-on-foo { - use foo.{t}; - - x: func() -> t; - } - interface invalid-semver { - x: func(); - } - interface valid-semver { - x: func(); - } - interface use-foo1 { - use foo.{t}; - - x: func() -> t; - } -} - - -package a:b@0.1.1 { - interface foo { - type t = u32; - - x: func(); - } - interface depend-on-foo { - use foo.{t}; - - x: func() -> t; - } - interface invalid-semver { - y: func(); - } - interface valid-semver { - x: func(); - - y: func(); - } - interface use-foo2 { - use foo.{t}; - - x: func() -> t; - } -} diff --git a/tests/cli/merge-with-similar-versions.wit.valid-semver.stdout b/tests/cli/merge-with-similar-versions.wit.valid-semver.stdout index 32fe73a9e9..be8d0727be 100644 --- a/tests/cli/merge-with-similar-versions.wit.valid-semver.stdout +++ b/tests/cli/merge-with-similar-versions.wit.valid-semver.stdout @@ -2,7 +2,7 @@ /// RUN[exports]: component wit % --merge-world-imports-based-on-semver exports /// RUN[imports-deps]: component wit % --merge-world-imports-based-on-semver imports-deps /// RUN[imports-deps2]: component wit % --merge-world-imports-based-on-semver imports-deps2 -/// RUN[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver +/// FAIL[invalid-semver]: component wit % --merge-world-imports-based-on-semver invalid-semver /// RUN[valid-semver]: component wit % --merge-world-imports-based-on-semver valid-semver /// RUN[fix-transitive]: component wit % --merge-world-imports-based-on-semver fix-transitive package foo:bar;