Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions crates/wit-component/tests/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ 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!(
Expand All @@ -51,6 +54,13 @@ 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:?}");
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package a:b;

interface a {
f1: func();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package a:b;

interface a {
f2: func();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package a:b;

interface a {
f2: func();

f1: func();
}

23 changes: 23 additions & 0 deletions crates/wit-component/tests/merge/commutative/from/a.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package wasi:io@0.2.0;

interface error {
resource error;
}

interface streams {
use error.{error};

resource output-stream {
check-write: func() -> result<u64, stream-error>;
write: func(contents: list<u8>) -> result<_, stream-error>;
blocking-write-and-flush: func(contents: list<u8>) -> result<_, stream-error>;
blocking-flush: func() -> result<_, stream-error>;
}

variant stream-error {
last-operation-failed(error),
closed,
}

resource input-stream;
}
53 changes: 53 additions & 0 deletions crates/wit-component/tests/merge/commutative/into/a.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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<borrow<pollable>>) -> list<u32>;
}

interface streams {
use error.{error};
use poll.{pollable};

variant stream-error {
last-operation-failed(error),
closed,
}

resource input-stream {
read: func(len: u64) -> result<list<u8>, stream-error>;
blocking-read: func(len: u64) -> result<list<u8>, stream-error>;
skip: func(len: u64) -> result<u64, stream-error>;
blocking-skip: func(len: u64) -> result<u64, stream-error>;
subscribe: func() -> pollable;
}

resource output-stream {
check-write: func() -> result<u64, stream-error>;
write: func(contents: list<u8>) -> result<_, stream-error>;
blocking-write-and-flush: func(contents: list<u8>) -> 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<input-stream>, len: u64) -> result<u64, stream-error>;
blocking-splice: func(src: borrow<input-stream>, len: u64) -> result<u64, stream-error>;
}
}

world imports {
import error;
import poll;
import streams;
}
53 changes: 53 additions & 0 deletions crates/wit-component/tests/merge/commutative/merge/io.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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<borrow<pollable>>) -> list<u32>;
}

interface streams {
use error.{error};
use poll.{pollable};

variant stream-error {
last-operation-failed(error),
closed,
}

resource input-stream {
read: func(len: u64) -> result<list<u8>, stream-error>;
blocking-read: func(len: u64) -> result<list<u8>, stream-error>;
skip: func(len: u64) -> result<u64, stream-error>;
blocking-skip: func(len: u64) -> result<u64, stream-error>;
subscribe: func() -> pollable;
}

resource output-stream {
check-write: func() -> result<u64, stream-error>;
write: func(contents: list<u8>) -> result<_, stream-error>;
blocking-write-and-flush: func(contents: list<u8>) -> 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<input-stream>, len: u64) -> result<u64, stream-error>;
blocking-splice: func(src: borrow<input-stream>, len: u64) -> result<u64, stream-error>;
}
}

world imports {
import error;
import poll;
import streams;
}
10 changes: 10 additions & 0 deletions crates/wit-component/tests/merge/disjoint-funcs/merge/foo.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package foo:foo;

interface a {
type t = u32;

b: func();

a: func();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package foo:%from;

interface a {
use foo:foo/a.{t};
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package foo:into;

interface a {
use foo:foo/a.{t};
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package foo:foo;

interface a {
type b = s32;

type a = u32;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package foo:%from;

interface a {
use foo:foo/a.{a};
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package foo:into;

interface a {
use foo:foo/a.{b};
}

14 changes: 14 additions & 0 deletions crates/wit-component/tests/merge/topo-sort-needed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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.
19 changes: 19 additions & 0 deletions crates/wit-component/tests/merge/topo-sort-needed/from/a.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo:bar;

/// `into` only has `user` with `get` — no `dep` interface at all.

interface user {
get: func() -> u32;
}
21 changes: 21 additions & 0 deletions crates/wit-component/tests/merge/topo-sort-needed/merge/bar.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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;
}

Loading
Loading